Spring事务
事务特性
事务有四大特性(ACID),原子性、持久性、一致性和隔离性
- 原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成。不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态
- 一致性(Consistency):在事务开始之前和事务事务结束之后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精准度、串联性以及后续数据库可以自发性地完成预定的工作
- 持久性(Isolation):事务处理结束后,对数据的修改就是永久的。即使系统故障也不会丢失
- 隔离性(Durability):数据库允许多个并发事务同时对其数据进行读写和修改的能力。隔离性可以防止多个事务并发执行时由交叉执行而导致数据的不一致。
spring事务隔离级别
Java Spring框架提供了一种方便的方式来管理数据库事务,它支持多种事务隔离级别。事务隔离级别决定了事务在并发执行时的隔离程度,包括对其他事务的可见性和可能出现的并发问题。
以下是Spring框架支持的事务隔离级别及其详细说明:
ISOLATION_DEFAULT
(默认):这是系统的默认隔离级别。根据具体数据库来定义,大多数数据库默认级别是可重复读。ISOLATION_READ_UNCOMMITTED
(读未提交):在这个级别,一个事务可以看到其他未提交事务的变动。这种级别可以导致脏读、不可重复读和幻读的问题。ISOLATION_READ_COMMITTED
(读提交):在这个级别,一个事务只能看到其他事务已经提交的变动。这种级别可以避免脏读问题,但可能会出现不可重复读和幻读的问题。ISOLATION_REPEATABLE_READ
(可重复读):在这个级别,同一事务中多次读取的数据是一致的。这种级别可以避免脏读和不可重复读的问题,但可能会出现幻读的问题。ISOLATION_SERIALIZABLE
(可串行化):这是最高的事务隔离级别。在这个级别,事务串行化顺序执行,可以避免脏读、不可重复读和幻读的问题。但是这种隔离级别效率低下,因为事务通常需要等待前一个事务完成,才能继续执行。
Spring事务的默认隔离级别与数据库一致
MySQL的事务隔离级别默认是可重复读(
REPEATABLE READ
),这是大多数数据库系统的默认设置。这个隔离级别可以避免脏读和不可重复读的问题,但可能会出现幻读的问题。
spring事务传播行为
Java Spring框架中的事务传播行为是指在一个事务方法被另一个事务方法调用时,如何处理事务的传播。事务传播行为定义了在一个方法中调用另一个方法时,事务应该如何启动、提交或回滚。
以下是Spring框架支持的事务传播行为及其详细说明:
PROPAGATION_REQUIRED
(必需):如果当前存在一个事务,那么就加入这个事务,如果当前没有事务,就新建一个事务。这是最常见的选择。PROPAGATION_SUPPORTS
(支持):支持当前事务,如果当前没有事务,就以非事务方式执行。PROPAGATION_MANDATORY
(强制):使用当前的事务,如果当前没有事务,就抛出异常。PROPAGATION_REQUIRES_NEW
(新建):新建事务,如果当前存在事务,把当前事务挂起。PROPAGATION_NOT_SUPPORTED
(不支持):以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。PROPAGATION_NEVER
(从不):以非事务方式执行,如果当前存在事务,抛出异常。PROPAGATION_NESTED
(嵌套):如果当前存在一个事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
Spring的事务传播行为默认是
PROPAGATION_REQUIRED
,也就是如果当前存在一个事务,就加入该事务;如果当前没有事务,就新建一个事务。
Spring中事务的实现
在Spring中,事务有两种实现方式:
- 编程式事务(手动操作事务)
- 声明式事务(利用注解自动控制事务的开启和提交)
编程式事务
Spring 中手动操作事务和 MySQL操作事务类似,也是有 3 个重要操作
开启事务(获取事务)
提交事务
回滚事务
Spring Boot 内置了两个对象,DataSourceTransactionManager (事务管理器)用来获取事务(开启事务)、提交或回滚事务的,而 TransactionDefinition 是事务的属性,在获取事务的时候需要将 TransactionDefinition 传递进去从而获得一个事务 TransactionStatus 对象
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations = {"classpath:spring.xml"})//加载配置文件public class UserTest {@Autowiredprivate UserService userService;@Autowiredprivate TransactionDefinition transactionDefinition;@Autowiredprivate DataSourceTransactionManager transactionManager;@Testpublic void test() {final TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);try {final User user = new User() {{this.setUsername("test" + System.currentTimeMillis());this.setPassword("123456");this.setCreateTime(new Date());}};userService.addUser(user);System.out.println(user);// User(id=13, username=test1694675733277, password=123456, salt=null, token=null, isEnabled=null, createTime=Thu Sep 14 15:15:33 CST 2023, modifiedTime=null)transactionManager.commit(transaction);}catch (Exception e){e.printStackTrace();transactionManager.rollback(transaction);}}}
声明式事务
声明式事务的实现,只需要在方法上添加 @Transactional 注解就可以实现,无序手动开启事务和提交事务,进入方法时自动开启事务,方法执行完全会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务
@Servicepublic class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Transactional(rollbackFor = Exception.class)@Overridepublic void addUser(User user) {userMapper.insertSelective(user);}}
@Transactional 参数设置
参数 | 作用 |
---|---|
value | 当你配置多个事务管理器时,可以使用该属性指定选择用哪个事务管理器 |
transactionManager | 同上 |
propagation | 事务的传播行为,默认值为 Propagation.REQUIRED |
isolation | 事务的隔离级别,默认值为 Isolation.DEFAULT |
timeout | 事务的超时时间,默认值为-1,如果超过该时间限制但事务还没完成,则自动回滚事务 |
readOnly | 指定事务是否为只读事务,默认值为 false,为了忽略那些不需要事务的方法,比如读取数据可以设置 read-only 为 true |
rolibackFor | 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型 |
rolibackForClassName | 同上 |
noRolibackFor | 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型 |
noRollbackForClassName | 同上 |
@Transactional的失效场景
- @Transactional标记的方法,非public修饰
- @Transactional标记的方法所在内,非Spring容器管理的bean
- @Transactional标记的方法,被类内部方法调用
- @Transactional 声明式事务默认只处理运行时异常,非运行时异常需要指定异常类型,例如
@Transactional(rollbackFor = Exception.class)
- 异常被
try catch语句块
捕获,未抛出异常(最难被排查到问题且容易忽略) - @Transactional中
Propagation
属性值设置错误即Propagation.NOT_SUPPORTED
(一般不会设置此种传播机制) - 数据库存储引擎不支持事务,如Mysql关系型数据库,如果选择的存储引擎是MyISAM而非InnoDB,则事务会不起作用(基本开发中不会遇到);
1 @Transactional标记的方法,非public修饰
@Componentpublic class TestServiceImpl {@ResourceTestMapper testMapper;@Transactionalvoid insert() {int re = testMapper.insert(new Test(10,20,30));if (re > 0) {throw new Exception("need intercept");}}}
2 @Transactional标记的方法,被类内部方法调用
@Componentpublic class TestServiceImpl {@ResourceTestMapper testMapper;@Transactionalpublic void insert() {int re = testMapper.insert(new Test(10,20,30));if (re > 0) {throw new Exception("need intercept");}}@Transactionalpublic void update() {testMapper.update(new Test(10,20,30));}public void handle(){update();insert();}}@RunWith(SpringRunner.class)@SpringBootTestpublic class DemoApplicationTests { @Resource TestServiceImpl testServiceImpl; @Test public voidtestInvoke(){testServiceImpl.handle(); }}
3 异常被try catch语句块
捕获,未抛出异常
@Componentpublic class TestServiceImpl {@ResourceTestMapper testMapper;@Transactionalvoid insert() {try{int re = testMapper.insert(new Test(10,20,30));if (re > 0) {throw new Exception("need intercept");}}catch(Exception e){// 打印 不抛出新异常}}}