Mybatis_第⼗部分:Mybatis源码剖析

Mybatis_第⼗部分:Mybatis源码剖析

10.1传统⽅式源码剖析:

源码剖析-初始化

Inputstream inputstream = Resources . getResourceAsStream ( "mybatis
config.xml" );
// 这⼀⾏代码正是初始化⼯作的开始。
SqlSessionFactory factory = new
SqlSessionFactoryBuilder (). build ( inputStream );
进⼊源码分析:
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 ;
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() 传递的是 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 的重载⽅法中
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 对象主要完成两个⼯作:
  • 对于JDBCPreparedStatement类型的对象,创建的过程中,我们使⽤的是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>
当解析 mappers 标签时,它会判断解析到的是 mapper 配置⽂件时,会再将对应配置⽂件中的增删 改查标签 封装成MappedStatement 对象,存⼊ mappedStatements 中。
( 上⽂介绍了 ) 当 判断解析到接⼝时,会 建此接⼝对应的MapperProxyFactory对象,存⼊ HashMap 中, key = 接⼝的字节码对象, value = 此接⼝对应的MapperProxyFactory 对象。

源码剖析-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>
注意
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 result
final List & lt ; Class & lt ; ?& gt ; & gt ; constructorArgTypes = new
ArrayList & 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 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 & 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 by
writeReplace
if ( 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 ( new
Class [ constructorArgTypes . size ()]);
Object [] valuesArray = constructorArgs . toArray ( new
Object [ 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
*/
@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 () & gt ; 0 ) {
return new JavassistSerialStateHolder ( original ,
lazyLoader . getProperties (), objectFactory , constructorArgTypes ,
constructorArgs );
} else {
return original ;
}
} else {
// 延迟加载数量⼤于 0
if ( 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 窗体中已经触发了延迟加载对象的⽅法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值