Mybatis
本文从mybatis 初始化流程开始,根据源码逐步剖析
首先先思考一下这几个问题
- Mybatis解决了什么问题? 无非是简化数据库操作、实现封装、让程序员更关注SQL本身、维护便利
- 它是如何解决这些问题的?
- Mybatis是运行在什么样的环境下的?
- 它如何读取解析用户定义的配置信息?即如何初始化的
- 它的环境结构是什么样的?
- 在这一环境下如何 实现做增删查改
- 如何执行SQL
- 如何执行动态SQL
- 如何拼接查询参数等等
我知道了这些原理后,我能做些什么?
例如用 Mybatis来扩展一个分页插件
Mybatis存在哪些我觉得可以改进的地方
正经的分割线
带着这些问题,我们从一个TestCase开始
传统jdbc流程
- 注册驱动
- 获取连接
- 准备SQL执行statement
- 设置SQL参数
- 执行SQL,获取到结果集
- 解析结果集
- 依次关闭结果集,statement,数据库连接
Class.forName(Driver.class.getName());
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xxx?characterEncoding=utf-8", "root", "root");
PreparedStatement preparedStatement = connection.prepareStatement("select * from test where id=?");
preparedStatement.setString(1,"1");
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
String columnName1 = resultSet.getMetaData().getColumnName(1);
String columnName2 = resultSet.getMetaData().getColumnName(2);
System.out.println(columnName1+":"+resultSet.getString(1));
System.out.println(columnName2+":"+resultSet.getString(2));
}
resultSet.close();
preparedStatement.close();
connection.close();
Mybatis初始化,主体流程
- 解析config.xml,读取主配置文件,解析
<configuration>
及下所有内容 - 获取 设置的
别名
,对象工厂
,对象反射信息
- 解析 各个
mappers
信息, 包括:xml
和注解
形式, 封装成MapperedStatement
放入Map中待用, 解析信息包括: - 自定义的缓存标签设置解析
- 自定义的参数映射解析
- resultMap标签映射配置
- sql标签解析
- 解析各个CRUD 标签
先从一段测试代码开始,以xml配置为例(注解方式流程解析思想都相同)
注意 阅读
序号 (1)、(2)、(3)、(4)、(5)、(6)
Test.java
(1)通过SqlSessionFactoryBuilder构建mybatis初始化组件,见下一代码栏 解释
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(“xxx/mybatis.xml”);
// 使用一个SqlSession作为此次连接, 主要讲这儿,初始化流程
SqlSession sqlSession = sqlSessionFactory.openSession();
// 动态代理 执行SQL
CustomMapper mapper = sqlSession.getMapper(DemoMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("id","1");
// 自定义了一个select方法,xml的形式
System.out.println(mapper.select(map));
sqlSession.close();
SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//解析config.xml(这里解析使用的是 java dom)
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//parse(): 解析config.xml里面的节点
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
XMLConfigBuilder
(2)如果解析过就不能再次解析mybatis主文件,将mybatis主文件中,configuration标签及其下的所有内容解析出来,见(3)
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
(3)
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
// 将xml中定义的别名,存入当前builder的typeAliasRegistry -- Map中
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
// 设置对象工厂,自定义返回对象的哪种实例
objectFactoryElement(root.evalNode("objectFactory"));
//MateObject 方便反射操作实体类的对象
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"));
// 解析mapper到map存储, 重点解析这儿,见(4)
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
XMLMapperBuilder
(4)
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 解析mapper.xml 见(5)
configurationElement(parser.evalNode("/mapper"));
// 记录加载过的resource
configuration.addLoadedResource(resource);
//绑定Namespace里面的Class对象
bindMapperForNamespace();
}
//重新解析之前解析不了的节点
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
(5)
// 解析mapper文件里面的节点,封装成MapperedStatement
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"));
// resultMap映射解析
resultMapElements(context.evalNodes("/mapper/resultMap"));
// sql语句解析
sqlElement(context.evalNodes("/mapper/sql"));
// 解析各个 CRUD 标签节点了 下面代码紧接着
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);
}
}
// 接上面
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
// 遍历所有CRUD节点
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 解析xml中的节点, 见下面(6)
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
// xml语句有问题时 存储到集合中 等解析完能解析的再重新解析
// 比如insert中依赖了 某个 select标签的,但是insert是按照先后顺序解析的,就会造成insert解析时,缺少select的MapperedStatement,从而抛出异常
configuration.addIncompleteStatement(statementParser);
}
}
}
XMLStatementBuilder
(6)
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));
// 是否是select命令
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 如果是查询则不刷新,不是则刷
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
// 如果是查询则使用二级缓存,是则不使用,即同一个namespace都共享缓存
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
// 是否需要处理嵌套查询结果 将sql语句处理成嵌套形式的
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// 替换Includes标签为对应的sql标签里面的值
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.
// 解析selectKey标签
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> 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;
}
//解析Sql 根据sql文本来判断是否需要动态解析 如果没有动态sql语句且 只有#{}的时候 直接静态解析使用?占位 当有 ${} 不解析
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");
// 解析单个mapper.xml 结束
// 添加到 Configuration中的 mappedStatements 集合中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
Mybatis初始化,主体流程
结合上面文字描述的流程,这里给出一张简图,能快速总结:
至此 Mybatis 的初始化解析结束
下篇文章将逐步剖析 Mybtis的执行原理