mybatis源码阅读
Mybatis源码解析
mybatis的使用主要有一下步骤:
-
创建
SqlSessionFactory
工厂对象// 加载mybatis配置文件 InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); // 创建SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
整个流程的关键在于
build()
方法,build方法中对配置文件进行解析XMLConfigBuilder
,并创建对象DefaultSqlSessionFactory
,mybatis默认的SqlSessionFactory为DefaultSqlSessionFactory,有两个重要的方法:XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); var5 = this.build(parser.parse());
通过XMLConfigBuilder来解析mybatis配置文件:
public Configuration parse() { if (this.parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } else { this.parsed = true; // 获取configuration标签下的内容 this.parseConfiguration(this.parser.evalNode("/configuration")); return this.configuration; } }
在parseConfiguration方法中:
private void parseConfiguration(XNode root) { try { // 首先读取外部配置文件,解析properties标签 propertiesElement(root.evalNode("properties")); // 解析settings标签 Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); // 解析typeAliases标签 typeAliasesElement(root.evalNode("typeAliases")); // 解析plugins标签 pluginElement(root.evalNode("plugins")); // 解析objectFactory标签 objectFactoryElement(root.evalNode("objectFactory")); // 解析objectWrapperFactory标签 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // 解析feflectorFactory reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // 解析environments标签 environmentsElement(root.evalNode("environments")); // 解析datasourceIdProvider标签 databaseIdProviderElement(root.evalNode("databaseIdProvider")); // 解析typeHandlers标签 typeHandlerElement(root.evalNode("typeHandlers")); // 解析mappers标签 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
-
解析properties标签
private void propertiesElement(XNode context) throws Exception { if (context != null) { // 首先获取<properties>标签下的子标签,取出<property>标签配置的变量信息 Properties defaults = context.getChildrenAsProperties(); String resource = context.getStringAttribute("resource"); String url = context.getStringAttribute("url"); if (resource != null && url != null) { throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other."); } if (resource != null) { defaults.putAll(Resources.getResourceAsProperties(resource)); } else if (url != null) { defaults.putAll(Resources.getUrlAsProperties(url)); } Properties vars = configuration.getVariables(); if (vars != null) { defaults.putAll(vars); } parser.setVariables(defaults); configuration.setVariables(defaults); } }
在解析properties标签时,优先加载
标签中的内容,然后加载resource属性配置的路径或url配置的路径,两个路径只能存在一个,如果两个都存在会抛出异常。而且加载外部文件中的属性会覆盖 标签中配置的相同名变量。加载完成后将变量信息保存如全局配置。
变量的替换
在mybatis的配置文件中,使用
${}
引用properties导入的环境变量,环境变量的替换发生在root.evalNode("")
期间。进入此方法:public XNode evalNode(Object root, String expression) { Node node = (Node) evaluate(expression, root, XPathConstants.NODE); if (node == null) { return null; } return new XNode(this, node, variables); }
可以看到调用了
new XNode(this,node,variables)
,而在此构造器中:public XNode(XPathParser xpathParser, Node node, Properties variables) { this.xpathParser = xpathParser; this.node = node; this.name = node.getNodeName(); this.variables = variables; this.attributes = parseAttributes(node); this.body = parseBody(node); }
调用了parseAttributes(node),进入此方法:
private Properties parseAttributes(Node n) { Properties attributes = new Properties(); NamedNodeMap attributeNodes = n.getAttributes(); if (attributeNodes != null) { for (int i = 0; i < attributeNodes.getLength(); i++) { Node attribute = attributeNodes.item(i); String value = PropertyParser.parse(attribute.getNodeValue(), variables); attributes.put(attribute.getNodeName(), value); } } return attributes; }
调用了
PropertyParser.parse(attribute.getNodeValue(), variables);
方法,进入此方法:public static String parse(String string, Properties variables) { VariableTokenHandler handler = new VariableTokenHandler(variables); GenericTokenParser parser = new GenericTokenParser("${", "}", handler); return parser.parse(string); }
可以看到就是在这里发生了变量的替换。
其他的步骤都是解析标签然后替换变量信息并解析标签的内容,如果没有值则设置为默认值。在最后
mapperElement()
方法中加载了mapper.xml文件并解析放入configuration中。至此第一步方法分析完毕,主要工作就是:
- 加载mybatis-config.xml配置文件
- 替换环境变量并解析配置文件及mapper.xml文件。
- 创建DefaultSqlSessionFactory对象并返回。
-
-
开启一个会话。
通过
SqlSession sqlSession = sqlSessionFactory.openSession();
可以开启一个session会话。进入openSession方法中:// 从方法名可以看出此方法为:从数据源中打开一个session会话 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { // 首先获取环境信息 final Environment environment = configuration.getEnvironment(); // 通过环境信息获取事物工厂: //<environments default="mysql"> //<environment id="mysql"> // <transactionManager type="JDBC"/> // <dataSource type="POOLED"> // .... // </dataSource> // </environment> //</environments> // 在配置文件中配置的是JDBC事物,然后就会返回相应的事物工厂。 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); // 通过事物工厂创建一个事物 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 通过配置信息获取执行器,执行器是mybatis的核心 final Executor executor = configuration.newExecutor(tx, execType); // 通过执行器创建一个DefaultSqlSession return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
进入
getTransactionFactoryFromEnvironment()
方法:private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) { if (environment == null || environment.getTransactionFactory() == null) { return new ManagedTransactionFactory(); } return environment.getTransactionFactory(); }
可以看到只是通过Environment获取到了事物工厂。
进入
transactionFactory.newTransaction()
@Override public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) { return new JdbcTransaction(ds, level, autoCommit); }
查看newExecutor方法
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { // 默认的ExecutorType为 SIMPLE executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } // 默认cacheEnabled属性为true,使用cachingExecutor装饰器装饰Executor if (cacheEnabled) { executor = new CachingExecutor(executor); } // 使用自定义插件中的Executor插件装饰执行器 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
通过上面的分析,我们可以得出结论:
- 在openSession阶段,并没有跟数据库建立连接。
- 创建了事物,执行器,并根据执行器创建了DefaultSqlSession。
-
获取Mapper接口:
当获取到sqlSession后,就会通过
ActorDao mapper = sqlSession.getMapper(ActorDao.class);
来获取一个Mapper接口。进入getMapper方法:public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); }
继续进入方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
通过mapperRegistry获取Mapper接口。在前面构建sqlSessionFactory阶段解析mapper.xml时,mapperElement方法中向MapperRegistry中注册了此mapper信息以及关联的mapper接口。那么就可以通过这个接口查到对应的Mapper信息。
继续进入getMapper方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { // 通过knowMappers获取接口对应的mapper代理工厂 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { // 通过代理工厂创建一个mapper接口的代理类。 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
进入newInstance方法
protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
可以看到通过Proxy代理也就是jdk动态代理创建了Mapper接口的代理类。代理类的类型为MapperProxy。
至此我们通过
ActorDao mapper = sqlSession.getMapper(ActorDao.class);
获取到的接口mapper就是一个代理类。总结这一阶段的事情:
- 通过制定的mapper接口类型,到mapperregistry中查找到对应的MapperProxyFactory,然后在通过MapperProxyFactory创建一个代理类。
- 目前为止仍未与数据库建立连接
-
调用接口的方法:
Actor actor = mapper.selectByPrimaryKey(1L);
通过断点进入此方法:
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 直接进入了MapperProxy的invoke方法 try { // 判断调用的方法是否是Object累的方法:equals,hashcode等,如果是,则直接调用 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { // 是否是类私有方法,如果是直接调用 return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } // 缓存次方法并返回MapperMethod final MapperMethod mapperMethod = cachedMapperMethod(method); // 执行方法 return mapperMethod.execute(sqlSession, args); }
private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; }
查看execute方法:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 判断调用的方法类型
switch (command.getType()) {
// 插入
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
// 更新
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
// 删除
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
// 查询
case SELECT:
// 方法无返回值且有结果处理器
if (method.returnsVoid() && method.hasResultHandler()) {
// 执行结果处理器并返回null
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// 方法返回为数据集
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
// 方法返回为Map
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
// 返回游标
result = executeForCursor(sqlSession, args);
} else {
// 转换参数为通用参数
Object param = method.convertArgsToSqlCommandParam(args);
// 执行查询方法
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
// 刷新statement
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
主要查看的方法是:
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
进入method.convertArgsToSqlCommandParam(args);
方法,最终调用方法为ParamNameResolver
的
public Object getNamedParams(Object[] args) {
// 获取参数的数组大小
final int paramCount = names.size();
if (args == null || paramCount == 0) {
// 没有参数
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
// 参数只有一个直接取出第一个
return args[names.firstKey()];
} else {
// 参数有多个,默认为param1, param2
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
// 如果参数有@param注解,那么注解的值会被缓存到names中,通过names替换param1,param2
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
然后 就是执行方法sqlSession.selectOne()方法,最终调用的是selectList:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 通过配置获取MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
// 通过执行器执行
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
最终执行query方法为:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 从缓存中获取-- 二级缓存
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 真正执行查询数据库的方法, delegate为真正工作的Executor
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
查看query方法:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
//一级缓存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 从数据库获取数据
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
继续进入queryFromDatabase方法:
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// doQuery方法: 真正执行查询的方法
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
进入doQuery方法,本次查询使用的是SimpleExecutor执行器,进入方法:
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
// 获取配置信息
Configuration configuration = ms.getConfiguration();
// 获取一个statementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 预处理StatementHandler
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
上面这个方法比较重要,主要做了以下事情:
-
获取StatementHandler,在newStatementHandler方法中,创建了一个RoutingStatementHandler方法,查看父类的构造方法可以看到包装了ParameterHandler,ResultSetHandler等处理器,然后又包装了StatementHandler
-
prepareStatement方法,真正与数据库建立连接的方法
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; // 创建数据库连接 Connection connection = getConnection(statementLog); // 通过数据库连接创建一个Statement stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); return stmt; }
-
使用Statement执行sql,查看handler.query()方法
@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { String sql = boundSql.getSql(); // 执行sql语句 statement.execute(sql); // 处理结果集 return resultSetHandler.<E>handleResultSets(statement); }
至此,mybatis源码的整体流程梳理完毕。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
2019-03-22 Collection集合复习方法回顾
2019-03-22 java学习笔记25(Collections类)
2019-03-22 Java学习笔记24(Map集合)
2019-03-22 java学习笔记23(Set接口)