mybatis作为持久层流行框架已经被很多产品使用,当然为了接入Spring这个业内的另一个流行框架,mybatis还是做了些事,通过分析除了明白支持Spring的机制原理还了解Spring对持久层接入留了那些口。
<!-- 配置SqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:**/dao/**/*.xml"/>
<property name="configLocation" value="classpath:spring/mybatis-config.xml" />
</bean>
<!-- 扫描Dao类工具 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.**.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
public interface UserDao {
void save(User user);
User query(String id);
}
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void saveUser(User user) {
userDao.save(user);
}
}
<mapper namespace="com.ss.dao.UserDao">
<select id="save" resultType="com.ss.dto.User">
select ....
</select>
</mapper>
这里对应 UserDao 的 sqlmap就省略具体sql了。
XML定义完两个Bean后,可见日常开发只需要添加Dao接口,以及对应的sqlmap,然后在调用的Service中就可以自动注入,非常方便。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>
SqlSessionFactoryBean 用于生产 SqlSessionFactory 的 FactoryBean
那么,SqlSessionFactory 有什么用? 如果没有使用Spring,那么我们怎么使用mybatis,如下:
SqlSession sqlSession = sqlSessionFactory.openSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
原来是用于openSession() 返回 SqlSession 的。
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor
从实现的接口可以看出,多半用于处理 BeanDefinition 的,该接口需要实现下面的方法。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
MapperScannerConfigurer 的实现源码
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if(this.processPropertyPlaceHolders) {
this.processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// 省略部分 code ...
// 最主要是下面的 scan 定义的basePackage
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
即扫描配置basePackage中dao接口类,然后对扫描结果 beanDefinitions 进行处理
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 调用父类进行扫描
Set beanDefinitions = super.doScan(basePackages);
if(beanDefinitions.isEmpty()) {
this.logger.warn("No MyBatis mapper was found in \'" + Arrays.toString(basePackages) + "\' package. Please check your configuration.");
} else {
// 对结果进行处理
this.processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
所以,主要的逻辑都集中在 processBeanDefinitions() 这个方法
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
Iterator i$ = beanDefinitions.iterator();
while(i$.hasNext()) {
BeanDefinitionHolder holder = (BeanDefinitionHolder)i$.next();
GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
if(this.logger.isDebugEnabled()) {
this.logger.debug("Creating MapperFactoryBean with name \'" + holder.getBeanName() + "\' and \'" + definition.getBeanClassName() + "\' mapperInterface");
}
// 这边使用的招数叫【偷梁换柱】, 将原来的 class 换成了 MapperFactoryBean, 还给它设置了需要的参数
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", Boolean.valueOf(this.addToConfig));
// 下面对 SqlSessionFactory 的引入处理
// 相关 code 省略
}
}
就是说最终通过 MapperFactoryBean 的 getObject() 来生成Dao接口的实例,然后Service中 @Autowired 获取到的就是该实例,至于为什么?因为实现 FactoryBean 接口。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
//其他 code ...
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
public Class<T> getObjectType() {
return this.mapperInterface;
}
public boolean isSingleton() {
return true;
}
}
到这里自动注入的秘密已经揭开,然后它怎么通过
this.getSqlSession().getMapper(this.mapperInterface)
来返回代理对象的,基本上也就是动态代理那套东西,感兴趣的可以翻阅 mybatis源码分析之mapper动态代理 写得蛮详细的。
说到持久层,那么事务管理不能避免,mybatis是怎么样跟Spring的事务管理结合到天衣无缝的,下面分析。
上一章中提到,方法
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
这里 getSqlSession() 还是我们所知道的那个 DefaultSqlSession 么,显然不是了
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if(!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
当 set 进时已经被包装了,所以真实都是调用 SqlSessionTemplate 的方法,SqlSessionTemplate 的密码都藏在它的构造方法中:
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property \'sqlSessionFactory\' is required");
Assert.notNull(executorType, "Property \'executorType\' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
// 生成了一个 SqlSession 的代理,调用 SqlSessionTemplate 的方法其实都转调了 sqlSessionProxy 这个代理
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}
public int insert(String statement) {
return this.sqlSessionProxy.insert(statement);
}
既然是动态代理,那么处理逻辑就都在那个 InvocationHandler 的实现中
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取 sqlSession
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
Object unwrapped;
try {
// 调用 sqlSession 执行方法
Object t = method.invoke(sqlSession, args);
if(!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
unwrapped = t;
} catch (Throwable var11) {
unwrapped = ExceptionUtil.unwrapThrowable(var11);
if(SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
DataAccessException translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
if(translated != null) {
unwrapped = translated;
}
}
throw (Throwable)unwrapped;
} finally {
// close sqlsession
if(sqlSession != null) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
return unwrapped;
}
}
简化成
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取 sqlSession
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
// 真实执行方法
Object t = method.invoke(sqlSession, args);
// close sqlSession
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
return unwrapped;
}
}
这样就是典型的 around 结构。
这时,如果没有事务管理框架的话,那么必然需要自己向 DataSource 获取 connection,然后根据需要开启事务,最后再commit 事务。
但是,如果有事务管理框架的话,就需要向框架获取 connection,因为这时事务可能已经被框架生成的代理开启了。
mybatis 也遵照这种处理方式,跟踪源码。
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
Assert.notNull(executorType, "No ExecutorType specified");
// TransactionSynchronizationManager.getResource 的源码就不贴了,本质就是 ThreadLocal 缓存了一个sessionFactorty
// 为key的, sessionHolder 为value的map, 这样每个线程都有自己的sqlsession,执行时没有线程同步问题
// sqlsession 本身线程不安全
SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if(session != null) {
return session;
} else {
if(LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
// 如果没有缓存就open一个,然后 regist,即缓存起来
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
}
public SqlSession openSession(ExecutorType execType) {
return this.openSessionFromDataSource(execType, (TransactionIsolationLevel)null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment e = this.configuration.getEnvironment();
// 从 Environment 获取 TransactionFactory
// transactionFactory.newTransaction 开启新事务
// TransactionFactory 接口有3个实现类
// 1. JdbcTransactionFactory
// 2. SpringManagedTransactionFactory
// 3. ManagedTransactionFactory
// 当独立使用时使用的是1,当与spring结合时使用的是3(后面说明这个)
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(e);
tx = transactionFactory.newTransaction(e.getDataSource(), level, autoCommit);
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
public class SpringManagedTransactionFactory implements TransactionFactory {
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new SpringManagedTransaction(dataSource);
}
}
// 简化省略的代码
public class SpringManagedTransaction implements Transaction {
public Connection getConnection() throws SQLException {
if(this.connection == null) {
this.openConnection();
}
return this.connection;
}
private void openConnection() throws SQLException {
// DataSourceUtils.getConnection 是获取当前线程的conn,也是ThreadLocal方式
// key为ds,value就是conn
// 如果事务框架已经开启事务,那么当前线程已经换成conn返回即可,没有的话通过ds获取一个再缓存
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
if(LOGGER.isDebugEnabled()) {
LOGGER.debug("JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional?" ":" not ") + "be managed by Spring");
}
}
}
到这已经明了,最终 SpringManagedTransaction 控制着 openConnection 大权,而它索要过来的conn是来自“官方”(spring)事务管理的conn。
这时,不管声明式事务和编程式事务只要遵守spring事务管理的都能起作用。
上面遗留一个问题:SpringManagedTransactionFactory 是何时被装配进 Evn中的?
这个要回到 SqlSessionFactoryBean 中
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
// ... 解析 XML配置,如cofnig mybatis-config.xml 及 mapperLocations 等
// 代码 省略
// 就是这里将 SpringManagedTransactionFactory 配置到 Env 中
if(this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
// ...
}
回到上面 1 的最后 SqlSessionUtils.closeSqlSession(),是不是真的将sqlSession关闭?sqlSession的关闭会把事务关闭或者连接关闭么?
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
Assert.notNull(session, "No SqlSession specified");
Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
// 只有 holder 丢失或者 session 不一致才会真实 session.close
// 其他情况只是 holder.released() 将引用数减一
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();
}
}
session.close() , DefaultSqlSession 的源码:
public void close() {
try {
this.executor.close(this.isCommitOrRollbackRequired(false));
this.dirty = false;
} finally {
ErrorContext.instance().reset();
}
}
// this.executor.close 代码:
public void close(boolean forceRollback) {
try {
try {
this.rollback(forceRollback);
} finally {
if(this.transaction != null) {
// 调用了 tx 的close
this.transaction.close();
}
}
} catch (SQLException var11) {
log.warn("Unexpected exception on closing transaction. Cause: " + var11);
} finally {
this.transaction = null;
this.deferredLoads = null;
this.localCache = null;
this.localOutputParameterCache = null;
this.closed = true;
}
}
// 这里的tx 是 SpringManagedTransaction, 上面已经分析
public void close() throws SQLException {
DataSourceUtils.releaseConnection(this.connection, this.dataSource);
}
// 中间代码省略,最终代码
public static void doReleaseConnection(Connection con, DataSource dataSource) throws SQLException {
// 当 holder没有丢失,conn 还是一致时,并不会真正的release
if(con != null) {
if(dataSource != null) {
ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
if(conHolder != null && connectionEquals(conHolder, con)) {
conHolder.released();
return;
}
}
logger.debug("Returning JDBC Connection to DataSource");
doCloseConnection(con, dataSource);
}
}
可见,mybatis的close在一般情况下并不会真正去调用 conn.close(), 而是拖给 SpringManagedTransaction 去处理判断是否真实close,还是holder.released()。