目录
一.JDBC流程回顾(以mysql为例)
二.mybatis底层执行流程
1.前期
1.1画图说明如何解析文件(前期)
一、解析PACKAGE
二、解析mappers
一.JDBC流程回顾(以mysql为例)
二.mybatis底层执行流程(1).加载驱动
Class.forName(com.mysql.jdbc.Driver);
(2).创建链接
String url = "jdbc:mysql://localhost:3306/0120mysql?characterEncoding=utf8"; String username = "root"; String password = "123456"; Connection conn = DriverManager.getConnection(url,username,password);
(3).创建状态参数
Statement stat = conn.createStatement();
(4).执行 *** 作
//改 String sql = "insert into dept values (50,'测试1','哈尔滨')"; stat.executeUpdate(sql); stat.execute() //查 ResultSet rs = stat.executeQuery(sql); //迭代器 - Iterator hasNext(判断是否有元素) next(移动指针位置指向元素) while(rs.next()){ //显示数据 System.out.println(rs.getInt("empno")+"-"+rs.getString("ename")+"-"+rs.getString("comm")+"-"+rs.getString("hiredate")+"-"+rs.getString("empno")); System.out.println(rs.getInt(1)+"-"+rs.getString(2)); }
(5).关闭
stat.close(); conn.close();
大概可以分为前、中、后三期,同时也是jdbc细化执行流程:
1.前期前期处理:加载驱动,创建连接-mybatis如何给必要的属性进行赋值,如何解析主配置文件
中期执行:创建状态参数 动态参数赋值 执行程序-mybatis具体执行流程
后期结果:处理结果集 关闭和事务处理-mybatis如何处理结果
如何解析主配置文件
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//使用mybatis第一步获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
}
public class SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//一定是parser.parse()这个方法解析的xml文件
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
换类了 public class XMLConfigBuilder extends BaseBuilder
1.1画图说明如何解析文件(前期)XNode root = parser.evalNode("/configuration");
所以说具体解析xml文件的就是上面画红的代码,已经完成了对xml解析的过程,底层一定用的dom4j
解析完成的元素形成了Xnode形成的对象root
所以说parseConfiguration负责具体把有价值的内容还原到属性上面
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
//解析主配置文件中的链接数据的
environmentsElement(root.evalNode("environments"));
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
//解析映射文件的
mapperElement(root.evalNode("mappers"));//首先我们应该明白的是 mappers标签的作用是用来指定映射文件路径,用于解析这个文件
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
一、解析PACKAGE二、解析mappers1.先解析扫描package(以下蓝体表示方法在对应类里)
XMLConfigBuilder.mapperElement(XNode parent)-->解析主配置文件中的Mappers标签
2.//如果mappers标签中写的是package分支(Configuration)
Configuration.addMappers(mapperPackage);
3. //继续调(MapperRegistry)
mapperRegistry.addMappers(packageName);
4. //继续(MapperRegistry)
mapperRegistry.addMappers(packageName, Object.class);
5. //继续(MapperRegistry)
public void addMappers(String packageName, Class> superType) { ResolverUtil> resolverUtil = new ResolverUtil<>(); resolverUtil.find(new ResolverUtil.IsA(superType), packageName); Set >> mapperSet = resolverUtil.getClasses(); for (Class> mapperClass : mapperSet) { addMapper(mapperClass); } } 6. //继续 type.isInterface()只解析接口(所以包里必须要有接口)
(MapperRegistry)
publicvoid addMapper(Class type) { if (type.isInterface()) {//必须得是接口 if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { knownMappers.put(type, new MapperProxyFactory<>(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse();//解析 loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } } 7. //继续(MapperAnnotationBuilder)
public void parse() { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { loadXmlResource();//解析xml文件的 configuration.addLoadedResource(resource); assistant.setCurrentNamespace(type.getName()); parseCache(); parseCacheRef(); Method[] methods = type.getMethods(); for (Method method : methods) { try { // issue #237 if (!method.isBridge()) { parseStatement(method); } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } parsePendingMethods(); }7. //继续(MapperAnnotationBuilder)
private void loadXmlResource() { // Spring may not know the real resource name so we check a flag // to prevent loading again a resource twice // this flag is set at XMLMapperBuilder#bindMapperForNamespace if (!configuration.isResourceLoaded("namespace:" + type.getName())) { String xmlResource = type.getName().replace('.', '/') + ".xml"; // #1347 InputStream inputStream = type.getResourceAsStream("/" + xmlResource); if (inputStream == null) { // Search XML mapper that is not in the module but in the classpath. try { inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource); } catch (IOException e2) { // ignore, resource is not required } } if (inputStream != null) { XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName()); xmlParser.parse(); } } }package处理注意事项
~
(1).package中的name属性代表要扫表的包,mybatis首先会扫描这个包下全部的类
(2).package的name所表示的包中必须有一个接口,xml文件的名字和这个接口的名字必须和一样且路径必须一致
(3).映射文件的namespace必须是与映射文件对应的接口的全限定名
1.mappers标签处理注意事项
他的解析直接发生在下面代码里
XMLMapperBuilder.parse()这个方法是最终解析的--真正负责映射文件的方法
(1)*******一个mapper里面只能.resource url和mapperClass必须三选一
String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { Class> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); }
1.1如何解析 映射xml里面的sql语句(继如上mapperParser.parse();之后)
a.类:XMLMapperBuilder
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
b.类:XMLMapperBuilder
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
c.类:XMLMapperBuilder
private void buildStatementFromContext(List list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
d.类: XMLMapperBuilder
private void buildStatementFromContext(List list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
e.类:XMLStatementBuilder:parseStatementNode()解析sql的核心程序
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
String parameterType = context.getStringAttribute("parameterType");
Class> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: and were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
f类: MapperBuilderAssistant
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class> parameterType,
String resultMap,
Class> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
g类:Configuration
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
三、前期处理的面试题(2) url是远程的
所以rescourse是本地源 url是远程源class需要指定一个接口进行处理:流程上等效于package中的某一个接口的解析过程
1.package和mapper的区别
package做的是全包的扫描,当扫描到接口,会找到与接口名字相匹配的映射xml文件(当然接口的名字和映射文件的名字必须一样),mapper则是指定特定的xml文件进行单独的加载.
2. mapper的三种属性的区别
rescourse:读本地的xml文件
url:读远程的文件(实际的工作种会防止泄密)
class:必须指定一个接口,来读取xml文件, 相当于package包的一个子集,加载流程和package差不多,也必须接口和映射文件名字一样.
eg:
3. environments切换数据源可以使用构建build方法时添加参数来指定使用哪个数据源
//这行代码指定使用叫developmentx的数据源
SqlSessionFactory sqlSessionFactory = builder.build(reader,"developmentx");
4.preparedstatement和statement以及sql注入问题(我们先看两段代码)
这就是sql注入而且用的是statement,每次执行都要重新编译sql语句(so可以拼串),效率低下.
String ID = "asdasdasdas' or '1'='1";
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8";
String username = "root";
String password = "5541129";
Connection connection = DriverManager.getConnection(url,username,password);
Statement stat = connection.createStatement();
String sql = "select * from mybatis.user where id = '"+ID+"'";
System.out.println(sql);
ResultSet rs = stat.executeQuery(sql);
if (rs.next()){
System.out.println(rs.getInt(1));
}
这是preparestatement,因为sql语句是预编译的,而且语句中使用了占位符,规定了sql语句的结构。用户可以设置"?"的值,但是不能改变sql语句的结构,因此想在sql语句后面加上如“or 1=1”实现sql注入是行不通的。实际开发中,一般采用PreparedStatement访问数据库,它不仅能防止sql注入,还是预编译的(不用改变一次参数就要重新编译整个sql语句,效率高),此外,它执行查询语句得到的结果集是离线的,连接关闭后,仍然可以访问结果集。
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8";
String username = "root";
String password = "5541129";
Connection connection = DriverManager.getConnection(url,username,password);
String sql = "select * from mybatis.user where id = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, "1");
ResultSet resultSet = preparedStatement.executeQuery();
if(resultSet.next()){
System.out.println(resultSet.getInt(1)+resultSet.getString(2));
}
区别与联系
联系
1.PreparedStatement和Statement都是用来执行SQL查询语句的API之一
2.PreparedStatement接口继承了Statement接口
区别:
1.PreparedStatement和Statement的sql语句放置的位置不同
2.Statement不对sql语句作处理,直接交给数据库;而PreparedStatement支持预编译,会将编译好的sql语句放在数据库端,相当于缓存。对于多次重复执行的sql语句,使用PreparedStatement可以使得代码的执行效率更高。
3.Statement的sql语句使用字符串拼接的方式,容易导致出错,且存在sql注入的风险;PreparedStatement使用“?”占位符提升代码的可读性和可维护性,并且这种绑定参数的方式,可以有效的防止sql注入
我们把链接Connection比作桥,那么statement和preparestatement便是车, statement需要每次拉一整条sql过去进行编译运行(所以不能防止sql注入,阻止不了拼串),所以sql必须是完整的,在执行的时候传入sql,而preparestatement就比较聪明,sql语句参数当问号,进行预编译把sql语句放在数据库端不动,把sql的完整结构(这样sql结构确定,就可以防止sql注入)传过去一次PreparedStatement preparedStatement = connection.prepareStatement(sql);,这样sql的结构就确定了,以后每次小车只需要拉值过去就可以了,提高了效率.
5.#{}和${}的区别
静态sql:在SQL执行前,会先将上面的SQL发送给数据库进行编译;执行时,直接使用编译好的SQL,替换占位符“?”就可以了。因为SQL注入只能对编译过程起作用,所以这样的方式就很好地避免了SQL注入的问题。
动态sql:原来什么样就是什么样,执行的时候执行整个sql语句
如上图1.1.e所示 XMLStatementBuilder:parseStatementNode()解析sql的核心程序
select * from mybatis.teacher where id = ${ID}
sqlSource = select * from mybatis.teacher where id = ${ID} 是不变的 称为动态sql
select * from mybatis.teacher where id = #{ID}
sqlSource = select * from mybatis.teacher where id = ?变成了预处理程序 称为静态sql所以区别是(#相当与预处理,考虑类型 )($表示元素可以拼串 $直接替换成值 不会考虑类型)
1."#"处理的sql叫静态sql,前期mapper文件解析的时候会把#部分变成?,将sql进行预编译到数据库端且sql语句结构不会再变,变成预处理sql,执行期间传入值直接执行,所以不会发生sql注入问题。
"$"处理的叫动态sql,前期处理时候不会被改变,运行sql时候,运行sql的时候才会进行编译和值替换,进行值拼串,容易发生sql注入。(这玩意可以结合orderby来用,用的时候参数类型写string类型,将来就可以根据自己的按什么排序规则动态的进行改变)
2.#预处理发生在文件解析过程钟只发生一次,所以执行效率比较高
$发生在程序执行期间,而且每次都要处理,所以执行效率相对于#会略低
3.如果出现了#和$同时存在的时候,sql处理过程就会变成动态sql,而不是静态sql
6.
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)