MyBatis 二级缓存实现机制的示例解析
目录
- 前置准备
- SqlSession与SqlSessionFactory
- MapperProxy
- DefaultSqlSession
- Configuration
- MapperRegistry
- MapperProxyFactory
- Executor
- MapperProxy
- MapperMethod
- DefaultSqlSession
- StatementHandler
- 简单总结
- 获取SqlSession流程
- Sql执行流程
- MyBATis一些重要的类
前置准备
既然要看MyBatis源码,当然是把源码拉取下来debug一步一步看才方便呢,这里已经拉取下来,并准备好例子了。
@Slf4j public class Main { public static void main(String[] args) { String resource = "org/apache/ibatis/yyqtest/resource/mybatis-config.XML"; InputStream inputStream = null; try { // 将XML配置文件构建为Configuration配置类 inputStream = Resources.getResourceAsStream(resource); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 通过加载配置文件流构建一个SqlSessionFactory DefaultSqlSessionFactory SqlSessionFactory sqlSessionFactory = null; sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = null; try { sqlSession = sqlSessionFactory.openSession(); RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class); //Role role = roleMapper.testManyParam(1L, "张三"); //Role role = roleMapper.testAnnotation(1L); Role role = roleMapper.testDynamicParam(1L, ""); log.info(role.getId() + ":" + role.getRoleName() + ":" + role.getNote()); sqlSession.commit(); } catch (Exception e) { // TODO Auto-generated catch block sqlSession.rollback(); e.printStackTrace(); } finally { sqlSession.close(); } } }
从上诉例子来看,我们观察到首先是解析配置文件,再获取SqlSession
,获取到Sqlsession
之后在进行各种的CRUD
操作,我们先来看下SqlSession
是怎么获取的。
SqlSession与SqlSessionFactory
我们根据时序图,一步一步解开SqlSession
的执行流程,首先来是通过SqlSessionFactoryBuilder
解析配置文件,再根据配置文件构建并返回一个DefaultSqlSessionFactory
,源码如下:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // 2. 创建XMLConfigBuilder对象用来解析XML配置文件,生成Configuration对象 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // 3. 将XML配置文件内的信息解析成Java对象Configuration对象 Configuration config = parser.parse(); // 4. 根据Configuration对象创建出SqlSessionFactory对象 return build(config); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { if (inputStream != null) { inputStream.close(); } } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
拿到SqlSessionFactory
之后,就可以根据这个SqlSessionFactory
拿到SqlSession
对象,源码如下:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); // 通过事务工厂从一个数据源来产生一个事务 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 获取执行器,这边获得的执行器已经代理拦截器的功能 final Executor executor = configuration.newExecutor(tx, execType); // 获取执行器后,再将configuration和executor封装成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(); } }
通过上面两个步骤拿到SqlSession
之后,就可以进行CRUD
了
- 将xml配置文件解析成流
SqlSessionFactory.build
将配置文件解析成Configuration
,并返回一个SqlSessionFactory
对象- 拿到
SqlSessionFactory
对象之后,从Environment
中拿到DataSource
并产生一个事务 - 根据事务
Transaction
和执行器类型(SIMPLE, REUSE, BATCH,默认是SIMPLE
)获取一个执行器Executor(–>该对象非常重要,事实上sqlsession的所有操作都是通过它完成的)
- 创建sqlsession对象。
MapperProxy
拿到Sqlsession
对象之后,就可以拿到我们所写的xxxMapper
,再根据这个xxxmapper
就可以调用方法,最终进行CRUD。我们来关注下我们这个xxxMapper
是一个接口,接口是不能被实例化的,为什么可以直接通过getMapper
方式拿到呢?xxxMapper
和我们的xxxMapper.xml
文件之间又是如何联系的?别急,我们接着往下看。
我们先来看下时序图:
通过这个时序图可以看到是通过代理的方式来搞定的,当执行到自己写的方法里面的时候,其实通过了MapperProxy
在进行代理。话不多说,我们直接来看下怎么获取MapperProxy
对象的吧: 哦吼↑ ↓
DefaultSqlSession
/** * 什么都不做,直接去configuration里面去找 * @param type Mapper interface class * @param <T> * @return */ @Override public <T> T getMapper(Class<T> type) { return configuration.getMapper(type, this); }
Configuration
// mapperRegistry实质上是一个Map,里面注册了启动过程中解析的各种Mapper.xml protected final MapperRegistry mapperRegistry = new MapperRegistry(this); /** * configuration也什么都不做,去mapperRegistry里面找 * @param type * @param sqlSession * @param <T> * @return */ public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
这里会发现,这个mapperRegistry
是什么东西啊,mapperRegistry
实质上是一个Map,里面注册了启动过程中解析的各种Mapper.xml
。我们点进去看看
Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
mapperRegistry的key是接口的Class类型
mapperRegistry的Value是MapperProxyFactory,用于生成对应的MapperProxy(动态代理类)
这里具体是怎么加进去的可以去了解了解解析配置的篇章,我们接着往下走,看看MapperRegistry
的源码:
MapperRegistry
/** * MapperRegistry标识很无奈,没人做,只有我来做了 * @param type * @param sqlSession * @param <T> * @return */ @SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { // MapperRegistry表示也要偷懒,偷偷的把粗活交给MapperProxyFactory去做。 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); // 如果配置文件中没有配置相关Mapper,直接抛异常 if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { // 我们看到mapper都是接口,因为接口是不能实例化的,这里通过动态代理实现,具体可以看下MapperProxy这个类 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
MapperProxyFactory
这里是将接口实例化的过程,我们进去看看:
MapperProxyFactory
public class MapperProxyFactory<T> { /** * 生成Mapper接口的动态代理类MapperProxy,MapperProxy实现了InvocationHandler接口 * * @param mapperProxy * @return */ @SuppressWarnings("unchecked") 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<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
通过这个MapperProxyFactory
就可以拿到类MapperProxy
代理对象,简单来说,我们通过这个代理就可以很方便的去使用我们写的一些dao层
的接口了,上例子
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class); Role role = roleMapper.testManyParam(1L, "张三"); Role role = roleMapper.testAnnotation(1L); Role role = roleMapper.testDynamicParam(1L, "");
拿到这个对象之后,接下来就是sql
语句的执行了呢,来,上源码,真正的主菜来了。
Executor
SqlSession
只是一个门面,前面做的一些事情都只是铺垫,真正的的干事的其实是Excutor,SqlSession
对数据库的操作都是通过Executor
来完成的。与SqlSess编程客栈ion
一样,Executor
也是动态创建的,老样子先上时序图:
在看MapperProxy
的方法之前www.devze.com,我们先回到获取Excutor
对象的方法configuration.newExecutor(tx, execType)
/** * Executor分成两大类,一类是CacheExecutor,另一类是普通Executor。 * * @param transaction * @param executorType * @return */ public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; // 这句再做一下保护,囧,防止粗心大意的人将defaultExecutorType设成null? executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; // 普通Executor又分为三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。 if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { // 执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String,Statement>内,供下一次使用。简言之,就是重复使用Statement对象 executor = new ReuseExecutor(this, transaction); } else { // 每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。 executor = new SimpleExsuaBRNoecutor(this, transaction); } if (cacheEnabled) { // CacheExecutor其实是封装了普通的Executor,和普通的区别是在查询前先会查询缓存中 // 是否存在结果,如果存在就使用缓存中的结果,如果不存在还是使用普通的Executor进行 // 查询,再将查询出来的结果存入缓存。 executor = new CachingExecutor(executor); } // 调用每个拦截器的方法,这里的话应该是为了方便扩展,方便开发者自定义拦截器做一些处理 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
从上诉代码可以看出,如果不开启缓存cache
的话,创建的Executor
只是3中基础类型之一,BatchExecutor
专门用于执行批量sql操作
,ReuseExecutor
会重用statement
执行sql
操作,SimpleExecutor
只是简单执行sql
没有什么特别的。
开启了缓存(默认开启)
,就会去创建CachingExecutor
,这个缓存执行器在查询数据库前会先查找缓存是否存在结果,如果存在就使用缓存中的结果,如果不存在还是使用普通的Executor
进行查询,再将查询出来的结果存入缓存。我们还看到这里有个加载插件的方法,就说明这里是可以被插件拦截的,如果定义了针对Executor
类型的插件,最终生成的Executor
对象是被各个插件插入后的代理对象。
接下来,咱们才要真正的去了解sql
的执行过程了。回到上面来看,我们拿到了MapperProxy
,调用Mapper接口
的所有方法都会优先调用到这个代理类的invoke方法(注意由于MyBatis
中的Mapper接口没有实现类,所以MpperProxy
这个代理对象中没有委托类,也就是说MapperProxy
干了代理类和委托类的事情),具体是怎么做的,上源码:
MapperProxy
MapperProxy
的invoke
方法本质上是交给了MapperMethodInvoker
,MapperMethodInvoker
实质就是封装了一层MapperMethod
。
/** * 通过动态代理后,所有的Mapper方法调用都会走这个invoke方法 * * @param proxy * @param method * @param args * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { // cachedInvoker封装了一个MapperMethod对象 // 拆分出来更好理解 MapperMethodInvoker mapperMethodInvoker = cachedInvoker(method); return mapperMethodInvoker.invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } }
MapperMethod
根据参数和返回值类型选择不同的sqlSession
来执行。这样mapper
对象和sqlSession
就真正关联起来了
/** * 这里其实就是先判断CRUD的类型,根据类型去选择到底执行了sqlSession中的哪个方法 * @param sqlSession * @param args * @return */ 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()) { 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 { // 从字面意义上看,讲转换的参数放到sql里面 // Map<String, Object> param Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatemenandroidts(); 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; }
既然又回到SqlSession
了,前面提到过,sqlsession
只是一个门面,真正发挥作用的是executor,对sqlsession
方法的访问最终都会落到executor的相应方法上去。Executor分成两大类,一类是CacheExecutor
,另一类是普通Executor
。Executor的创建前面已经介绍了,那么咱们就看看SqlSession
的CRUD方法了,为了省事,还是就选择其中的一个方法来做分析吧。这儿,咱们选择了selectList
方法。这里有宝宝就好奇了,从上面的代码来看只有selectOne
方法呢,哪里来的selectList
。其实selectOne
方法里面本质就是调用了selectList
方法。
@Override public <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. // 转而去调用selectList,很简单的,如果得到0条则返回null,得到1条则返回1条,得到多条报TooManyResultsException错 // 这里没有查询到结果的时候会返回null。因此一般建议mapper中编写resultType使用包装类型 List<T> list = this.selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; } }
石锤了!!!,接下来我们来看下selectList方法。
DefaultSqlSession
/** * * @param statement 全限定名称+方法名 例如org.apache.ibatis.yyqtest.mapper.RoleMapper.testAnnotation * @param parameter * @param rowBounds * @param handler * @param <E> * @return */ private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { try { // 根据传入的全限定命+方法名从映射的map中取出MappedStatement对象 MappedStatement ms = configuration.getMappedStatement(statement); // 之类能看到CRUD实际上是交给Executor处理 return executor.query(ms, wrapCollection(parameter), rowBounds, handler); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
这里的statement是全限定名称+方法名字
,例如org.apache.ibatis.yyqtest.mapper.RoleMapper.testAnnotation
,具体怎么来的,可以自己debug看下是怎么得到的。这里的getMapperStatement
做了什么呢。话不多说上源码。
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection") .conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource() + " and " + targetValue.getResource()); public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) { if (validateIncompleteStatements) { buildAllStatements(); } return mappedStatements.get(id); }
映射的语句
,存在Map里,这里应该是扫描配置文件的时候就已经加进去了 key是全限定名+方法名
value是对应的mappedStatements
。这个MapperStatements
方法到底有什么作用呢,我们先别急。先回到executor.query
的方法继续往下走。
StatementHandler
可以看出,Executor
本质上也是个甩手掌柜,具体的事情原来是StatementHandler
来完成的。当Executor将指挥棒交给StatementHandler
后,接下来的工作就是StatementHandler
的事了。我们先看看StatementHandler
是如何创建的:
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }
可以看到每次创建的StatementHandler
都是RoutingStatementHandler
,它只是一个分发者,他一个属性delegate用于指定用哪种具体的StatementHandler
。可选的StatementHandler
有SimpleStatementHandler、PreparedStatementHandler
和CallableStatementHandler
三种。选用哪种在mapper配置文件的每个statement里指定,默认的是PreparedStatementHandler
。同时还要注意到StatementHandler
是可以被拦截器拦截的,和Executor一样,被拦截器拦截后的对像是一个代理对象。
tatementHandler
创建后需要执行一些初始操作,比如statement的开启和参数设置、对于PreparedStatement
还需要执行参数的设置操作等。代码如下:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); return stmt; }
这里呢会创建一个StatementHandler
对象,这个对象中同时会 封装ParameterHandler
和ResultSetHandler
对象。调用StatementHandler
预编译参数 以及设置参数值,使用ParameterHandler
来给sql
设置参数。
参数设置完毕后,执行数据库操作(update或query
)。如果是query最后还有个查询结果的处理过程。接下来,咱们看看StatementHandler
的一个实现类 PreparedStatementHandler
(这也是我们最常用的,封装的是PreparedStatement
), 看看它使怎么去处理的:
@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); // 将处理好的结果交给了ResultSetHandler return resultSetHandler.handleResultSets(ps); }
到这里,一次sql
的执行流程就执行完了!
简单总结
获取SqlSession流程
SqlSessuibFactoryBuilder
解析配置文件,并将这些配置放到一个Configuration
对象,这个对象中包含了MyBatis需要的所有配置,然后会用这个Configuration对象再去创建一个SqlSessionFactory
对象。- 拿到
SqlSessionFactory
对象后,会调用SqlSessionFactory的openSession
方法,这个方法根据解析配置文件的事务和执行器类型来创建一个执行器,这个执行器就是真正sql
的执行者。最后将执行器和configuration
封装起来返回一个DefaultSqlSession - 拿到
SqlSession
之后就能执行各种CRUD的方法了。
Sql执行流程
- 拿到
SqlSession
之后调用getMapper
方法,拿到Maper
接口的代理对象MapperProxy
,调用Mapper接口的所有方法都会调用MapperProxy
的invoke方法。 - 到达invoke方法,就会掉用执行器的executer方法。
- 往下,层层调下来会进入
Executor
组件(如果配置插件会对Executor
进行动态代 理)的query方法,这个方法中会创建一个StatementHandler
对象,这个对象中同时会 封装ParameterHandler
和ResultSetHandler
对象。调用StatementHandler
预编译参数 以及设置参数值,使用ParameterHandler
来给sql
设置参数。 - 调用
StatementHandler
的增删改查方法获得结果,ResultSetHandler
对结果进行封 装转换,请求结束。
MyBatis一些重要的类
MapperRegistry
: 本质上是一个Map其中的key是Mapper接口的全限定名, value的MapperProxyFactory;MapperProxyFactory
: 这个类是MapperRegistry中存的value值,在通过 sqlSession获取Mapper时,其实先获取到的是这个工厂,然后通过这个工厂创建 Mapper的动态代理类;MapperProxy
: 实现了InvocationHandler接口,Mapper的动态代理接口方法的调 用都会到达这个类的invoke方法;MapperMethod
: 判断你当前执行的方式是增删改查哪一种,并通过SqlSession执 行相应的操作;SqlSession
: 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必 要数据库增删改查功能;Executor
: MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓 存的维护;StatementHandler
: 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设 置参数、将Statement结果集转换成List集合。ParameterHandler
: 负责对用户传递的参数转换成JDBC Statement 所需要的参数ResultSetHandler
: 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;TypeHandler
: 负责java数据类型和jdbc数据类型之间的映射和转换MappedStatement
: MappedStatement维护了一条节点 的封装SqlSource
: 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到 BoundSql对象中,并返回BoundSql
: 表示动态生成的SQL语句以及相应的参数信息Configuration
: MyBatis所有的配置信息都维持在Confi编程客栈guration对象之中。
到此这篇关于MyBatis 二级缓存实现机制的示例解析的文章就介绍到这了,更多相关MyBatis 二级缓存机制内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论