MyBatis源码剖析
# MyBatis源码剖析
# 传统方式源码剖析
分析用例:
public void testSelectAll() throws IOException {
// 加载核⼼配置⽂件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 获得sqlSession⼯⼚对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 获得sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 执⾏sql语句
List<User> userList = sqlSession.selectList("userMapper.findAll");
// 打印结果
System.out.println(userList);
// 释放资源
sqlSession.close();
}
# 初始化
// 加载核⼼配置⽂件, 读成字节输入流。
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
// 解析配置文件,封装Configuration对象,获得sqlSession⼯⼚对象(DefaultSqlSessionFactory)
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
进入源码分析:
// 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);
} finally {
ErrorContext.instance().reset();
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
MyBatis在初始化的时候,会将MyBatis的配置信息全部加载到内存中,使用 org.apache.ibatis.session.Configuration
实例来维护。接下来对配置文件进行解析。首先,对 Configuration
对象进行介绍:
Configuration对象的结构和xml配置文件的对象几乎相同。回顾一下xml中的配置标签:properties (属性)、settings (设置)、typeAliases (类型别名)、typeHandlers (类型处理器)、objectFactory (对象工厂)、mappers (映射器)等等标签。Configuration也有对应的对象属性来封 装它们也就是说,初始化配置文件信息的本质就是创建Configuration对象,将解析的xml数据封装到Configuration内部属性中:
/**
* 3.XMLConfigBuilder解析mybatis的配置文件
*/
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
/**
* 4.解析 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
* @param root configuration根节点
*/
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);
// 加载自定义的Log实现类
loadCustomLogImpl(settings);
// 解析 <typeAliases /> 标签
typeAliasesElement(root.evalNode("typeAliases"));
// 解析<plugins />标签
pluginElement(root.evalNode("plugins"));
// 解析 <objectFactory /> 标签
objectFactoryElement(root.evalNode("objectFactory"));
// 解析 <objectWrapperFactory /> 标签
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析 <reflectorFactory /> 标签
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 赋值 <settings /> 至 Configuration 属性
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 解析<environments /> 标签
environmentsElement(root.evalNode("environments"));
// 解析 <databaseIdProvider /> 标签
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析 <typeHandlers /> 标签
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析 <mappers /> 标签
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
Configuration对象有个重要的属性:mappedStatements
,它是 Map<String, MappedStatement>
数据,即MappedStatement 映射,key为 ${namespace}.${id}
。与Mapper配置文件中的每个 select/update/insert/delete
节点相对应,另外,比较特殊的是,<selectKey/>
解析后,也会对应一个 MappedStatement 对象。 简单来说**MappedStatement
的主要作用是描述一条SQL语句。**
初始化过程总结:回顾刚开始介绍的加载配置文件的过程中,会对 mybatis-config.xml
中的各个标签都进行解析,其中有mappers标签用来引入mapper.xml文件或者配置mapper接口的目录。解析方法在XMLConfigBuilder 中处理:
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties"));
// ..... 标签解析处理
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
而对于每个mapper.xml文件的标签有专门的MappedStatement 对象描述,如:
<select id="getUser" resultType="user" >
select * from user where id = #{id}
</select>
一个 select
标签会在初始化配置文件时被解析封装成一个MappedStatement对象,然后存储在Configuration对象的mappedStatements属性中,mappedStatements是一个HashMap,存储时key=全限定类名+方法名
,value =对应的MappedStatement对象
。
/**
* MappedStatement 映射,key为${namespace}.${id}
*/
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
到此对xml配置文件的解析就结束了,回到步骤2.中调用的重载build方法,可以发现最后又调用了build的重载方法:
/**
* 5.调用的build重载方法
* @param config 解析的Configuration对象
* @return DefaultSqlSessionFactory
*/
public SqlSessionFactory build(Configuration config) {
// 传入Configuration对象,创建 DefaultSqlSessionFactory 对象
return new DefaultSqlSessionFactory(config);
}
至此,初始化步骤结束。
# 执行SQL流程
继续分析,初始化完毕后,我们就要执⾏SQL:
// 获得sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 执⾏sql语句
List<User> userList = sqlSession.selectList("userMapper.findAll");
先简单介绍一下SqlSession和Executor。
SqlSession:SqlSession是⼀个接⼝,它有两个实现类:DefaultSqlSession (默认)和SqlSessionManager (弃⽤,不做介绍)。SqlSession是MyBatis中⽤于和数据库交互的顶层类,通常将它与ThreadLocal绑定,⼀个会话使⽤⼀个SqlSession,通过它可以执行sql命令,获取映射器和管理事务,在使⽤完毕后需要close。
/**
* The default implementation for {@link SqlSession}.
* Note that this class is not Thread-Safe.
*
* @author Clinton Begin
*/
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
// ......
}
SqlSession中有两个最重要的参数:configuration(与初始化时的相同),executor (执⾏器)。
Executor:Executor也是⼀个接⼝,他有三个常⽤的实现类:
- BatchExecutor (重⽤语句并执⾏批量更新)
- ReuseExecutor (重⽤预处理语句 prepared statements)
- SimpleExecutor (普通的执⾏器,默认)
接下来,看看获得sqlSession的方法:
/**
* 6. 进⼊ openSession ⽅法
*/
@Override
public SqlSession openSession() {
// getDefaultExecutorType()传递的是SimpleExecutor
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
/**
* 7. 进⼊penSessionFromDataSource
* openSession的多个重载⽅法可以指定获得的SeqSession的Executor类型和事务的处理
* @param execType executor的类型
* @param level 事务隔离级别
* @param autoCommit 是否开启事务
* @return 返回sqlSession
*/
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()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
执⾏ sqlsession 中的 api:
/**
* 8.进⼊selectList⽅法,多个重载⽅法。
* @param statement Unique identifier matching the statement to use.
*/
@Override
public <E> List<E> selectList(String statement) {
return this.selectList(statement, null);
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}
/**
* @param statement 全限定名 + ⽅法名, 用于取出MappedStatement
* @param parameter 参数
* @param rowBounds ⽤以逻辑分⻚⻚
* @param handler 结果处理,默认null
*/
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
// 根据传⼊的全限定名 + ⽅法名从映射的Map中取出MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
// 调⽤Executor中的query⽅法处理 ,rowBounds是⽤来逻辑分⻚,wrapCollection(parameter)是⽤来装饰集合或者数组参数
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
# Executor源码剖析
继续源码中的步骤,进⼊executor.query(),此⽅法在SimpleExecutor的⽗类BaseExecutor中实现:
/**
* 查询方法
*/
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 根据传⼊的参数动态获得SQL语句,最后返回⽤BoundSql对象表示
BoundSql boundSql = ms.getBoundSql(parameter);
// 为本次查询创建缓存的Key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
/**
* query重载方法
*/
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 尝试从缓存中查找
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 如果缓存中没有本次查找的值,那么从数据库中查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
/**
* 从数据库中查询数据值
*/
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实现BaseExecutor的doQuery方法
*/
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 传⼊参数创建StatementHandler对象来执⾏查询
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;
}
/**
* 连接池获得连接的⽅法(JdbcTransaction.openConnection)
*/
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
// 从连接池获得连接
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommit);
}
上述的Executor.query()⽅法⼏经转折,最后会创建⼀个StatementHandler对象,然后将必要的参数传递给StatementHandler,使⽤StatementHandler来完成对数据库的查询,最终返回List结果集。
从上⾯的代码中我们可以看出,Executor的功能和作⽤是:
- 根据传递的参数,完成SQL语句的动态解析,⽣成BoundSql对象,供StatementHandler使⽤
- 为查询创建缓存,以提⾼性能
- 创建JDBC的Statement连接对象,传递给StatementHandler对象,返回List查询结果。
# StatementHandler源码剖析
StatementHandler对象主要完成两个⼯作:
- 创建JDBC的PreparedStatement类型的对象时,我们使⽤的是SQL语句字符串会包含若⼲个?占位符,所以需要再对占位符进⾏设值。StatementHandler通过
parameterize(statement)
⽅法对Statement 进⾏占位符设值; - StatementHandler 通过
List query(Statement statement, ResultHandler resultHandler)
⽅法来执⾏Statement,和将Statement对象返回的resultSet
封装成List。
进⼊到 StatementHandler 的 parameterize(statement) ⽅法的实现查看源代码:
@Override
public void parameterize(Statement statement) throws SQLException {
// 使用ParameterHandler对象来完成对Statement的设值
parameterHandler.setParameters((PreparedStatement) statement);
}
/**
* 对某⼀个Statement进⾏设置参数
*/
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // 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();
}
try {
// 设置参数
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
从上述的代码可以看到:StatementHandler的 parameterize(Statement)
⽅法调⽤了ParameterHandler的 setParameters(statement)
⽅法,这个⽅法负责根据我们输⼊的参数,对Statement对象的?占位符处进⾏赋值。
返回前面的说到的 SimpleExecutor.doQuery
方法,查询是调用了StatementHandler的 List query(Statement statement, ResultHandler resultHandler)
:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 调⽤execute()⽅法,得到resultSet
ps.execute();
// 将resultSet交给ResultSetHandler处理,得到List
return resultSetHandler.handleResultSets(ps);
}
从上述代码我们可以看出,StatementHandler的 List query(Statement statement, ResultHandler resultHandler)
的实现,是调⽤Statement的 execute()
方法,然后将resultSet 交给ResultSetHandler的 handleResultSets(Statement stmt)
⽅法处理结果。 它会将结果集转换成List结果集。
//
// HANDLE RESULT SETS
//
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
// 多ResultSet的结果集合,每个ResultSet对应⼀个Object对象。⽽实际上,每个Object是List<Object>对象。
// 在不考虑存储过程的多ResultSet的情况,普通的查询实际就⼀个ResultSet,也就是说,multipleResults最多就⼀个元素。
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
// 获得⾸个ResultSet对象,并封装成ResultSetWrapper对象
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 获得ResultMap数组, 在不考虑存储过程的多ResultSet的情况,普通的查询实际就⼀个ResultSet,也就是说,resultMaps最多就⼀个元素。
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
// 校验
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
// 获得ResultMap对象
ResultMap resultMap = resultMaps.get(resultSetCount);
// 处理ResultSet,将结果添加到multipleResults中
handleResultSet(rsw, resultMap, multipleResults, null);
// 获得下⼀个ResultSet对象,并封装成ResultSetWrapper对象
rsw = getNextResultSet(stmt);
// 清理
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
// 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);
}
# Mapper代理方式源码剖析
回顾下Mapper代理方式的写法:
public void testSelectAll() throws IOException {
// 前三步一样
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 这⾥不再调⽤SqlSession的api,⽽是获得了接⼝对象,调⽤了接⼝中的⽅法
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> all = mapper.findAll();
System.out.println(userList);
sqlSession.close();
}
思考⼀个问题:通常的Mapper接⼝都没有实现的⽅法却可以使⽤,为什么呢?答案很简单:动态代理。
开始之前,介绍⼀下MyBatis初始化时对接⼝的处理:MapperRegistr是 Configuration
中的⼀个属性,它内部维护⼀个 HashMap
⽤于存放 mapper接⼝
的⼯⼚类,每个接⼝对应⼀个⼯⼚类。
我们知道xml配置中,mappers
中可以配置接⼝的包路径或者某个具体的接⼝类:
<mappers>
<mapper class="org.example.hello.mybatis.mapper.UserMapper"/>
<package name="org.example.hello.mybatis.mapper"/>
</mappers>
当解析mappers标签时,它会判断:
- 如果解析到的是mapper配置⽂件时,会再将对应配置⽂件中的增删改查标签封装成
MappedStatement
对象,存⼊mappedStatements
中(上⽂介绍了)。 - 当判断解析到接⼝时,会构建此接⼝对应的
MapperProxyFactory
对象,存⼊HashMap
中,key=接⼝的字节码对象,value=此接⼝对应的MapperProxyFactory
对象。
# getmapper()源码剖析
进⼊sqlSession.getMapper(UserMapper.class )的源码:
// DefaultSqlSession类中的getMapper
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
// Configuration类中的getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// MapperRegistry类的getMapper
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 从MapperRegistry类中的knownMappers(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, MapperMethodInvoker> methodCache;
/**
* 构造函数传⼊了SqlSession,说明每个Session中的代理对象的不同的!
*/
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> 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 {
// 返回的是PlainMethodInvoker,调用了invoke方法。它是MapperProxy的内部类。
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
// PlainMethodInvoker类
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
// 重点:invoke方法实质上是调用了MapperMethod的execute方法
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
// MapperMethod类的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);
// 执⾏UPDATE操作, 转换rowCount
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
// 转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 执⾏DELETE操作, 转换rowCount
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
// ⽆返回,并且有ResultHandler⽅法参数,则将查询的结果,提交给ResultHandler 进⾏处理
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
// 执⾏查询,返回列表
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
// 执⾏查询,返回Map
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
// 执⾏查询,返回Cursor
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
// 执⾏查询,返回单个对象
} else {
// 转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 查询单条
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
// 返回结果为null,并且返回类型为基本类型,则抛出BindingException异常
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
// 返回结果
return result;
}
# 二级缓存源码剖析
⼆级缓存构建在⼀级缓存之上,在收到查询请求时,MyBatis⾸先会查询⼆级缓存,若⼆级缓存未命中,再去查询⼀级缓存,⼀级缓存没有,再查询数据库:
- ⼆级缓存 ---> ⼀级缓存 ----> 数据库
与⼀级缓存不同,⼆级缓存和具体的命名空间绑定,⼀个Mapper中有⼀个Cache,相同Mapper中的 MappedStatement
共⽤⼀个Cache,⼀级缓存则是和 SqlSession 绑定。
# 启用二级缓存
分为三步⾛:
开启全局⼆级缓存配置:
<settings> <setting name="cacheEnabled" value="true"/> </settings>
在需要使⽤⼆级缓存的Mapper配置⽂件中配置标签:
<cache></cache>
在具体CURD标签上配置
useCache=true
<select id="findById" resultType="org.example.hello.mybatis.entity.User" useCache="true"> select * from user where id = #{id} </select>
# <cache/>
标签的解析
根据之前的MyBatis源码剖析,xml的解析⼯作主要交给 XMLConfigBuilder.parse()
⽅法来实现:
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
* @param root configuration根节点
*/
private void parseConfiguration(XNode root) {
try {
// ... ...
// 解析 <mappers /> 标签
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
/**
* 解析mappers标签
*/
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");
// resource 方式加载
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// ⽣成XMLMapperBuilder,并执⾏其parse⽅法
mapperParser.parse();
}
// url 方式加载
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
try(InputStream inputStream = Resources.getUrlAsStream(url)){
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
// ⽣成XMLMapperBuilder,并执⾏其parse⽅法
mapperParser.parse();
}
// class 方式加载
} 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();
parsePendingCacheRefs();
parsePendingStatements();
}
// configurationElement()
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
// cache属性相关处理
cacheRefElement(context.evalNode("cache-ref"));
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. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
// cacheElement()
private void cacheElement(XNode context) {
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) {
// ⽣成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();
// 添加到Configuration中
configuration.addCache(cache);
// 将cache赋值给MapperBuilderAssistant.currentCache
currentCache = cache;
return cache;
}
从这里我们可以看到⼀个 Mapper.xml
只会解析⼀次标签,也就是只创建⼀次Cache对象,放进configuration中,并将cache赋值给MapperBuilderAssistant.currentCache。
接下来我们看一看 buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
干了些什么?
// XMLMapperBuilder.buildStatementFromContext
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 每⼀条执⾏语句转换成⼀个MappedStatement
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
// XMLStatementBuilder.parseStatementNode();
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
// ......各种解析
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
// 创建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 resultSets) {
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)
// ...
.useCache(valueOrDefault(useCache, isSelect))
// 在这⾥将之前⽣成的Cache封装到MappedStatement
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
我们可以看到它将Mapper中创建的Cache对象,加⼊到了每个MappedStatement对象中,也就是同⼀个Mapper中的Cache对象是共享的。
至此有关于 <cache/>
标签的解析就到这了。
# 查询源码分析
# CachingExecutor
// CachingExecutor.query
@Override
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);
}
@Override
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/>标签中创建的,它保存在Configuration中。
// 之前解析xml时每⼀个MappedStatement都有⼀个Cache对象,就是这里
Cache cache = ms.getCache();
// 如果配置⽂件中没有配置 <cache>,则 cache 为空
if (cache != null) {
// 如果需要刷新缓存的话就刷新:flushCache="true"
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
// 访问⼆级缓存
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
// 缓存未命中
if (list == null) {
// 如果没有值,则执⾏查询,这个查询实际也是先⾛⼀级缓存查询,⼀级缓存也没有的话,则进⾏DB查询
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 缓存查询结果
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
如果设置了flushCache="true",则每次查询都会刷新缓存。
<select id="findById" resultType="org.example.hello.mybatis.entity.User" useCache="true" flushCache="true">
select * from user where id = #{id}
</select>
注意,⼆级缓存是从 MappedStatement 中获取的。由于 MappedStatement 存在于全局配置中,可以多个 CachingExecutor 获取到,这样就会出现线程安全问题。除此之外,若不加以控制,多个事务共⽤⼀个缓存实例,会导致脏读问题。⾄于脏读问题,需要借助其他类来处理,也就是上⾯代码中tcm变量对应的类型,下⾯分析⼀下。
# TransactionalCacheManager
/**
* 事务缓存管理器
* @author Clinton Begin
*/
public class TransactionalCacheManager {
// Cache 与 TransactionalCache 的映射关系表
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
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放进caches中
// TransactionalCache 也是⼀种装饰类,为 Cache 增加事务功能
return MapUtil.computeIfAbsent(transactionalCaches, cache, TransactionalCache::new);
}
}
TransactionalCacheManager 内部维护了 Cache 实例与 TransactionalCache 实例间的映射关系,该类也仅负责维护两者的映射关系,真正做事的还是 TransactionalCache。TransactionalCache 是⼀种缓存装饰器,可以为 Cache 实例增加事务功能。之前提到的脏读问题正是由该类进⾏处理的。下⾯分析⼀下该类的逻辑:
# TransactionalCache
public class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
// 真正的缓存对象,和上⾯的Map<Cache, TransactionalCache> 中的Cache是同⼀个
private final Cache delegate;
private boolean clearOnCommit;
// 在事务被提交前,所有从数据库中查询的结果将缓存在此集合中
private final Map<Object, Object> entriesToAddOnCommit;
// 在事务被提交前,当缓存未命中时,CacheKey 将会被存储在此集合中
private final Set<Object> entriesMissedInCache;
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap<>();
this.entriesMissedInCache = new HashSet<>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public Object getObject(Object key) {
// issue #116
// 查询的时候是直接从delegate中去查询的,也就是从真正的缓存对象中查询
Object object = delegate.getObject(key);
if (object == null) {
// 缓存未命中,则将 key 存⼊到 entriesMissedInCache 中
entriesMissedInCache.add(key);
}
// issue #146
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() {
// 清空 entriesToAddOnCommit,但不清空 delegate 缓存
clearOnCommit = true;
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("Unexpected exception while notifying a rollback to the cache adapter. "
+ "Consider upgrading your cache adapter to the latest version. Cause: " + e);
}
}
}
}
存储⼆级缓存对象时,是放到了 TransactionalCache.entriesToAddOnCommit
这个map中,但是每次查询的时候是直接从 TransactionalCache.delegate
中去查询的,所以这个⼆级缓存查询数据库后,设置缓存值是没有⽴刻⽣效的,主要是因为直接存到 delegate 会导致脏数据问题。
# 为何只有SqlSession提交或关闭之后?
我们来看下SqlSession.commit()⽅法做了什么?
// DefaultSqlSession.commit
@Override
public void commit(boolean force) {
try {
// 调用了executor.commit方法
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()) {
// 这里执行了TransactionalCache的commit
txCache.commit();
}
}
// TransactionalCache.commit()
public void commit() {
// 根据 clearOnCommit 的值决定是否清空 delegate
if (clearOnCommit) {
delegate.clear();
}
// 刷新未缓存的结果到 delegate 缓存中
flushPendingEntries();
// 重置 entriesToAddOnCommit 和 entriesMissedInCache
reset();
}
// TransactionalCache.flushPendingEntries()
private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
// 将 entriesToAddOnCommit 中的内容转存到 delegate 中
// 在这⾥才真正的将entriesToAddOnCommit的对象逐个添加到delegate中,只有这时⼆级缓存才真正的⽣效
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
// 存⼊空值
delegate.putObject(entry, null);
}
}
}
# 二级缓存的刷新
我们来看看SqlSession的更新操作:
// DefaultSqlSession.update
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// CachingExecutor.update
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
// CachingExecutor.flushCacheIfRequired
private void flushCacheIfRequired(MappedStatement ms) {
// 获取MappedStatement对应的Cache
Cache cache = ms.getCache();
// Cache不为空 并且 设置了flushCache="true"
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}
}
所以,MyBatis⼆级缓存只适⽤于不常进⾏增、删、改的数据,⽐如国家⾏政区省市区街道数据。⼀但数据变更,MyBatis会清空缓存。因此⼆级缓存不适⽤于经常进⾏更新的数据。
在⼆级缓存的设计上,MyBatis⼤量地运⽤了装饰者模式,如CachingExecutor, 以及各种Cache接⼝的装饰器。
- ⼆级缓存实现了Sqlsession之间的缓存数据共享,属于namespace级别
- ⼆级缓存具有丰富的缓存策略。
- ⼆级缓存可由多个装饰器,与基础缓存组合⽽成
- ⼆级缓存⼯作由 ⼀个缓存装饰执⾏器
CachingExecutor
和 ⼀个事务型预缓存TransactionalCache
完成。
# 延迟加载源码剖析
# 什么是延迟加载?
例如:在开发过程中很多时候我们并不需要总是在加载⽤户信息时就⼀定要加载他的订单信息。此时,就是我们所说的延迟加载。
Q: 在⼀对多中,当我们有⼀个⽤户,它有个100个订单
- 在查询⽤户的时候,要不要把关联的订单查出来?
- 在查询订单的时候,要不要把关联的⽤户查出来?
A:延迟加载
- 在查询⽤户时,⽤户下的订单应该是,什么时候⽤,什么时候查询。
- 在查询订单时,订单所属的⽤户信息应该是随着订单⼀起查询出来
延迟加载:就是在需要⽤到数据时才进⾏加载,不需要⽤到数据时就不加载数据。延迟加载也称懒加载。
- **优点:**先从单表查询,需要时再从关联表去关联查询,⼤⼤提⾼数据库性能,因为查询单表要⽐关联查询多张表速度要快。
- **缺点:**因为只有当需要⽤到数据时,才会进⾏数据库查询,这样在⼤批量数据查询时,因为查询⼯作也要消耗时 间,所以可能造成⽤户等待时间变⻓,造成⽤户体验下降。
应用场景:
- ⼀对多,多对多:通常情况下采⽤延迟加载
- ⼀对⼀,多对⼀:通常情况下采⽤⽴即加载
**注意:**延迟加载是基于嵌套查询来实现的。
# 实现1:局部延迟加载
在 <association>
和 <collection>
标签中都有⼀个 fetchType
属性,通过修改它的值,可以修改局部的加载策略。
- fetchType="lazy",懒加载策略
- fetchType="eager" ⽴即加载策略
<resultMap id="userMap" type="org.example.hello.mybatis.entity.User">
<result column="uid" property="id"></result>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<collection property="orderList" ofType="org.example.hello.mybatis.entity.Order" column="id"
select="org.example.hello.mybatis.mapper.OrderMapper.findByUid" fetchType="lazy">
</collection>
</resultMap>
<select id="findAllLazy" resultMap="userMap">
select * from user
</select>
# 实现2:全局延迟加载
在Mybatis的核⼼配置⽂件中可以使⽤<setting/>
标签修改全局的加载策略。
<settings>
<!--开启全局延迟加载功能-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
注意:局部延迟加载优先级高于全局延迟加载优先级。
# 延迟加载实现原理
延迟加载的原理是,使⽤ CGLIB 或 Javassist( 默认 ) 创建⽬标对象的代理对象。当调⽤代理对象的延迟加载属性的 getting
⽅法时,会进⼊拦截器⽅法处理。⽐如调⽤ a.getB().getName()
⽅法,进⼊拦截器的 invoke(...)
⽅法,发现 a.getB()
需要延迟加载时,那么就会单独发送事先保存好的查询关联B对象的SQL ,把B查询上来,然后调⽤a.setB(b)
⽅法,于是 a 对象 b 属性就有值了,接着完成 a.getB().getName()
⽅法的调⽤。这就是延迟加载的基本原理。
总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定⽅法,执⾏数据加载。
# 延迟加载源码剖析
MyBatis延迟加载主要使⽤:Javassist(默认)或者Cglib(从3.5.10后标记为废弃)实现。
# Setting配置加载
我们先来看看Configurationd对Setting配置解析:
public class Configuration {
protected Environment environment;
// ...
// 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考lazyLoadTriggerMethods)。默认false(3.4.1及之前是true)
protected boolean aggressiveLazyLoading;
// 延迟加载指定对象的哪些方法触发一次延迟加载
protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
// 默认关闭延迟加载
protected boolean lazyLoadingEnabled = false;
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
//...
/**
* 默认用JavassistProxyFactory
*/
public void setProxyFactory(ProxyFactory proxyFactory) {
if (proxyFactory == null) {
proxyFactory = new JavassistProxyFactory();
}
this.proxyFactory = proxyFactory;
}
// ...
}
# 延迟加载代理对象创建&执行
我们前面源码剖析的时候知道Mybatis的查询结果是由 ResultSetHandler
接⼝的 handleResultSets()
⽅法处理的。ResultSetHandler
接⼝只有⼀个实现 DefaultResultSetHandler
,它有一个处理延迟加载的⼀个核⼼的⽅法 createResultObject
:
/**
* 生成映射结果对象,懒加载相关的核心方法
*/
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
this.useConstructorMappings = false; // reset previous mapping result
final List<Class<?>> constructorArgTypes = new ArrayList<>();
final List<Object> constructorArgs = new ArrayList<>();
// 创建返回的结果映射的真实对象
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// issue gcode #109 && issue #149
// 判断属性有没配置嵌套查询,并且开启了延迟加载,如果有就创建代理对象
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
// 创建延迟加载代理对象
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
break;
}
}
}
this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
return resultObject;
}
我们知道MyBaits默认采⽤javassistProxy进⾏代理对象的创建,我们看看JavasisstProxyFactory的实现:
public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory {
private static final String FINALIZE_METHOD = "finalize";
private static final String WRITE_REPLACE_METHOD = "writeReplace";
// ...
/**
* ProxyFactory接口createProxy的实现
* @param target 目标结果对象
* @param lazyLoader 延迟加载配置对象
* @param configuration 配置对象
* @param objectFactory 对象工厂
* @param constructorArgTypes 构造参数类型
* @param constructorArgs 构造参数值
* @return 代理对象
*/
@Override
public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
}
// ...
/**
* 创建代理对象(核心逻辑)
*/
static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
ProxyFactory enhancer = new ProxyFactory();
enhancer.setSuperclass(type);
try {
// 通过获取对象⽅法,判断是否存在该⽅法
type.getDeclaredMethod(WRITE_REPLACE_METHOD);
// ObjectOutputStream will call writeReplace of objects returned by writeReplace
if (LogHolder.log.isDebugEnabled()) {
LogHolder.log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this");
}
} catch (NoSuchMethodException e) {
// 没找到该⽅法,实现接⼝
enhancer.setInterfaces(new Class[] { WriteReplaceInterface.class });
} catch (SecurityException e) {
// nothing to do here
}
Object enhanced;
Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]);
Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]);
try {
// 创建新的代理对象
enhanced = enhancer.create(typesArray, valuesArray);
} catch (Exception e) {
throw new ExecutorException("Error creating lazy proxy. Cause: " + e, e);
}
// 设置代理执⾏器
((Proxy) enhanced).setHandler(callback);
return enhanced;
}
/**
* 代理对象的实现
*/
private static class EnhancedResultObjectProxyImpl implements MethodHandler {
private final Class<?> type;
private final ResultLoaderMap lazyLoader;
// ...
/**
* 代理对象执行
* @param enhanced 原对象
* @param method 原对象方法
* @param methodProxy 代理方法
* @param args 方法参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
final String methodName = method.getName();
try {
synchronized (lazyLoader) {
if (WRITE_REPLACE_METHOD.equals(methodName)) {
Object original;
if (constructorArgTypes.isEmpty()) {
original = objectFactory.create(type);
} else {
original = objectFactory.create(type, constructorArgTypes, constructorArgs);
}
PropertyCopier.copyBeanProperties(type, enhanced, original);
if (lazyLoader.size() > 0) {
return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
} else {
return original;
}
} else {
// 延迟加载数量⼤于0
if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
// ⼀次性加载所有需要要延迟加载属性或者包含触发延迟加载⽅法
if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
lazyLoader.loadAll();
// 判断是否为set⽅法,set⽅法不需要延迟加载
} else if (PropertyNamer.isSetter(methodName)) {
final String property = PropertyNamer.methodToProperty(methodName);
lazyLoader.remove(property);
// 判断是否为get⽅法,get⽅法需要延迟加载
} else if (PropertyNamer.isGetter(methodName)) {
final String property = PropertyNamer.methodToProperty(methodName);
if (lazyLoader.hasLoader(property)) {
// 延迟加载单个属性
lazyLoader.load(property);
}
}
}
}
}
return methodProxy.invoke(enhanced, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
}
Tips:如果在IDEA进行调试时,把断点打到代理执⾏逻辑当中,发现延迟加载的代码永远都不能进⼊,总是会被提前执⾏。 那么主要的原因是默认情况下 aggressiveLazyLoading=true
,而我们在调试的时候,IDEA的Debuger窗体中已经触发了延迟加载对象的⽅法。