• Mybatis深入:使用Spring事务管理


    使用Spring事务管理

    现在我们来学习一下Spring提供的事务管理(Spring事务管理分为编程式事务和声明式事务,但是编程式事务过于复杂并且具有高度耦合性,违背了Spring框架的设计初衷,因此这里只讲解声明式事务)声明式事务是基于AOP实现的。

    使用声明式事务非常简单,我们只需要在配置类添加@EnableTransactionManagement注解即可,这样就可以开启Spring的事务支持了。接着,我们只需要把一个事务要做的所有事情封装到Service层的一个方法中即可,首先需要在配置文件中注册一个新的Bean,事务需要执行必须有一个事务管理器:

    1. @Bean
    2. public TransactionManager transactionManager(@Autowired DataSource dataSource){
    3. return new DataSourceTransactionManager(dataSource);
    4. }

    接着编写Mapper操作:

    1. @Mapper
    2. public interface TestMapper {
    3. @Insert("insert into student(name, sex) values('测试', '男')")
    4. void insertStudent();
    5. }

    这样会向数据库中插入一条新的学生信息,接着,假设我们这里有一个业务需要连续插入两条学生信息,首先编写业务层的接口:

    1. public interface TestService {
    2. void test();
    3. }

    接着,我们再来编写业务层的实现,我们可以直接将其注册为Bean,交给Spring来进行管理,这样就可以自动将Mapper注入到类中了,并且可以支持事务:

     

    1. @Component
    2. public class TestServiceImpl implements TestService{
    3. @Resource
    4. TestMapper mapper;
    5. @Transactional
    6. @Override
    7. public void test() {
    8. mapper.insertStudent();
    9. if(true) throw new RuntimeException("我是测试异常!");
    10. mapper.insertStudent();
    11. }
    12. }

    我们只需在方法上添加@Transactional注解,即可表示此方法执行的是一个事务操作,在调用此方法时,Spring会通过AOP机制为其进行增强,一旦发现异常,事务会自动回滚。最后我们来调用一下此方法:

    1. @Slf4j
    2. public class Main {
    3. public static void main(String[] args) {
    4. log.info("项目正在启动...");
    5. ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
    6. TestService service = context.getBean(TestService.class);
    7. service.test();
    8. }
    9. }

    得到的结果是出现错误:

    1. 十二月 08, 2021 3:09:29 下午 com.test.Main main
    2. 信息: 项目正在启动...
    3. 十二月 08, 2021 3:09:29 下午 com.zaxxer.hikari.HikariDataSource getConnection
    4. 信息: HikariPool-1 - Starting...
    5. 十二月 08, 2021 3:09:29 下午 com.zaxxer.hikari.HikariDataSource getConnection
    6. 信息: HikariPool-1 - Start completed.
    7. Exception in thread "main" java.lang.RuntimeException: 我是测试异常!
    8. at com.test.service.TestServiceImpl.test(TestServiceImpl.java:22)
    9. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    10. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    11. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    12. at java.lang.reflect.Method.invoke(Method.java:498)
    13. at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
    14. at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
    15. at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    16. at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
    17. at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
    18. at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
    19. at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    20. at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
    21. at com.sun.proxy.$Proxy30.test(Unknown Source)
    22. at com.test.Main.main(Main.java:17)

    我们发现,整个栈追踪信息中包含了大量aop包下的相关内容,也就印证了,它确实是通过AOP实现的,那么我们接着来看一下,数据库中的数据是否没有发生变化(出现异常回滚了)

    结果显而易见,确实被回滚了,数据库中没有任何的内容。

    我们接着来研究一下@Transactional注解的一些参数:

    1. @Target({ElementType.TYPE, ElementType.METHOD})
    2. @Retention(RetentionPolicy.RUNTIME)
    3. @Inherited
    4. @Documented
    5. public @interface Transactional {
    6. @AliasFor("transactionManager")
    7. String value() default "";
    8. @AliasFor("value")
    9. String transactionManager() default "";
    10. String[] label() default {};
    11. Propagation propagation() default Propagation.REQUIRED;
    12. Isolation isolation() default Isolation.DEFAULT;
    13. int timeout() default -1;
    14. String timeoutString() default "";
    15. boolean readOnly() default false;
    16. Classextends Throwable>[] rollbackFor() default {};
    17. String[] rollbackForClassName() default {};
    18. Classextends Throwable>[] noRollbackFor() default {};
    19. String[] noRollbackForClassName() default {};
    20. }

    我们来讲解几个比较关键的信息:

    • transactionManager:指定事务管理器
    • propagation:事务传播规则,一个事务可以包括N个子事务
    • isolation:事务隔离级别,不多说了
    • timeout:事务超时时间
    • readOnly:是否为只读事务,不同的数据库会根据只读属性进行优化,比如MySQL一旦声明事务为只读,那么久不允许增删改操作了。
    • rollbackFor和noRollbackFor:发生指定异常时回滚或是不回滚,默认发生任何异常都回滚

    除了事务的传播规则,其他的内容其实已经给大家讲解过了,那么我们就来看看事务的传播。事务传播一共有七种级别:

    Spring默认的传播级别是PROPAGATION_REQUIRED,那么我们来看看,它是如何传播的,现在我们的Service类中一共存在两个事务,而一个事务方法包含了另一个事务方法:

     

    1. @Transactional
    2. public void test() {
    3. test2();
    4. if(true) throw new RuntimeException("我是测试异常!"); //发生异常时,会回滚另一个事务吗?
    5. }
    6. @Transactional
    7. public void test2() {
    8. mapper.insertStudent();
    9. }

    最后我们得到结果,另一个事务被回滚了,也就是说,相当于另一个事务直接加入到此事务中了,也就是表中所描述的那样。

    如果单独执行test2()则会开启一个新的事务,而执行test()则会直接让内部的test2()加入到当前事务中。

    1. @Transactional
    2. public void test() {
    3. test2();
    4. }
    5. @Transactional(propagation = Propagation.SUPPORTS)
    6. public void test2() {
    7. mapper.insertStudent();
    8. if(true) throw new RuntimeException("我是测试异常!");
    9. }

    现在我们将test2()的传播级别设定为SUPPORTS,那么这时如果单独调用test2()方法,并不会以事务的方式执行,当发生异常时,虽然依然存在AOP增强,但是不会进行回滚操作,而现在再调用test()方法,才会以事务的方式执行。

    我们接着来看MANDATORY,它非常严格,如果当前方法并没有在任何事务中进行,会直接出现异常:

    1. @Transactional
    2. public void test() {
    3. test2();
    4. }
    5. @Transactional(propagation = Propagation.MANDATORY)
    6. public void test2() {
    7. mapper.insertStudent();
    8. if(true) throw new RuntimeException("我是测试异常!");
    9. }

    直接运行test2()方法,报错如下:

     

    1. Exception in thread "main" org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
    2. at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:362)
    3. at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:595)
    4. at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:382)
    5. at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
    6. at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    7. at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
    8. at com.sun.proxy.$Proxy29.test2(Unknown Source)
    9. at com.test.Main.main(Main.java:17)

    NESTED级别表示如果存在外层事务,则此方法单独创建一个子事务,回滚只会影响到此子事务,实际上就是利用创建Savepoint,然后回滚到此保存点实现的。NEVER级别表示此方法不应该加入到任何事务中,其余类型适用于同时操作多数据源情况下的分布式事务管理,这里暂时不做介绍。

    至此,有关Spring的核心内容就讲解完毕了。

  • 相关阅读:
    计算机考研——机试指南(更新ing)
    懒羊羊闲话1
    实现paho.mqtt.cpp库编译
    JS操作字符串常见方法
    Day15--加入购物车-初始化vuex
    双飞翼布局
    用HTML5和JavaScript实现黑客帝国风格的字符雨效果
    后端编译与优化(JIT,即时编译器)
    ASM字节码操作类库(打开java语言世界通往字节码世界的大门) | 京东云技术团队
    pyinstaller 使用
  • 原文地址:https://blog.csdn.net/Leon_Jinhai_Sun/article/details/126440938