数据库事务正确执行的四要素
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