Springboot的@Transactional 注解实现数据库事务处理

Springboot的@Transactional 注解实现数据库事务处理

一、注解式事务

注意事项:

  • @Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能.
  • 用 spring 事务管理器,由spring来负责数据库的打开,提交,回滚.默认遇到运行期例外(throw new RuntimeException(“注释”);)会回滚,即遇到不受检查(unchecked)的例外时回滚;而遇到需要捕获的例外(throw new Exception(“注释”);)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .如果让unchecked例外不回滚:@Transactional(notRollbackFor=RunTimeException.class)
  • @Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。然而,请注意仅仅@Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据,能够被可以识别 @Transactional注解和上述的配置适当的具有事务行为的beans所使用。上面的例子中,其实正是``元素的出现 开启 了事务行为。
1、启动类
  • @EnableTransactionManagement 开启事务
@SpringBootApplication	
@EnableTransactionManagement
public class StartSpringBootMain {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(StartSpringBootMain.class, args);
    }
}
2、业务类添加注解
  • @Transactional 数据库事务注解
    • propagation 事务传播行为
    • isolation 事务的隔离级别
    • timeout 事务超时设置,默认是30秒
    • readOnly 设置事务为只读事务,true为只读,false为可读写,默认为false
    • rollbackFor/rollbackForClassName 用于设置需要进行回滚的业务类名称
    • value/transactionManager
    • noRollbackFor/noRollbackForClassName 设置为不需要进行回滚操作的异常
@Service
@Transactional(readOnly = true,rollbackFor = Exception.class)
public class UserService {
    @Autowired(required = false)
    private UserMapper userMapper;

    @Transactional(readOnly = false,rollbackFor = Exception.class)
    public Integer updates(User blues,User zhangsan) throws Exception{
        int num = userMapper.updateById(blues);
        System.out.println(1/0);
        num = num + userMapper.updateById(zhangsan);
        return num;
    }
}
3、事务传播行为介绍
(1) Propagation.REQUIRED

如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)-(required)

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
     methodB();
    // do something
}
 
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
  	  // do something
}
//=========分析==============//**
1、单独调用methodB时,因为上下文不存在事务,会开启一个新事物
2、调用methodA时,因为不存在事务,会开启一个事务,
    当执行到methodB方法时,有事务会将事务加入到当前的事务中来
**//=======================
(2)Propagation.SUPPORTED

容器不为这个方法开启事务(not_supported)

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
	 methodB();
	// do something
}
 
// 事务属性为SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
    // do something
}
//=========分析==============/**
1、单独调用methodB时,是非事务执行
2、调用methodA时,因为不存在事务,会开启一个事务,
    当执行到methodB方法时,有事务会将事务加入到当前的事务中来
**//=======================//
(3)Propagation.REQUIRES_NEW

不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务(requires_new)

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
doSomeThingA();
methodB();
doSomeThingB();
// do something else
}
// 事务属性为REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
    // do something
}
//=========分析==============/**
1、单独调用methodB时,因为当前没有事务,会抛出异常
2、调用methodA时,因为不存在事务,会开启一个事务,
    当执行到methodB方法时,有事务会将事务加入到当前的事务中来
**//=======================//
(4)Propagation.MANDATORY

必须在一个已有的事务中执行,否则抛出异常(mandatory)

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
 methodB();
// do something
}
 
// 事务属性为MANDATORY
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
    // do something
}
//=========分析==============/**
1、单独调用methodB时,因为当前没有事务,会抛出异常
2、调用methodA时,因为不存在事务,会开启一个事务,
    当执行到methodB方法时,会将A的事务挂起来,新建一个事务B,执行完成B事务后,再打开A事务,执行完成后提交事务
**//=======================//
(5)Propagation.NEVER

必须在一个没有的事务中执行,否则抛出异常(与Propagation.mandatory相反)(never)

@Transactional(propagation = Propagation.NEVER)
public void methodA() {
 methodB();
	// do something
}
//=========分析==============/**
1、单独调用methodA时,因为当前有事务,会抛出异常,如果没有事务,则正常执行
**//=======================//
(6)Propagation.NOT_SUPPORTS

如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务(supports)

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
     methodB();
    // do something
}
 
@Transactional(propagation = Propagation.NOT_SUPPORTS)
public void methodB() {
  	  // do something
}
//=========分析==============//**
1、单独调用methodB时,以非事务执行。
2、调用methodA时,因为不存在事务,会开启一个事务A,
    当执行到methodB方法时,会强制把A事务挂起,以非事务执行methodB,执行完成B在执行A事务
**//=======================
(7)Propagation.NOT_SUPPORTS

如果一个活动的事务存在,则运行在一个嵌套的事务中。 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。
这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。
需要JDBC 驱动的java.sql.Savepoint类。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true(属性值默认为false)。

PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:
它们非常类似,都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务。
使用 PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它需要JTA事务管理器的支持。

使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED时,需要JDBC 3.0以上驱动及1.4以上的JDK版本支持。其它的JTATrasactionManager实现可能有不同的支持方式。

PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。

另一方面, PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。

由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back.

@Transactional(propagation = Propagation.REQUIRED)
public void methodA(){
  doSomeThingA();
  methodB();
  doSomeThingB();
}
 
@Transactional(propagation = Propagation.NEWSTED)
public void methodB(){
  //……
}
//=========分析==============//**
1、如果单独调用methodB方法,则按REQUIRED属性执行
2、调用methodA时,上下文不存在事务,则创建事务A,当methodB方法调用之前,调用setSavepoint方法,保存当前的状态到savepoint。如果methodB方法调用失败,则恢复到之前保存的状态。但是需要注意的是,这时的事务并没有进行提交,如果后续的代码(doSomeThingB()方法)调用失败,则回滚包括methodB方法的所有操作。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。
**//=======================
4、事务的隔离级别

​ MYSQL: 默认为REPEATABLE_READ级别

​ SQLSERVER: 默认为READ_COMMITTED

  1. @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    读取未提交数据(会出现脏读, 不可重复读) 基本不使用,可能会造成

  2. @Transactional(isolation = Isolation.READ_COMMITTED)
    读取已提交数据(会出现不可重复读和幻读)

  3. @Transactional(isolation = Isolation.REPEATABLE_READ)
    可重复读(会出现幻读)

  4. @Transactional(isolation = Isolation.SERIALIZABLE)
    串行化

二、编程式事务

1、创建一个编程式事务的配置类

添加这个类,根据知己需要修改切入点,然后放到能被spring boot扫描到的包下即可

/**
 * @ClassName: GlobalTransactionAdviceConfig
 * @Description: AOP全局事务管理配置
 *
 * 声明式事务说明:
 *      1.如果将业务逻辑放到service层面来处理,则能够保证事务安全,即便使用了AOP来切入service方法也能保证事务安全;
 *      2.如果多个service在controller层做业务逻辑(本身就是错误的),则不能保证事务安全。
 * 对于2中的情况,应该尽量避免,因为本身就是错误的;
 * 这种情况在面向切面编程中也有可能碰到,如,因为必要切入点为controller(应尽量避免,原则应切service),切面程序跟controller业务逻辑不同,
 *   service不同,会导致事务混乱;
 *
 * 如果出现上述情况,则可以使用编程式事务管理(也就是手动控制事务)
 *      在controller逻辑开始之前手动开启/获取事务,然后在controller逻辑结束后再根据需要提交或者回滚事务;
 *      在AOP中也是如此,在before中手动开启/获取事务(这一步是必须的),在after中处理切面逻辑,然后根据需要提交或者回滚事务,如果由于异常需要回滚事务,记得修改返回信息
 *
 **/

@Aspect
@Configuration
public class GlobalTransactionAdviceConfig {
    private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.example.demo.service..*.*(..))";

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Bean
    public TransactionInterceptor txAdvice() {
        DefaultTransactionAttribute txAttr_REQUIRED = new DefaultTransactionAttribute();
        txAttr_REQUIRED.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

        DefaultTransactionAttribute txAttr_REQUIRED_READONLY = new DefaultTransactionAttribute();
        txAttr_REQUIRED_READONLY.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        txAttr_REQUIRED_READONLY.setReadOnly(true);

        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
        source.addTransactionalMethod("add*", txAttr_REQUIRED);
        source.addTransactionalMethod("insert*", txAttr_REQUIRED);
        source.addTransactionalMethod("save*", txAttr_REQUIRED);
        source.addTransactionalMethod("create*", txAttr_REQUIRED);
        source.addTransactionalMethod("delete*", txAttr_REQUIRED);
        source.addTransactionalMethod("update*", txAttr_REQUIRED);
        source.addTransactionalMethod("exec*", txAttr_REQUIRED);
        source.addTransactionalMethod("set*", txAttr_REQUIRED);
        source.addTransactionalMethod("get*", txAttr_REQUIRED_READONLY);
        source.addTransactionalMethod("query*", txAttr_REQUIRED_READONLY);
        source.addTransactionalMethod("find*", txAttr_REQUIRED_READONLY);
        source.addTransactionalMethod("list*", txAttr_REQUIRED_READONLY);
        source.addTransactionalMethod("count*", txAttr_REQUIRED_READONLY);
        source.addTransactionalMethod("is*", txAttr_REQUIRED_READONLY);
        source.addTransactionalMethod("select*", txAttr_REQUIRED_READONLY);
        return new TransactionInterceptor(transactionManager, source);
    }

    @Bean
    public Advisor txAdviceAdvisor() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
        return new DefaultPointcutAdvisor(pointcut, txAdvice());
    }
}
2、添加User的AOP类

如果事务不在Service下面,而是在Controller中

切入点为controller时,如何使用编程式事务管理控制事务

@Component
@Aspect
public class ResUserAspect {

    @Autowired
    private IPartnerService partnerService;
    @Autowired
    private PlatformTransactionManager platformTransactionManager;
    @Autowired
    private TransactionDefinition transactionDefinition;

    private TransactionStatus transactionStatus;
	// 验证切入点为service时,AOP编程中的事务问题
    @Pointcut("execution(public * com.example.demo.controllers.UserController.insertUser(..))")
    private void insertUser(){}

    @Before(value = "insertUser()")
    public void before(){
        //在切入点程序执行之前手动开启事务 - 必须的操作
        transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
    }
   //切入点为controller时的事务验证
    @Transactional
    @AfterReturning(pointcut = "insertUser()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result){

        if (!(result instanceof JSONMsg)){
            System.out.println(result.getClass());
            return;
        }
        JSONMsg jsonMsg = (JSONMsg) result;
        try{

            if (jsonMsg.getCode() == 200){
                ResPartner partner = new ResPartner();
                ResUser user = (ResUser) jsonMsg.getData();
                partner.setId(user.getId());
                partner.setName(user.getName());
                partner.setDisplayName(user.getLogin());
                int a = 1/0;
                int i = partnerService.add(partner);
                System.out.println(i);
            }

            platformTransactionManager.commit(transactionStatus);  // 手动提交事务
            System.out.println("提交事务");
        }catch (Exception e){
            platformTransactionManager.rollback(transactionStatus); // 出现异常,回滚事务
            System.out.println("回滚事务");
            System.out.println(e.getMessage());

            //修改返回数据
            jsonMsg.setCode(400);
            jsonMsg.setMsg(e.getMessage());
        }

    }
}

三、代码中编程式事务

1、使用transactionTemplate进行事务控制
@Autowired
private TransactionTemplate transactionTemplate;
	
public void testTranscation(){
    // 设置隔离级别
    transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
    // 设置传播属性
    transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    Boolean isSuccess = transactionTemplate.execute(new TransactionCallback<Boolean>() {
        public Boolean doInTransaction(TransactionStatus status) {
            Boolean result = true;
            try {
                // TODO 
            } catch (Exception e) {
                status.setRollbackOnly();
                result = false;
                // TODO 
            }
            return result;
        }
    });   
}
//=========================测试==============//
@Component
public class TransactionalSample {
    @Autowired
    private ManualDemo manualDemo;

    public void testManualCase() {
        System.out.println("======= 编程式事务 start ========== ");
        manualDemo.query("transaction before", 220);
        manualDemo.testTransaction(220);
        manualDemo.query("transaction end", 220);
        System.out.println("======= 编程式事务 end ========== ");
    }
}

你可能感兴趣的:(spring,boot,数据库,java)