Spring的事务
!!!个人见解,仅供参考。
一、什么是事务?
事务是一组操作,它们被当作一个单独的执行单元,要么全部成功执行,要么全部失败执行。事务具有四个基本特性,通常被称为 ACID 特性:
- 原子性(Atomicity):事务是一个不可分割的工作单位,要么全部执行成功,要么全部失败回滚。
- 一致性(Consistency):事务执行后,数据库的状态应该与执行前的状态保持一致。
- 隔离性(Isolation):多个事务并发执行时,各自的操作互不干扰,每个事务感觉不到其他事务的存在。
- 持久性(Durability):事务一旦提交,其结果应该永久保存在数据库中,即使系统发生故障。
二、事务的作用
引入事务是为了确保数据库操作的一致性、可靠性和完整性。事务是一组数据库操作,它们被视为一个单独的工作单元,要么全部执行成功,要么全部失败回滚,以保证数据库的一致性。
- 保证数据一致性:事务可以确保数据库在任何时候都保持一致状态。即使在多个操作同时进行的情况下,事务可以使数据在操作之间保持一致,避免数据不一致的情况发生。
- 支持并发控制:在多用户环境中,可能会有多个用户同时对数据库进行读写操作。事务可以通过并发控制机制来管理多个并发操作,确保数据的完整性和一致性。
- 保证数据的完整性:事务可以确保数据操作的完整性,即当一组操作执行失败时,可以回滚事务,使数据库恢复到操作之前的状态,避免数据丢失或损坏。
- 提高系统性能:通过使用事务,可以减少数据库锁的持有时间,从而降低了系统的资源竞争,提高了系统的并发性能。
综上所述,引入事务可以提高数据库操作的可靠性和性能,确保数据的一致性和完整性,是数据库管理中的重要概念之一。
三、Spring为什么要提供事务管理?
- 简化事务管理:事务管理是应用程序开发中的一个常见需求,但手动管理事务的代码通常会导致代码的重复和冗长。Spring 提供了简洁的 API 和声明式事务管理的方式,使得开发者可以更轻松地管理事务,将事务逻辑与业务逻辑分离,提高了代码的可读性和可维护性。
- 提高数据一致性和完整性:通过事务管理,Spring 可以确保一组数据库操作要么全部成功提交,要么全部回滚,从而保证了数据的一致性和完整性。这对于涉及多个数据库操作的业务逻辑尤为重要,可以避免出现数据不一致的情况。
- 支持多种事务管理方式:Spring 提供了多种事务管理方式,包括编程式事务管理和声明式事务管理。开发者可以根据实际需求选择合适的事务管理方式,从而灵活地应对不同的业务场景。
- 与其他 Spring 特性的集成:Spring 的事务管理与其他 Spring 特性(如
Spring MVC
、Spring Data
等)紧密集成,可以轻松地与这些特性结合使用,提供全面的解决方案。 - 跨多种数据访问技术的支持:Spring 的事务管理可以跨多种数据访问技术,包括
JDBC、Hibernate、JPA
等,使得开发者可以在不同的数据访问层面使用统一的事务管理方式。
综上所述,Spring 提供事务管理的目的是为了简化开发者处理事务的复杂性,确保数据的一致性和完整性,并提供灵活的事务管理方式,以满足不同业务场景的需求。
四、Spring事务的使用
1、Spring事务入门
目前流行的方式通过@Transactional
注解来实现声明式事务管理。以下是一个简单的示例,展示了如何在 Spring 中配置和使用声明式事务管理:
@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student>
implements StudentService {
@Autowired
private StudentMapper studentMapper;
@Override
@Transactional
public void addDoubleStudent() {
Student s1 = Student.builder()
.money(100.0)
.name("张三")
.build();
Student s2 = Student.builder()
.money(100.0)
.name("王五")
.build();
//分别向数据库插入两个学生
studentMapper.insert(s1);
//发生异常
int i = 1 / 0;
studentMapper.insert(s2);
}
}
如果没有加事务的话,只有 studentMapper.insert(s1)
会成功,然后遇到int i = 1 / 0
就会抛出ArithmeticException
异常从而方法停止,那么studentMapper.insert(s2)
就失败了。如果在方法上加上 @Transactional
注解的话,这个方法就是一个事务,根据事务的特性,那么两个插入操作都会失败,数据就会回滚。
2、@Transactional的详细说明
@Transactional
注解用于实现声明式事务管理。通过在方法或类级别添加@Transactional
注解,可以告诉Spring框架将这些方法或类的操作纳入事务管理的范围内。
下面是@Transactional
注解的一些重要细节:
- 在方法级别使用:
@Transactional
注解可以添加在类的方法上,以声明该方法应该在一个事务中执行。当方法被调用时,Spring框架会在方法执行之前开启一个事务,在方法执行之后根据方法的执行情况来提交或回滚事务。 - 在类级别使用:除了在方法级别使用外,
@Transactional
注解还可以添加在类上。这意味着该类的所有公共方法都将在事务中执行。类级别的注解可以用于指定默认的事务传播行为和事务管理器。 - 设置事务属性:
@Transactional
注解提供了一系列属性,用于配置事务的行为,比如传播行为(Propagation)、隔离级别(Isolation)、超时时间(Timeout)、只读状态(Read-only)等。 - 传播行为(Propagation):传播行为定义了方法中的事务如何与外部事务交互。例如,REQUIRES_NEW表示每次调用该方法都会创建一个新的事务,而REQUIRES_PROPAGATION表示该方法会加入一个已经存在的事务中,如果不存在事务,则创建一个新的事务。
- 异常处理:默认情况下,当被注解的方法抛出一个未检查异常(RuntimeException)时,事务会被回滚。但你可以通过
rollbackFor
和noRollbackFor
属性来指定特定的异常应该导致事务回滚或不回滚。 - 注解驱动事务:
@Transactional
注解是基于AOP(面向切面编程)实现的。Spring通过代理模式在运行时为被注解的方法创建一个事务代理,在方法执行前后添加事务管理逻辑。 - 事务管理器:
@Transactional
注解依赖于配置好的事务管理器来实际地管理事务。Spring框架提供了不同类型的事务管理器,如基于JDBC的、基于JPA的、基于Mybatis的等。 - 应用范围:
@Transactional
注解可以应用于Spring的各个组件,包括Spring MVC的Controller、Service层、DAO层等。
3、事务的传播行为
4、事务的隔离级别
事务的隔离级别就是只事务与事务之间的隔离程度。TransactionDefinition
中定义了五种隔离基本。如图:
在MySQL中,默认的事务隔离级别是可重复读(Repeatable Read)。
5、@Transactional的详细使用
在 Spring 中,@Transactional
注解用于声明一个方法应该被包装在事务中。这个注解提供了多个属性来配置事务的行为。以下是 @Transactional
注解的一些常用成员:
- value:可用于指定要应用的事务管理器的名称。
- transactionManager:指定要用于事务管理的 Bean 名称。通常与
value
属性一起使用,用于明确指定事务管理器。 - propagation:指定事务的传播行为,默认值是
Propagation.REQUIRED
。可以使用上述提到的 7 种传播行为之一。 - isolation:指定事务的隔离级别,默认是
Isolation.DEFAULT
。可以选择Isolation
枚举中的值,如Isolation.READ_COMMITTED
、Isolation.REPEATABLE_READ
等。 - timeout:指定事务的超时时间,单位是秒。如果方法执行时间超过指定的时间,事务将被自动回滚。
- readOnly:指定事务是否为只读。如果设置为
true
,表示这个事务只读取数据但不修改数据,可以帮助提升性能。 - rollbackFor:用于指定哪些异常触发事务回滚。默认情况下,Spring 只回滚
RuntimeException
和其子类异常,以及Error
和其子类异常。如果需要对其他异常也进行回滚,可以在这个属性中指定。 - noRollbackFor:与
rollbackFor
相反,用于指定哪些异常不触发事务回滚。 - rollbackForClassName:与
rollbackFor
类似,但是使用异常类名来指定需要回滚的异常。
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, readOnly = true, timeout = 30, rollbackFor = {SQLException.class, MyException.class})
public void myTransactionalMethod() {
// 业务逻辑
}
在这个例子中,myTransactionalMethod()
方法被设置为在一个REQUIRED传播行为下执行,使用READ_COMMITTED隔离级别,只读模式为true,超时时间为30秒,如果遇到SQLException或MyException异常则回滚事务。
五、Spring编程式事务管理(可跳过)
编程式事务管理是手动编码是实现事务的开始、提交、回滚等操作,而声明式事务管理是基于Spring AOP技术实现的。(大多数场景声明式事务管理就够了,在事务层次复杂的情况下才推荐编程事)。!!!!!!!
1、Spring编程式事务管理的快速入门
@Autowired
private TransactionTemplate transactionTemplate;
@Override
public void addStudent() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
// .... 业务代码
studentMapper.insert(s1);
int i = 1 / 0;
studentMapper.insert(s2);
} catch (Exception e) {
//回滚
transactionStatus.setRollbackOnly();
}
}
});
}
这段代码是一个典型的 Spring Framework 中使用的事务管理模板。让我解释一下它的作用:
transactionTemplate.execute()
:这是一个事务模板的方法调用,它可以帮助你在代码中执行一段逻辑,并在一个事务内进行管理。TransactionCallbackWithoutResult
:这是一个 Spring 提供的回调接口,用于执行一段逻辑,它的doInTransactionWithoutResult
方法会在事务内执行。doInTransactionWithoutResult(TransactionStatus transactionStatus)
:这是TransactionCallbackWithoutResult
接口的方法,它定义了在事务内执行的逻辑。transactionStatus.setRollbackOnly()
:如果在执行业务逻辑时发生了异常,这行代码将会设置事务的回滚状态,这样事务就会在执行完成后回滚,保证数据的一致性和完整性。
因此,这段代码的作用是在一个事务内执行一段业务逻辑,并在发生异常时进行事务回滚,以确保数据的完整性。
2、相关源码的分析
编程式事务的实现主要涉及到以下几个核心类:
PlatformTransactionManager
:事务管理器接口,定义了对事务进行操作的方法,如开始事务、提交事务、回滚事务等。TransactionDefinition
:事务定义接口,定义了事务的一些属性,如事务的隔离级别、超时时间、是否只读等。TransactionStatus
:事务状态接口,定义了事务的当前状态,如是否已经开始、是否已经回滚等。TransactionTemplate
是 Spring 框架提供的一个便捷的编程式事务管理工具类,用于简化编程式事务的操作。它封装了事务的开始、提交、回滚等操作(也就是封装了PlatformTransactionManager
里面的操作)。
TransactionTemplate
的方法,如图所示:
TransactionTemplate
中的核心方法execute()
:
主要的业务代码就就是调用TransactionCallback
接口中的doInTransaction
方法。目前只有TransactionCallbackWithoutResult
类实现了TransactionCallback
接口,TransactionCallbackWithoutResult
如图所示:
可以看到这是一个抽象类,doInTransaction()
方法中调用了doInTransactionWithoutResult(TransactionStatus transactionStatus)
。这段代码是一个23种设计模式中的模板方法模式的典型实现,用于执行事务的回调操作。
3. 待补充…
六、Spring事务失效的场景总结
1.未加@Transactional注解或者没有被Spring管理
如果目标类没有被Spring管理那么即使有@Transactional
注解也会失效。
如果方法未使用 @Transactional
注解声明,那么该方法将不会被 Spring 的事务管理器拦截,导致事务不生效。解决方法是在需要事务管理的方法上添加 @Transactional
注解。
@Transactional
public void someTransactionalMethod() {
// 业务逻辑
}
2.事务注解位置错误
@Transactional
注解应该放在公有方法上,如果放在私有方法、静态方法、final的方法或者内部方法上,可能会导致事务失效。解决方法是确保 @Transactional
注解放在合适的位置。
像IDEA这样的工具都会提示的。如图所示:
save
会受到事务管理但是query
就不会受到事务的管理,数据库也不会回滚。
public void query(Student s1) {
studentMapper.insert(s2);
save(s1);
}
@Transactional
public void save(Student s1) {
studentMapper.insert(s1);
throw new RuntimeException();
}
3.默认的事务传播行为不适用
@Transactional
注解有一个 propagation
属性,用于指定事务的传播行为。如果使用了不适合的传播行为,可能会导致事务失效。需要根据业务逻辑来选择合适的传播行为。这种错误在某些场景极为隐秘和复杂,很难察觉,在事务层次复杂的业务推荐使用编程式事务。
4.异常处理不当
异常处理不当在使用@Transactional
注解时可能会导致事务不正确地提交或回滚,从而导致数据一致性问题。默认情况下,Spring的@Transactional
注解只会在方法抛出RuntimeException
或Error
时回滚事务,而不会回滚受检异常。
-
适当地处理异常:在方法中捕获异常后,要确保以合适的方式进行处理。如果可以处理异常并且继续正常执行,确保事务的一致性。但如果异常表示了一个无法继续执行的严重问题,应该重新抛出异常以触发事务回滚。
-
重新抛出异常:当捕获到的异常无法在当前方法中处理时,应该考虑重新抛出异常以触发事务回滚。可以使用
throw
语句重新抛出异常,或者声明方法抛出异常并让调用者处理。 -
使用
rollbackFor
属性:@Transactional
注解提供了rollbackFor
属性,可以指定哪些异常需要触发事务回滚。通过在@Transactional
注解中设置rollbackFor
属性,可以确保捕获到指定类型的异常时触发事务回滚。
示例代码:
@Service
@Transactional(rollbackFor = Exception.class) // 指定捕获到任何异常时都回滚事务
public class MyService {
@Autowired
private MyRepository myRepository;
public void myMethod() {
try {
// 可能会抛出异常的操作
myRepository.someMethod();
} catch (Exception e) {
// 异常处理逻辑
// 如果需要回滚事务,则重新抛出异常
throw e;
}
}
}
5.数据库不支持事务
在MySql老的版本是用的是MyISAM作为默认引擎,它不支持事务,会导致是事务失效。Mysql的新的版本MySql支持了InnoDB,它支持是事务。(目前推荐使用5.7之后的版本)。默认也是开启的。在mysql的配置文件里面可以配置默认的数据库引擎库。