Mybatis_第⼗部分:Mybatis源码剖析
10.1传统⽅式源码剖析:
源码剖析-初始化
Inputstream inputstream = Resources . getResourceAsStream ( "mybatisconfig.xml" );// 这⼀⾏代码正是初始化⼯作的开始。SqlSessionFactory factory = newSqlSessionFactoryBuilder (). build ( inputStream );
进⼊源码分析:
Inputstream inputstream = Resources . getResourceAsStream ( "mybatisconfig.xml" );// 这⼀⾏代码正是初始化⼯作的开始。SqlSessionFactory factory = newSqlSessionFactoryBuilder (). build ( inputStream );// 1. 我们最初调⽤的 buildpublic SqlSessionFactory build ( InputStream inputStream ){// 调⽤了重载⽅法return build ( inputStream , null , null );}// 2. 调⽤的重载⽅法public SqlSessionFactory build ( InputStream inputStream , Stringenvironment ,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 buildingSqlSession." , 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 beused 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 MapperConfiguration . 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 MapperConfiguration.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 ;j
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() 传递的是 SimpleExecutorreturnopenSessionFromDataSource ( 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 );// 根据参数创建指定类型的 Executorfinal Executor executor = configuration . newExecutor ( tx , execType );// 返回的是 DefaultSqlSessionreturn new DefaultSqlSession ( configuration , executor , autoCommit );} catch ( Exception e ){closeTransaction ( tx ); // may have fetched a connection so lets callclose()}
执⾏
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 , Objectparameter , 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 queryingdatabase. Cause: + e, e);} finally {ErrorContext . instance (). reset ();}
源码剖析-executor
继续源码中的步骤,进⼊
executor.query()
// 此⽅法在 SimpleExecutor 的⽗类 BaseExecutor 中实现public < E > List < E > query ( MappedStatement ms , Object parameter , RowBoundsrowBounds , ResultHandler resultHandler ) throws SQLException {// 根据传⼊的参数动态获得 SQL 语句,最后返回⽤ BoundSql 对象表示BoundSql boundSql = ms . getBoundSql ( parameter );// 为本次查询创建缓存的 KeyCacheKey key = createCacheKey ( ms , parameter , rowBounds , boundSql );return query ( ms , parameter , rowBounds , resultHandler , key , boundSql );}// 进⼊ query 的重载⽅法中public < E > List < E > query ( MappedStatement ms , Object parameter , RowBoundsrowBounds , ResultHandler resultHandler , CacheKey key , BoundSql boundSql )throws SQLException {ErrorContext . instance (). resource ( ms . getResource ()). activity ( "executinga 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 #601deferredLoads . clear ();if ( configuration . getLocalCacheScope () ==LocalCacheScope . STATEMENT ) { // issue #482 clearLocalCache();}}return list ;}// 从数据库查询private < E > List < E > queryFromDatabase ( MappedStatement ms , Objectparameter , 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 , RowBoundsrowBounds , 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 , LogstatementLog ) 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 ( "settingparameters ").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 #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 );}}}}
从上述的代码可以看到
,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 来处理 ResultSetreturn 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 ( "handlingresults" ). 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 = newSqlSessionFactoryBuilder (). 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>
•
当解析
mappers
标签时,它会判断解析到的是
mapper
配置⽂件时,会再将对应配置⽂件中的增删 改查标签 封装成MappedStatement
对象,存⼊
mappedStatements
中。
(
上⽂介绍了
)
当 判断解析到接⼝时,会 建此接⼝对应的MapperProxyFactory对象,存⼊
HashMap
中,
key =
接⼝的字节码对象,
value =
此接⼝对应的MapperProxyFactory
对象。
源码剖析-getmapper()
进⼊
sqlSession.getMapper(UserMapper.class )
中
//DefaultSqlSession 中的 getMapperpublic < T > T getMapper ( Class < T > type ) {return configuration . < T > getMapper ( type , this );}//configuration 中的给 g etMapperpublic < T > T getMapper ( Class < T > type , SqlSession sqlSession ) {return mapperRegistry . getMapper ( type , sqlSession );}//MapperRegistry 中的 g etMapperpublic < T > T getMapper ( Class < T > type , SqlSession sqlSession ) {// 从 MapperRegistry 中的 HashMap 中拿 MapperProxyFactoryfinal MapperProxyFactory < T > mapperProxyFactory =( MapperProxyFactory < T > ) knownMappers . get ( type );if ( mapperProxyFactory == null ) {throw new BindingException ( "Type " + type + " is not known to theMapperRegistry." );}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 ) throwsThrowable {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 操作// 转换 rowCountresult = rowCountResult ( sqlSession . insert ( command . getName (),param ));break ;}case UPDATE : {// 转换参数Object param = method . convertArgsToSqlCommandParam ( args );// 转换 rowCountresult = rowCountResult ( sqlSession . update ( command . getName (),param ));break ;}case DELETE : {// 转换参数Object param = method . convertArgsToSqlCommandParam ( args );// 转换 rowCountresult = 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 primitivereturn 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 usedonce." );}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 #631environmentsElement ( 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 = newXMLMapperBuilder ( 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 = newXMLMapperBuilder ( 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 onlyspecify 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 包装到对应的 MappedStatementbuildStatementFromContext ( 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 ,如果没有⾃定义,这⾥使⽤和⼀级缓存相同的 PERPETUALString 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.currentCachecurrentCache = 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 , StringrequiredDatabaseId ) {for ( XNode context : list ) {final XMLStatementBuilder statementParser = newXMLStatementBuilder ( configuration , builderAssistant , context ,requiredDatabaseId );try {// 每⼀条执⾏语句转换成⼀个 MappedStatementstatementParser . 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 = newMappedStatement . Builder ( configuration , id , sqlSource , sqlCommandType ).... flushCacheRequired ( valueOrDefault ( flushCache , ! isSelect )). useCache ( valueOrDefault ( useCache , isSelect )). cache ( currentCache ); // 在这⾥将之前⽣成的 Cache 封装到 MappedStatementParameterMap 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
// CachingExecutorpublic < E > List < E > query ( MappedStatement ms , Object parameterObject , RowBoundsrowBounds , ResultHandler resultHandler ) throws SQLException {BoundSql boundSql = ms . getBoundSql ( parameterObject );// 创建 CacheKeyCacheKey key = createCacheKey ( ms , parameterObject , rowBounds , boundSql );return query ( ms , parameterObject , rowBounds , resultHandler , key ,boundSql );}public < E > List < E > query ( MappedStatement ms , Object parameterObject , RowBoundsrowBounds , 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 = newHashMap < 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 ) {// 从映射表中获取 TransactionalCacheTransactionalCache 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 ;@Overridepublic 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 ;}}@Overridepublic void putObject ( Object key , Object object ) {// 将键值对存⼊到 entriesToAddOnCommit 这个 Map 中中,⽽⾮真实的缓存对象delegate 中entriesToAddOnCommit . put ( key , object );}@Overridepublic Object removeObject ( Object key ) {return null ;}@Overridepublic void clear () {clearOnCommit = true ;// 清空 entriesToAddOnCommit ,但不清空 delegate 缓存entriesToAddOnCommit . clear ();}public void commit () {// 根据 clearOnCommit 的值决定是否清空 delegateif ( clearOnCommit ) {delegate . clear ();}// 刷新未缓存的结果到 delegate 缓存中flushPendingEntries ();// 重置 entriesToAddOnCommit 和 entriesMissedInCachereset ();}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
@Overridepublic 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()@Overridepublic 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 ) throwsSQLException {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>
注意
7.
。
<!-- 关闭⼀对⼀ 延迟加载 --><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 resultfinal List & lt ; Class & lt ; ?& gt ; & gt ; constructorArgTypes = newArrayList & lt ; Class & lt ; ?& gt ; & gt ;();final List & lt ; Object & gt ; constructorArgs = new ArrayList & lt ; Object & gt ;();//#mark 创建返回的结果映射的真实对象Object resultObject = createResultObject ( rsw , resultMap ,constructorArgTypes , constructorArgs , columnPrefix );if ( resultObject != null & amp ; & amp ; ! hasTypeHandlerForResultObject ( rsw ,resultMap . getType ())) {final List & lt ; ResultMapping & gt ; propertyMappings =resultMap . getPropertyResultMappings ();for ( ResultMapping propertyMapping : propertyMappings ) {// 判断属性有没配置嵌套查询,如果有就创建代理对象if ( propertyMapping . getNestedQueryId () != null & amp ; & amp ;propertyMapping . isLazy ()) {//#mark 创建延迟加载代理对象resultObject =configuration . getProxyFactory (). createProxy ( resultObject , lazyLoader ,configuration , objectFactory , constructorArgTypes , constructorArgs );break ;}}}this . useConstructorMappings = resultObject != null & amp ; & amp ;! constructorArgTypes . isEmpty (); // set current mapping resultreturn resultObject ;}
默认采⽤
javassistProxy
进⾏代理对象的创建
JavasisstProxyFactory
实现
public class JavassistProxyFactory implementsorg . apache . ibatis . executor . loader . ProxyFactory {/*** 接⼝实现* @param target ⽬标结果对象* @param lazyLoader 延迟加载对象* @param configuration 配置* @param objectFactory 对象⼯⼚* @param constructorArgTypes 构造参数类型* @param constructorArgs 构造参数值* @return*/@Overridepublic Object createProxy ( Object target , ResultLoaderMap lazyLoader ,Configuration configuration , ObjectFactory objectFactory , List & lt ; Class & lt ; ?& gt ; & gt ; constructorArgTypes , List & lt ; Object & gt ; 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 & lt ; ?& gt ; type , MethodHandler callback ,List & lt ; Class & lt ; ?& gt ; & gt ; constructorArgTypes , List & lt ; Object & gt ;constructorArgs ) {ProxyFactory enhancer = new ProxyFactory ();enhancer . setSuperclass ( type );try {// 通过获取对象⽅法,判断是否存在该⽅法type . getDeclaredMethod ( WRITE_REPLACE_METHOD );// ObjectOutputStream will call writeReplace of objects returned bywriteReplaceif ( log . isDebugEnabled ()) {log . debug ( WRITE_REPLACE_METHOD + & quot ; method was found on bean& quot ; + type + & quot ;, make sure it returns this & quot ;);}} catch ( NoSuchMethodException e ) {// 没找到该⽅法,实现接⼝enhancer . setInterfaces ( new Class []{ WriteReplaceInterface . class });} catch ( SecurityException e ) {// nothing to do here}Object enhanced ;Class & lt ; ?& gt ;[] typesArray = constructorArgTypes . toArray ( newClass [ constructorArgTypes . size ()]);Object [] valuesArray = constructorArgs . toArray ( newObject [ constructorArgs . size ()]);try {// 创建新的代理对象enhanced = enhancer . create ( typesArray , valuesArray );} catch ( Exception e ) {throw new ExecutorException ( & quot ; Error creating lazy proxy . Cause :& quot ; + e , e );}// 设置代理执⾏器(( Proxy ) enhanced ). setHandler ( callback );return enhanced ;}/*** 代理对象执⾏* @param enhanced 原对象* @param method 原对象⽅法* @param methodProxy 代理⽅法* @param args ⽅法参数* @return* @throws Throwable*/@Overridepublic 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 () & gt ; 0 ) {return new JavassistSerialStateHolder ( original ,lazyLoader . getProperties (), objectFactory , constructorArgTypes ,constructorArgs );} else {return original ;}} else {// 延迟加载数量⼤于 0if ( lazyLoader . size () & gt ; 0 & amp ; & amp ;! FINALIZE_METHOD . equals ( methodName )) {//aggressive ⼀次加载性所有需要要延迟加载属性或者包含触发延迟加载⽅法if ( aggressive || lazyLoadTriggerMethods . contains ( methodName )) {log . debug ( & quot ; ==& gt ; laze lod trigger method : & quot ; +methodName + & quot ;, proxy method : & quot ; + methodProxy . getName () + & quot ;class : & quot ; + 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 ( & quot ; load one : & quot ; + methodName );}}}}}return methodProxy . invoke ( enhanced , args );} catch ( Throwable t ) {throw ExceptionUtil . unwrapThrowable ( t );}}}
注意事项
1. IDEA
调试问题 当配置
aggressiveLazyLoading=true
,在使⽤
IDEA
进⾏调试的时候,如果断点打到 代理执⾏逻辑当中,你会发现延迟加载的代码永远都不能进⼊,总是会被提前执⾏。 主要产⽣的 原因在aggressiveLazyLoading
,因为在调试的时候,
IDEA
的
Debuger
窗体中已经触发了延迟加载对象的⽅法。