Spring
事务是用于解决数据库操作中的一致性和隔离性问题的机制。数据库事务是一组操作,要么全部成功执行,要么全部回滚,以确保数据的完整性和一致性。Spring
事务管理的主要目的是确保在多个数据库操作中,要么所有操作都成功提交,要么所有操作都回滚,从而保持数据的一致性。它提供了以下几个方面的解决方案:原子性(Atomicity
):事务要么全部成功执行,要么全部回滚,确保数据库操作的原子性。
一致性(Consistency
):事务在执行前后,数据库的状态应保持一致。如果事务执行失败,数据库应该回滚到事务开始之前的状态。
隔离性(Isolation
):事务应该在相互之间隔离,以避免并发操作引起的问题。它确保了在并发环境下,每个事务都能够独立地执行,并且不会相互干扰。
持久性(Durability
):一旦事务提交,其结果应该持久保存在数据库中,即使发生系统故障或重启。
Spring
事务管理通过使用注解或编程方式来定义事务边界,它可以应用于各种数据访问技术(如JDBC
、Hibernate
、JPA
等)。它还提供了不同的传播行为和隔离级别,以满足不同的业务需求。Spring
事务,开发人员可以简化数据库操作的管理,并确保数据的一致性和可靠性,从而提高应用程序的可靠性和性能。springboot
项目,springboot
通过DataSourceTransactionManagerAutoConfiguration
类,默认开启了事务。只需要配置spring.datasource
相关参数即可。spring
项目,则需要在applicationContext.xml
文件中,手动配置事务相关参数。如果忘了配置,事务肯定是不会生效的。Spring
管理的组件:确保类被标记为 @Service
、 @Component
或其他适当的注解,以便 Spring
能够扫描并管理该类。@Transactional
注解标记事务方法:确保需要进行事务管理的方法被标记为 @Transactional
注解,以便 Spring
能够识别并应用事务管理。Spring
会在运行时生成一个代理对象来管理事务。这个代理对象会拦截被注解的方法,并在方法执行前后进行事务的开启、提交或回滚等操作。Spring
的事务代理是基于接口实现的,因此只有public
方法才能被代理。如果事务方法是private
、protected
或默认访问级别的,Spring
无法生成代理对象,从而导致事务不生效。/**
* 不生效
*/
@Transactional
private void ransactionOne() {
User user = new User();
user.setUsername("张三");
userMapper.insertUser(user);
methodOne();
int a = 3 / 0;
logger.info(String.valueOf(a));
}
判断是否是public
的
有事务的类
CGLIB
代理,Spring
可以代理非public
方法。可以通过配置 proxy-target-class
属性为true
来启用基于类的代理。public
的,但需要启用基于类的代理。SpringBoot
事务示例:通过在 @EnableTransactionManagement
注解上设置 proxyTargetClass
属性为true
来启用基于类的代理。@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
public class AppConfig {
// 配置其他的Bean和组件
}
public
方法也可以被代理。但需要注意,启用基于类的代理可能会带来一些性能开销,因此只有在确实需要代理非public
方法时才应使用。@EnableTransactionManagement
注解,并根据需要设置 proxyTargetClass
属性,就可以在Spring Boot
中使用基于类的代理来进行事务管理了。Spring
使用CGLIB
库的Enhancer
类来生成代理对象,生成的代理对象中包含EnhancerBySpringCGLIB
作为标识符Spring
中,事务是通过动态代理来实现的。当一个类被代理时,Spring
会创建一个代理对象来包装原始对象,从而在方法调用前后添加事务处理逻辑。然而,对于 final
方法,由于无法重写,因此无法创建代理对象,事务管理器也就无法对其进行事务处理。@Service
public class UserService {
@Transactional
public final void add(UserModel userModel){
saveData(userModel);
updateData(userModel);
}
}
this
是被真实对象,所以会直接走methodTwo
的业务逻辑,而不会走切面逻辑,所以事务失败。/**
* 同一个类中的方法调用
*/
@Override
public void transactionSix() {
User user = new User();
user.setUsername("张三");
userMapper.insertUser(user);
// 没有事务的方法调用有事务的方式,相当于使用 this 调用不会走 AOP 的逻辑
methodTwo();
}
@Transactional(rollbackFor = Throwable.class)
public void methodTwo() {
User user = new User();
user.setUsername("李四");
userMapper.insertUser(user);
int a = 5 / 0;
logger.info(String.valueOf(a));
}
@Transactional
注解@EnableAspectJAutoProxy(exposeProxy = true)
在启动类中添加,会由Cglib
代理实现。methodTwo
的事务生效,可以把methodTwo
写到一个新的service
,用service
调用/**
* 同一个类中的方法调用,调用 service 的方法,insetUser 方法事务可以生效
*/
@Override
public void transactionSeven(){
User user = new User();
user.setUsername("张三");
userMapper.insertUser(user);
transactionHelpService.insetUser();
}
@Transactional(rollbackFor = Throwable.class)
@Override
public void insetUser() {
User user = new User();
user.setUsername("李四");
userMapper.insertUser(user);
int a = 5 / 0;
logger.info(String.valueOf(a));
}
数据库本身无法支持事务的场景下,例如使用Mysql
的MyISAM
引擎
在MySQL5.5
往后,默认采用InnoDB存储引擎
,在这之前采用MyISAM存储引擎
。
InnoDB
是MySQL
的默认事务型引擎
,它被设计用来处理大量的短期(short-lived)事务
。可以确保事务的完整提交(Commit)和回滚(Rollback)
。
MyISAM
提供了大量的特性,包括全文索引、压缩、空间函数(GIS)
等,但MyISAM
不支持事务、行级锁、外键
,有一个毫无疑问的缺陷就是崩溃后无法安全恢复
。
Propagation.NEVER
:这种类型的传播特性不支持事务,如果有事务则会抛异常。REQUIRED
,REQUIRES_NEW
,NESTED
try...catch
了异常@Transactional
@Override
public void testOne() {
try {
User user = new User();
user.setUsername("张三");
userMapper.insertUser(user);
int a = 3 / 0;
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
Spring Boot
中,事务是通过AOP
(面向切面编程)机制实现的。当使用 @Transactional
注解标记一个方法时,Spring
会在方法开始前创建一个事务,并在方法执行结束后根据方法的执行结果来决定是否提交或回滚事务。Spring
会捕获该异常并将事务标记为“回滚”。这意味着在方法执行结束后,Spring
会自动回滚该事务并撤销对数据库的任何更改。Spring
就无法感知到该异常,并且不会将事务标记为“回滚”。这意味着在方法执行结束后,Spring
会将事务提交,而不是回滚,这可能会导致不一致的数据状态。spring
事务,默认情况下只会回滚RuntimeException
(运行时异常)和Error
(错误),对于普通的Exception
(非运行时异常),它不会回滚。RuntimeException
类型的异常,Spring
会认为这个异常是不可恢复的,也就是说无法通过异常处理来修复。在这种情况下,Spring
会回滚事务,撤销之前的所有数据库操作,以保证数据的一致性。RuntimeException
类型的异常,Spring
会认为这个异常是可以恢复的,意味着可以通过异常处理来修复。在这种情况下,Spring
不会回滚事务,而是允许应用程序继续执行。RuntimeException
类型的异常,应用程序可能会尝试通过其他方式来处理异常并继续执行。而如果发生了 RuntimeException
类型的异常,应用程序可能无法继续执行,因此需要回滚事务以撤销之前的数据库操作。/**
* 在代码中手动抛出别的异常 事务不回滚
*/
@Override
public void testTwo() throws Exception {
try {
User user = new User();
user.setUsername("张三");
userMapper.insertUser(user);
int a = 3 / 0;
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new Exception("test");
}
}
在使用@Transactional
注解声明事务时,有时我们想自定义回滚的异常,spring
也是支持的。可以通过设置rollbackFor
参数
rollbackFor
参数指定了回滚事务异常的类型,需要检查抛出的异常是否是指定的异常,如果不一致则回滚不了
开发规范中会提示需要指定rollbackFor
参数,事务默认只会在捕获到未被处理的 RuntimeException
或 Error 时才会回滚。如果你的代码中捕获了异常并进行了处理,但没有再次抛出 RuntimeException
或 Error,事务将不会回滚。确保在捕获异常时,将异常重新抛出或手动触发回滚。
commit()
方法来提交事务,而没有调用 rollback()
方法来回滚事务,事务将不会回滚。确保在需要回滚的情况下,正确地调用了回滚方法。TransactionTemplate
类可以手动启动、提交或回滚事务