上一篇提到了和Spring整合后,Mybatis的BatchExecutor無法真正生效,本篇就好好分析分析這里面的原因
一 配置文件
<!-- 配置sqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 實例化sqlSessionFactory時需要使用上述配置好的數據源以及SQL映射文件 --> <property name="dataSource" ref="dataSource" /> <!-- 自動掃描me/gacl/mapping/目錄下的所有SQL映射的xml文件, 省掉Configuration.xml里的手工配置 value="classpath:me/gacl/mapping/*.xml"指的是classpath(類路徑)下me.gacl.mapping包中的所有xml文件 UserMapper.xml位於me.gacl.mapping包下,這樣UserMapper.xml就可以被自動掃描 --> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:me/gacl/mapping/*.xml" /> </bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 掃描me.gacl.dao這個包以及它的子包下的所有映射接口類 --> <property name="basePackage" value="me.gacl.dao" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> </bean>
接下來我們就好好分析這兩個類 SqlSessionFactoryBean ,MapperScannerConfigurer
二 SqlSessionFactoryBean
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
它是一個FactoryBean,那我們只需要關注getObject方法就好了
@Override public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { afterPropertiesSet(); } return this.sqlSessionFactory; }
afterPropertiesSet有一處還是值得研究的
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
...
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
mybatis中很重要的調用鏈上,一個sqlSession包含一個executor,一個executor包含一個transaction,這個transanction是真正提供jdbc的connection的,這里負責創建transaction的是spirng提供的
SpringManagedTransactionFactory,就表示提供connection的任務由spring完成。
這樣,spring容器內就有一個java bean 類型是 SqlSessionFactory,name是我們配的 sqlSessionFactory
三 MapperScannerConfigurer
它實現了接口 BeanDefinitionRegistryPostProcessor 就說明它具有向beanFactory注冊BeanDefinition的能力
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); scanner.setAddToConfig(this.addToConfig); scanner.setAnnotationClass(this.annotationClass); scanner.setMarkerInterface(this.markerInterface); scanner.setSqlSessionFactory(this.sqlSessionFactory); scanner.setSqlSessionTemplate(this.sqlSessionTemplate); scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName); scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); scanner.setResourceLoader(this.applicationContext); scanner.setBeanNameGenerator(this.nameGenerator); scanner.registerFilters(); scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
因為我們只配了 <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> 所以這里 this.sqlSessionFactory = null
basePackage = me.gacl.dao
直接跳到 ClassPathMapperScanner.doScan
public Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); } else { for (BeanDefinitionHolder holder : beanDefinitions) { GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition(); if (logger.isDebugEnabled()) { logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface"); } // the mapper interface is the original class of the bean // but, the actual class of the bean is MapperFactoryBean definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());//先把原始的類型取出來塞到BD的屬性里 me.gacl.dao.UserMapper definition.setBeanClass(MapperFactoryBean.class);//然后重新給BD賦予class,這樣這個bean的類型就是 MapperFactoryBean definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false; if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) { definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); explicitFactoryUsed = true; } else if (this.sqlSessionFactory != null) { definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory); explicitFactoryUsed = true; }
一個BeanDefinition就這樣被完成了,並注冊到beanFactory里。它有幾個重要的屬性
1 該Bean的class是 MapperFactoryBean
2 它有屬性 mapperInterface 這里是 me.gacl.dao.UserMapper
3 它有屬性 sqlSessionFactory 就是在上一小節得到的 sqlSessionFactory (Mybatis的原生類)
好了,到這里后我們要分析的代碼就是 MapperFactoryBean
四 MapperFactoryBean
這個類不得了,可以說不能執行Batch的原因就出在他身上
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { private Class<T> mapperInterface; private boolean addToConfig = true;
可以看出來 MapperFactoryBean 本身就一個屬性 mapperInterface 表示的是 me.gacl.dao.UserMapper
主要的功能和屬性都在 SqlSessionDaoSupport
而且它本身就是一個 FactoryBean 還是來看 getObject,getMapper就是生成Dao中接口的代理類
public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); }
getSqlSession()的實現就至關重要了,接下來就是重點分析 SqlSessionTemplate。這里請記住兩個至關重要的類SqlSessionDaoSupport ,SqlSessionTemplate
public abstract class SqlSessionDaoSupport extends DaoSupport { private SqlSession sqlSession; private boolean externalSqlSession; public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (!this.externalSqlSession) { this.sqlSession = new SqlSessionTemplate(sqlSessionFactory); } }
這個類看上去有點奇怪,他本身實現了SqlSession接口,但是成員變量還有一個sqlSessionProxy,這個就是很常見的組合模式,干活的肯定是sqlSessionProxy
public class SqlSessionTemplate implements SqlSession { private final SqlSessionFactory sqlSessionFactory; private final ExecutorType executorType; private final SqlSession sqlSessionProxy; private final PersistenceExceptionTranslator exceptionTranslator;
熟悉的動態代理代碼,那么重點當然是分析實現InvocationHandler的邏輯了
this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor());
private class SqlSessionInterceptor implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);//這個sqlSession就是 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);//spring會在每次執行后都關閉,但是這個關閉並不是sqlSession的關閉,僅僅是計數-1 }//也就是說提交完全交給spring的事務去弄 } }
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, "No SqlSessionFactory specified"); notNull(executorType, "No ExecutorType specified"); SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);//這里每次都是null if (holder != null && holder.isSynchronizedWithTransaction()) { if (holder.getExecutorType() != executorType) { throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction"); } holder.requested(); if (logger.isDebugEnabled()) { logger.debug("Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction"); } return holder.getSqlSession(); } if (logger.isDebugEnabled()) { logger.debug("Creating a new SqlSession"); } SqlSession session = sessionFactory.openSession(executorType);//所以這里每次都會執行,也就是每次都會new出來Executor
正因為每次都需要搞出來一個新的SqlSession,每個SqlSession里都會new一個Executor,所以批量執行是沒法完成的。
這篇文章講解了spring和mybatis的整合原理,進而分析出了為啥batch沒有效果,這里說的沒有效果不是說里面的Executor不是BatchExecuto,而是並不是批量提交的,而是單條提交的。