10-Mybatis源码剖析 10.1 传统⽅式源码剖析 10.1.1 初始化过程 Inputstream inputstream = Resources.getResourceAsStream("mybatis-config.xml" ); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); public SqlSessionFactory build (InputStream inputStream) { return build(inputStream, null , null ); } public SqlSessionFactory build (InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession." , e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { } } }
MyBatis在初始化的时候,会将MyBatis的配置信息全部加载到内存中,使⽤org.apache.ibatis.session.Configuration
实例来维护
下⾯进⼊对配置⽂件解析部分:
⾸先对Configuration对象进⾏介绍:
Configuration对象的结构和xml配置⽂件的对象⼏乎相同。 回顾⼀下xml中的配置标签有哪些:properties (属性),settings (设置),typeAliases (类型别名),typeHandlers (类型处理器),objectFactory (对象⼯⼚),mappers (映射器)
等 Configuration也有对应的对象属性来封装它们。 也就是说,初始化配置⽂件信息的本质就是创建Configuration对象,将解析的xml数据封装到Configuration内部属性中。
public Configuration parse () { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once." ); } parsed = true ; parseConfiguration(parser.evalNode("/configuration" )); return configuration; } private void parseConfiguration (XNode root) { try { propertiesElement(root.evalNode("properties" )); Properties settings = settingsAsProperties(root.evalNode("settings" )); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases" )); pluginElement(root.evalNode("plugins" )); objectFactoryElement(root.evalNode("objectFactory" )); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory" )); reflectorFactoryElement(root.evalNode("reflectorFactory" )); settingsElement(settings); 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); } }
介绍⼀下 MappedStatement
: 作⽤:MappedStatement与Mapper配置⽂件中的⼀个select/update/insert/delete节点相对应。mapper中配置的标签都被封装到了此对象中,主要⽤途是描述⼀条SQL语句。初始化过程 :回顾刚开 始介绍的加载配置⽂件的过程中,会对mybatis-config.xml
中的各个标签都进⾏解析,其中有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> ms = 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⽅法:
public SqlSessionFactory build (Configuration config) { return new DefaultSqlSessionFactory(config); }
10.1.2 执⾏SQL流程 1. SqlSession 先简单介绍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为执⾏器
2. Executor Executor也是⼀个接⼝,他有三个常⽤的实现类:
BatchExecutor
(重⽤语句并执⾏批量更新)ReuseExecutor
(重⽤预处理语句 prepared statements)SimpleExecutor
(普通的执⾏器,默认)继续分析,初始化完毕后,我们就要执⾏SQL了,以selectList为例。
SqlSession sqlSession = factory.openSession(); List<User> list = sqlSession.selectList("com.lemon.mapper.UserMapper.getUsers" ); public SqlSession openSession () { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null , false ); } private SqlSession openSessionFromDataSource (ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null ; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } public <E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } public <E> List<E> query (MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } 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(); } deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { 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; } public <E> List<E> doQuery (MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null ; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } } private Statement prepareStatement (StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); return stmt; } protected Connection getConnection (Log statementLog) throws SQLException { Connection connection = transaction.getConnection(); if (statementLog.isDebugEnabled()) { return ConnectionLogger.newInstance(connection, statementLog, queryStack); } else { return connection; } }
上述的Executor.query()
⽅法⼏经转折,最后会创建⼀个StatementHandler
对象,然后将必要的参数传递给StatementHandler,使⽤StatementHandler来完成对数据库的查询,最终返回List结果集。
从上⾯的代码中我们可以看出,Executor的功能和作⽤是:
根据传递的参数,完成SQL语句的动态解析,⽣成BoundSql对象,供StatementHandler使⽤; 为查询创建缓存,以提⾼性能; 创建JDBC的Statement连接对象,传递给StatementHandler 对象,返回List查询结果。 3. StatementHandler StatementHandler对象主要完成两个⼯作:
对于JDBC的PreparedStatement类型的对象,创建的过程中,我们使⽤的是SQL语句字符串会包含若⼲个?
占位符,我们其后再对占位符进⾏设值。StatementHandler通过parameterize(statement)
⽅法对 Statement 进⾏设值; StatementHandler 通过 List query(Statement statement, ResultHandler resultHandler)
⽅法来完成执⾏Statement,和将Statement对象返回的resultSet封装成List; 进⼊到 StatementHandler 的 parameterize(statement)⽅法的实现:
public void parameterize (Statement statement) throws SQLException { parameterHandler.setParameters((PreparedStatement) statement); } public void setParameters (PreparedStatement ps) { 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)) { 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 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对象的?
占位符处进⾏赋值。
进⼊到StatementHandler 的 List query(Statement statement, ResultHandler resultHandler)⽅法的实现:
public <E> List<E> query (Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.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()); final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0 ; ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null ); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } 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++; } } 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(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> list = mapper.getUserByName("tom" ); }
思考⼀个问题:通常的Mapper接⼝我们都没有实现的⽅法却可以使⽤,是为什么呢? 答案很简单动态代理
开始之前介绍⼀下MyBatis初始化时对接⼝的处理:MapperRegistry
是Configuration
中的⼀个属性,它内部维护⼀个HashMap⽤于存放mapper接⼝的⼯⼚类,每个接⼝对应⼀个⼯⼚类。mappers中可以配置接⼝的包路径,或者某个具体的接⼝类。
<mappers> <mapper class ="com.lemon.mapper.UserMapper" /> <package name="com.lemon.mapper" /> </mappers>
当解析mappers
标签时,它会判断解析到的是mapper配置⽂件时,会再将对应配置⽂件中的增删改查标签封装成MappedStatement对象,存⼊mappedStatements
中。 当判断解析到接⼝时,会建此接⼝对应的MapperProxyFactory
对象,存⼊HashMap中,key =接⼝的字节码对象,value =此接⼝对应的MapperProxyFactory对象 。 10.2.1 -getmapper() 进⼊ sqlSession.getMapper(UserMapper.class )
中
public <T> T getMapper (Class<T> type) { return configuration.getMapper(type, this ); } public <T> T getMapper (Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } public <T> T getMapper (Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null ) { throw new BindingException("Type " + type + " is not known to the MapperRegistry." ); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } public T newInstance (SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } public class MapperProxy <T > implements InvocationHandler , Serializable { private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache; public MapperProxy (SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) { this .sqlSession = sqlSession; this .mapperInterface = mapperInterface; this .methodCache = methodCache; } }
10.2.2 invoke() 在动态代理返回了示例后,我们就可以直接调⽤mapper类中的⽅法了,但代理对象调⽤⽅法,执⾏是在MapperProxy``中的invoke
⽅法中:
public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this , args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
进⼊execute⽅法:
public Object execute (SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break ; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break ; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break ; } case SELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null ; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } 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()); } 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 绑定。
10.3.1 启⽤⼆级缓存 分为三步⾛:
开启全局⼆级缓存配置
<settings > <setting name ="cacheEnabled" value ="true" /> </settings >
在需要使⽤⼆级缓存的Mapper配置⽂件中配置标签
在具体CURD标签上配置 useCache=true
<select id ="findById" resultType ="com.lemon.pojo.User" useCache ="true" > select * from user where id = #{id} </select >
10.3.2 标签 < cache/> 的解析 根据之前的mybatis源码剖析,xml的解析⼯作主要交给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; } private void parseConfiguration (XNode root) { try { propertiesElement(root.evalNode("properties" )); Properties settings = settingsAsProperties(root.evalNode("settings" )); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases" )); pluginElement(root.evalNode("plugins" )); objectFactoryElement(root.evalNode("objectFactory" )); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory" )); reflectorFactoryElement(root.evalNode("reflectorFactory" )); settingsElement(settings); 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); } } 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 (resource != null && url == null && mapperClass == null ) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); 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." ); } } } } } public void parse () { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper" )); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); } 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" )); cacheElement(context.evalNode("cache" )); parameterMapElement(context.evalNodes("/mapper/parameterMap" )); resultMapElements(context.evalNodes("/mapper/resultMap" )); sqlElement(context.evalNodes("/mapper/sql" )); 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); } } private void cacheElement (XNode context) throws Exception { if (context != null ) { 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(); builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); } } public Cache useNewCache (Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval, Integer size, boolean readWrite, boolean blocking, Properties props) { Cache cache = new CacheBuilder(currentNamespace) .implementation(valueOrDefault(typeClass, PerpetualCache.class)) .addDecorator(valueOrDefault(evictionClass, LruCache.class)) .clearInterval(flushInterval) .size(size) .readWrite(readWrite) .blocking(blocking) .properties(props) .build(); configuration.addCache(cache); currentCache = cache; return cache; }
我们看到⼀个Mapper.xml只会解析⼀次标签,也就是只创建⼀次Cache
对象,放进configuration
中,并将cache赋值给MapperBuilderAssistant.currentCache
。
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
将Cache包装到MappedStatement中。
private void buildStatementFromContext (List<XNode> list) { if (configuration.getDatabaseId() != null ) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null ); } private void buildStatementFromContext (List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } } public void parseStatementNode () { String id = context.getStringAttribute("id" ); String databaseId = context.getStringAttribute("databaseId" ); if (!databaseIdMatchesCurrent(id, databaseId, this .requiredDatabaseId)) { return ; } 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" ); builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); } 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.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType) .resource(resource) .fetchSize(fetchSize) .timeout(timeout) .statementType(statementType) .keyGenerator(keyGenerator) .keyProperty(keyProperty) .keyColumn(keyColumn) .databaseId(databaseId) .lang(lang) .resultOrdered(resultOrdered) .resultSets(resultSets) .resultMaps(getStatementResultMaps(resultMap, resultType, id)) .resultSetType(resultSetType) .flushCacheRequired(valueOrDefault(flushCache, !isSelect)) .useCache(valueOrDefault(useCache, isSelect)) .cache(currentCache); 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中所有的SQL标签中。
有关于标签的解析就到这了。
10.3.3 CachingExecutor public <E> List<E> query (MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); 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 { Cache cache = ms.getCache(); if (cache != null ) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null ) { ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null ) { list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
如果设置了flushCache="true"
,则每次查询都会刷新缓存。
<select id ="findbyId" resultType ="com.lemon.pojo.user" useCache ="true" flushCache ="true" > select * from user where id = #{id} </select >
如上,注意⼆级缓存是从 MappedStatement 中获取的
。由于 MappedStatement 存在于全局配置中,可以多个 CachingExecutor
获取到,这样就会出现线程安全问题。除此之外,若不加以控制,多个事务共⽤⼀个缓存实例,会导致脏读问题。⾄于脏读问题,需要借助其他类来处理,也就是上⾯代码中 tcm
变量对应的类型。
下⾯分析⼀下。
10.3.4 TransactionalCacheManager public class TransactionalCacheManager { private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>(); public void clear (Cache cache) { getTransactionalCache(cache).clear(); } public Object getObject (Cache cache, CacheKey key) { return getTransactionalCache(cache).getObject(key); } public void putObject (Cache cache, CacheKey key, Object value) { 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) { return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new ); } }
TransactionalCacheManager
内部维护了 Cache 实例与 TransactionalCache
实例间的映射关系,该类也仅负责维护两者的映射关系,真正做事的还是 TransactionalCache。
TransactionalCache
是⼀种缓存装饰器,可以为 Cache 实例增加事务功能。我在之前提到的脏读问题正是由该类进⾏处理的。
下⾯分析⼀下该类的逻辑。
10.3.5 TransactionalCache public class TransactionalCache implements Cache { private static final Log log = LogFactory.getLog(TransactionalCache.class); private final Cache delegate; private boolean clearOnCommit; private final Map<Object, Object> entriesToAddOnCommit; private final Set<Object> entriesMissedInCache; public TransactionalCache (Cache delegate) { this .delegate = delegate; this .clearOnCommit = false ; this .entriesToAddOnCommit = new HashMap<>(); this .entriesMissedInCache = new HashSet<>(); } @Override public String getId () { return delegate.getId(); } @Override public int getSize () { return delegate.getSize(); } @Override public Object getObject (Object key) { Object object = delegate.getObject(key); if (object == null ) { entriesMissedInCache.add(key); } if (clearOnCommit) { return null ; } else { return object; } } @Override public ReadWriteLock getReadWriteLock () { return null ; } @Override public void putObject (Object key, Object object) { entriesToAddOnCommit.put(key, object); } @Override public Object removeObject (Object key) { return null ; } @Override public void clear () { clearOnCommit = true ; entriesToAddOnCommit.clear(); } public void commit () { if (clearOnCommit) { delegate.clear(); } flushPendingEntries(); 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()) { 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 { delegate.removeObject(entry); } catch (Exception e) { log.warn("Unexpected exception while notifiying a rollback to the cache adapter." + "Consider upgrading your cache adapter to the latest version. Cause: " + e); } } } }
存储⼆级缓存对象的时候是放到了TransactionalCache.entriesToAddOnCommit
这个map中,但是每次查询的时候是直接从TransactionalCache.delegate
中去查询的,所以这个⼆级缓存查询数据库后,设置缓存值是没有⽴刻⽣效的,主要是因为直接存到 delegate 会导致脏数据问题。
10.3.6 为何只有SqlSession提交或关闭之后缓存才会有效? SqlSession提交或关闭会刷新缓存。
1. SqlSession 那我们来看下SqlSession.commit()⽅法做了什么:
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(); } } public void commit (boolean required) throws SQLException { delegate.commit(required); tcm.commit(); } public void commit () { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.commit(); } } public void commit () { if (clearOnCommit) { delegate.clear(); } flushPendingEntries(); reset(); } private void flushPendingEntries () { for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) { delegate.putObject(entry.getKey(), entry.getValue()); } for (Object entry : entriesMissedInCache) { if (!entriesToAddOnCommit.containsKey(entry)) { delegate.putObject(entry, null ); } } }
2. ⼆级缓存的刷新 我们来看看SqlSession的更新操作
public int update (String statement, Object parameter) { try { dirty = true ; MappedStatement ms = configuration.getMappedStatement(statement); return executor.update(ms, wrapCollection(parameter)); } catch (Exception e) { throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } public int update (MappedStatement ms, Object parameterObject) throws SQLException { flushCacheIfRequired(ms); return delegate.update(ms, parameterObject); } private void flushCacheIfRequired (MappedStatement ms) { Cache cache = ms.getCache(); if (cache != null && ms.isFlushCacheRequired()) { tcm.clear(cache); } }
MyBatis⼆级缓存只适⽤于不常进⾏增、删、改的数据,⽐如国家⾏政区省市区街道数据。⼀但数据变更,MyBatis会清空缓存。因此⼆级缓存不适⽤于经常进⾏更新的数据。
10.3.7 总结 在⼆级缓存的设计上,MyBatis⼤量地运⽤了装饰者模式,如CachingExecutor, 以及各种Cache接⼝的装饰器。
⼆级缓存实现了Sqlsession之间的缓存数据共享,属于namespace级别
⼆级缓存具有丰富的缓存策略。 ⼆级缓存可由多个装饰器,与基础缓存组合⽽成 ⼆级缓存⼯作由 ⼀个缓存装饰执⾏器CachingExecutor
和 ⼀个事务型预缓存TransactionalCache
完成。 10.4 延迟加载源码剖析 10.4.1 什么是延迟加载? 介绍 问题
在开发过程中很多时候我们并不需要总是在加载⽤户信息时就⼀定要加载他的订单信息。此时就是我们所说的延迟加载。
举个栗⼦
在⼀对多中,当我们有⼀个⽤户,它有个100个订单
在查询⽤户的时候,要不要把关联的订单查出来? 在查询订单的时候,要不要把关联的⽤户查出来? 回答:
在查询⽤户时,⽤户下的订单应该是,什么时候⽤,什么时候查询。 在查询订单时,订单所属的⽤户信息应该是随着订单⼀起查询出来。 2. 延迟加载 就是在需要⽤到数据时才进⾏加载,不需要⽤到数据时就不加载数据。延迟加载也称懒加载。
3. 优缺点 在多表中:
⼀对多,多对多:通常情况下采⽤延迟加载 ⼀对⼀(多对⼀):通常情况下采⽤⽴即加载 注意:延迟加载是基于嵌套查询来实现的
10.4.2 使用 1. 局部延迟加载 在association和collection标签中都有⼀个fetchType属性,通过修改它的值,可以修改局部的加载策略。
<resultMap id ="userMap" type ="com.lemon.pojo.User" > <id property ="id" column ="id" > </id > <result property ="username" column ="username" > </result > <collection property ="orderList" ofType ="com.lemon.pojo.Order" select ="com.lemon.mapper.IOrderMapper.findOrderByUid" column ="id" fetchType ="lazy" > <id property ="id" column ="oid" /> <result property ="orderTime" column ="ordertime" /> <result property ="total" column ="total" /> </collection > </resultMap >
2. 全局延迟加载 在Mybatis的核⼼配置⽂件中可以使⽤setting标签修改全局的加载策略。
<settings > <setting name ="lazyLoadingEnabled" value ="true" /> </settings >
3. 关闭⼀对⼀延迟加载 <resultMap id ="orderMap" type ="order" > <id column ="id" property ="id" > </id > <result column ="ordertime" property ="ordertime" > </result > <result column ="total" property ="total" > </result > <association property ="user" column ="uid" javaType ="user" select ="com.lemon.dao.UserMapper.findById" fetchType ="eager" > </association > </resultMap >
10.4.3 延迟加载原理实现 它的原理是,使⽤ CGLIB
或Javassist( 默认 )
创建⽬标对象的代理对象。当调⽤代理对象的延迟加载属性的 Getter
⽅法时,进⼊拦截器⽅法。⽐如调⽤ a.getB().getName()
⽅法,进⼊拦截器的invoke(...)
⽅法,发现 a.getB() 需要延迟加载时,那么就会单独发送事先保存好的查询关联 B 对象的 SQL ,把 B 查询上来,然后调⽤a.setB(b) ⽅法,于是 a 对象 b属性就有值了,接着完成a.getB().getName() ⽅法的调⽤。这就是延迟加载的基本原理。
总结: 延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定⽅法,执⾏数据加载。
10.4.4 延迟加载原理(源码剖析) MyBatis延迟加载主要使⽤:Javassist
,Cglib实现
,类图展示:
1. Setting 配置加载 public class Configuration { protected boolean aggressiveLazyLoading; protected Set<String> lazyLoadTriggerMethods = new HashSet<String> (Arrays.asList(new String[] { "equals" , "clone" , "hashCode" , "toString" })); protected boolean lazyLoadingEnabled = false ; public void setProxyFactory (ProxyFactory proxyFactory) { if (proxyFactory == null ) { proxyFactory = new JavassistProxyFactory(); } this .proxyFactory = proxyFactory; } }
2. 延迟加载代理对象创建 Mybatis的查询结果是由ResultSetHandler接⼝的handleResultSets()⽅法处理的。ResultSetHandler 接⼝只有⼀个实现,DefaultResultSetHandler
,接下来看下延迟加载相关的⼀个核⼼的⽅法:
private Object createResultObject (ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { this .useConstructorMappings = false ; 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) { if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); break ; } } } this .useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); return resultObject; }
默认采⽤javassistProxy进⾏代理对象的创建:
protected ProxyFactory proxyFactory = new JavassistProxyFactory();
JavasisstProxyFactory实现:
class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory { /** * 接口实现 * * @param target 目标结果对象 * @param lazyLoader 延迟加载对象 * @param configuration 配置 * @param objectFactory 对象工厂 * @param constructorArgTypes 构造参数类型 * @param constructorArgs 构造参数值 * @return */ 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); } static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { // 创建 javassist ProxyFactory 对象 ProxyFactory enhancer = new ProxyFactory(); // 设置父类 enhancer.setSuperclass(type); // 根据情况,设置接口为 WriteReplaceInterface 。和序列化相关,可以无视 try { type.getDeclaredMethod(WRITE_REPLACE_METHOD); // 如果已经存在 writeReplace 方法,则不用设置接口为 WriteReplaceInterface // 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}); // 如果不存在 writeReplace 方法,则设置接口为 WriteReplaceInterface } 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; } // 代理对象实现,核心逻辑执行 private static class EnhancedResultObjectProxyImpl implements MethodHandler { public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { final Class<?> type = target.getClass(); // 创建 EnhancedResultObjectProxyImpl 对象 EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); // 创建代理对象 Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs); // 将 target 的属性,复制到 enhanced 中 PropertyCopier.copyBeanProperties(type, target, enhanced); return enhanced; } // 代理对象执行 public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable { final String methodName = method.getName(); try { synchronized (lazyLoader) { // 忽略 WRITE_REPLACE_METHOD ,和序列化相关 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 { if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) { // aggressive 一次加载性所有需要延迟加载属性或者包含触发延迟加载方法 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); } } } }
注意事项 IDEA调试问题 : 当配置aggressiveLazyLoading=true,在使⽤IDEA进⾏调试的时候,如果断点打到代理执⾏逻辑当中,你会发现延迟加载的代码永远都不能进⼊,总是会被提前执⾏。 主要产⽣的原因在aggressiveLazyLoading
,因为在调试的时候,IDEA的Debuger窗体中已经触发了延迟加载对象的⽅法。