当两个事务对同一个数据库的记录进行操作时,那么,他们之间的影响是怎么样的呢?这就出现了事务隔离级别的概念。数据库的隔离性与并发控制有很大关系。数据库的隔离级别是数据库的事务特性ACID的一部分。ACID,即原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。Spring的事务隔离级别有四个:READ_UNCOMMITTEDREAD_COMMITTEDREPEATABLE_READSERIALIZABLE。还有一个,是数据库默认的隔离级别DEFAULT

MySQL默认是REPEATABLE_READ。其余绝大多数数据库默认的是READ_COMMITTED。



1.事务操作时会产生的错误

(1)脏读  ------  修改且未提交引起

事务B读取了事务A已修改但未提交成功的事务,如:张三的工资为5000,事务A中把他的工资改为8000,但事务A尚未提交。与此同时,事务B正在读取张三的工资,读取到张三的工资为8000。随后,事务A发生异常,而回滚了事务。张三的工资又回滚为5000。最后,事务B读取到的张三工资为8000的数据即为脏数据,事务B做了一次脏读。

(2)不可重复读  ------  修改引起

事务A中两次读取某个值结果不同,由于期间被事务B修改并提交了,如:在事务A中,读取到张三的工资为5000,操作没有完成,事务还没提交。与此同时,事务B把张三的工资改为8000,并提交了事务。随后,在事务A中,再次读取张三的工资,此时工资变为8000。在一个事务中前后两次读取的结果并不致,导致了不可重复读。

(3)幻读  ------  添加新记录引起

事务A中两次读取记录结果数不同,由于期间被事务B新增了一条记录并提交了,事务A先读取所有工资为5000的人数为10人。此时,事务B插入一条工资也为5000的记录。这时,事务A再次读取工资为5000的员工,记录为11人。此时产生了幻读。



2.Spring五大事务隔离级别

(1)ISOLATION_DEFAULT 默认事务隔离级别

这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。

(2)ISOLATION_READ_UNCOMMITTED 读未提交级别

这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。 

(3) ISOLATION_READ_COMMITTED 读已提交级别  

保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻读。

(4) ISOLATION_REPEATABLE_READ  重复读级别

一个事务可以多次从数据库读取某条记录,而且多次读取的那条记录都是一致的,相同的。这种事务隔离级别可以防止脏读和不可重复读,但是可能出现幻像读。

(5)ISOLATION_SERIALIZABLE 串行级别

这是花费最高代价但是最可靠的事务隔离级别。事务执行时,会在所有级别上加锁,比如read和write时都会加锁,仿佛事务是以串行的方式进行的,而不是一起并发发生的。除了防止脏读,不可重复读外,还避免了幻读。

总的来说如图所示:

spring事务的隔离级别嵌套事务 spring事务的隔离级别有哪些_python



3.事务传播行为

spring支持编程式事务管理和声明式事务管理两种方式。

  1. 编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
  2. 声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。

       显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。

声明式事务例子:

/*
* readOnly表示事务是否只读的,不能进行插入更新操作
* propagation = Propagation.REQUIRED表示执行这个类实例方法需开启事务,传播级别为REQUIRED
* rollbackFor = Throwable.class表示遇到Throwable类及子类(即发生了异常)时事务进行回滚操作
*/ 
@Transactional(readOnly=false , propagation = Propagation.REQUIRED , rollbackFor = Throwable.class) 
public class BasicDaoImpl extends BasicDao{ 
 
    @Override 
    public Object save(Object entity) throws Exception { 
        return super.save(entity); 
    } 
 }

其实事务传播行为就是 多个事务方法相互调用时,事务如何在这些方法间传播。

七大事务传播行为分别是:

  • PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
  • PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。

举例具体说明:

例1:REQUIRED

@Test  
public void testRequires(){  
    sService.addStudent();  
}

//sService对应类中
@Transactional(propagation = Propagation.REQUIRED)  
public void addStudent(){  
    String sql = "insert into student(name) values('st0')";  
    jdbcTemplate.execute(sql);  
    tService.addTeacher();  //在调用addStudent方法中调用事务等级同为REQUIRED的另一个方法
    throw new RuntimeException();  //制造异常
}

//tService对应类中
@Transactional(propagation = Propagation.REQUIRED)  
public void addTeacher(){  
    String sql = "insert into teacher(name) values ('t5')";  
    jdbcTemplate.execute(sql);  
}

 在该例中,两个addStudent中调用addTeacher,两个方法中的事务发生传递,由于addTeacher行为是REQUIRED,如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务,在addStudent中已经创建了一个事务,则加入。经测试无论在tService还是sService如果不抛出异常,那么数据提交成功,如果抛出异常,数据提交失败。这说明tService和sService使用的是同一个事务,并且只要方法被调用就开启事务。

例2:REQUIRES_NEW

@Test  
public void testRequires(){  
    sService.addStudent();  
}

//sService对应类中
@Transactional(propagation = Propagation.REQUIRED)  
public void addStudent(){  
    String sql = "insert into student(name) values('st0')";  
    jdbcTemplate.execute(sql);  
    tService.addTeacher();  //在调用addStudent方法中调用事务等级同为REQUIRED_NEW的另一个方法
    throw new RuntimeException();  //制造异常
}

//tService对应类中
@Transactional(propagation = Propagation.REQUIRED_NEW)  
public void addTeacher(){  
    String sql = "insert into teacher(name) values ('t5')";  
    jdbcTemplate.execute(sql);  
}

由于addTeacher行为是REQUIRED_NEW,创建一个新的事务,如果当前存在事务,则把当前事务挂起,在addStudent中已经创建了一个事务,则挂起addStudent事务,并新建一个事务执行addTeacher。经测试如果在addStudent中抛出异常,学生数据不能正确提交,教师信息被正确提交。说明sService和tService是在两个独立的事务中运行,并且只要方法被调用就开启事务。