@Transactional的用法详解及Transactional事务无效的源码分析

数据库事务正确执行的四要素

1.原子性

事务是不可分割的最小的工作单元,事务内的操作要么全做,要么全不做,不能只做一部分。

2.一致性

事务执行前数据库的数据按照逻辑处于正确的状态,事务执行后数据库的数据按照逻辑也处于正确状态。如果事务执行前后不是逻辑应该的正确状态,那么数据是不一致的。

3.隔离性

在并发事务的条件下,事务之间互不影响,即一个事务的内部操作对其他事务不影响,需要设置事务的隔离级别来指定隔离性。

4.持久性

事务一旦执行成功,对数据库数据的修改是永久的,不会因为各种异常导致数据不一致或丢失。

数据库事务并发执行会遇到的问题

脏读:一个事务A读取了另一个事务B没有提交的数据,如果事务B回滚了,那么事务A读取的数据不正确。

丢失更新:两个事务同时更新一条数据,导致最后一个事物的更新覆盖了前面事务的更新值。一般由于没加锁的原因造成。

不可重复读:同一个事物内两次读取数据结果不同。例如事务A读取数据,其中事务B对数据做修改,造成同一个事务中事务A再次读取数据结果与上一次不同。

幻读:一个事务A读取了另一个事务B新insert的数据。同一个事务中事务A后一次读取要比前一次多了数据。

SQL的4个等级的事务隔离级别

级别性能
未提交读(READ UNCOMMITTED )最低隔离级别,一个事务能读取到别的事务未提交的更新数据,很不安全,可能出现丢失更新、脏读、不可重复读、幻读
提交读(READ COMMITTED)一个事务能读取到别的事务提交的更新数据,不能看到未提交的更新数据,不会出现丢失更新、脏读,但可能出现不可重复读、幻读;
可重复读(REPEATABLE READ)保证同一事务中先后执行的多次查询将返回同一结果,不受其他事务影响,不可能出现丢失更新、脏读、不可重复读,但可能出现幻读;
序列化(SERIALIZABLE)最高隔离级别,不允许事务并发执行,而必须串行化执行,最安全,不可能出现更新、脏读、不可重复读、幻读,但是效率最低。

事务的隔离级别越高,数据库事务并发执行的性能越差,MySQL支持四种事务等级,默认事务的隔离级别是REPEATABLE READ。Oracle数据库支持READ COMMITTED和SERIALIZABLE这两种事务隔离级别,Oracle数据库不支持脏读。Oracle默认的数据库隔离级别是READ COMMITTED。Sqlserver默认的数据库隔离级别是READ COMMITTED。

@Transactional参数注解详解

  • propagation:事务传播行为参数

       @Transactional(propagation=Propagation.REQUIRED) :如果有事务, 那么加入事务, 没有的话新建一个(默认情况下) 
  @Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不为这个方法开启事务 
  @Transactional(propagation=Propagation.REQUIRES_NEW) :不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务 
  @Transactional(propagation=Propagation.MANDATORY) :必须在一个已有的事务中执行,否则抛出异常 
  @Transactional(propagation=Propagation.NEVER) :必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反) 
  @Transactional(propagation=Propagation.SUPPORTS) :如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.  

  • timeout:事务超时设置 

       @Transactional(timeout=50) //默认是50秒

  • isolation :事务隔离级别

       @Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读, 不可重复读) 基本不使用 
  @Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读) 
  @Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读) 
  @Transactional(isolation = Isolation.SERIALIZABLE):串行化

       一般项目中不会配置isolation,isolation 为默认值,也就是所连接数据库的默认事务隔离级别。

Isolation isolation() default Isolation.DEFAULT;

 /**
	 *事务隔离级别为连接数据库的默认隔离级别
	 */   
DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
  • readOnly:  设置为true,表示当前事务是只读事务,设置false,表示当前事务为非只读事务。
  • rollbackFor:  设置需要回滚的异常类,只有这种异常类及其子类抛出异常后,事务才会回滚。
//抛出单个异常类
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
//抛出多个异常类
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Throwable.class,RuntimeException.class})
  • noRollbackFor:  设置不需要回滚的异常类,当事务抛出这种异常的时候,事务不回滚。
//指定多个异常类
@Transactional(propagation = Propagation.REQUIRED, noRollbackFor = {Throwable.class,RuntimeException.class})
//指定单一异常类
@Transactional(propagation = Propagation.REQUIRED, noRollbackFor=Throwable.class)
  • rollbackForClassName:设置需要回滚的异常类名称,只有抛出这种异常的时候,事务回滚。
//指定多个异常类
@Transactional(propagation = Propagation.REQUIRED, rollbackForClassName = {"Throwable","RuntimeException"})
//指定一个异常类
	@Transactional(propagation = Propagation.REQUIRED, rollbackForClassName = "Throwable")
  • noRollbackForClassName:设置不需要回滚的异常类名称,抛出这种异常的时候,事务不回滚。
@Transactional(propagation = Propagation.REQUIRED, noRollbackForClassName = {"Throwable","RuntimeException"})
	@Transactional(propagation = Propagation.REQUIRED, noRollbackForClassName = "Throwable")

  @Transactional 的Spring事务配置

<bean id="transactionManager"
		  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>
<!-- 使用annotation定义事务 -->
	<tx:annotation-driven transaction-manager="transactionManager"/>

@Transactional事务失效的例子介绍

1.@Transactional注解在非public方法上时,事务回滚不起作用

   

@Service
public class StudentService {
    private static final Logger logger = LoggerFactory.getLogger(SelfEmployedInfoImportService.class);
    @Autowired
    private ScoreMapper scoreMapper;
    @Autowired
    private StudentMapper studentMapper;
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Throwable.class)
      //在非public方法上用@Transactional
      void save(ScoreVo scoreVo, StudentVo studentVo){
        try{
            studentMapper.insert(studentVo);
            scoreMapper.insert(scoreVo);
        }catch(Exception e){
            logger.error("错误",e);
            throw e;
        }

    }
}


@Service
public class StudentServiceTest {

    @Autowired
    private StudentService studentService;
    
    //同一个包中引用非public方法

    public void save(ScoreVo scoreVo, StudentVo studentVo){
        studentService.save(scoreVo,studentVo);
    }
}

//测试用例
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-config.xml"})
public class MainToken {
    @Autowired
	private StudentService studentService;
    @Autowired
	private StudentServiceTest studentServiceTest;
	@Test
	public void saveStudentTest(){
		ScoreVo scoreVo=new ScoreVo();
		scoreVo.setScore(new BigDecimal("100"));
		scoreVo.setScSubject("语文");
		StudentVo student=new StudentVo();
		student.setStName("lily");
		student.setStNumber("1");
		studentServiceTest.save(scoreVo,student);
	}
}

 事务测试效果:student表有数据,score表没有数据,事务没有起作用。

 

    2.在类内部调用@Transactional事务失效

@Service
public class StudentService {
    private static final Logger logger = LoggerFactory.getLogger(SelfEmployedInfoImportService.class);
    @Autowired
    private ScoreMapper scoreMapper;
    @Autowired
    private StudentMapper studentMapper;
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Throwable.class)
      //方法save和方法saveLevel是同一个类StudentService的内部方法
      public void save(ScoreVo scoreVo, StudentVo studentVo){
        try{
            studentMapper.insert(studentVo);
            scoreMapper.insert(scoreVo);
        }catch(Exception e){
            logger.error("错误",e);
            throw e;
        }

    }

    //方法save和方法saveLevel是同一个类StudentService的内部方法
    public void saveLevel(ScoreVo scoreVo, StudentVo studentVo){
        //同一个类内的方法调用另一个方法
        save(scoreVo,studentVo);
    }

}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-config.xml"})
public class MainToken {
    @Autowired
	private StudentService studentService;
    @Autowired
	private StudentServiceTest studentServiceTest;

	@Test
	public void saveTest(){
		ScoreVo scoreVo=new ScoreVo();
		scoreVo.setScore(new BigDecimal("100"));
		scoreVo.setScSubject("语文");
		StudentVo student=new StudentVo();
		student.setStName("lily");
		student.setStNumber("1");
		studentService.saveLevel(scoreVo,student);
	}

}

测试结果:student表有数据,score表没有数据,事务没有起作用。

 

如果改为内部方法用代理去访问,((类名) AopContext.currentProxy()),则事务起作用。代码如下

@Service
public class StudentService {
    private static final Logger logger = LoggerFactory.getLogger(SelfEmployedInfoImportService.class);
    @Autowired
    private ScoreMapper scoreMapper;
    @Autowired
    private StudentMapper studentMapper;
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Throwable.class)
      //方法save和方法saveLevel是同一个类StudentService的内部方法
      public void save(ScoreVo scoreVo, StudentVo studentVo){
        try{
            studentMapper.insert(studentVo);
            scoreMapper.insert(scoreVo);
        }catch(Exception e){
            logger.error("错误",e);
            throw e;
        }

    }

    //方法save和方法saveLevel是同一个类StudentService的内部方法
    public void saveLevel(ScoreVo scoreVo, StudentVo studentVo){
        //同一个类内的方法调用另一个方法
        ((StudentService) AopContext.currentProxy()).save(scoreVo,studentVo);
    }

}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-config.xml"})
public class MainToken {
    @Autowired
	private StudentService studentService;
    @Autowired
	private StudentServiceTest studentServiceTest;

	@Test
	public void saveTest(){
		ScoreVo scoreVo=new ScoreVo();
		scoreVo.setScore(new BigDecimal("100"));
		scoreVo.setScSubject("语文");
		StudentVo student=new StudentVo();
		student.setStName("lily");
		student.setStNumber("1");
		studentService.saveLevel(scoreVo,student);
	}

}

测试结果:student表无数据,score表无数据,事务有效。

3.事务有异常没抛出,异常被捕捉了,导致事务失效。

@Service
public class StudentService {
    private static final Logger logger = LoggerFactory.getLogger(SelfEmployedInfoImportService.class);
    @Autowired
    private ScoreMapper scoreMapper;
    @Autowired
    private StudentMapper studentMapper;
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Throwable.class)
      public void save(ScoreVo scoreVo, StudentVo studentVo){
        try{
            studentMapper.insert(studentVo);
            scoreMapper.insert(scoreVo);
        }catch(Exception e){
            logger.error("错误",e);
        }

    }


    public void saveLevel(ScoreVo scoreVo, StudentVo studentVo){

        ((StudentService) AopContext.currentProxy()).save(scoreVo,studentVo);
    }

}


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-config.xml"})
public class MainToken {
    @Autowired
	private StudentService studentService;
    @Autowired
	private StudentServiceTest studentServiceTest;

	@Test
	public void saveTest(){
		ScoreVo scoreVo=new ScoreVo();
		scoreVo.setScore(new BigDecimal("100"));
		scoreVo.setScSubject("语文");
		StudentVo student=new StudentVo();
		student.setStName("lily");
		student.setStNumber("1");
		studentService.saveLevel(scoreVo,student);
	}

}

测试结果:student表有数据,score表无数据,事务失效。

 

@Transactional事务失效的源码分析

见链接:https://blog.youkuaiyun.com/qq_20597727/article/details/84900994

               https://blog.youkuaiyun.com/qq_20597727/article/details/84868035

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值