前言
对于mybatis
之前已经讲了mybatis 中接口注入spring源码分析,mybatis 接口依赖注入源码分析。现在mybatis
的接口能够放入Spring
,并且能够依赖注入,这一节讲的重点就是以下代码的具体流程。
@Autowired
private CardMapper cardMapper;
public void select() {
cardMapper.selectCardById(1);
}
重点关注类
MapperProxy
:
SqlSessionTemplate
:
SqlSessionUtils
:
SqlSessionFactory
:
Configuration
:
CachingExecutor
:
Executor
:
SqlSession
:
org.apache.ibatis.transaction.Transaction
:
Connection
:
RoutingStatementHandler
:
StatementHandler
:
Statement
:
ResultSetHandler
一创建SqlSession
SqlSession
用于对外执行sql
,并且一般来说SqlSession
使用了之后就要关闭掉,防止内存泄漏。既然说的是执行sql
流程,那么SqlSession
的创建就一定是避不开的。通常我们都会结合Spring
一起使用mybatis
,并且SqlSession
的创建,关闭、提交、回滚都交给Spring
管理,对于使用者来说完全无感知。就如下面这段代码,执行Sql
的时候,用户完全不需要关心SqlSession
的管理。这里SqlSession
的创建我们具体指SqlSessionTemplate
和DefaultSqlSession
。
@Autowired
private CardMapper cardMapper;
public void select() {
cardMapper.selectCardById(1);
}
1.1 创建SqlSessionTemplate
SqlSessionTemplate
是Spring
用来管理SqlSession
的生命周期的,并且这个类是全局唯一的,线程安全的,在项目启动的时候就会创建,创建后最后会赋值到MapperProxy
中。先简单的看下SqlSessionTemplate
构造函数
//SqlSessionTemplate
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
// 生成代理
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
@Override
public <T> T selectOne(String statement) {
// 使用代理查询
return this.sqlSessionProxy.<T> selectOne(statement);
}
SqlSessionInterceptor
是SqlSessionTemplate
内部类,并且实现了InvocationHandler
接口,用于代理SqlSession
,所以只要是sqlSessionProxy
调用了SqlSession
的方法都会调用SqlSessionInterceptor
中的invoke
方法。具体的invoke
方法我会在下面讲解。这里先贴下创建SqlSessionTemplate
的代码,该代码存在于mybatis.spring.boot.autoconfigure
包下。
//MybatisAutoConfiguration
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
1.2 创建DefaultSqlSession
创建DefaultSqlSession的流程图)
创建
DefaultSqlSession
的过程首先是调用接口的xxx
方法(cardMapper.selectCardById(1)
),接着就是MapperProxy对象的
invoke方法,
其实在mybatis 接口依赖注入源码分析就简单的展示了MapperProxy
的源码,其实Spring
帮我们的接口依赖注入对象就是MapperProxy
对象。当我们调用接口中的xxx
方法就直接调用代理类MapperProxy
中的invoke
方法。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
....
final MapperMethod mapperMethod = cachedMapperMethod(method);
// sqlSession 就是SqlSessionTemplate,执行sqlSessionTemplate中对应的方法。
return mapperMethod.execute(sqlSession, args);
}
当SqlSessionTemplate
调用select
方法的时候,就是sqlSessionProxy
调用对应的方法,接着就是调用SqlSessionInterceptor
的invoke
方法。
在上没有展示SqlSessionInterceptor
的invoke
方法,这里讲下。以下段代码主要分三部分。
- 创建
SqlSession
。 - 执行
SqlSession
对应的方法。 - 关闭
SqlSession
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
二、获取SQL
在mybatis 解析xml流程中介绍了类似<select/>
是以何种形式保存在Configuration
对象中。以下是关键代码,MappedStatement
表示<select/>
等节点,ms.getId()
是接口中的包名+类名+方法名(test.CardMapper.select
)。因此现在SQL
是以MappedStatement
形式存在Configuration
的Map
中,key为包名+类名+方法名
,所以只要知道执行方法的key(包名+类名+方法名
)就可以获取SQL
。
#Configuration
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("xxxxx");
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
其实当你调用接口的某个方法的时候就已经知道这个key(包名+类名+方法名
)是什么了,以下是mybatis
组装key的代码。
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
//组装id key
String statementId = mapperInterface.getName() + "." + methodName;
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
以下是DefaultSqlSession
执行查询代码,具体解释见代码。
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
//从map中获取MappedStatement(<select/>),statement就是包名+类名+方法名
MappedStatement ms = configuration.getMappedStatement(statement);
// 执行器进行查询
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
在流程图上添加操作。
image.png
三、 走缓存 or 数据库
有了SQL
之后,接下来是考虑走缓存还是走数据库。首先mybatis
有一级缓存PerpetualCache
以及二级缓存。
SqlSession
的何时关闭有关,而SqlSession
的关闭与事务关联。如果外层没有事务,则一次查询后,缓存随着SqlSession
的关闭而被清除。如果外层有事务的话,SqlSession
查询一次后不会关闭,mybatis
会继续将session
保留在SessionHolder
上,并且存活时间延迟到外层事务完成后关闭。
SqlSessionUtils
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
notNull(session, NO_SQL_SESSION_SPECIFIED);
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
if ((holder != null) && (holder.getSqlSession() == session)) {
// 持有减一
holder.released();
} else {
//关闭session
session.close();
}
}
SqlSessionUtils#SqlSessionSynchronization
事务完成后关闭
public void afterCompletion(int status) {
if (this.holderActive) {
// afterCompletion may have been called from a different thread
// so avoid failing if there is nothing in this one
TransactionSynchronizationManager.unbindResourceIfPossible(sessionFactory);
this.holderActive = false;
this.holder.getSqlSession().close();
}
this.holder.reset();
}
一级缓存是内存中的缓存,对象是PerpetualCache
,在调用Executor
构造函数的时候就会初始化一级缓存。
## BaseExecutor
protected BaseExecutor(Configuration configuration, Transaction transaction) {
...
//一级缓存
this.localCache = new PerpetualCache("LocalCache");
...
}
image.png
接下来贴一段代码查询代码。
## BaseExecutor
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.");
}
// 相当于<select flushCache=true/>
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;
}
继续添加代码流程。<select flushCache=true/>
这个清除缓存(一级&二级缓存)
四 走数据库
走数据库就要涉及到Connection
的管理以及参数解析。
4.1 Connection
mybatis
中是由org.apache.ibatis.transaction.Transaction
管理Connection
的生命周期(creation, preparation, commit/rollback and close)
,如果你整合了Spirng
,那么具体的类是org.mybatis.spring.transaction.SpringManagedTransaction
,但是SpringManagedTransaction
本身不会直接负责Connection
的创建和关闭,他向外提供Connection
的创建和关闭功能都是由javax.sql.DataSource
负责,比如创建连接、维护空闲连接池、活跃连接池等等都是由DateSource
管理。由于这个DateSource
是一个顶级接口,因此这提供了一个很好的扩展性,你可以选择自己喜欢的DateSource
(c3po,druid等),由它来管理你的Connection
。
Executor
主要就是依靠Transaction
来进行Connection
的管理的,Executor
的构造函数中由一个参数就是Transaction
,那么这个Transaction
是来源哪里?
答案是从Environment
中获取,以下是Environmment
的三个属性。在org.mybatis.spring.SqlSessionFactoryBean
创建Environmment
的时候,如果没有TransactionFactory
就默认给一个SpringManagedTransactionFactory
对象并将他传给Environmment
的构造函数
#org.mybatis.spring.SqlSessionFactoryBean
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
而SpringManagedTransactionFactory
生产的实例对象就是SpringManagedTransaction
。因此Executor
的Transaction
是来源自Environmment
的属性对象TransactionFactory
。
##Environment
private final String id;
// 用于创建Transaction
private final TransactionFactory transactionFactory;
private final DataSource dataSource;
4.2 参数解析
mybatis
解析参数主要是通过ParameterHandler
,ParameterHandler
是一个接口,只是定义了解析参数的两个方法getParameterObject
以及setParameters
,mybatis
默认提供了DefaultParameterHandler
,DefaultParameterHandler
将入参一一对应到sql
上,以下是参数解析的主要的代码。
##DefaultParameterHandler
// 这里保存了<JavaType, TypeHandler>的对应关系
private final TypeHandlerRegistry typeHandlerRegistry;
// <select/>
private final MappedStatement mappedStatement;
// xxxMapper.select()的入参对象
private final Object parameterObject;
//
private final BoundSql boundSql;
private final Configuration configuration;
@Override
public void setParameters(PreparedStatement ps) {
// 入参都会按照sql的参数顺序一一的放入parameterMappings 里面。
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);
}
// 获取参数的类型处理器,比如string 对应StringTypeHandler
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 e) {
} catch (SQLException e) {
}
}
}
}
}
五、执行SQL
执行SQL
是通过StatementHandler
执行,当然这是个接口,具体的操作由子类去实现。
RoutingStatementHandler
: 仅仅是选择让哪个StatementHandler
去执行SQL
,下面是代码是RoutingStatementHandler
的构造函数,里面根据StatementType
选择不同的StatementHandler
,而StatementType
在写<select statementType="PREPARED"/>
类似的语句的时候就已经决定了是什么类型的,默认是PREPARED
,
#RoutingStatementHandler
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
总结
两条路:
SqlSession
->获取SQL
->走缓存
。
SqlSession
->获取SQL
->走数据库
->管理连接
->参数解析
->执行SQL
。
网友评论