本系列是针对 MyBatis3 的源码分析文章已收录至精进MyBatis系列 系列其它文章 https://www.wormholestack.com/tag/MyBatis/源码阅读环境：https://gitee.com/M-Analysis/source_mybatis3 已填充关键注释1. 延迟加载的介绍及使用本文将针对MyBatis提供的延迟加载（懒加载）原理剖析。1.1 延迟加载是什么？简单的来说延迟加载就是，在需要用到数据的时候进行加载，不需要用到数据就不进行加载。假设数据库中涉及两张表，用户表AND订单表（一对多的关系），假设一个有很多订单，那么在查询用户的时候，需不需要当前用户关联的订单数据查询出来？通常来说查询用户信息的时候，肯定是需要用到用户订单的时候在查询为好，尤其是一对多的多表查询，通常都建议采用延迟加载，因为单表查询肯定要比多表关联查询速度要快，先从单表查询，接着需要时在关联表进行关联查询，会提高数据库性能。1.2 如何启用延迟加载MyBatis默认不启用延迟加载，启用延迟加载则需要我们进行一些简单的配置即可。首先可以MyBatis SqlMapper核心配置文件中设置setting属性 设置全局懒加载 <settings> <setting name="lazyLoadingEnabled" value="true" /> <setting name="aggressiveLazyLoading" value="false"/> </settings> 或者在mapper配置文件中配置fetchType标签 设置局部懒加载 <resultMap id="userMap" type="com.mryan.pojo.User"> <id property="id" column="id"></id> <result property="username" column="username"></result> <collection property="orderList" ofType="com.mryan.pojo.Order" select="com.mryan.mapper.IOrderMapper.findOrderByUid" column="id" fetchType="lazy"> <id property="id" column="uid"/> <result property="orderTime" column="ordertime"/> <result property="total" column="total"/> </collection> </resultMap> select * from orders where uid = #{uid} 注意这里 局部的懒加载策略优先级药高于全局的懒加载策略1.3 测试延迟加载的作用为测试多表关联，表结构及pojo结构如下：User public class User implements Serializable { private Integer id; private String username; // 用户关联的订单数据 private List<Order> orderList; //省略 Orders public class Order implements Serializable { private Integer id; private String orderTime; private Double total; // 表明该订单属于哪个用户 private User user; //省略 在完成上述配置之后，跑一个单元测试，试一下延迟加载是否生效 /* 测试 延迟加载查询 */ @Test public void TEST_QUERY_LAZY()throws IOException{ InputStream inputStream=Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession=factory.openSession(); User user=sqlSession.selectOne("com.mryan.mapper.IUserMapper.findById",1); //延迟加载生效 下方输出语句 不涉及到orders表 于是不会打印orders相关日志 System.out.println("user："+user.getUsername()); //涉及orders表 才会执行相关SQL语句 加载orders执行日志 （延迟加载 什么时候用什么时候查） System.out.println("orders："+user.getOrderList()); } 可以发现user.getUsername()的时候只触发了查询用户的sql语句，而user.getOrderList()时才触发了查询订单的sql语句，由此可见延迟加载生效，做到了不用不查，用到在查。2. 延迟加载源码剖析下面将对源码进行阅读，剖析Mybatis延迟加载的原理。在MyBatis系列文章第一篇浅析MyBatis执行SQL流程就对SQL的执行流程进行了讲解。简单复习一下大致流程，SQL查询语句的执行是由SqlSession分发交由Executor托管执行，调度StatementHandler负责JDBCstatement操作，之后下发给ParameterHandler负责对用户传递参数进行转化处理SQL参数，再接着执行SQL语句，最后通过ResultSetHandler对返回结果进行封装处理返回。根据刚刚对延迟加载功能的测试，我们也能大致找到突破入口，通过最后的ResultSetHandler对结果封装处理返回的时候，根据调用的getting方法的实例名称，来相对应的加载目标对象结果，不就实现了延迟加载的功能吗。MyBatis其实就是这么做的，接下来根据翻看源码来证实我们的猜想2.1 源码剖析 /** * MyBatis 配置 * * @author Clinton Begin */ public class Configuration { /** * 当开启时，任何方法的调用都会加载该对象的所有属性。否则，每个属性会按需加载（参考lazyLoadTriggerMethods) */ protected boolean aggressiveLazyLoading; /** * 指定哪个对象的方法触发一次延迟加载。 */ protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString")); /** * 延迟加载的全局开关。当开启时，所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 */ protected boolean lazyLoadingEnabled = false; } Configuration的lazyLoadingEnabled对应mapper配置文件中配置fetchType标签因为有了分析SQL执行流程的基础，所以这次直接定位到关键代码处。DefaultResultSetHandler#handleResultSets()此方法就是在执行完SQL查询语句后处理封装结果集的核心代码 // 处理 {@link java.sql.ResultSet} 结果集 @Override public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); // 多ResultSet的结果集合，每个 ResultSet 对应一个Object 对象。而实际上，每个Object 是List<Object> 对象。 // 在不考虑存储过程的多ResultSet的情况，普通的查询，实际就一个 ResultSet ，也就是说，multipleResults最多就一个元素。 final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0; // 获得首个ResultSet对象，并封装成ResultSetWrapper对象 ResultSetWrapper rsw = getFirstResultSet(stmt); // 获得ResultMap数组 // 在不考虑存储过程的多ResultSet的情况，普通的查询，实际就一个 ResultSet ，也就是说，resultMaps就一个元素。 List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); // 校验 while (rsw != null && resultMapCount > resultSetCount) { // 获得ResultMap对象 ResultMap resultMap = resultMaps.get(resultSetCount); // 关键代码！！！处理 ResultSet ，将结果添加到multipleResults中 handleResultSet(rsw, resultMap, multipleResults, null); // 获得下一个 ResultSet 对象，并封装成 ResultSetWrapper 对象 rsw = getNextResultSet(stmt); // 清理 cleanUpAfterHandlingResultSet(); // resultSetCount ++ resultSetCount++; } // 因为 `mappedStatement.resultSets` 只在存储过程中使用，本系列暂时不考虑，忽略即可 String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } // 如果是 multipleResults 单元素，则取首元素返回 return collapseSingleResultList(multipleResults); } 直接看最主要的方法handleResultSet(rsw, resultMap, multipleResults, null); 这个方法主要是为了处理ResultSet，将结果添加到multipleResults中 // 处理 ResultSet ，将结果添加到 multipleResults 中 private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException { try { // 暂时忽略，因为只有存储过程的情况，调用该方法，parentMapping为非空 if (parentMapping != null) { handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping); } else { // 如果没有自定义的 resultHandler，则创建默认的 DefaultResultHandler if (resultHandler == null) { DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); // 核心代码！！！ 处理ResultSet返回的每一行Row handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); // 添加defaultResultHandler的处理的结果，到multipleResults中 multipleResults.add(defaultResultHandler.getResultList()); } else { //核心代码！！！ 处理ResultSet返回的每一行 Row handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); } } } finally { // issue #228 (close resultsets) // 关闭 ResultSet 对象 closeResultSet(rsw.getResultSet()); } } 由handleRowValues方法处理ResultSet返回的每一行Row,调用了createResultObject()方法创建结果对象，就是在这个方法中完成了懒加载相关处理逻辑。 // 创建映射后的结果对象 private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { // useConstructorMappings ，表示是否使用构造方法创建该结果对象。此处将其重置 this.useConstructorMappings = false; // reset previous mapping result final List<Class<?>> constructorArgTypes = new ArrayList<>(); // 记录使用的构造方法的参数类型的数组 final List<Object> constructorArgs = new ArrayList<>(); // 记录使用的构造方法的参数值的数组 // 创建映射后的结果对象 Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { // 如果有内嵌的查询，并且开启延迟加载，则创建结果对象的代理对象 final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { // issue gcode #109 && issue #149 if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); break; } } } // 判断是否使用构造方法创建该结果对象 this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result return resultObject; } 对上述代码做一些精简方便找到重点 // 创建映射后的结果对象 private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { //省略代码 if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { // 如果有内嵌的查询，并且开启延迟加载，则创建结果对象的代理对象 final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { // issue gcode #109 && issue #149 //如果当前开启懒加载配置 if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { //创建代理对象 resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); break; } } } // 判断是否使用构造方法创建该结果对象 this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result return resultObject; } 其实定位到这里，就拨开了一些云雾，在创建映射后的结果对象流程中会判断是否开启了延迟加载配置，如果开启了延迟加载配置会创建一个代理对象，将结果返回。那么程序如何知道是否开启了延迟加载的配置呢？通过ResultMapping#lazy方法而ResultMapping正是每一个结果字段的映射为什么需要创建一个代理对象呢？这里我们做一个猜想，在章节1.3中的单元测试中为了验证延迟加载功能。 /* 测试 延迟加载查询 */ @Test public void TEST_QUERY_LAZY()throws IOException{ InputStream inputStream=Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession=factory.openSession(); User user=sqlSession.selectOne("com.mryan.mapper.IUserMapper.findById",1); //下方输出语句 不涉及到orders表 于是不会打印orders相关日志 System.out.println("user："+user.getUsername()); //延迟加载生效 涉及orders表 才会执行相关SQL语句 加载orders执行日志 （延迟加载 什么时候用什么时候查） System.out.println("orders："+user.getOrderList()); } 在执行完sqlSession.selectOne语句之后，如果创建了一个代理对象，动态的判断代理对象的getting方法是否是需要延迟加载的局部属性，例如getOrderList被判定延迟加载配置生效进入代理对象拦截器方法，那么就会单独发送实现保存好的查询语句将orderList查询上来，然后通过反射将orderList设置进去，然后完成getorderList的调用，实际上我们猜测的延迟加载的基本原理，总结一下猜想：延迟加载通过动态代理实现，通过代理拦截指定getting方法，执行数据加载。那么MyBatis是如何处理的呢？如下：MyBatis代理工厂接口的两种实现，其中默认使用的是Javassits模式，具体实现如下： /** * 代理工厂接口 * * @author Eduardo Macarron */ public interface ProxyFactory { // 设置属性，目前是空实现, 暂时跳过 void setProperties(Properties properties); // 创建代理对象 Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs); } /** * 基于 Javassist 的 ProxyFactory 实现类 * * @author Eduardo Macarron */ @SuppressWarnings("Duplicates") public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory { @Override public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); } } 代理类的拦截方法如下（省略无关代码方便阅读）： @Override public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable { if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) { // 加载所有延迟加载的属性 if (aggressive || lazyLoadTriggerMethods.contains(methodName)) { lazyLoader.loadAll(); // 如果调用了 setting 方法，则不在使用延迟加载 } else if (PropertyNamer.isSetter(methodName)) { final String property = PropertyNamer.methodToProperty(methodName); lazyLoader.remove(property); // 移除 // 如果调用了 getting 方法，则执行延迟加载 } else if (PropertyNamer.isGetter(methodName)) { final String property = PropertyNamer.methodToProperty(methodName); if (lazyLoader.hasLoader(property)) { lazyLoader.load(property); } } } } } // 继续执行原方法 return methodProxy.invoke(enhanced, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } 在拦截方法中，判断了当前执行的方法是否拥有延迟加载属性，如果启用了延迟加载并且是getting方法，那么执行load加载方法，如果不是则继续执行原方法逻辑。而load加载方法其实也就是去执行一次SQL查询方法将结果set到代理对象中 public void load(final Object userObject) throws SQLException { if (this.metaResultObject == null || this.resultLoader == null) { //省略代码 // 获得 Configuration 对象 final Configuration config = this.getConfiguration(); // 获得 MappedStatement 对象 final MappedStatement ms = config.getMappedStatement(this.mappedStatement); // 获得对应的 MetaObject 对象 this.metaResultObject = config.newMetaObject(userObject); // 创建 ResultLoader 对象 this.resultLoader = new ResultLoader(config, new ClosedExecutor(), ms, this.mappedParameter, metaResultObject.getSetterType(this.property), null, null); } //关键代码！！resultLoader.loadResult(); this.metaResultObject.setValue(property, this.resultLoader.loadResult()); } 关键代码resultLoader.loadResult() 去主动调用了一次查询方法并将结果进行提取。 /** * 加载结果 * * @return 结果 */ public Object loadResult() throws SQLException { // 查询结果 List<Object> list = selectList(); // 提取结果 resultObject = resultExtractor.extractObjectFromList(list, targetType); // 返回结果 return resultObject; } 最终通过setValue方法将加载结果设置到代理对象中。到此整个延迟加载的流程就完结了。3. 总结事实上和之前我们的猜想是一致的，延迟加载主要就是通过动态代理模式实现，通过代理拦截指定方法，从而现用现查，将结果设置到代理对象中返回，执行数据加载。撒花!!!但未完结，MyBatis作为一个优秀的框架，用到了相当多的设计模式，相当优美，下一篇文章，让我们走进MyBatis的设计模式。预告下篇文章：MyBatis设计模式
本系列是针对 MyBatis3 的源码分析文章已收录至精进MyBatis系列 系列其它文章 https://www.wormholestack.com/tag/MyBatis/源码阅读环境：https://gitee.com/M-Analysis/source_mybatis3 已填充关键注释1. 缓存的意义众所周知，和数据库打交道避免不了磁盘IO操作，那如果频繁的IO操作一定会对性能造成影响，所以减少与数据库的交互次数从而降低数据库压力进而提升查询效率是必要的。缓存是其中一种实现方式，简单的理解其实缓存就是内存中专门的一块区域，当从数据库中查询到一些数据将其放入缓存中，下次查询相同的数据时可以直接从缓存中获取数据即可，这样可减少了一步和数据库交互的过程。MyBatis提供了三级缓存机制，虽然MyBatis的缓存机制有些鸡肋，大部分开发人员多数情况都只会使用MyBatis默认缓存配置，又虽然MyBatis缓存机制有一些不足之处，出于学习还是决定写下此文章。2. 一级缓存2.1 猜想由一串测试代码引出今天的主角---一级缓存 /** * 测试一级缓存 */ @Test public void TEST_QUERY_BY_FIRST_CACHE() { //代理模式获取代理类 IUserMapper userMapper = sqlSession.getMapper(IUserMapper.class); //第⼀次sql语句查询 将查询结果放入缓存中 User user1 = userMapper.findById(1); System.out.println("第一次查询：" + user1); //第⼆次sql语句查询，由于是同⼀个sqlSession,会去查询缓存 如果缓存中没有则查库 缓存中有则直接取缓存 User user2 = userMapper.findById(1); System.out.println("第二次查询：" + user2); System.out.println(user1 == user2); } 上述测试代码，在同一个sqlSession中执行2次查询操作并记录操作日志，不过会发现上图sql执行语句只记录了一次查询操作，言外之意其实在同一个session中第一次sql查询会查库，后将查询结果放入缓存中，第二次sql查询，就会直接去查询缓存。不过目前来说也只是猜想，稍后我们翻看源码的时候会验证此猜想。不过话说回来，在同一个sqlSession这个前提下，多次查询走缓存不去查库，那如果库中数据更改了呢，数据被修改，删除，或者新增呢，岂不是数据不一致了，所以我猜想当执行了数据修改，删除，新增操作会重置缓存（重新查一次库），避免脏读，实际上MyBatis就是这么做的。编写如下测试类，运行代码： /** * 测试一级缓存 提交 是否重置缓存 */ @Test public void TEST_QUERY_COMMIT_BY_FIRST_CACHE() { //代理模式获取代理类 IUserMapper userMapper = sqlSession.getMapper(IUserMapper.class); //第⼀次sql语句查询 将查询结果放入缓存中 User user1 = userMapper.findById(1); System.out.println("第一次查询：" + user1); //更新操作 并提交sqlSession user1.setUsername("MRyan666"); userMapper.updateById(user1); sqlSession.commit(); User user2 = userMapper.findById(1); System.out.println("第二次查询：" + user2); System.out.println(user1 == user2); } 根据上述测试代码，初步证实了我的猜想在同一的sqlSession作用范围下，如果中间操作sqlSession执行了commit操作，其实也就是（删除，更新，新增）那么会清除sqlSession的缓存，保障缓存中拿到的数据一定是最新的，避免脏读。到这里先透一个底，其实上面所提及到的缓存在MyBatis中被命名为一级缓存。2.2 证明说了这么多猜想，眼见为实，接下来跟随作者一起通过对MyBatis源码解读探究一级缓存的原理及本质，在开始之前，提出几个问题，带入问题分析源码事半功倍。一级缓存是什么 数据结构是怎样的？一级缓存什么时候被构建一级缓存的不足及优化一起走进源码的世界上文我们探究了MyBatis执行SQL语句的流程 原文链接，虽然当时仅仅只是提了一嘴缓存相关，但是却提供了一个入口点，那就是SqlSession#selectList()-》DefaultSqlSession#selectList()-》Executor#query()-》BaseExecutor#query() //此方法在SimpleExecutor的父类BaseExecutor中实现 @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //根据传入的参数动态获得SQL语句，最后返回用BoundSql对象表示 BoundSql boundSql = ms.getBoundSql(parameter); //为本次查询创建缓存的Key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); // 查询 return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } 上述代码就是查询语句的实际执行逻辑，关键代码createCacheKey创建了缓存所需要的key @Override public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { if (closed) { throw new ExecutorException("Executor was closed."); } // 创建 CacheKey 对象 CacheKey cacheKey = new CacheKey(); // 设置 id、offset、limit、sql 到 CacheKey 对象中 cacheKey.update(ms.getId()); //offset当前为0 cacheKey.update(rowBounds.getOffset()); //limit=Integer.MAXVALUE cacheKey.update(rowBounds.getLimit()); //具体的SQL语句 cacheKey.update(boundSql.getSql()); //设置 ParameterMapping 数组的元素对应的每个 value 到 CacheKey 对象中 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); // mimic DefaultParameterHandler logic 这块逻辑，和 DefaultParameterHandler 获取 value 是一致的。 for (ParameterMapping parameterMapping : parameterMappings) { if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } // Sql所携带的参数 cacheKey.update(value); } } // 设置 Environment.id 到 CacheKey 对象中 if (configuration.getEnvironment() != null) { // issue #176 cacheKey.update(configuration.getEnvironment().getId()); } return cacheKey; } 其中ms.getId()就是MappedStatement的id，就是SQL语句所在位置的包名+类名+SQL名称cacheKey会调用5次update方法将所需要的数据存入updateList中 public void update(Object object) { // 方法参数 object 的 hashcode int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object); count++; // checksum 为 baseHashCode 的求和 checksum += baseHashCode; // 计算新的 hashcode 值 baseHashCode *= count; hashcode = multiplier * hashcode + baseHashCode; // 添加 object 到 updateList 中 updateList.add(object); } CacheKey重写了quals()方法 @Override public boolean equals(Object object) { if (this == object) { return true; } if (!(object instanceof CacheKey)) { return false; } final CacheKey cacheKey = (CacheKey) object; if (hashcode != cacheKey.hashcode) { return false; } if (checksum != cacheKey.checksum) { return false; } if (count != cacheKey.count) { return false; } // 比较 updateList 数组 //除去hashcode、checksum和count，如果两条SQL的updateList中下列五个值相同，即可以认为是相同的SQL-》Statement Id + Offset + Limmit + Sql + Params for (int i = 0; i < updateList.size(); i++) { Object thisObject = updateList.get(i); Object thatObject = cacheKey.updateList.get(i); if (!ArrayUtil.equals(thisObject, thatObject)) { return false; } } return true; } 除去hashcode、checksum和count，如果两条SQL的updateList中下列五个值相同，即可以认为是相同的SQL如果你还是不理解，那么就看如下图案例继续往下走，创建完缓存Key进入查询逻辑 @SuppressWarnings("unchecked") @Override 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()); // 已经关闭，则抛出 ExecutorException 异常 if (closed) { throw new ExecutorException("Executor was closed."); } // 清空本地缓存，如果 queryStack 为零，并且要求清空本地缓存。 if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { // queryStack + 1 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 - 1 queryStack--; } if (queryStack == 0) { // 执行延迟加载 for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 // 清空 deferredLoads deferredLoads.clear(); // 如果缓存级别是 LocalCacheScope.STATEMENT ，则进行清理 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; } 目光集中到关键代码 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; 上述代码实际意义是从localCache中获取结果，而这localCache从字面上理解就是缓存没跑了，实际上他确实是一级缓存。 /** * 本地缓存，即一级缓存 */ protected PerpetualCache localCache; 还记得文章开头的代入问题吗，localCache的数据结构是什么呢？实际上他就是一个本地map容器来存储缓存 /** * 永不过期的 Cache 实现类，基于 HashMap 实现类 * * @author Clinton Begin */ public class PerpetualCache implements Cache { /** * 标识 */ private final String id; /** * 缓存容器 */ private Map<Object, Object> cache = new HashMap<>(); } 继续回到刚才查询的逻辑中从一级缓存(localCache的Map容器中)中获取查询结果，如果能获取到则后处理，如果缓存获取不到的话，则从数据库中查询。 // 从一级缓存中，获取查询结果 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); } 详细来看看从数据库查询的处理queryFromDatabase // 从数据库中读取操作 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; // 在缓存中，添加占位对象。此处的占位符，和延迟加载有关，可见 `DeferredLoad#canLoad()` 方法 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; } 由此可见当缓存中没有触发一次查库后将查到的数据结果存入到一级缓存（localCache的Map容器）中。一级缓存的生命周期缓存的生命周期无非3步，初始化->缓存生成->缓存销毁上面我们讲到了缓存的生成过程，那么初始化是怎么处理的呢？如下：org.apache.ibatis.executor.BaseExecutor#BaseExecutor() protected BaseExecutor(Configuration configuration, Transaction transaction) { this.transaction = transaction; this.deferredLoads = new ConcurrentLinkedQueue<>(); //一级缓存初始化 this.localCache = new PerpetualCache("LocalCache"); this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache"); this.closed = false; this.configuration = configuration; this.wrapper = this; // 自己 } 那缓存销毁的过程是怎么处理的呢？定位到如下方法中org.apache.ibatis.executor.BaseExecutor#query() public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ... if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } ... } @Override public void clearLocalCache() { if (!closed) { localCache.clear(); localOutputParameterCache.clear(); } } 如果配置缓存作用域localCacheScope是STATEMENT则会每次清空缓存localCacheScope缓存的作用域有SESSION和STATEMENT2个配置选项，在中可以配置，默认情况下是SESSION。 /** * 本地( 一级 )缓存范围 * * @author Eduardo Macarron */ public enum LocalCacheScope { /** * 会话级 */ SESSION, /** * SQL 语句级 */ STATEMENT } 除此以外，我们再看下调用到clearLocalCache()方法的地方，就是清除缓存的 @Override public int update(MappedStatement ms, Object parameter) throws SQLException { //省略部分代码 clearLocalCache(); //省略部分代码 } @Override public void commit(boolean required) throws SQLException { //省略部分代码 clearLocalCache(); //省略部分代码 } @Override public void rollback(boolean required) throws SQLException { //省略部分代码 clearLocalCache(); .//省略部分代码 } @Override public void close(boolean forceRollback) { //省略部分代码 rollback(forceRollback); //省略部分代码 localCache = null; } 由上述的方法可以看出来，缓存在执行update()、commit()、rollback()、close()都会销毁。除此以外，就是在sqlSession被回收时会被销毁。因为缓存是跟随PerpetualCache localCache这个对象，而localCache是BaseExecutor--> DefaultSqlSession 的属性，也就是缓存的作用域是SqlSession，随着sqlSession消失而消失。阅读源码进行验证 @Override public void close() { try { // 关闭执行器 executor.close(isCommitOrRollbackRequired(false)); // 关闭所有游标 closeCursors(); // 重置 dirty 为 false dirty = false; } finally { ErrorContext.instance().reset(); } } executor.close(isCommitOrRollbackRequired(false));关闭执行器会伴随着事务的rollback，刚才提到rollback会销毁一级缓存。DefaultSqlSession#close() @Override public void close(boolean forceRollback) { try { // 回滚事务 try { rollback(forceRollback); } finally { // 关闭事务 if (transaction != null) { transaction.close(); } } } catch (SQLException e) { // Ignore. There's nothing that can be done at this point. log.warn("Unexpected exception on closing transaction. Cause: " + e); } finally { // 置空变量 transaction = null; deferredLoads = null; localCache = null; localOutputParameterCache = null; closed = true; } } BaseExecutor#rollback() @Override public void rollback(boolean required) throws SQLException { if (!closed) { try { // 清空本地缓存 clearLocalCache(); // 刷入批处理语句 flushStatements(true); } finally { if (required) { // 是否要求回滚事务。如果是，则回滚事务。 transaction.rollback(); } } } } 总结翻看完一级缓存的声明周期，其实会发现，一级缓存的生命周期其实和SqlSession声明周期一致。来吧，跑一个测试代码来验证刚才所说一级缓存在执行update()、commit()、rollback()、close() 或者SqlSession回收时都会销毁。 /** * 测试一级缓存commit()是否重置缓存 */ @Test public void TEST_QUERY_COMMIT_BY_FIRST_CACHE() { //代理模式获取代理类 IUserMapper userMapper = sqlSession.getMapper(IUserMapper.class); //第⼀次sql语句查询 将查询结果放入缓存中 User user1 = userMapper.findById(1); System.out.println("第一次查询：" + user1); //更新操作 并提交sqlSession user1.setUsername("MRyan666"); userMapper.updateById(user1); sqlSession.commit(); User user2 = userMapper.findById(1); System.out.println("第二次查询：" + user2); System.out.println(user1 == user2); } 运行结果如下 石锤了：由于中途SqlSession进行了commit操作导致缓存被销毁，于是第二次查询还是会查一次库：到此为止一级缓存已经被揭开了神秘的面纱。2.3 总结还记得文章刚开始的带入问题吗，一级缓存有什么不足吗？1. 使用一级缓存的时候作用在同一个SqlSession下，因为缓存不能跨会话共享，不同的会话之间对于相同的数据可能有不一样的缓存。当在有多个会话或者分布式环境下，可能会存在脏数据的问题。那这种问题如何解决呢？可以将一级缓存级别设置为Statement的级别2. 一级缓存的数据结构是HashMap，众所周知HashMap是线程不安全的，那一级缓存是否也是线程不安全，但由于一级缓存只存作用在同一个sqlSession中，所以基本上不会出现线程问题。关于一级缓存作用域为同一个SqlSession下的局限性，MyBatis提供了二级缓存。3. 二级缓存MyBatis提供了二级缓存，不过二级缓存不是默认开启的，若需开启二级缓存，需要做一些配置操作（可自定义二级缓存）二级缓存构建在一级缓存之上，一级缓存是和SqlSession绑定，而二级缓存是和Mapper具体的命名空间绑定，二级缓存是全局性的，一个Mapper中有一个Cache，相同的Mapper中多个不同的MappedStatement共用一个Cache。3.1 开启二级缓存1. 开启全局二级缓存配置 <settings> <setting name="cacheEnabled" value="true"/> </settings> 2. 在需要使用二级缓存的sqlMapConfig Mapper配置文件中配置标签 <cache></cache> 3. 在CURDmapper标签中配置useCache=true select * from user where id = #{id} 由此设置二级缓存将被开启，接下来跑个简单测试类来了解下二级缓存的使用3.2 测试二级缓存刚才也说了二级缓存作用范围在命名空间也就是namepace下的所有操作语句都影响这用一个Cache。接下来通过几个测试代码，来看一看二级缓存到底是什么吧。1）开启二级缓存，进行查询但不进行事务提交 /** * 测试二级缓存 sqlSession不commit 二级缓存不生效 */ @Test public void TEXT_SECOND_LEVEL_CACHE_INVALIDATION() { SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class); IUserMapper mapper2 = sqlSession2.getMapper(IUserMapper.class); User user1 = mapper1.findById(1); User user2 = mapper2.findById(1); System.out.println("第一次查询：" + user1); System.out.println("第二次查询：" + user2); System.out.println(user1 == user2); } 上述代码执行结果如下：然而发现二级缓存并没有起到作用，并没有生效，也就是说sqlSession1查询完数据之后，sqlSession2再进行同样的查询时并没有像想象中一样直接从缓存中获取数据，而是重新查库。2）上述同样的测试代码，不过将sqlSession1提交事务 这时二级缓存生效吗? /** * 测试二级缓存 sqlSession commit 二级缓存生效 */ @Test public void TEXT_SECOND_LEVEL_CACHE() { SqlSession sqlSession1 = sqlSessionFactory.openSession(); SqlSession sqlSession2 = sqlSessionFactory.openSession(); IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class); IUserMapper mapper2 = sqlSession2.getMapper(IUserMapper.class); User user1 = mapper1.findById(1); //sqlSession提交 二级缓存生效 sqlSession1.commit(); User user2 = mapper2.findById(1); System.out.println("第一次查询：" + user1); System.out.println("第二次查询：" + user2); System.out.println(user1 == user2); } 很明显，当提交事务之后 二级缓存生效了，那这是什么原理呢，稍后阅读源码的时候会详细说明。3.3 源码分析在上文原文链接分析SQL语句执行的过程中，我们了解了配置文件的加载和解析，其中缓存相关并没有去做说明，在本文会详细说明。其中xml解析工作实际上是交给XMLConfigBuilder#parse()方法实现,其中对配置文件中缓存相关的解析逻辑如下XMLConfigBuilder#cacheElement() // 解析 <cache /> 标签 private void cacheElement(XNode context) throws Exception { if (context != null) { //解析<cache/>标签的type属性，这里我们可以自定义cache的实现类，比如redisCache，如果没有自定义，这里使用和一级缓存相同的PERPETUAL String type = context.getStringAttribute("type", "PERPETUAL"); Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type); // 获得负责过期的 Cache 实现类 String eviction = context.getStringAttribute("eviction", "LRU"); Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction); // 清空缓存的频率。0 代表不清空 Long flushInterval = context.getLongAttribute("flushInterval"); // 缓存容器大小 Integer size = context.getIntAttribute("size"); // 是否序列化 boolean readWrite = !context.getBooleanAttribute("readOnly", false); // 是否阻塞 boolean blocking = context.getBooleanAttribute("blocking", false); // 获得 Properties 属性 Properties props = context.getChildrenAsProperties(); // 创建 Cache 对象 builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); } } 构建Cache对象逻辑如下： /** * 创建 Cache 对象 * * @param typeClass 负责存储的 Cache 实现类 * @param evictionClass 负责过期的 Cache 实现类 * @param flushInterval 清空缓存的频率。0 代表不清空 * @param size 缓存容器大小 * @param readWrite 是否序列化 * @param blocking 是否阻塞 * @param props Properties 对象 * @return Cache 对象 */ public Cache useNewCache(Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval, Integer size, boolean readWrite, boolean blocking, Properties props) { // 1.生成Cache对象 Cache cache = new CacheBuilder(currentNamespace) //这里如果我们定义了<cache/>中的type，就使用自定义的Cache,否则使用和一级缓存相同的PerpetualCache .implementation(valueOrDefault(typeClass, PerpetualCache.class)) .addDecorator(valueOrDefault(evictionClass, LruCache.class)) .clearInterval(flushInterval) .size(size) .readWrite(readWrite) .blocking(blocking) .properties(props) .build(); // 2.添加到Configuration中 configuration.addCache(cache); // 3.并将cache赋值给MapperBuilderAssistant.currentCache currentCache = cache; return cache; } 这里会发现每一个Mapper.xml文件都会被解析，创建一次Cache对象（就是二级缓存），存入Configuration中，并且赋值给MapperBuilderAssistant.currentCache接着在XMLMapperBuilder#configurationElement()解析Mapper节点的时候将生成的Cache包装到MappedStatement中 // 解析 `<mapper />` 节点 private void configurationElement(XNode context) { try { //省略上面代码 // 解析 <insert /> <update /> <delete /> 节点们 // 重点看这里！！！！ // 这里会将生成的Cache包装到对应的MappedStatement 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); } } // 解析 <insert /> <update /> <delete /> 节点们 private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); // 上面两块代码，可以简写成 buildStatementFromContext(list, configuration.getDatabaseId()); } private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { //遍历 <insert /> <update /> <delete /> 节点们 for (XNode context : list) { // 创建 XMLStatementBuilder 对象，执行解析 final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { // 每一条执行语句转换成一个MappedStatement statementParser.parseStatementNode(); } catch (IncompleteElementException e) { // 解析失败，添加到 configuration 中 configuration.addIncompleteStatement(statementParser); } } } 省略中间繁琐的过程，列出调用路径，感兴趣的小伙伴可以自行阅读源码、buildStatementFromContext#buildStatementFromContext()->XMLStatementBuilder#parseStatementNode()->MapperBuilderAssistant#addMappedStatement()在addMappedStatement方法中会构建MappedStatemet对象并将MappedStatement添加到Configuration对象中。 // 构建 MappedStatement 对象 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) { //省略代码 // 创建 MappedStatement 对象 MappedStatement statement = statementBuilder.build(); // 添加到 configuration 中 configuration.addMappedStatement(statement); return statement; } 至此缓存标签的解析任务就结束了配置文件加载解析之后肯定是要进行查询了，查询实际调用的是Executor执行器，而MyBatis二级缓存实现了Executor->CachingExecutor @Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // 获得 BoundSql 对象 BoundSql boundSql = ms.getBoundSql(parameterObject); // 创建 CacheKey 对象 CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); // 查询 return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } @Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { //从mappedStatement中获取Cache（二级缓存是从MappedStatement中获取，MappedStatement存在于全局配置中，会有多个CachingExecutor获取，可能会出现线程安全问题，例如脏读） //也就是我们上面解析Mapper中<cache/>标签中创建的，它保存在Configration中 //我们在初始化解析xml时分析过每一个MappedStatement都有一个Cache对象，就是这里 Cache cache = ms.getCache(); // 如果配置文件中没有配置 <cache>，则cache为空 if (cache != null) { //对应配置文件中的flushCache="true" 如果设置了flushCache为true则每次查询都会刷新缓存 flushCacheIfRequired(ms); //对应配置文件中的useCache 如果设置了useCache为true则走二级缓存 if (ms.isUseCache() && resultHandler == null) { // 暂时忽略，存储过程相关 ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") // 从二级缓存中，获取结果 List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { // 如果没有值，则执行查询，这个查询实际也是先走一级缓存查询，一级缓存也没有的话，则进行DB查询 list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 缓存查询结果 重点！！！！！ tcm.putObject(cache, key, list); // issue #578 and #116 } // 如果存在，则直接返回结果 return list; } } // 不使用缓存，则从数据库中查询(如果不是第一次查会查一级缓存) return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } 其中的重点逻辑，如果flushCache="true" 则每次都会刷新缓存。由于二级缓存是从MappedStatement中获取，是存在于全局配置，如果被多个CachingExecutor获取到，则一定会出现线程安全问题导致脏读，所以MyBatis为解决这个问题，在查询的过程中提供了TransactionalCacheManager作为事务缓存管理器 /** * {@link TransactionalCache} 事务缓存管理器 * * @author Clinton Begin */ public class TransactionalCacheManager { /** * // Cache 与 TransactionalCache 的映射关系表 */ private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>(); /** * 添加 Cache + KV ，到缓存中 * * @param cache Cache 对象 * @param key 键 * @param value 值 */ public void putObject(Cache cache, CacheKey key, Object value) { // 直接存入TransactionalCache的缓存中 getTransactionalCache(cache).putObject(key, value); } /** * 提交所有 TransactionalCache */ public void commit() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.commit(); } } } 实际上TransactionalCacheManager内部维护了一个Cache实例和TransactionalCache之间的映射，在CachingExecutor查询时缓存查询结果并事务提交之前会将所有从数据库中查询的结果放到entriesToAddOnCommit缓存集合中而它并不是真正的缓存对象Cache。只有查询的时候会直接从delegate（真正的缓存对象中查询），详细解释请看下述代码 public class TransactionalCache implements Cache { /** * 委托的 Cache 对象。 * * 实际上，就是二级缓存 Cache 对象。 */ private final Cache delegate; /** * 在事务被提交前，所有从数据库中查询的结果将缓存在此集合中 */ private final Map<Object, Object> entriesToAddOnCommit; /** * 在事务被提交前，当缓存未命中时，CacheKey 将会被存储在此集合中 */ private final Set<Object> entriesMissedInCache; @Override public Object getObject(Object key) { // 查询的时候是直接从delegate中去查询的，也就是从真正的缓存对象中查询 Object object = delegate.getObject(key); // 如果不存在，则添加到 entriesMissedInCache 中 if (object == null) { // 缓存未命中，则将 key 存入到 entriesMissedInCache 中 entriesMissedInCache.add(key); } // issue #146 // 如果clearOnCommit为true ，表示处于持续清空状态，则返回 null if (clearOnCommit) { return null; // 返回 value } else { return object; } } @Override public void putObject(Object key, Object object) { // 将键值对存入到entriesToAddOnCommit这个Map中，而非真实的缓存对象delegate中 entriesToAddOnCommit.put(key, object); } public void commit() { // 如果clearOnCommit为true ，则清空delegate缓存 if (clearOnCommit) { delegate.clear(); } // 将entriesToAddOnCommit、entriesMissedInCache 刷入delegate(cache) 中 flushPendingEntries(); // 重置 reset(); } /** * 将entriesToAddOnCommit、entriesMissedInCache刷入delegate(二级缓存)中 */ private void flushPendingEntries() { // 将entriesToAddOnCommit中的内容转存到delegate中 for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) { //在这里真正的将entriesToAddOnCommit的对象以此添加到delegate中，只有这时，二级缓存才真正的生效 delegate.putObject(entry.getKey(), entry.getValue()); } // 将entriesMissedInCache刷入delegate中 for (Object entry : entriesMissedInCache) { if (!entriesToAddOnCommit.containsKey(entry)) { delegate.putObject(entry, null); } } } } 总结一下：存储二级缓存对象的时候将对象放入了TransactionalCache.entriesToAddOnCommit这个Map中(但是每次查询的时候是直接从TransactionalCache.delegate中查询)，所以我们的测试案例一才会没有生效。但是当SqlSession提交或者关闭时，二级缓存就会生效，这时为什么呢？来看一看SqlSession得commit方法做了什么CahingExecutor#commit() @Override public void commit(boolean required) throws SQLException { // 执行 delegate 对应的方法 delegate.commit(required); // 提交 TransactionalCacheManager tcm.commit(); } TransactionalCacheManger#commit() /** * 提交所有 TransactionalCache */ public void commit() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.commit(); } } TransactionalCache#commit() public void commit() { // 如果clearOnCommit为true ，则清空delegate缓存 if (clearOnCommit) { delegate.clear(); } // 将entriesToAddOnCommit、entriesMissedInCache 刷入delegate(cache) 中 flushPendingEntries(); // 重置 reset(); } 再来看看flushPendingEntries方法 /** * 将entriesToAddOnCommit、entriesMissedInCache刷入delegate(二级缓存)中 */ private void flushPendingEntries() { // 将entriesToAddOnCommit中的内容转存到delegate中 for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) { //在这里真正的将entriesToAddOnCommit的对象以此添加到delegate中，只有这时，二级缓存才真正的生效 delegate.putObject(entry.getKey(), entry.getValue()); } // 将entriesMissedInCache刷入delegate中 for (Object entry : entriesMissedInCache) { if (!entriesToAddOnCommit.containsKey(entry)) { delegate.putObject(entry, null); } } } 在本方法中才真正的将entriesToAddOnCommit的对象以此添加到delegate中，只有这时，二级缓存才真正的生效，由此也验证了我们的测试二案例。SqlSession的更新操作具体做了什么？CachingExecutor#update @Override public int update(MappedStatement ms, Object parameterObject) throws SQLException { // 如果需要清空缓存，则进行清空 flushCacheIfRequired(ms); // 执行 delegate 对应的方法 return delegate.update(ms, parameterObject); } /** * 如果需要清空缓存，则进行清空 * * @param ms MappedStatement 对象 */ private void flushCacheIfRequired(MappedStatement ms) { Cache cache = ms.getCache(); if (cache != null && ms.isFlushCacheRequired()) { // 是否需要清空缓存 //重点！！！ tcm.clear(cache); } } TransactionalCacheManager#clear() /** * 清空缓存 * * @param cache Cache 对象 */ public void clear(Cache cache) { getTransactionalCache(cache).clear(); } TransactionalCache#clear() @Override public void clear() { // 标记clearOnCommit为true clearOnCommit = true; // 清空entriesToAddOnCommit 清除缓存 entriesToAddOnCommit.clear(); } 由此会发现如果数据进行了变更MyBatis会清除缓存3.4 二级缓存和一级缓存谁先被执行呢？二级缓存-》一级缓存-》数据库查询逻辑如下：CachingExecutor#query() @Override 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); //对应配置文件中的useCache 如果设置了useCache为true则走二级缓存 if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") // 从二级缓存中，获取结果 List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { // 如果没有值，则执行查询，这个查询实际也是先走一级缓存查询，一级缓存也没有的话，则进行DB查询 list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 缓存查询结果 tcm.putObject(cache, key, list); // issue #578 and #116 } // 如果存在，则直接返回结果 return list; } } // 不使用缓存，则从数据库中查询(如果不是第一次查会查一级缓存) return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } 如果二级缓存关闭，直接判断一级缓存是否有数据，如果没有就查数据库如果二级缓存开启，先判断二级缓存有没有数据，如果有就直接返回；如果没有，就查询一级缓存，如果有就返回，没有就查询数据库3.5 总结1. MyBatis二级缓存并不适用于经常增删改的数据，一旦数据变更二级缓存就会别MyBatis清空销毁。2. 虽然二级缓存相对于一级缓存SqlSession之间共享缓存数据的粒度更细，可以达到namespace级别，但在二级缓存的作用域下一个Mapper文件，也就是一个namespace下所有的sqlSession都共用二级缓存，虽然可以解决一级缓存的脏读问题，但是并不能解决本身访问两个不同的Mapper存在的脏读问题（因为在二级缓存也是基于本地实现的（数据结构HashMap））。3. 所以在工作中并不建议开启二级缓存。可以将MyBatis当做一个单纯的ORM框架使用不过话又说回来，虽然MyBatis的缓存机制有些鸡肋，但是并不妨碍它是一个优秀的开源框架，设计的相当优美，代码量也不是很多，适合阅读学习。到此为止，先从缓存存在的意义说起，并从单元测试作为入口点出发，阅读MyBatis源码证明了关于一级缓存和二级缓存的猜想，总结了MyBatis缓存机制的不足。撒花！！！但未完结，下一篇文章，让我们对延迟加载原理进行剖析。预告下篇文章：延迟加载原理剖析
本系列是针对 MyBatis3 的源码分析文章已收录至精进MyBatis系列 系列其它文章 https://www.wormholestack.com/tag/MyBatis/源码阅读环境：https://gitee.com/M-Analysis/source_mybatis3 已填充关键注释本文主要通过浅析MyBatis如何执行一个SQL语句（流程）为引，引出MyBatis的整体框架设计。作为MyBatis系列第一篇文章，必然先了解一下MyBatis的由来，所以在文章开始之前，我们来思考一个问题，MyBatis为什么诞生？1. 诞生一个轮子的诞生定是有其原因的，JDBC的诞生链接了程序和数据库，可以为多种关系数据库提供统一访问。 但是传统的JDBC方式存在很多缺点例如：数据库连接创建、释放频繁造成系统资源浪费，从⽽影响系统性能。Sql语句在代码中硬编码，造成代码不易维护，实际应用中sql变化的可能较⼤，sql变动需要改变 java代码。使用preparedStatement向占有位符号传参数存在硬编码，因为sql语句的where条件不⼀定，可能多也可能少，修改sql还要修改代码，系统不易维护。对结果集解析存在硬编码(查询列名)，sql变化导致解析代码变化，系统不易维护。为解决以上的问题，MyBatis被创造出来了，那它究竟是怎么规避解决以上原生JDBC的缺点呢，请往下看，正文来喽。2. 架构设计上面有提到JDBC的问题数据库连接创建，释放频繁导致的资源浪费，为解决这类问题，我们能想到的解决方案：肯定是池化，利用使用数据库连接池加载并初始化连接资源，池化管理。SQL语句在代码中的硬编码问题，为解决这类问题，我们能想到的解决方案：将SQL语句抽离到XML配置文件中，不需要改动代码，配置作为分离SQL及业务之间的桥梁。占位符传参硬编码问题，为解决这类问题，我们能想到的解决方案：利用反射，内省等技术，自动将实体与表进行属性和字段自动映射。当然我不是说说而已，MyBatis其实也是这么做的，ps：（这里我们先简单提一嘴，后面的文章《手撸简易版MBatis》会详细介绍）重点来喽，MyBatis架构师如何设计的Mybatis的功能架构分为三层：API接口层：提供给外部使用的接口 API，开发⼈员通过这些本地API来操纵数据库。接口层⼀接收 到调用请求就会调用数据处理层来完成具体的数据处理。MyBatis和数据库的交互有两种方式：使用传统的MyBatis提供的API使用Mapper代理的方式数据处理层：负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成⼀次数据库操作。基础⽀撑层：负责最基础的功能⽀撑，包括连接管理、事务管理、配置加载和缓存处理，这些都是共用的东西，将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。2.1 主要组件及其相互关系上述层次图中涉及到的重要模块，下述详细说明：SqlSession--》作为MyBatis⼯作的主要顶层API，表示和数据库交互的会话，完成必要数 据库增删改查功能。 Executor--》调度执行StatementHandler、ParameterHandler、ResultHandler执行相应的SQL语句 StatementHandler--》封装了JDBC Statement操作，负责对JDBC statement的操作，如设置参数、将Statement结果集转换成List集合。 ParameterHandler--》负责对用户传递的参数转换成JDBC Statement所需要的参数。处理SQL参数。 ResultSetHandler--》结果集ResultSet封装处理返回。 TypeHandler--》负责java数据类型和jdbc数据类型之间的映射和转换。 MappedStatement--》 MappedStatement维护了⼀条＜select | update | delete | insert＞节点的封装。 SqlSource--》负责根据用户传递的parameterObject，动态地生成SQL语句，将信息封装到BoundSql对象中，并返回。 BoundSql--》表示动态生成的SQL语句以及相应的参数信息。3. 执行SQL语句大概流程加载配置，以XML为例，将主配置文件内容解析到Configuration中，将SQL配置信息加载成为多个MapperdStatement对象，存入内存中。接口层调用MyBatis提供的API 传入参数对象，将请求传递给下层的数据处理层进行处理。简单的来说数据处理层完成的任务如下：Parameterhandler完成参数映射（参数映射配置，参数映射 类型解析）根据SQL的ID搜寻对应的MappedStatement对象，根绝传入的参数对象解析MappedStatement对象，SqlSource做SQL语句解析，动态SQL生成，获取需要执行的SQL语句和执行必要参数。Executor执行SQL语句，获取数据库连接，根据得到的最终SQL语句和执行传入参数到数据库执行，并得到执行结果。根据MappedStatement对象中的结果映射配置对得到的执行结果ResultSetHandler进行转换处理，并得到最终的处理结果。释放连接资源。最终将ResultSet结果集逐层返回。简单描述了一下大概流程，如此没有活力的文字肯定不适合理解，那接下来就是源码剖析验证以上观点4. 源码剖析就以单元测试执行数据库查询为例，对初始化到SQL的执行流程，进行剖析。4.1 初始化以标准的单元测试作为入口 开始分析 /* 测试查询 */ @Test public void TEST_QUERY_ONE_TO_ONE() throws IOException { //1. 加载sqlMapConfig.xml配置文件，将其转换成输入流存入内存中 InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); //2. 解析配置文件，封装Configuration对象 创建DefaultSqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); // 3. 生产了DefaultSqlSession实例对象 设置了事务不自动提交 完成了executor对象的创建 SqlSession sqlSession = sqlSessionFactory.openSession(); //JDK动态代理 执行JDBC操作 IOrderMapper mapper = sqlSession.getMapper(IOrderMapper.class); List<Order> orderAndUser = mapper.findOrderAndUser(); for (Order order : orderAndUser) { System.out.println(order); } }//2. 解析配置文件，封装Configuration对象 创建DefaultSqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);从代码结构上来看，通过默认的SqlSessionFactoryBuilder构建了一个SqlSessionFactory对象 入参是上一步加载到内存字节流的SqlMapConfig的配置，来看下具体代码是怎么做的。 /** * 1. build入口 * * @param inputStream * @return */ public SqlSessionFactory build(InputStream inputStream) { //调用重载方法 return build(inputStream, null, null); } /** * 2. 重载build * * @param inputStream * @param environment * @param properties * @return */ public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { //创建XMLConfigBuilder 解析Mybatis配置文件 其实就是解析sqlMapConfig.xml XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); //parse 执行XML解析 //build 创建 DefaultSqlSessionFactory 对象 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. } } } 关键代码parser.parse()的主要工作其实是通过XPathParser解析sqlMapConfig.xml将配置信息全部加载到内存中 利用org.apache.ibatis.session.Configuration实例维护（实际上根据MyBatis配置文件标签定位 读取节点内数据 封装到Configuration中） /** * 解析 XML 成 Configuration 对象。 * * @return Configuration 对象 */ public Configuration parse() { // 若已解析，抛出 BuilderException 异常 if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } // 标记已解析 parsed = true; ///parser是XPathParser解析器对象，读取节点内数据，<configuration>是MyBatis配置文件中的顶层标签 // 解析 XML configuration 节点 parseConfiguration(parser.evalNode("/configuration")); return configuration; }/** * 解析 XML * * 具体 MyBatis 有哪些 XML 标签，参见 《XML 映射配置文件》http://www.mybatis.org/mybatis-3/zh/configuration.html * * @param root 根节点 */ private void parseConfiguration(XNode root) { try { //issue #117 read properties first // 解析 <properties/> 标签 propertiesElement(root.evalNode("properties")); // 解析 <settings/> 标签 Properties settings = settingsAsProperties(root.evalNode("settings")); // 加载自定义的 VFS 实现类 loadCustomVfs(settings); // 解析 <typeAliases /> 标签 typeAliasesElement(root.evalNode("typeAliases")); // 解析 <plugins/> 标签 pluginElement(root.evalNode("plugins")); // 解析 <objectFactory/> 标签 objectFactoryElement(root.evalNode("objectFactory")); // 解析 <objectWrapperFactory/> 标签 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // 解析 <reflectorFactory/> 标签 reflectorFactoryElement(root.evalNode("reflectorFactory")); // 赋值 <settings/> 到 Configuration 属性 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 解析 <environments/> 标签 environmentsElement(root.evalNode("environments")); // 解析 <databaseIdProvider/> 标签 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); } } 什么是Configuration？Configuration故名之意MyBatis配置核心 其对象结构和XML配置文件几乎相对应其中最为主要的当属MappedStatement /** * MappedStatement 映射 *· * KEY：`${namespace}.${id}` */ protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection"); /** * MapperRegistry对象 */ protected final MapperRegistry mapperRegistry = new MapperRegistry(this); MappedStatement与Mapper配置⽂件中的⼀个select/update/insert/delete节点相对应。mapper中配置的标签都被封装到了此对象中，主要⽤途是描述⼀条SQL语句。 /** * 映射的语句，每个 、<insert />、<update />、<delete /> 对应一个 MappedStatement 对象 * * * @author Clinton Begin */ public final class MappedStatement { /** * 资源引用的地址 */ private String resource; /** * Configuration 对象 */ private Configuration configuration; /** * 编号 */ private String id; /** * 语句类型 */ private StatementType statementType; /** * 结果集类型 */ private ResultSetType resultSetType; /** * ParameterMap 对象 */ private ParameterMap parameterMap; /** * ResultMap 集合 */ private List<ResultMap> resultMaps;也就是说一个自定义SQL Mapper配置文件的标签(select、insert、update、delete等）会在初始化配置文件时被解析封装成多个MappedStatement对象，并存储在Configuration对象的mappedStatements属性中，MappedStatement映射 KEY：${namespace}.${id}=类全限定名+方法名衔接上文，在SqlSessionFactoryBuilder#build方法执行完parser.parse()逻辑，会创建DefaultSqlSessionFactory对象 /** * 创建 DefaultSqlSessionFactory 对象 * * @param config * @return */ public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); 到此初始化操作实际上已经完毕了，接下来则是分析执行SQL的流程。4.2 执行SQL流程上一步拿到了SqlSessionFactory 利用工厂方式设计模式创建SqlSessionSqlSession是⼀个接⼝，是MyBatis和数据库交互的顶层类，通常将它与ThreadLocal绑定，⼀个会话使⽤⼀个SqlSession,并且在使⽤完毕后需要close，它有两个实现类：DefaultSqlSession (默认)和SqlSessionManager (弃⽤)public class DefaultSqlSession implements SqlSession { private final Configuration configuration; //执行器 private final Executor executor; private final boolean autoCommit; private boolean dirty; private List<Cursor<?>> cursorList; public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) { this.configuration = configuration; this.executor = executor; this.dirty = false; this.autoCommit = autoCommit; } //省略 }DefaultSqlSession中的两个重要属性，其一Configuration就是上面讲过的MyBatis配置核心，另一个Executor则是执行器，源码openSession中实际上也是交给了Executor来专门执行操作Executor也是⼀个接⼝，他有三个常⽤的实现类： BatchExecutor (重⽤语句并执⾏批量更新) ReuseExecutor (重⽤预处理语句 prepared statements) SimpleExecutor (普通的执⾏器，默认) /** * openSession * * @return */ @Override public SqlSession openSession() { //configuration.getDefaultExecutorType() 取SIMPLE SimpleExecutor return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } /** * openSession的多个重载方法可以指定获得的SeqSession的Executor类型和事务的处理 * * @param execType 为Executor的类型 * @param level 事务隔离级别 * @param autoCommit 否开启事务 * @return */ private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { // 获得Environment对象 final Environment environment = configuration.getEnvironment(); // 创建Transaction对象 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 创建Executor对象 final Executor executor = configuration.newExecutor(tx, execType); // 创建DefaultSqlSession对象 return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { // 如果发生异常，则关闭Transaction对象 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是MyBatis和数据库交互的顶层API接口层，实际的执行工作还是交给Executor来操作。比如selectList @Override public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); } private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { try { //根据传⼊的全限定名+⽅法名从映射的Map中取出MappedStatement对象 MappedStatement ms = configuration.getMappedStatement(statement); //调⽤Executor中的⽅法处理 RowBounds是⽤来逻辑分⻚ wrapCollection(parameter)是⽤来装饰集合或者数组参数 return executor.query(ms, wrapCollection(parameter), rowBounds, handler); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } 从源码里其实可以看出，在Executor执行真正的query方法之前，从上一步封装好的Configuration中根据全限定名+方法名从映射Map中取出对应的MappedStatement对象，传递给Executor执行继续源码中的步骤 进入executor.query() /** * SimpleExecutor的⽗类BaseExecutor中实现 * * @param ms * @param parameter * @param rowBounds * @param resultHandler * @param <E> * @return * @throws SQLException */ @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //根据传⼊的参数动态获得SQL语句，最后返回⽤BoundSql对象表示 BoundSql boundSql = ms.getBoundSql(parameter); //为本次查询创建缓存的Key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }源码中getBoundSql这一步，其实就是标记处理，对#{}的解析工作，将实际的参数替换值存储，完成占位符解析工作，解析出来真实sql语句缓存这一块暂时先跳过，后续章节《深入浅出MyBatis三级缓存》会详细剖析调用的查询逻辑 /** * 查询重载 * * @param ms * @param parameter * @param rowBounds * @param resultHandler * @param key * @param boundSql * @param <E> * @return * @throws SQLException */ @SuppressWarnings("unchecked") @Override 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; } /** * 从数据库中查询 * * @param ms * @param parameter * @param rowBounds * @param resultHandler * @param key * @param boundSql * @param <E> * @return * @throws SQLException */ private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; // 在缓存中，添加占位对象。此处的占位符，和延迟加载有关，可见 `DeferredLoad#canLoad()` 方法 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; } 源码中执行了doQuery（ms, parameter, rowBounds, resultHandler, boundSql）方法 实际上是抽象方法，其中SimpleExecutor中的具体实现如下： /** * SimpleExecutor中实现⽗类的doQuery抽象方法 * * @param ms * @param parameter * @param rowBounds * @param resultHandler * @param boundSql * @param <E> * @return * @throws SQLException */ @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(); //传⼊参数创建StatementHanlder对象来执⾏查询 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); //创建jdbc中的statement对象 stmt = prepareStatement(handler, ms.getStatementLog()); // StatementHandler 进⾏处理 return handler.query(stmt, resultHandler); } finally { //关闭StatementHandler closeStatement(stmt); } } /** * 创建Statement的⽅法 * * @param handler * @param statementLog * @return * @throws SQLException */ private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; //条代码中的getConnection⽅法经过重重调用最后会调⽤openConnection⽅法，从连接池中获取连接 Connection connection = getConnection(statementLog); //创建Statement对象 stmt = handler.prepare(connection, transaction.getTimeout()); //设置SQL参数，PrepareStatement对象占位符 handler.parameterize(stmt); return stmt; } 上述的Executor.query()⽅法⼏经转折，最后会创建⼀个StatementHandler对象，然后将必要的参数传递给StatementHandler，使⽤StatementHandler来完成对数据库的查询，最终返回List结果集。总结一下，从上述源码分析中，得到的信息：Executor的功能根据参数传递，对SQL语句解析，动态替换生成BoundSql对象，传递给StatementHandler调用创建JDBCStatement连接对象，传递给StatementHandler对象那么问题来了，StatementHandler在最开始分析的那张结构层次的图中有过显示，那它是什么，作用是什么？4.3 剖析StatementHandlerStatementHandler对象封装了JDBC Statement操作，负责对JDBC statement的操作，如设置参数、将Statement结果集转换成List集合。其实它主要完成两个工作：对于JDBC的PreparedStatement类型的对象，创建的过程中，我们使⽤的是SQL语句字符串会包含若⼲个？占位符，我们其后再对占位符进⾏设置。StatementHandler通过parameterize(statement)⽅法对Statement进⾏设置；StatementHandler通过List query(Statement statement, ResultHandler resultHandler)方法来完成执⾏Statement，和将Statement对象返回的resultSet封装成List；进⼊到StatementHandler的parameterize(statement)方法的实现： @Override public void parameterize(Statement statement) throws SQLException { //使⽤ParameterHandler对象来完成对Statement的设值 parameterHandler.setParameters((PreparedStatement) statement); } @Override public void setParameters(PreparedStatement ps) { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); // 遍历ParameterMapping数组 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { for (int i = 0; i < parameterMappings.size(); i++) { // 获得ParameterMapping对象 ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } // 获得typeHandler、jdbcType属性 TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } // 设置 ? 占位符的参数 try { typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (TypeException | SQLException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } } }从上述的代码可以看到StatementHandler的parameterize(Statement)方法调用了ParameterHandler的setParameters(statement)方法，ParameterHandler的setParameters(Statement )⽅法负责根据我们输⼊的参数，对statement对象的?占位符处进行赋值。进入PreparedStatementHandler#query方法 @Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { // 1.调⽤preparedStatement。execute()方法， // 将resultSet交给ResultSetHandler处理 PreparedStatement ps = (PreparedStatement) statement; //执行JDBC操作数据库 ps.execute(); //2.使⽤ ResultHandler 来处理 ResultSet return resultSetHandler.handleResultSets(ps); }上述代码我们可以看出，StatementHandler的query方法其实是调用了ResultSetHandler的handleResultSets方法，handleResultSets方法会将Statement语句执行后生成的resultSet结果集转换成List结果集具体操作如下源码所示： // // HANDLE RESULT SETS // // 处理 {@link java.sql.ResultSet} 结果集 @Override public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); // 多 ResultSet 的结果集合，每个 ResultSet 对应一个 Object 对象。而实际上，每个 Object 是 List<Object> 对象。 // 在不考虑存储过程的多 ResultSet 的情况，普通的查询，实际就一个 ResultSet ，也就是说，multipleResults 最多就一个元素。 final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0; // 获得首个 ResultSet 对象，并封装成 ResultSetWrapper 对象 ResultSetWrapper rsw = getFirstResultSet(stmt); // 获得 ResultMap 数组 // 在不考虑存储过程的多 ResultSet 的情况，普通的查询，实际就一个 ResultSet ，也就是说，resultMaps 就一个元素。 List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); // 校验 while (rsw != null && resultMapCount > resultSetCount) { // 获得ResultMap对象 ResultMap resultMap = resultMaps.get(resultSetCount); // 处理ResultSet ，将结果添加到 multipleResults 中 handleResultSet(rsw, resultMap, multipleResults, null); // 获得下一个ResultSet对象，并封装成 ResultSetWrapper 对象 rsw = getNextResultSet(stmt); // 清理 cleanUpAfterHandlingResultSet(); // resultSetCount ++ resultSetCount++; } // 因为 `mappedStatement.resultSets` 只在存储过程中使用，本系列暂时不考虑，忽略即可 String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } // 如果是 multipleResults 单元素，则取首元素返回 return collapseSingleResultList(multipleResults); }拿到了ResultSet结果集，在由下不断回传给上层最后传递到SqlSession，一个SQL语句执行完毕。不知道大家还记不记得文章开头我们引出了一张图，在此进行完源码剖析之后，再回味这张图，思考一下会不会有恍然大悟的感觉。4.4 Mapper代理方式思考⼀个问题，通常的Mapper接⼝我们都没有实现的方法却可以使用 //JDK动态代理 IOrderMapper mapper = sqlSession.getMapper(IOrderMapper.class); List<Order> orderAndUser = mapper.findOrderAndUser(); for (Order order : orderAndUser) { System.out.println(order); }是因为什么呢？答案很简单 动态代理的作用上面我们刚分析过，在MyBatis初始化的时候，Configuration内部维护了一个HashMap用于存放Mapper接口的工厂类MapperRegistry，在mappers中可以配置接口的包路径，类路径<mappers> <mapper class="com.mryan.mapper.UserMapper"/> <package name="com.mryan.mapper"/> </mappers>解析mappers标签，根绝配置文件中是增删改查的标识标签封装成MappedStatement对象 存入mappedStatements中，为下文使用，解析的时候会将接口对应的MapperProxyFactory对象，存入Map中（key：class，value：MapperProxyFactory） public class DefaultSqlSession implements SqlSession { //省略 @Override public <T> T getMapper(Class<T> type) { return configuration.getMapper(type, this); } //省略 }进入configuration.getMapper(type, this)方法public class Configuration { //省略 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } //省略 }继续往下走进入mapperProxyFactory.newInstance(sqlSession)方法 public class MapperRegistry { //省略 @SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { // 获得MapperProxyFactory对象 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); // 不存在，则抛出BindingException异常 if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } // 通过JDK动态代理工厂生成实例。 try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } //省略 } @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy); } //MapperProxyFactory类中的newInstance方法 public T newInstance(SqlSession sqlSession) { // 创建了JDK动态代理的invocationHandler接口的实现类mapperProxy final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); // 调用了重载方法 return newInstance(mapperProxy); } 其中MapperProxy其实就是通过动态代理创建的实现类，进入MapperProxy是实现方法public class MapperProxy<T> implements InvocationHandler, Serializable {霍，这一打眼，没跑了，JDK动态代理，实现了InvocationHandler接口重写了invoke方法，进入invoke方法查看源码 @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 { // 重点在这：MapperMethod最终调用了执行的方法 return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } @Override public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { //关键 return mapperMethod.execute(sqlSession, args); }分析到这里胜利的曙光终于到出现了SqlSession有insert有update有select有delete，通过代理模式，程序怎么知道什么操作改调用什么方法呢？mapperMethod.execute方法实际做的就是“分发”的一个过程，根据判断mapper方法类型，来确定最终调用的是SqlSession的哪个方法 public Object execute(SqlSession sqlSession, Object[] args) { Object result; //判断mapper中的方法类型，最终调用的还是SqlSession中的方法 switch (command.getType()) { case INSERT: { // 转换参数 Object param = method.convertArgsToSqlCommandParam(args); // 执行 INSERT 操作 // 转换 rowCount result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { // 转换参数 Object param = method.convertArgsToSqlCommandParam(args); // 转换 rowCount result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { // 转换参数 Object param = method.convertArgsToSqlCommandParam(args); // 转换 rowCount result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: // 无返回，并且有 ResultHandler 方法参数，则将查询的结果，提交给 ResultHandler 进行处理 if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; // 执行查询，返回列表 } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); // 执行查询，返回 Map } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); // 执行查询，返回 Cursor } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); // 执行查询，返回单个对象 } else { // 转换参数 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.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } // 返回结果为 null ，并且返回类型为基本类型，则抛出 BindingException 异常 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; }到此为止，对MyBatis的诞生，架构设计，初始化源码剖析，执行SQL语句源码剖析，MyBatis的代理模式，已经分析完了。撒花 但未完结，有关MyBatis相关可不仅仅如此，如此优秀的框架一定还有很多令人学习寻味的地方，下一篇文章，让我们走进MyBatis的三级缓存。预告下篇文章：深入浅出MyBatis三级缓存