@Transactional事务的使用和注意事项及其属性

点击查看

事务管理

提示
@Transactional注解只能应用到public可见度的方法上,可以被应用于接口定义和接口方法,方法会覆盖类上面声明的事务。

示例:

例如用户新增需要插入用户表、用户与岗位关联表、用户与角色关联表,如果插入成功,那么一起成功,如果中间有一条出现异常,那么回滚之前的所有操作, 这样可以防止出现脏数据,就可以使用事务让它实现回退。
做法非常简单,我们只需要在方法或类添加@Transactional注解即可。

@Transactional
public int insertUser(User user)
{
	// 新增用户信息
	int rows = userMapper.insertUser(user);
	// 新增用户岗位关联
	insertUserPost(user);
	// 新增用户与角色管理
	insertUserRole(user);
	return rows;
}

常见坑点

常见坑点1:遇到检查异常时,事务开启,也无法回滚。 例如下面这段代码,用户依旧增加成功,并没有因为后面遇到检查异常而回滚!!

@Transactional
public int insertUser(User user) throws Exception
{
	// 新增用户信息
	int rows = userMapper.insertUser(user);
	// 新增用户岗位关联
	insertUserPost(user);
	// 新增用户与角色管理
	insertUserRole(user);
	// 模拟抛出SQLException异常
	boolean flag = true;
	if (flag)
	{
		throw new SQLException("发生异常了..");
	}
	return rows;
}

原因分析:因为Spring的默认的事务规则是遇到运行异常(RuntimeException)和程序错误(Error)才会回滚。如果想针对检查异常进行事务回滚,可以在@Transactional注解里使用 rollbackFor属性明确指定异常。

例如下面这样,就可以正常回滚:

@Transactional(rollbackFor = Exception.class)
public int insertUser(User user) throws Exception
{
	// 新增用户信息
	int rows = userMapper.insertUser(user);
	// 新增用户岗位关联
	insertUserPost(user);
	// 新增用户与角色管理
	insertUserRole(user);
	// 模拟抛出SQLException异常
	boolean flag = true;
	if (flag)
	{
		throw new SQLException("发生异常了..");
	}
	return rows;
}

常见坑点2:在业务层捕捉异常后,发现事务不生效。 这是许多新手都会犯的一个错误,在业务层手工捕捉并处理了异常,你都把异常“吃”掉了,Spring自然不知道这里有错,更不会主动去回滚数据。

例如:下面这段代码直接导致用户新增的事务回滚没有生效。

@Transactional
public int insertUser(User user) throws Exception
{
	// 新增用户信息
	int rows = userMapper.insertUser(user);
	// 新增用户岗位关联
	insertUserPost(user);
	// 新增用户与角色管理
	insertUserRole(user);
	// 模拟抛出SQLException异常
	boolean flag = true;
	if (flag)
	{
		try
		{
			// 谨慎:尽量不要在业务层捕捉异常并处理
			throw new SQLException("发生异常了..");
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
	return rows;
}

推荐做法:在业务层统一抛出异常,然后在控制层统一处理。

@Transactional
public int insertUser(User user) throws Exception
{
	// 新增用户信息
	int rows = userMapper.insertUser(user);
	// 新增用户岗位关联
	insertUserPost(user);
	// 新增用户与角色管理
	insertUserRole(user);
	// 模拟抛出SQLException异常
	boolean flag = true;
	if (flag)
	{
		throw new RuntimeException("发生异常了..");
	}
	return rows;
}

一、注意事项

  • 不要在接口上声明@Transactional ,而要在具体类的方法上使用 @Transactional 注解,不然注解可能无效。
  • 不要将@Transactional放置在类级的声明中,放在类声明,会使得全部方法都有事务。所以@Transactional应该放在方法级别,不需要使用事务的方法,就不要放置事务,好比查询方法。不然对性能是有影响的。
  • 使用了@Transactional的方法,对同一个类里面的方法调用, @Transactional无效。
    好比有一个类Test,它的一个方法A,A再调用Test本类的方法B(无论B是否public仍是private),但A没有声明注解事务,而B有。则外部调用A以后,B的事务是不会起做用的。(常常在这里出错)
  public class Test{

  public void A(){ 
     B(); 
 }
  
   @Transactional
   public void B(){  
     
       }
}

二、异常回滚效果

  1. 使用了@Transactional的方法,只能是public,@Transactional注解的方法都是被外部其余类调用才有效,故只能是public。道理和上面的有关联。故在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错,但事务无效。

  2. 抛出受检查异常XXXException,事务会回滚。

  3. 抛出运行时异常NullPointerException,事务会回滚。

  4. 在service中加上@Transactional,如果是直接调该方法,会回滚,如果是间接调用,不会回滚。(即上文3提到的)

  5. 在service中的private加上@Transactional,事务不会回滚。

注意点

  1. Spring默认情况下会对(RuntimeException)及其子类来进行回滚,在遇见Exception及其子类的时候则不会进行回滚操作。
  2. @Transactional既可以作用于接口,接口方法上以及类上,类的方法上。但是Spring官方不建议接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外,@Transactional注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在protected、private或者默认可见性的方法上使用@Transactional 注解,这将被忽略,也不会抛出任何异常。
  3. Spring默认使用的是jdk自带的基于接口的代理,而没有使用基于类的CGLIB代理。
  4. 如果在接口、实现类或方法上都指定了@Transactional 注解,则优先级顺序为:方法>实现类>接口;
  5. 建议只在实现类或实现类的方法上使用@Transactional,而不要在接口上使用,这是因为如果使用JDK代理机制(基于接口的代理)是没问题;而使用CGLIB代理(继承)机制时就会遇到问题,因为其使用基于类的代理而不是接口,这是因为接口上的@Transactional注解是“不能继承的”;

测试

    @Override
    public  void test(User user){
            test1(user);
    }
    
    @Transactional
    public  void test1(User user){
           test2(user);
           test3(user);
           test4(user);
    }
    
    public  void test2(User user){
        int i = userMapper.addUser(user);
    }
    
    public void test3(User user){
        user.setUsername("333");
        int i = userMapper.updateUser(user);
        throw new RuntimeException("制造一个异常");//制造一个异常
        //抛异常按理应该会滚,addUser(user)执行了,test3的updateUser(user)没执行,
        //但运行后test2,test3两个方法都执行了,什么鬼?
       //要用代理对象调用,事务才会生效
    }
    
    public void test4(User user){
        user.setUsername("444");
        int i = userMapper.updateUser(user);
    }

结果

以上代码运行结果:方法test3()抛出异常,事务不会进行回滚,方法test2和test3都执行了,test4不执行,即使在test2,test3,test4都加事务注解@Transactional,也不会回滚

分析

  1. 因为spring是基于接口代理,即JDk动态代理去处理事务的,这就说明是基于方法拦截的(记住这点)
  2. 那么spring进入第一个方法test()时,它没有开启事务,就对里面嵌套的其他方法也忽略了,这也是正常的处理逻辑。
  3. 试想一下,如果spring不是这样处理就会出现逻辑混乱。比如此时test()方法里面执行了一些DB操作,然后再调用其他方法test1(),如果发生异常,此时test()里面的DB操作部分没有回滚,test1()里面却回滚了?那岂不是出现了逻辑混乱?

解决

    @Transactional
    @Override
    public  void test(User user){
            test1(user);
    }

    public  void test1(User user){
           test2(user);
           test3(user);
           test4(user);
    }

    public  void test2(User user){
        int i = userMapper.addUser(user);
    }

    public void test3(User user){
        user.setUsername("333");
        int i = userMapper.updateUser(user);
        throw new RuntimeException("制造一个异常");//制造一个异常
    }

    public void test4(User user){
        user.setUsername("444");
        int i = userMapper.updateUser(user);
    }

结果

以上代码运行结果,方法test3()抛出异常,事务进行回滚,方法test2,test3,test4都不执行。

try catch

    @Transactional
    @Override
    public  void test(User user){
        int i = userMapper.addUser(user);
        //把test2(user)放在try里面目的:抓住异常,不管test2(user)有没有异常,都可以正常执行addUser(user)方法
        try {
          UserService proxy=(UserService) AopContext.currentProxy();//获取代理对象
          proxy.test2(user);//用代理对象调用方法开启事务
        //test2(user);//单独调用时
        } catch (Exception e) {
            logger.error("修改出现了异常",e.getMessage());
        }
    }

    @Transactional
    @Override
    public void test2(User user){
        user.setUsername("888");
        int i = userMapper.updateUser(user);
        int x=1/0;//制造一个异常
        // throw new RuntimeException("制造一个异常");//制造一个异常
    }

总结

1.在方法里没有try catch抓异常时,被调用的方法入口处如果没有加事务注解,那么方法内调用其他的方法(不管其他方法前面有没有加事务注解),其他的方法的事务都不会生效;被调用的方法入口处如果加了事务注解,那么方法内调用其他的方法(不管其他方法前面有没有加事务注解),其他的方法的事务都会生效。

2.在方法里有try catch抓异常时,(如下代码)都加了事务注解,当抛出异常后,两个方法都不会回滚,并且数据库记录都发生改变(事务失效)事务失效解决:
获取当前对象的动态代理对象即可。

三、@Transactional()添加属性

Exception异常

  1. 让Exception异常也进行回滚操作,在调用该方法前加上:
	@Transactional(rollbackFor = Exception.class)
  1. 让RuntimeException不进行回滚操作,在调用该方法前加上:
	@Transactional(noRollbackFor=RunTimeException.class)

只读事务

  1. 在整个方法运行前就不会开启事务:
    这样就做成一个只读事务,可以提高效率
@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)

更多

在这里插入图片描述

查看方法的事务是否已经被执行

TransactionSynchronizationManager.isActualTransactionActive() 

返回true:该方法中存在事务,false则不存在

事务的传播及其属性的意义:

//如果有事务,那么加入事务,没有的话新创建一个
@Transactional(propagation=Propagation.REQUIRED)

//这个方法不开启事务
@Transactional(propagation=Propagation.NOT_SUPPORTED)

//不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Transactional(propagation=Propagation.REQUIREDS_NEW)

//必须在一个已有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation.MANDATORY)

//不能在一个事务中执行,就是当前必须没有事务,否则抛出异常
@Transactional(propagation=Propagation.NEVER)

//其他bean调用这个方法,如果在其他bean中声明了事务,就是用事务。没有声明,就不用事务。
@Transactional(propagation=Propagation.SUPPORTS)

//如果一个活动的事务存在,则运行在一个嵌套的事务中,如果没有活动的事务,则按照REQUIRED属性执行,它使用一个单独的事务。这个书屋拥有多个回滚的保存点,内部事务的回滚不会对外部事务造成影响,它只对DataSource TransactionManager事务管理器起效。
@Transactional(propagation=Propagation.NESTED)

//只读,不能更新,删除
@Transactional(propagation=Propagation.REQUIRED,readOnly=true)

//超时30秒
@Transactional(propagation=Propagation.REQUIRED,timeout=30)

//数据库隔离级别
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT)
  • 36
    点赞
  • 161
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tan.]der

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值