本文主旨
方便你快速分析mybatis源码,梳理其流程,掌握主要知识点.
官方简介:
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
本文介绍
mybatis相信大家都用的多了,本文主要介绍mybatis的整体设计思想、核心源码实现,并从中体悟面向对象思维的设计理念.-----哈哈,正所谓,技近乎艺,艺近乎道!
架构介绍
mybatis结构其结构可以分为三层
- 配置初始化:负责mybatis的初始化,环境、数据、插件配置组装工作
- 调用会话: 一次查询的调用过程
- 映射转换: java对象、数据库结果间相互映射处理
源码流程梳理
层级如此,我们再来看看调用时序:
mybati流程图- Configuration作为mybatis的顶层配置类,管理一切mybatis的环境、缓存信息
2.当你调用getMapper方法获取对应mapper类时(或通过spring注入mapper类时)其实最后调用了Configuration里的getMapper方法, 看下getMapper对应代码:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
其实调用的是对应的缓存类MapperRegistry管理Mapper代理对象
- 打开MapperRegistry类,熟悉的代理模式方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
此处获取对应的MapperProxy代理类,看下此类核心invoker方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//声明对应代理内部方法直接调用,如object的方法
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);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
//最后实际执行方法,还是回到了SqlSession接口类上
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;
}
-
看完MapperProxy代理类,我们知道mybatis最终返回给用户使用的是MapperProxy类,但实际干活的还是SqlSession对象,但SqlSession实际是会话级对象,即其scope作用域为线程级别,那此处这个代理类中的成员变量是怎么回事呢?
-
答案是此处的SqlSession实现类为SqlSessionTemplate类,其是SqlSession默认实现的模版方法类,属于application级别,看下其构造方法
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
//此处生成对应的sqlSession代理执行类,每个线程将在SqlSessionInterceptor创建单独的SqlSession
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
- 原来又是一层动态代理啊!继续看其invoker方法,实际分为下面三部
- 获取实际会话对象,此时注意同一个事务中可以获取相同的SqlSession
- 调用实际会话对象相应方法
- 关闭资源
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取线程级别SqlSession会话对象,同一个事务中可以获取相同的SqlSession,进行统一事务和缓存、延迟加载等处理
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
//调用实际sqlSession会话对象
Object result = method.invoke(sqlSession, args);
............
//关闭资源
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
- 经过多层代理,终于找到了我们会话级别的SqlSession,当然还有一些细节流程,如事务处理、 TransactionSynchronizationManager(便于用户定义事务提交后处理流程) 处理器管理等等
8.下面SqlSession的实现类,默认为DefaultSqlSession,在mybatis中认为,任何查询返回结果都概括为一个list,单个结果只是特殊的list,所以selectList方法为其核心查询方法
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
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();
}
}
9.首先MappedStatement为定义的方法对应的sql信息,映射信息等等数据,executor又是一种重要概念,代表了mybatis中最终的执行器.
- 继续,BaseExecutor明细的模版方法,执行query,跟代码注释梳理下核心逻辑:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//ErrorContext存储了对应的myatis调用栈信息,便于排查错误
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
c.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
此处,我们看到了熟悉的mybatis一级缓存概念,对于关联查询等操作,一级缓存还是很有用的,懒加载此处也有实现,在结果集映射时也会操作deferredLoads此对象,进行懒加载操作.
顺便此处说下二级缓存.mybatis的二级缓存通过CachingExecutor实现,其是对BaseExecutor的包装类,当缓存中没有时,一样会调用BaseExecutor相应方法进行从数据库中获取数据.
- 下面核心落到了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 {
//调用实际查询方法
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;
}
- 下面进入SimpleExecutor类的doQuery方法
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();
//包装 Statement对象,此处一样用委派模式,进行包装不同的处理类,默认实现为PreparedStatementHandler,并且对于mybatis的plugins也是在此处进行封装
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//处理参数验证,TypeHandler等转换在此前置处理
stmt = prepareStatement(handler, ms.getStatementLog());
//最后通过此PreparedStatementHandler执行query方法,并通过resultHandler进行结果映射
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
- 下面就简单了
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//熟悉的jdbc操作,获取db数据
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
//结果集映射处理
return resultSetHandler.<E> handleResultSets(ps);
}
- 在resultSetHandler中,会通过反射创建对应的对象实体,赋值到结果集中,如果有懒加载需要,会添加deferredLoad对列,等待用的时候加载对应属性.
总结
好了,mybatis主要远嘛流程梳理完了.
-
mybatis多处用到了动态代理模式,方便使用者,sql和接口定义分离模式
-
其中对于SqlSession的代理Template封装很巧妙,充分学习了模版类的神奇之处
-
ErrorContext实现对于异常排查问题,方便使用
-
代理无处不在,handler方便扩展,mybatis主旨在于参数映射,半封装处理,面向对象思维将各模块组装到一起.
网友评论