以下分析基于,Spring中使用Mybatis,并且配置事务
<context:property-placeholder location="classpath:OracleJDBC.properties" />
<!-- datasource -->
<bean id="datasource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
p:driverClassName="${driver}"
p:url="${url}"
p:username="${username}"
p:password="${password}"/>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"
p:dataSource-ref="datasource"/>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="com.daodao.mybatis.mapper"/>
</bean>
<bean id="testDaoService" class="com.daodao.mybatis.testDaoService"
p:stra="tttt"
p:intb="1111"/>
<!--事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"/>
</bean>
<!--声明事务-->
<tx:annotation-driven transaction-manager="transactionManager"/>
spring中配置以下两种信息都可以用来生成mapper接口对应的实例:
org.mybatis.spring.mapper.MapperFactoryBean, 获取Mapper的Proxy
org.mybatis.spring.mapper.MapperScannerConfigurer 不用每次都给Mapper指定MapperFactoryBean
以MapperFactoryBean为例
1. 获取mapper对应的实例(JDK动态代理)
@Override
public T getObject() throws Exception {
//这一步类似于SqlSessionTemplate.getMapper(Class<T> type)
return getSqlSession().getMapper(this.mapperInterface);
}
补充:JDK动态代理具体实现:
MapperProxyFactory
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
mapperProxy为org.apache.ibatis.binding.MapperProxy
2. mapper的方法调用 触发InvokeHandler:org.apache.ibatis.binding.MapperProxy
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
a. final MapperMethod mapperMethod = cachedMapperMethod(method);
这里准备两个信息:
一个是crud的type信息+sql语句
一个是mapper被调用方法的输入参数+返回值信息
b. return mapperMethod.execute(sqlSession, args);
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
简单来说会根据sql的type选择调用SqlSessionTemplate的不同方法
3. 以result = sqlSession.selectOne(command.getName(), param);为例其中sqlSession为SqlSessionTemplate但是所有的处理方法都是交给了属性sqlSessionProxy
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.<T> selectOne(statement, parameter);
}
sqlSessionProxy的InvokeHandler为 org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor
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) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
a. SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
这一步首先尝试从Thread.currentThread().threadLocals中获取sqlsession,获取成功则直接返回该sqlsession。获取失败则新建一个sqlsession。最后判断如果当前是处于transaction则放入Thread.currentThread().threadLocals,否则直接返回。
b. Object result = method.invoke(sqlSession, args);
这一步调用DefaultSqlSession对应的方法
c. closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
这一步调用sqlsession.close(),有个小逻辑,如果当前处于spring 的transaction中则标记一下清除,等transaction结束的时候close(借transaction东风)。如果当前不处于transaction则直接close
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)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Releasing transactional SqlSession [" + session + "]");
}
holder.released();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Closing non transactional SqlSession [" + session + "]");
}
session.close();
}
}
总结:
准备阶段:
1. spring容器实例化mapper
BlogMapper mapper = SqlSessionTemplate.getMapper(BlogMapper.class);
2.custom代码的调用逻辑
Blog blog = mapper.selectBlog(101);
执行阶段:
3. mapper是个Proxy, MapperProxy中主要负责挑选调用SqlSessionTemplate的方法
if (SqlCommandType.INSERT == command.getType()) {
} else if (SqlCommandType.UPDATE == command.getType()) {
} else if (SqlCommandType.DELETE == command.getType()) {
} else if (SqlCommandType.SELECT == command.getType()) {
}
4. 以下是Mybatis正常的操作流程:
session = DefaultSqlSessionFactory.openSession(false);
try{
操作
} catch(Exception){
session.commit();
} finally{
session.close();
}
以下是Mybatis结合Spring之后,SqlSessionTemplate$SqlSessionInterceptor中流程:
总的来说就是如果存在Transaction则把sqlSession的开闭交给Spring Transaction去处理
//判断TransactionSynchronizationManager中有没有session
//1. 如果有则直接返回该session
//2. 否则执行DefaultSqlSessionFactory.openSession(false);。
SqlSession sqlSession = getSqlSession();
try {
//调用DefaultSqlSession的对应crud方法(传入sql+参数)
Object result = method.invoke(sqlSession, args);
//判断当前Thread有没有Spring的Transaction
//1. 如果没有则执行session.commit();
//2. 否则等待Spring Transaction结束的时候commit()
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
//判断TransactionSynchronizationManager中有没有session
//1. 如果有则标记一下release,最后会在Spring Transaction结束的时候被清除。
//2. 否则执行session.close();
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
throw t;
} finally {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
SqlSessionTemplate 类似于JdbcTemplate,是能够保证每次操作sqlsession的打开和关闭。
DefaultSqlSession类似于jdbc的connection,需要手动的打开关闭。
注意:
如果当前Spring打开事务,虽然在运行时在Thread.currentThread().threadLocals会同时拥有以下两个,但是事实上只有一个connection,DefaultSqlSession实际操作的connection就是Spring事务一开始打开的connection
{org.apache.ibatis.session.defaults.DefaultSqlSessionFactory@67b37beb=org.mybatis.spring.SqlSessionHolder@f3e4c91, org.apache.commons.dbcp.BasicDataSource@6836d2c3=org.springframework.jdbc.datasource.ConnectionHolder@4314cb68}