事务实现方式
Spring事务机制主要包括声明式事务和编程式事务。
- 编程式事务:通过编程的方式管理事务,这种方式带来了很大的灵活性,但很难维护。
- 声明式事务(推荐使用):将事务管理代码从业务方法中分离出来,通过aop进行封装。Spring声明式事务使得我们无需要去处理获得连接、关闭连接、事务提交和回滚等这些操作。使用
@Transactional
注解开启声明式事务。
编程式事务实现方式
1.通过PlatformTransactionManager控制事务
@Test
public void platformTransactionManagerTest() throws Exception {
//定义一个数据源
DataSource dataSource = new DataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/int?characterEncoding=UTF-8&serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setInitialSize(5);
//定义一个JdbcTemplate,用来方便执行数据库增删改查
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//1.定义事务管理器,给其指定一个数据源(可以把事务管理器想象为一个人,这个人来负责事务的控制操作)
PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
//2.定义事务属性:TransactionDefinition,
// TransactionDefinition可以用来配置事务的属性信息,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。
TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
//3.开启事务:调用platformTransactionManager.getTransaction开启事务操作,得到事务状态(TransactionStatus)对象
TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
//4.执行业务操作,下面就执行2个插入操作
try {
System.out.println("before:" + jdbcTemplate.queryForList("SELECT * from t_user"));
jdbcTemplate.update("insert into t_user (name) values (?)", "test1-1");
jdbcTemplate.update("insert into t_user (name) values (?)", "test1-2");
//5.提交事务:platformTransactionManager.commit
platformTransactionManager.commit(transactionStatus);
} catch (Exception e) {
//6.回滚事务:platformTransactionManager.rollback
platformTransactionManager.rollback(transactionStatus);
}
System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
}
2.通过TransactionTemplate控制事务
@Test
public void transactionTemplateTest() throws Exception {
//定义一个数据源
DataSource dataSource = new DataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/int?characterEncoding=UTF-8&serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setInitialSize(5);
//定义一个JdbcTemplate,用来方便执行数据库增删改查
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//1.定义事务管理器,给其指定一个数据源(可以把事务管理器想象为一个人,这个人来负责事务的控制操作)
PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
//2.定义事务属性:TransactionDefinition,TransactionDefinition可以用来配置事务的属性信息,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
transactionDefinition.setTimeout(10);
//3.创建TransactionTemplate对象
TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager,transactionDefinition);/**
* 4.通过TransactionTemplate提供的方法执行业务操作
* 主要有2个方法:
* (1).executeWithoutResult(Consumer<TransactionStatus> action):没有返回值的,需传递一个Consumer对象,在accept方法中做业务操作
* (2).<T> T execute(TransactionCallback<T> action):有返回值的,需要传递一个TransactionCallback对象,在doInTransaction方法中做业务操作
* 调用execute方法或者executeWithoutResult方法执行完毕之后,事务管理器会自动提交事务或者回滚事务。
* 那么什么时候事务会回滚,有2种方式:
* (1)transactionStatus.setRollbackOnly();将事务状态标注为回滚状态
* (2)execute方法或者executeWithoutResult方法内部抛出异常
* 什么时候事务会提交?
* 方法没有异常 && 未调用过transactionStatus.setRollbackOnly();
*/
transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {
@Override
public void accept(TransactionStatus transactionStatus) {
jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-1");
jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-2");
}
});
System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
}
四种事务隔离级别
事务的隔离级别就是一种控制事务并发执行的事务对数据操作的规则。在标准SQL(SQL 92)中定义了四种事务的隔离级别:
READ UNCOMMITTED
(读未提交):事务执行过程中可以读取到并发执行的其他事务中未提交的数据。在这种事务隔离级别下可能会引起的问题包括:脏读,不可重复读和幻读。READ COMMITTED
(读已提交):事务执行过程中只能读到其他事务已经提交的数据。读已提交保证了并发执行的事务不会读到其他事务未提交的修改数据,避免了脏读问题。在这种事务隔离级别下可能引发的问题包括:不可重复读和幻读。REPEATABLE READ
(可重复读):当前事务执行开始后,所读取到的数据都是该事务刚开始时所读取的数据和自己事务内修改的数据。这种事务隔离级别下,无论其他事务对数据怎么修改,在当前事务下读取到的数据都是该事务开始时的数据,所以这种隔离级别下可以避免不可重复读的问题,但还是有可能出现幻读。SERIALIZABLE
(串行化):事务的执行是串行执行。这种隔离级别下,可以避免脏读、不可重复读和幻读等问题,但是由于事务一个一个执行,所以性能就比较低。
脏读
脏读其实就是读到了,其他事务回滚前的未提交的脏数据。四种事务隔离级别中只有读未提交才会出现这种问题。
不可重复读
不可重复读就是在一个事务多次相同的查询可能会查询到不同的数据。读未提交和读已提交的隔离级别下都可能会出现不可重复读的问题。
- 在读未提交隔离级别下,当前事务是可以读到其他事务未提交的数据,所以其他事务一直对数据进行修改的话,当前事务多次读取的数据就会不同。
- 在读已提交的隔离级别下,当前事务只能读到其他事务已提交的数据,所以在其他事务修改还未提交时,当前事务的多次读取是一样的,但是一旦其他事务提交了修改,当前事务再读取到的数据,就与之前不一致了,这也是不可重复读的现场。
幻读
其他事务在一个尚未提交的当前事务的读取的行的范围中插入新行或删除现有行,会对当前事务的对数据的读取产生幻象。幻读在读未提交、读已提交和可重复读三种事务隔离级别下都会出现。
Spring事务隔离级别
Isolation.DEFAULT
:使用数据库的默认级别,对于Oracle来说默认事务隔离级别是读已提交,对于MySQL来说默认事务隔离级别是可重复读。@Transactional
默认使用这个隔离级别。Isolation.READ_UNCOMMITTED
:读未提交Isolation.READ_COMMITTED
:读已提交Isolation.REPEATABLE_READ
:可重复读Isolation.SERIALIZABLE
:串行化
七个事务传播行为
在TransactionDefinition接口中定义了七个事务传播行为:
PROPAGATION_REQUIRED
如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。如果嵌套调用的两个方法都加了事务注解,并且运行在相同线程中,则这两个方法使用相同的事务。如果运行在不同线程中,则会开启新的事务。PROPAGATION_REQUIRES_NEW
总是开启一个新的事务。需要使用JtaTransactionManager作为事务管理器。PROPAGATION_SUPPORTS
如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。PROPAGATION_NOT_SUPPORTED
总是非事务地执行,并挂起任何存在的事务。需要使用JtaTransactionManager作为事务管理器。PROPAGATION_MANDATORY
如果已经存在一个事务,支持当前事务。如果不存在事务,则抛出异常IllegalTransactionStateException。PROPAGATION_NEVER
总是非事务地执行,如果存在一个活动事务,则抛出异常。PROPAGATION_NESTED
如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务, 则按PROPAGATION_REQUIRED
属性执行。
PROPAGATION_NESTED
与PROPAGATION_REQUIRES_NEW
的区别
使用PROPAGATION_REQUIRES_NEW
时,内层事务与外层事务是两个独立的事务。一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。
使用PROPAGATION_NESTED
时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。