项目使用的是SpringMVC+Mybatis,如果service的方法里面全都是查询,那么加不加@Transactional有什么影响呢?
SpringMVC集成Mybatis的常见配置如下:
<bean id="dataSource" 。。。>
bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath*:com/**/*Mapper.xml" />
<property name="configLocation" value="classpath:sqlMapping.xml" />
bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="****" />
bean>
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
bean>
首先我们来看下spring是如何跟mybatis关联到一起的,看源码:
从MapperScannerConfigurer开始,它会扫描basePackage下面的接口,并且把接口注册到Spring容器:
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
。。。
@Override
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));
}
。。。
}
//接着看ClassPathMapperScanner:
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
。。。
private MapperFactoryBean<?> mapperFactoryBean = new MapperFactoryBean<Object>();
@Override
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 {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
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.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
//重点看这里,这里的意思是每一个Mapper接口被注入到Spring容器以后,实际上是一个mapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
//给这个mapperFactoryBean注入了其他的属性:addToConfig
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
//给这个mapperFactoryBean注入了其他的属性:sqlSessionFactory
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;
}
//给这个mapperFactoryBean注入了其他的属性:sqlSessionTemplate
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
}
//下面我们来看MapperFactoryBean
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
}
//继续看SqlSessionDaoSupport
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);
}
}
public SqlSession getSqlSession() {
return this.sqlSession;
}
}
//这里可以看出来,spring操作mybatis的时候使用的SqlSession,并不是mybatis默认的DefaultSqlSession,而是使用自定义的SqlSessionTemplate。
//下面就到了SqlSessionTemplate如何获取SqlSession:
public class SqlSessionTemplate implements SqlSession, DisposableBean {
。。。
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;
//这里是生成了SqlSession的一个代理类,具体的操作还得去看SqlSessionInterceptor
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
}
//SqlSessionInterceptor:
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);
}
}
}
}
//我们来跟一下getSqlSession,因为拿到了SqlSession就可以操作数据库了:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
//首先从TransactionSynchronizationManager中查找holder,然后从holder中查找SqlSession
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {//如果找到了就直接返回了
return session;
}
//如果找不到,再去新建一个SqlSession
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
//holder只有在开启了事务才会有值,因此如果在service的方法上加上了@Transactional的注解,那么框架就会把holder给缓存到TransactionSynchronizationManager中。
//如果同一个SqlSession的两个查询操作,是可以使用mybatis的一级缓存的,因此,加了@Transactional以后,里面的两个同样的查询语句只会查询数据库一次,而如果不加,每次都是新建一个SqlSession,因此一级缓存也就不起作用了!
//可以写代码验证一下:
@Service
public class ProductService {
@Autowired
ProductDao productDao;
@Transactional
public Product getByBm(String bankBm, String pdctBm) {
productDao.selectByBm(bankBm, pdctBm);
return productDao.selectByBm(bankBm, pdctBm);
}
}
//getByBm中调用了2次selectByBm,如果不加@Transactional会查询数据库2次,加上只会查询一次,很有意思!