mybatis学习第⼗部分:Mybatis源码剖析
10.1传统⽅式源码剖析:
源码剖析-初始化
Inputstream inputstream = Resources.getResourceAsStream("mybatis- config.xml"); //这⼀⾏代码正是初始化⼯作的开始。 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
进⼊源码分析:
// 1.我们最初调⽤的build public SqlSessionFactory build (InputStream inputStream){ //调⽤了重载⽅法 return build(inputStream, null, null); } // 2.调⽤的重载⽅法 public SqlSessionFactory build (InputStream inputStream, String environment, Properties properties){ try { // XMLConfigBuilder是专⻔解析mybatis的配置⽂件的类 XMLConfigBuilder parser = new XMLConfigBuilder(inputstream, environment, properties); //这⾥⼜调⽤了⼀个重载⽅法。parser.parse()的返回值是Configuration对象 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e) }
MyBatis在初始化的时候,会将MyBatis的配置信息全部加载到内存中,使⽤
org.apache.ibatis.session.Configuratio n 实例来维护
下⾯进⼊对配置⽂件解析部分:
⾸先对Configuration对象进⾏介绍:
Configuration对象的结构和xml配置⽂件的对象⼏乎相同。
回顾⼀下xml中的配置标签有哪些:
properties (属性),settings (设置),typeAliases (类型别名),typeHandlers (类型处理
器),objectFactory (对象⼯⼚),mappers (映射器)等 Configuration也有对应的对象属性来封
装它们
也就是说,初始化配置⽂件信息的本质就是创建Configuration对象,将解析的xml数据封装到
Configuration内部属性中
/** * 解析 XML 成 Configuration 对象。 */ public Configuration parse () { //若已解析,抛出BuilderException异常 if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } //标记已解析 parsed = true; // 解析 XML configuration 节点 parseConfiguration(parser.evalNode("/configuration")); return configuration; } /** *解析XML */ 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 /> 标签 databaseldProviderElement(root.evalNode("databaseldProvider")); // 解析 <typeHandlers /> 标签 typeHandlerElement(root.evalNode("typeHandlers")); //解析<mappers />标签 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration.Cause:" + e, e); } }
介绍⼀下 MappedStatement :
作⽤:MappedStatement与Mapper配置⽂件中的⼀个select/update/insert/delete节点相对应。
mapper中配置的标签都被封装到了此对象中,主要⽤途是描述⼀条SQL语句。
初始化过程:回顾刚开 始介绍的加载配置⽂件的过程中,会对mybatis-config.xm l中的各个标签都进⾏
解析,其中有mappers 标签⽤来引⼊mapper.xml⽂件或者配置mapper接⼝的⽬录。
<select id="getUser" resultType="user" > select * from user where id=#{id} </select>
这样的⼀个select标签会在初始化配置⽂件时被解析封装成⼀个MappedStatement对象,然后存储在
Configuration对象的mappedStatements属性中,mappedStatements 是⼀个HashMap,存储时key
=全限定类名+⽅法名,value =对应的MappedStatement对象。
•在configuration中对应的属性为
Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement> ("Mapped Statements collection")
在 XMLConfigBuilder 中的处理:
private void parseConfiguration(XNode root) { try { //省略其他标签的处理 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause:" + e, e); } }
到此对xml配置⽂件的解析就结束了,回到步骤2.中调⽤的重载build⽅法
// 5.调⽤的重载⽅法 public SqlSessionFactory build(Configuration config) { //创建了 DefaultSqlSessionFactory 对象,传⼊ Configuration 对象。 return new DefaultSqlSessionFactory(config); }
源码剖析-执⾏SQL流程
先简单介绍SqlSession :
SqlSession是⼀个接⼝,它有两个实现类:DefaultSqlSession (默认)和
SqlSessionManager (弃⽤,不做介绍)
SqlSession是MyBatis中⽤于和数据库交互的顶层类,通常将它与ThreadLocal绑定,⼀个会话使⽤⼀
个SqlSession,并且在使⽤完毕后需要close
public class DefaultSqlSession implements SqlSession { private final Configuration configuration; private final Executor executor; }
SqlSession中的两个最重要的参数,configuration与初始化时的相同,Executor为执⾏器
Executor:
Executor也是⼀个接⼝,他有三个常⽤的实现类:
BatchExecutor (重⽤语句并执⾏批量更新)
ReuseExecutor (重⽤预处理语句 prepared statements)
SimpleExecutor (普通的执⾏器,默认)
继续分析,初始化完毕后,我们就要执⾏SQL 了
SqlSession sqlSession = factory.openSession(); List<User> list = sqlSession.selectList("com.lagou.mapper.UserMapper.getUserByName");
获得 sqlSession
//6. 进⼊ o penSession ⽅法。 public SqlSession openSession() { //getDefaultExecutorType()传递的是SimpleExecutor return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } //7. 进⼊penSessionFromDataSource。 //ExecutorType 为Executor的类型,TransactionIsolationLevel为事务隔离级别, autoCommit是否开启事务 //openSession的多个重载⽅法可以指定获得的SeqSession的Executor类型和事务的处理 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); //根据参数创建指定类型的Executor 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() }
执⾏ sqlsession 中的 api
//8.进⼊selectList⽅法,多个重载⽅法。 public <E > List < E > selectList(String statement) { return this.selectList(statement, null); public <E > List < E > selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); public <E > List < E > selectList(String statement, Object parameter, RowBounds rowBounds) { try { //根据传⼊的全限定名+⽅法名从映射的Map中取出MappedStatement对象 MappedStatement ms = configuration.getMappedStatement(statement); //调⽤Executor中的⽅法处理 //RowBounds是⽤来逻辑分⻚ // wrapCollection(parameter)是⽤来装饰集合或者数组参数 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(); }
源码剖析-executor
继续源码中的步骤,进⼊executor.query()
//此⽅法在SimpleExecutor的⽗类BaseExecutor中实现 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); } //进⼊query的重载⽅法中//此⽅法在SimpleExecutor的⽗类BaseExecutor中实现 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); } //进⼊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; } //从数据库查询 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(); //传⼊参数创建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 { closeStatement(stmt); } } //创建Statement的⽅法 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; //条代码中的getConnection⽅法经过重重调⽤最后会调⽤openConnection⽅法,从连接池 中获 得连接。 Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); return stmt; } //从连接池获得连接的⽅法 protected void openConnection() throws SQLException { if (log.isDebugEnabled()) { log.debug("Opening JDBC Connection"); } //从连接池获得连接 connection = dataSource.getConnection(); if (level != null) { connection.setTransactionIsolation(level.getLevel()); } }
上述的Executor.query()⽅法⼏经转折,最后会创建⼀个StatementHandler对象,然后将必要的参数传
递给
StatementHandler,使⽤StatementHandler来完成对数据库的查询,最终返回List结果集。
从上⾯的代码中我们可以看出,Executor的功能和作⽤是:
(1、根据传递的参数,完成SQL语句的动态解析,⽣成BoundSql对象,供StatementHandler使⽤; (2、为查询创建缓存,以提⾼性能 (3、创建JDBC的Statement连接对象,传递给*StatementHandler*对象,返回List查询结果。
源码剖析-StatementHandler
StatementHandler对象主要完成两个⼯作:
对于JDBC的PreparedStatement类型的对象,创建的过程中,我们使⽤的是SQL语句字符串会包
含若⼲个?占位符,我们其后再对占位符进⾏设值。StatementHandler通过
parameterize(statement)⽅法对 S tatement 进⾏设值;
StatementHandler 通过 List query(Statement statement, ResultHandler resultHandler)⽅法来
完成执⾏Statement,和将Statement对象返回的resultSet封装成List;
进⼊到 StatementHandler 的 parameterize(statement)⽅法的实现:
public void parameterize(Statement statement) throws SQLException { //使⽤ParameterHandler对象来完成对Statement的设值 parameterHandler.setParameters((PreparedStatement) statement); } /** ParameterHandler 类的 setParameters(PreparedStatement ps) 实现 * 对某⼀个Statement进⾏设置参数 * */ public void setParameters(PreparedStatement ps) throws SQLException { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { for (int i = 0; i < parameterMappings.size(); i++) { 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); } // 每⼀个 Mapping都有⼀个 TypeHandler,根据 TypeHandler 来对 preparedStatement 进 ⾏设置参数 TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull(); //设置参数 typeHandler.setParameter(ps, i + 1, value, jdbcType); } } } }
从上述的代码可以看到,StatementHandler的parameterize(Statement)⽅法调⽤了
ParameterHandler的setParameters(statement)⽅法,
ParameterHandler的setParameters(Statement )⽅法负责根据我们输⼊的参数,对statement对象的
?占位符处进⾏赋值。
进⼊到StatementHandler 的 List query(Statement statement, ResultHandler resultHandler)⽅法的
实现:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { // 1.调⽤preparedStatemnt。execute()⽅法,然后将resultSet交给ResultSetHandler处 理 PreparedStatement ps = (PreparedStatement) statement; ps.execute(); //2.使⽤ ResultHandler 来处理 ResultSet return resultSetHandler.<E> handleResultSets(ps); }
从上述代码我们可以看出,StatementHandler 的List query(Statement statement, ResultHandler
resultHandler)⽅法的实现,是调⽤了 ResultSetHandler 的 handleResultSets(Statement)⽅法。
ResultSetHandler 的 handleResultSets(Statement)⽅法会将 Statement 语句执⾏后⽣成的 resultSet
结 果集转换成List结果集
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); }
10.2 Mapper代理⽅式:
回顾下写法:
public static void main(String[] args) { //前三步都相同 InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = factory.openSession(); //这⾥不再调⽤SqlSession的api,⽽是获得了接⼝对象,调⽤接⼝中的⽅法。 UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> list = mapper.getUserByName("tom"); }
思考⼀个问题,通常的Mapper接⼝我们都没有实现的⽅法却可以使⽤,是为什么呢?答案很简单动态
代理
开始之前介绍⼀下MyBatis初始化时对接⼝的处理:MapperRegistry是Configuration中的⼀个属性,
它内部维护⼀个HashMap⽤于存放mapper接⼝的⼯⼚类,每个接⼝对应⼀个⼯⼚类。mappers中可以
配置接⼝的包路径,或者某个具体的接⼝类。
<mappers> <mapper class="com.lagou.mapper.UserMapper"/> <package name="com.lagou.mapper"/> </mappers>
源码剖析-getmapper()
进⼊ sqlSession.getMapper(UserMapper.class)中
//DefaultSqlSession 中的 getMapper public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); } //configuration 中的给 g etMapper public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } //MapperRegistry 中的 g etMapper public <T> T getMapper(Class<T> type, SqlSession sqlSession) { //从 MapperRegistry 中的 HashMap 中拿 MapperProxyFactory 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); } } //MapperProxyFactory 类中的 newInstance ⽅法 public T newInstance(SqlSession sqlSession) { //创建了 JDK动态代理的Handler类 final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); //调⽤了重载⽅法 return newInstance(mapperProxy); } //MapperProxy 类,实现了 InvocationHandler 接⼝ public class MapperProxy<T> implements InvocationHandler, Serializable { //省略部分源码 private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache; //构造,传⼊了 SqlSession,说明每个session中的代理对象的不同的! public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } //省略部分源码 }
源码剖析-invoke()
在动态代理返回了示例后,我们就可以直接调⽤mapper类中的⽅法了,但代理对象调⽤⽅法,执⾏是
在MapperProxy中的invoke⽅法中
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); } // 获得 MapperMethod 对象 final MapperMethod mapperMethod = cachedMapperMethod(method); //重点在这:MapperMethod最终调⽤了执⾏的⽅法 return mapperMethod.execute(sqlSession, args); }
进⼊execute⽅法:
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; }
10.3 ⼆级缓存源码剖析:
⼆级缓存构建在⼀级缓存之上,在收到查询请求时,MyBatis ⾸先会查询⼆级缓存,若⼆级缓存未命
中,再去查询⼀级缓存,⼀级缓存没有,再查询数据库。
⼆级缓存------》 ⼀级缓存------》数据库
与⼀级缓存不同,⼆级缓存和具体的命名空间绑定,⼀个Mapper中有⼀个Cache,相同Mapper中的
MappedStatement共⽤⼀个Cache,⼀级缓存则是和 SqlSession 绑定。
启⽤⼆级缓存
分为三步⾛:
1)开启全局⼆级缓存配置:
<settings> <setting name="cacheEnabled" value="true"/> </settings>
2) 在需要使⽤⼆级缓存的Mapper配置⽂件中配置标签
<cache></cache>
3)在具体CURD标签上配置 useCache=true
<select id="findById" resultType="com.lagou.pojo.User" useCache="true"> select * from user where id = #{id} </select>
标签 < cache/> 的解析
根据之前的mybatis源码剖析,xml的解析⼯作主要交给XMLConfigBuilder.parse()⽅法来实现
// XMLConfigBuilder.parse() public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration"));// 在这⾥ return configuration; } // parseConfiguration() // 既然是在xml中添加的,那么我们就直接看关于mappers标签的解析 private void parseConfiguration(XNode root) { try { Properties settings = settingsAsPropertiess(root.evalNode("settings")); propertiesElement(root.evalNode("properties")); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectionFactoryElement(root.evalNode("reflectionFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); // 就是这⾥ mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } // mapperElement() private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); // 按照我们本例的配置,则直接⾛该if判断 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); // ⽣成XMLMapperBuilder,并执⾏其parse⽅法 mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
我们来看看解析Mapper.xml
// XMLMapperBuilder.parse() public void parse() { if (!configuration.isResourceLoaded(resource)) { // 解析mapper属性 configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingChacheRefs(); parsePendingStatements(); }
// configurationElement()
private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); // 最终在这⾥看到了关于cache属性的处理 cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); // 这⾥会将⽣成的Cache包装到对应的MappedStatement buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); } } // cacheElement() 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); String eviction = context.getStringAttribute("eviction", "LRU"); Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction); Long flushInterval = context.getLongAttribute("flushInterval"); Integer size = context.getIntAttribute("size"); boolean readWrite = !context.getBooleanAttribute("readOnly", false); boolean blocking = context.getBooleanAttribute("blocking", false); Properties props = context.getChildrenAsProperties(); // 构建Cache对象 builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); } }
先来看看是如何构建Cache对象的
MapperBuilderAssistant.useNewCache()
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中,
并将cache赋值给MapperBuilderAssistant.currentCache
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));将Cache
包装到MappedStatement
// buildStatementFromContext() private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); } //buildStatementFromContext() private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { // 每⼀条执⾏语句转换成⼀个MappedStatement statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } } // XMLStatementBuilder.parseStatementNode(); public void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); ... Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String parameterType = context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = resolveClass(parameterType); String resultMap = context.getStringAttribute("resultMap"); String resultType = context.getStringAttribute("resultType"); String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); ... // 创建MappedStatement对象 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); } // builderAssistant.addMappedStatement() public MappedStatement addMappedStatement( String id, ...) { if (unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; //创建MappedStatement对象 MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType) ... .flushCacheRequired(valueOrDefault(flushCache, !isSelect)) .useCache(valueOrDefault(useCache, isSelect)) .cache(currentCache);// 在这⾥将之前⽣成的Cache封装到MappedStatement ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement); return statement; }
我们看到将Mapper中创建的Cache对象,加⼊到了每个MappedStatement对象中,也就是同⼀个
Mapper中所有的2
有关于标签的解析就到这了。
查询源码分析
CachingExecutor
// CachingExecutor public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); // 创建 CacheKey CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // 从 MappedStatement 中获取 Cache,注意这⾥的 Cache 是从MappedStatement中获取的 // 也就是我们上⾯解析Mapper中<cache/>标签中创建的,它保存在Configration中 // 我们在上⾯解析blog.xml时分析过每⼀个MappedStatement都有⼀个Cache对象,就是这⾥ Cache cache = ms.getCache(); // 如果配置⽂件中没有配置 <cache>,则 cache 为空 if (cache != null) { //如果需要刷新缓存的话就刷新:flushCache="true" flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); // 访问⼆级缓存 List<E> list = (List<E>) tcm.getObject(cache, key); // 缓存未命中 if (list == null) { // 如果没有值,则执⾏查询,这个查询实际也是先⾛⼀级缓存查询,⼀级缓存也没 有的话,则进⾏DB查询 list = delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 缓存查询结果 tcm.putObject(cache, key, list); } return list; } } return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
如果设置了flushCache="true",则每次查询都会刷新缓存
<!-- 执⾏此语句清空缓存 --> <select id="findbyId" resultType="com.lagou.pojo.user" useCache="true" flushCache="true" > select * from t_demo </select>
如上,注意⼆级缓存是从 MappedStatement 中获取的。由于 MappedStatement 存在于全局配置
中,可以多个 CachingExecutor 获取到,这样就会出现线程安全问题。除此之外,若不加以控制,多个
事务共⽤⼀个缓存实例,会导致脏读问题。⾄于脏读问题,需要借助其他类来处理,也就是上⾯代码中
tcm 变量对应的类型。下⾯分析⼀下。
TransactionalCacheManager
/** 事务缓存管理器 */ public class TransactionalCacheManager { // Cache 与 TransactionalCache 的映射关系表 private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>(); public void clear(Cache cache) { // 获取 TransactionalCache 对象,并调⽤该对象的 clear ⽅法,下同 getTransactionalCache(cache).clear(); } public Object getObject(Cache cache, CacheKey key) { // 直接从TransactionalCache中获取缓存 return getTransactionalCache(cache).getObject(key); } public void putObject(Cache cache, CacheKey key, Object value) { // 直接存⼊TransactionalCache的缓存中 getTransactionalCache(cache).putObject(key, value); } public void commit() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.commit(); } } public void rollback() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.rollback(); } } private TransactionalCache getTransactionalCache(Cache cache) { // 从映射表中获取 TransactionalCache TransactionalCache txCache = transactionalCaches.get(cache); if (txCache == null) { // TransactionalCache 也是⼀种装饰类,为 Cache 增加事务功能 // 创建⼀个新的TransactionalCache,并将真正的Cache对象存进去 txCache = new TransactionalCache(cache); transactionalCaches.put(cache, txCache); } return txCache; } }
TransactionalCacheManager 内部维护了 Cache 实例与 TransactionalCache 实例间的映射关系,该类
也仅负责维护两者的映射关系,真正做事的还是 TransactionalCache。TransactionalCache 是⼀种缓
存装饰器,可以为 Cache 实例增加事务功能。我在之前提到的脏读问题正是由该类进⾏处理的。下⾯分
析⼀下该类的逻辑。
TransactionalCache
public class TransactionalCache implements Cache { //真正的缓存对象,和上⾯的Map<Cache, TransactionalCache>中的Cache是同⼀个 private final Cache delegate; private boolean clearOnCommit; // 在事务被提交前,所有从数据库中查询的结果将缓存在此集合中 private final Map<Object, Object> entriesToAddOnCommit; // 在事务被提交前,当缓存未命中时,CacheKey 将会被存储在此集合中 private final Set<Object> entriesMissedInCache; @Override public Object getObject(Object key) { // 查询的时候是直接从delegate中去查询的,也就是从真正的缓存对象中查询 Object object = delegate.getObject(key); if (object == null) { // 缓存未命中,则将 key 存⼊到 entriesMissedInCache 中 entriesMissedInCache.add(key); } if (clearOnCommit) { return null; } else { return object; } } @Override public void putObject(Object key, Object object) { // 将键值对存⼊到 entriesToAddOnCommit 这个Map中中,⽽⾮真实的缓存对象 delegate 中 entriesToAddOnCommit.put(key, object); } @Override public Object removeObject(Object key) { return null; } @Override public void clear() { clearOnCommit = true; // 清空 entriesToAddOnCommit,但不清空 delegate 缓存 entriesToAddOnCommit.clear(); } public void commit() { // 根据 clearOnCommit 的值决定是否清空 delegate if (clearOnCommit) { delegate.clear(); } // 刷新未缓存的结果到 delegate 缓存中 flushPendingEntries(); // 重置 entriesToAddOnCommit 和 entriesMissedInCache reset(); } public void rollback() { unlockMissedEntries(); reset(); } private void reset() { clearOnCommit = false; // 清空集合 entriesToAddOnCommit.clear(); entriesMissedInCache.clear(); } private void flushPendingEntries() { for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) { // 将 entriesToAddOnCommit 中的内容转存到 delegate 中 delegate.putObject(entry.getKey(), entry.getValue()); } for (Object entry : entriesMissedInCache) { if (!entriesToAddOnCommit.containsKey(entry)) { // 存⼊空值 delegate.putObject(entry, null); } } } private void unlockMissedEntries() { for (Object entry : entriesMissedInCache) { try { // 调⽤ removeObject 进⾏解锁 delegate.removeObject(entry); } catch (Exception e) { log.warn("..."); } } } }
存储⼆级缓存对象的时候是放到了TransactionalCache.entriesToAddOnCommit这个map中,但是每
次查询的时候是直接从TransactionalCache.delegate中去查询的,所以这个⼆级缓存查询数据库后,设
置缓存值是没有⽴刻⽣效的,主要是因为直接存到 delegate 会导致脏数据问题
为何只有SqlSession提交或关闭之后?
那我们来看下SqlSession.commit()⽅法做了什么
SqlSession
@Override public void commit(boolean force) { try { // 主要是这句 executor.commit(isCommitOrRollbackRequired(force)); dirty = false; } catch (Exception e) { throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } // CachingExecutor.commit() @Override public void commit(boolean required) throws SQLException { delegate.commit(required); tcm.commit();// 在这⾥ } // TransactionalCacheManager.commit() public void commit() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.commit();// 在这⾥ } } // TransactionalCache.commit() public void commit() { if (clearOnCommit) { delegate.clear(); } flushPendingEntries();//这⼀句 reset(); } // TransactionalCache.flushPendingEntries() private void flushPendingEntries() { for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) { // 在这⾥真正的将entriesToAddOnCommit的对象逐个添加到delegate中,只有这时,⼆ 级缓存才真正的⽣效 delegate.putObject(entry.getKey(), entry.getValue()); } for (Object entry : entriesMissedInCache) { if (!entriesToAddOnCommit.containsKey(entry)) { delegate.putObject(entry, null); } } }
⼆级缓存的刷新
我们来看看SqlSession的更新操作
public int update(String statement, Object parameter) { int var4; try { this.dirty = true; MappedStatement ms = this.configuration.getMappedStatement(statement); var4 = this.executor.update(ms, this.wrapCollection(parameter)); } catch (Exception var8) { throw ExceptionFactory.wrapException("Error updating database. Cause: " + var8, var8); } finally { ErrorContext.instance().reset(); } return var4; } public int update(MappedStatement ms, Object parameterObject) throws SQLException { this.flushCacheIfRequired(ms); return this.delegate.update(ms, parameterObject); } private void flushCacheIfRequired(MappedStatement ms) { //获取MappedStatement对应的Cache,进⾏清空 Cache cache = ms.getCache(); //SQL需设置flushCache="true" 才会执⾏清空 if (cache != null && ms.isFlushCacheRequired()) { this.tcm.clear(cache); } }
MyBatis⼆级缓存只适⽤于不常进⾏增、删、改的数据,⽐如国家⾏政区省市区街道数据。⼀但数据变
更,MyBatis会清空缓存。因此⼆级缓存不适⽤于经常进⾏更新的数据。
总结:
在⼆级缓存的设计上,MyBatis⼤量地运⽤了装饰者模式,如CachingExecutor, 以及各种Cache接⼝的
装饰器。
⼆级缓存实现了Sqlsession之间的缓存数据共享,属于namespace级别
⼆级缓存具有丰富的缓存策略。
⼆级缓存可由多个装饰器,与基础缓存组合⽽成
⼆级缓存⼯作由 ⼀个缓存装饰执⾏器CachingExecutor和 ⼀个事务型预缓存TransactionalCache完成。
10.4延迟加载源码剖析:
问题
在开发过程中很多时候我们并不需要总是在加载⽤户信息时就⼀定要加载他的订单信息。此时就是我们所说的延迟加载。
举个栗⼦
* 在⼀对多中,当我们有⼀个⽤户,它有个100个订单 在查询⽤户的时候,要不要把关联的订单查出来? 在查询订单的时候,要不要把关联的⽤户查出来? * 回答 在查询⽤户时,⽤户下的订单应该是,什么时候⽤,什么时候查询。 在查询订单时,订单所属的⽤户信息应该是随着订单⼀起查询出来。
延迟加载
就是在需要⽤到数据时才进⾏加载,不需要⽤到数据时就不加载数据。延迟加载也称懒加载。
* 优点: 先从单表查询,需要时再从关联表去关联查询,⼤⼤提⾼数据库性能,因为查询单表要⽐关联查询多张表 速度要快。 * 缺点: 因为只有当需要⽤到数据时,才会进⾏数据库查询,这样在⼤批量数据查询时,因为查询⼯作也要消耗时 间,所以可能造成⽤户等待时间变⻓,造成⽤户体验下降。 * 在多表中: ⼀对多,多对多:通常情况下采⽤延迟加载 ⼀对⼀(多对⼀):通常情况下采⽤⽴即加载 * 注意: 延迟加载是基于嵌套查询来实现的
实现
局部延迟加载
在association和collection标签中都有⼀个fetchType属性,通过修改它的值,可以修改局部的加载策略。
<!-- 开启⼀对多 延迟加载 --> <resultMap id="userMap" type="user"> <id column="id" property="id"></id> <result column="username" property="username"></result> <result column="password" property="password"></result> <result column="birthday" property="birthday"></result> <!-- fetchType="lazy" 懒加载策略 fetchType="eager" ⽴即加载策略 --> <collection property="orderList" ofType="order" column="id" select="com.lagou.dao.OrderMapper.findByUid" fetchType="lazy"> </collection> </resultMap> <select id="findAll" resultMap="userMap"> SELECT * FROM `user` </select>
全局延迟加载
在Mybatis的核⼼配置⽂件中可以使⽤setting标签修改全局的加载策略。
<settings> <!--开启全局延迟加载功能--> <setting name="lazyLoadingEnabled" value="true"/> </settings>
注意
<!-- 关闭⼀对⼀ 延迟加载 --> <resultMap id="orderMap" type="order"> <id column="id" property="id"></id> <result column="ordertime" property="ordertime"></result> <result column="total" property="total"></result> <!-- fetchType="lazy" 懒加载策略 fetchType="eager" ⽴即加载策略 --> <association property="user" column="uid" javaType="user" select="com.lagou.dao.UserMapper.findById" fetchType="eager"> </association> </resultMap> <select id="findAll" resultMap="orderMap"> SELECT * from orders </select>
延迟加载原理实现
它的原理是,使⽤ CGLIB 或 Javassist( 默认 ) 创建⽬标对象的代理对象。当调⽤代理对象的延迟加载属
性的 getting ⽅法时,进⼊拦截器⽅法。⽐如调⽤ a.getB().getName() ⽅法,进⼊拦截器的
invoke(...) ⽅法,发现 a.getB() 需要延迟加载时,那么就会单独发送事先保存好的查询关联 B
对象的 SQL ,把 B 查询上来,然后调⽤ a.setB(b) ⽅法,于是 a 对象 b 属性就有值了,接着完
成 a.getB().getName() ⽅法的调⽤。这就是延迟加载的基本原理
总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定⽅法,执⾏数据加载。
延迟加载原理(源码剖析)
MyBatis延迟加载主要使⽤:Javassist,Cglib实现,类图展示:
Setting 配置加载:
public class Configuration { /** aggressiveLazyLoading: * 当开启时,任何⽅法的调⽤都会加载该对象的所有属性。否则,每个属性会按需加载(参考 lazyLoadTriggerMethods). * 默认为true * */ protected boolean aggressiveLazyLoading; /** * 延迟加载触发⽅法 */ protected Set<String> lazyLoadTriggerMethods = new HashSet<String> (Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" })); /** 是否开启延迟加载 */ protected boolean lazyLoadingEnabled = false; /** * 默认使⽤Javassist代理⼯⼚ * @param proxyFactory */ public void setProxyFactory(ProxyFactory proxyFactory) { if (proxyFactory == null) { proxyFactory = new JavassistProxyFactory(); } this.proxyFactory = proxyFactory; } //省略... }
延迟加载代理对象创建
Mybatis的查询结果是由ResultSetHandler接⼝的handleResultSets()⽅法处理的。ResultSetHandler
接⼝只有⼀个实现,DefaultResultSetHandler,接下来看下延迟加载相关的⼀个核⼼的⽅法
<code class="language-Java">//#mark 创建结果对象 private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { this.useConstructorMappings = false; // reset previous mapping result final List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>(); final List<Object> constructorArgs = new ArrayList<Object>(); //#mark 创建返回的结果映射的真实对象 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) { // 判断属性有没配置嵌套查询,如果有就创建代理对象 if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { //#mark 创建延迟加载代理对象 resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); break; } } } this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result return resultObject; }
默认采⽤javassistProxy进⾏代理对象的创建
JavasisstProxyFactory实现
public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory { /** * 接⼝实现 * @param target ⽬标结果对象 * @param lazyLoader 延迟加载对象 * @param configuration 配置 * @param objectFactory 对象⼯⼚ * @param constructorArgTypes 构造参数类型 * @param constructorArgs 构造参数值 * @return */ @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); } //省略... /** * 代理对象实现,核⼼逻辑执⾏ */ private static class EnhancedResultObjectProxyImpl implements MethodHandler { /** * 创建代理对象 * @param type * @param callback * @param constructorArgTypes * @param constructorArgs * @return */ static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { ProxyFactory enhancer = new ProxyFactory(); enhancer.setSuperclass(type); try { //通过获取对象⽅法,判断是否存在该⽅法 type.getDeclaredMethod(WRITE_REPLACE_METHOD); // ObjectOutputStream will call writeReplace of objects returned by writeReplace if (log.isDebugEnabled()) { log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this"); } } catch (NoSuchMethodException e) { //没找到该⽅法,实现接⼝ enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class}); } catch (SecurityException e) { // nothing to do here } Object enhanced; Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]); Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]); try { //创建新的代理对象 enhanced = enhancer.create(typesArray, valuesArray); } catch (Exception e) { throw new ExecutorException("Error creating lazy proxy. Cause: " + e, e); } //设置代理执⾏器 ((Proxy) enhanced).setHandler(callback); return enhanced; } /** * 代理对象执⾏ * @param enhanced 原对象 * @param method 原对象⽅法 * @param methodProxy 代理⽅法 * @param args ⽅法参数 * @return * @throws Throwable */ @Override public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable { final String methodName = method.getName(); try { synchronized (lazyLoader) { if (WRITE_REPLACE_METHOD.equals(methodName)) { //忽略暂未找到具体作⽤ Object original; if (constructorArgTypes.isEmpty()) { original = objectFactory.create(type); } else { original = objectFactory.create(type, constructorArgTypes, constructorArgs); } PropertyCopier.copyBeanProperties(type, enhanced, original); if (lazyLoader.size() > 0) { return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs); } else { return original; } } else { //延迟加载数量⼤于0 if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) { //aggressive ⼀次加载性所有需要要延迟加载属性或者包含触发延迟加载⽅法 if (aggressive || lazyLoadTriggerMethods.contains(methodName)) { log.debug("==> laze lod trigger method:" + methodName + ",proxy method:" + methodProxy.getName() + " class:" + enhanced.getClass()); //⼀次全部加载 lazyLoader.loadAll(); } else if (PropertyNamer.isSetter(methodName)) { //判断是否为set⽅法,set⽅法不需要延迟加载 final String property = PropertyNamer.methodToProperty(methodName); lazyLoader.remove(property); } else if (PropertyNamer.isGetter(methodName)) { final String property = PropertyNamer.methodToProperty(methodName); if (lazyLoader.hasLoader(property)) { //延迟加载单个属性 lazyLoader.load(property); log.debug("load one :" + methodName); } } } } } return methodProxy.invoke(enhanced, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } }
注意事项
1. IDEA调试问题 当配置aggressiveLazyLoading=true,在使⽤IDEA进⾏调试的时候,如果断点打到
代理执⾏逻辑当中,你会发现延迟加载的代码永远都不能进⼊,总是会被提前执⾏。 主要产⽣的
原因在aggressiveLazyLoading,因为在调试的时候,IDEA的Debuger窗体中已经触发了延迟加载
对象的⽅法
从上述代码我们可以看出,StatementHandler 的List query(Statement statement, ResultHandlerresultHandler)⽅法的实现,是调⽤了 ResultSetHandler 的 handleResultSets(Statement)⽅法。String propertyName = parameterMapping.getProperty();if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448ask first for additional paramsvalue = 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); }// 每⼀个 Mapping都有⼀个 TypeHandler,根据 TypeHandler 来对preparedStatement 进 ⾏设置参数TypeHandler typeHandler = parameterMapping.getTypeHandler();JdbcType jdbcType = parameterMapping.getJdbcType();if (value == null && jdbcType == null) jdbcType =configuration.getJdbcTypeForNull();//设置参数typeHandler.setParameter(ps, i + 1, value, jdbcType);}}}}public <E> List<E> query(Statement statement, ResultHandler resultHandler)throws SQLException {// 1.调⽤preparedStatemnt。execute()⽅法,然后将resultSet交给ResultSetHandler处理PreparedStatement ps = (PreparedStatement) statement;ps.execute();//2.使⽤ ResultHandler 来处理 ResultSetreturn resultSetHandler.<E> handleResultSets(ps);}ResultSetHandler 的 handleResultSets(Statement)⽅法会将 Statement 语句执⾏后⽣成的 resultSet结 果集转换成List结果集
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix