Spring整合MyBatis下@Transactional无效解决方案

场景复现

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完成事务提交,按道理这样实现没什么毛病,但是问题是事务不起作用,有心的可以先思考一下为什么不起作用。

问题分析

以上配置存在如下问题:

  1. @Transactional 只能应用在作用于为public的方法上,其它的无效,也不会抛出异常,由AOP的触发机制决定的;
  2. @Transactional 只作用于外部调用的情况下(触发AOP),函数内部调用也无效;
  3. 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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值