mybatis做为持久层流行框架已经被不少产品使用,固然为了接入Spring这个业内的另外一个流行框架,mybatis仍是作了些事,经过分析除了明白支持Spring的机制原理还了解Spring对持久层接入留了那些口。java
<!-- 配置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了。spring
XML定义完两个Bean后,可见平常开发只须要添加Dao接口,以及对应的sqlmap,而后在调用的Service中就能够自动注入,很是方便。sql
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 的,该接口须要实现下面的方法。session
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
MapperScannerConfigurer 的实现源码mybatis
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 进行处理app
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()。