事务配置

0、前言

​  近期,我在项目中使用事务注解(@Transactional)的时候发现一个问题:事务失效。

/**
 * 事务回滚失败
 * example1 还是保存了
 */
@Transactional
public void saveEntity(ExampleVo exampleVo) {
 	 Example1 example1 = exampleVo.getExample1();
     example1Dao.save(example1);
	
     // throw ArithmeticException
     int res = 10/0;
     
     Example2 example2 = exampleVo.getExample2();
     example2Dao.save(example2);
 }

排查:

  • @Transactional 排查,rollbackFor 属性默认为 RuntimeException 和 Error,ArithmeticException 继承于 RuntimeException,没有发现问题原因;

  • 添加全局事务配置,没有发现问题原因;

  • 数据库排查,发现问题原因,数据库表所使用的存储引擎为 MyISAM,MyISAM 不支持事务。将数据库表的存储引擎改为 InnoDB 后,问题解决。

// 显示数据库表所使用的存储引擎 
show create table example1;

// 修改存储引擎
alter table example1 engine = InnoDB;

在这里插入图片描述

问题产生的原因:

​  该项目使用的持久化框架为 JPA,同时数据库表由 JPA 自动生成,而由 JPA 自动生成的表若无配置表的存储引擎,JPA 默认采用 MyISAM 作为表的存储引擎。

spring:
  jpa:
    hibernate:
      dialect: org.hibernate.dialect.MySQL5Dialect

1、事务注解

​  @Transactional 开启事务,可以标注在类或方法上。

官方注释:Describes a transaction attribute on an individual method or on a class.

比较重要的属性:

  • propagation:传播类型,默认 Propagation.REQUIRED

  • isolation:隔离级别,默认 Isolation.DEFAULT(采用数据库的默认隔离级别);

  • rollbackFor:触发事务回滚的异常类型,默认为 RuntimeExceptionError


2、全局事务配置

2.1、启动类
@SpringBootApplication
@EnableTransactionManagement
public class MessageApplication {
    public static void main(String[] args) {
        SpringApplication.run(MessageApplication.class, args);
    }
}
2.2、配置类
/**
 *  source: https://blog.csdn.net/qq_52596258/article/details/119407315
 */
@Configuration
public class GlobalTransactionConfig {

    private final PlatformTransactionManager transactionManager;

    @Autowired
    public GlobalTransactionConfig(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    /**
     * 事务管理配置
     */
    @Bean
    public TransactionInterceptor getTxAdvice() {

        // 设置第一个事务管理的模式(适用于“增删改”)
        RuleBasedTransactionAttribute txAttr_required = new RuleBasedTransactionAttribute();
        // 设置事务传播行为(当前存在事务则加入其中,如果没有则新创建一个事务)
        txAttr_required.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        // 指定事务回滚异常类型(设置为“Exception”级别)
        txAttr_required.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
        // 设置事务隔离级别(读以提交的数据,此处可不做设置,数据库默认的隔离级别就行)
        txAttr_required.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);


        // 设置第二个事务管理的模式(适用于“查”)
        RuleBasedTransactionAttribute txAttr_readonly = new RuleBasedTransactionAttribute();
        // 设置事务传播行为(当前存在事务则挂起,继续执行当前逻辑,执行结束后恢复上下文事务)
        txAttr_readonly.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
        // 指定事务回滚异常类型(设置为“Exception”级别)
        txAttr_readonly.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
        // 设置事务隔离级别(读以提交的数据,此处可不做设置,数据库默认的隔离级别就行)
        txAttr_readonly.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        // 设置事务是否为“只读”(非必须,只是声明该事务中不会进行修改数据库的操作,可减轻由事务机制造成的数据库,属压力于性能优化推荐配置)
        txAttr_readonly.setReadOnly(true);


        // 事务管理规则,承载需要进行事务管理的方法名(模糊匹配),及事务的传播行为和隔离级别等属性
        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
        //source.addTransactionalMethod("insert*",txAttr_required);  可以直接设置,推荐使用map集合或者properties文件操作存储

        /**
         * 创建一个 map 用于存储需要进行事务管理的方法名(模糊匹配)
         */
        Map<String, TransactionAttribute> map = new HashMap<>();
        // 增删改的操作需要设置为REQUIRED的传播行为
        map.put("insert*", txAttr_required);
        map.put("add*", txAttr_required);
        map.put("save*", txAttr_required);
        map.put("increase*", txAttr_required);
        map.put("delete*", txAttr_required);
        map.put("remove*", txAttr_required);
        map.put("update*", txAttr_required);
        map.put("alter*", txAttr_required);
        map.put("modify*", txAttr_required);
        // 查询的操作设置为REQUIRED_NOT_SUPPORT非事务传播行为,并设置为只读,减轻数据库压力
        map.put("select*", txAttr_readonly);
        map.put("get*", txAttr_readonly);

        // 注入上述匹配好的map集合
        source.setNameMap(map);

        // 实例化事务拦截器(整合事务管理和事务操作数据源-要操作的事务方法)
        TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, source);
        // 并将事务通知返回
        return txAdvice;
    }

    /**
     * 利用 AspectJExpressionPointcut 设置切面
     *
     * @return
     */
    @Bean
    public Advisor txAdviceAdvisor() {

        // 声明切入面(也就是所有切入点的逻辑集合,所有切入点形成的切面)
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        // 设置切入点的路径
        pointcut.setExpression("execution(* com.example.message.service..*.*(..))");

        // 返回aop配置:整合切面(切入点集合) 和  配置好的事务通知(也就是事务拦截操作)
        Advisor advisor = new DefaultPointcutAdvisor(pointcut, getTxAdvice());

        return advisor;
    }

}

3、AOP 补充

切入点表达式语法:

execution( [访问控制权限修饰符] 返回值类型 [全限定类名] 方法名(形式参数列表) [异常] )

  • 访问控制权限修饰符:可选项,默认任意类型;
  • 返回值类型:必选项,* 表示任意类型;
  • 全限定类名:可选项,… 表示当前包以及子包下的所有类,默认所有类;
  • 方法名:必选项,* 表示任意方法;
  • 形式参数列表,必选项,() 表示没有参数的方法,(…) 表示参数类型和个数任意的方法,(, String) 表示第一个参数类型任意,第二个参数类型为 String 。
/** 
 *  表示
 *    任意访问控制权限修饰符 
 *  * 任意返回值 
 *  com.example.message.service.. com.example.message.service 包及其子包下的
 *  *. 所有类下的
 *  * 任意方法
 *  (..) 形式参数列表任意 
 */
execution(* com.example.message.service..*.*(..)) 

更多推荐