场景复现
Spring + MyBatis 整合,将事务交给 Spring 处理,配置如下:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"></property>
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.sankuai.dao.mapper" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
<bean id="donateTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
</bean>
<!-- 用注解来实现事务管理 -->
<tx:annotation-driven transaction-manager="donateTransactionManager" proxy-target-class="true"/>
业务调用过程:
@Override
public Object bus() {
try {
//前置处理
// 事务提交
trans();
// 后置处理
} catch (Exception e) {
log.error("error : {}", e);
}
return rsp;
}
@Transactional
private void trans() {
// 多条SQL
}
bus调用trans完成事务提交,按道理这样实现没什么毛病,但是问题是事务不起作用,有心的可以先思考一下为什么不起作用。
问题分析
以上配置存在如下问题:
- @Transactional 只能应用在作用于为public的方法上,其它的无效,也不会抛出异常,由AOP的触发机制决定的;
- @Transactional 只作用于外部调用的情况下(触发AOP),函数内部调用也无效;
- SpringMVC扫描时也有可能覆盖(这里没出现,如:applicationContext.xml设置了component-scan,spring-mvc.xml也component-scan了同一个包,也会导致覆盖无效);
解决方案
有如下四种解决方案:
1. 操作MyBatis的sqlSession
sqlSession.rollBack();
...
sqlSession.commit();
无效,因为整合了spring-mybatis,已将事务交于spring处理,此法不通
2. 直接在bus上@Transactional
简单易用,缺点是将事务拉长,影响巨大,如果方法体中有RPC,那绝对不能用这种方法
3. 使用编程式事务
实现如下:
1.简单封装事务助手类
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
/**
* @author 黄平财
* @date 2017/11/27 16:46
* @email hpingcai@gmail.com
*/
public class TransactionHelper {
public static final String TRANSACTIONAL_BEAN_ID = "transactionManager";
private PlatformTransactionManager transactionManager;
// 记录事务状态
private TransactionStatus status;
// 可以在外部定义事务规则
private DefaultTransactionDefinition definition;
/**
* 默认规则创建事务过程
*
* @return
*/
public static TransactionHelper newInstance() {
return new TransactionHelper();
}
/**
* 根据事务规则创建事务
*
* @param definition
* @return
*/
public static TransactionHelper newInstance(DefaultTransactionDefinition definition) {
return new TransactionHelper(definition);
}
private TransactionHelper(DefaultTransactionDefinition definition) {
this.definition = definition;
transactionManager = (PlatformTransactionManager) SpringContextHolder.getBean(TRANSACTIONAL_BEAN_ID);
}
private TransactionHelper() {
this(new DefaultTransactionDefinition());
this.definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
}
/**
* 开启事务
*/
public void begin() {
status = transactionManager.getTransaction(definition);
}
public void rollBack() {
transactionManager.rollback(status);
finish();
}
public void commit() {
transactionManager.commit(status);
finish();
}
private void finish() {
definition = null;
status = null;
transactionManager = null;
}
}
2.持有SpringContext对象
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
/**
* @author 黄平财
* @date 2017/11/27 17:15
* @email hpingcai@gmail.com
*/
@Component
public class SpringContextHolder implements ApplicationContextAware {
private static ApplicationContext APPLICATION_CONTEXT;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextHolder.APPLICATION_CONTEXT = applicationContext;
}
public static Object getBean(String name) {
return APPLICATION_CONTEXT.getBean(name);
}
public static Object getBean(String name, Class clazz) {
return APPLICATION_CONTEXT.getBean(name, clazz);
}
@PreDestroy
public void destroy() {
SpringContextHolder.APPLICATION_CONTEXT = null;
}
}
3.使用
private void trans() {
TransactionHelper helper = TransactionHelper.newInstance();
try{
helper.begin();
// 多条SQL
helper.commit();
}catch (Exception e){
if(null!=helper){
helper.rollBack();
}
throw e;
}
}
4.使方法走AOP代理
((XXService)AopContext.currentProxy()).trans()
结语
第四种方案较为简介靠谱.
引用
静态持有ApplicationContext:https://www.cnblogs.com/wcyBlog/p/4657885.html