精华内容
下载资源
问答
  • 主要介绍了Spring @Transactional注解失效解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • @Transactional注解失效@Transactional注解失效1、看数据库表的引擎是否支持事务2、看是否对发生的异常进行了捕获3、看是否开启了事务注解4、看方法的访问修饰符是不是public5、看是否被未注解的方法调用 ...

    @Transactional注解失效

    1、看数据库表的引擎是否支持事务

    对于MySQL数据库,常用的引擎有MYISAM和InnoDB,但MYISAM引擎不支持事务操作,InnoDB引擎才支持事务操作,所以可以先查看表的引擎是否支持事务。

    2、看是否对发生的异常进行了捕获

    @Service
    @Transactional
    public class UserService {
        @Autowired
        private UserDao userDao;
    
        public void transferMoney(){
            try {
                userDao.decrOne();
                int a = 10/0;
                userDao.incrOne();
            }catch (ArithmeticException e) {
                e.printStackTrace();
            }
        }
    }
    

    此时异常已经被捕获,所以事务不会生效

    @Service
    @Transactional
    public class UserService {
        @Autowired
        private UserDao userDao;
    
        public void transferMoney(){
            try {
                userDao.decrOne();
                int a = 10/0;
                userDao.incrOne();
            }catch (NullPointerException e) {
                e.printStackTrace();
            }
        }
    }
    

    由于此时发生的异常为ArithmeticException,但是捕获的异常为NullPointerException,所以此时事务是会生效并且回滚。

    如果是在service层抛出异常,而在调用service的方法上(如controller层)捕获了异常,则事务仍然能够回滚

    3、看是否开启了事务注解

    <!--创建事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="datasource"/>
    </bean>
    
    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
    

    4、看方法的访问修饰符是不是public

    @Transactional注解只能对可见度为public的方法有效,如果使用protected等修饰符,事务不会起作用。

    @Service
    @Transactional
    public class UserService {
        @Autowired
        private UserDao userDao;
    	//访问修饰符为protected,注解不生效
        protected void transferMoney(){
            userDao.decrOne();
            int a = 10/0;
            userDao.incrOne();
        }
    }
    

    5、看是否被未注解的方法调用

    @Service
    public class UserService {
       @Autowired
       private UserDao userDao;
    
       @Transactional
       public void transferMoney(){
           userDao.decrOne();
           int a = 10/0;
           userDao.incrOne();
       }
    
       public void test() {
           transferMoney();
       }
    }
    

    test方法没有@transactional注解,此时调用transferMoney方法,则transferMoney方法发生异常之后不会回滚。

    展开全文
  • 新手疑问之为什么我已经加上了@Transactional注解,还是失效呢??? 这个很好回答,要么没有生成代理类调用,要么没有开启事务。可以参考细说@Transactional用法及原理 老鸟致命疑问之为什么我已经加上了@...

    ==> 学习汇总(持续更新)
    ==> 从零搭建后端基础设施系列(一)-- 背景介绍


    前言:这一篇文章通过分析根源,基本可以解决所有事务失效的情况了,但是因为spring掌握的有限,所以写的时候可能会有些地方描述得不是很好!大家可以指出我来改进!


    • 新手疑问之为什么我已经加上了@Transactional注解,还是失效呢???
      这个先从表面上判断(其根本原因下面讲,新手可能较难理解),可以参考细说@Transactional用法及原理
      ,简单了解失效的表面原因。

    • 老鸟致命疑问之为什么我已经加上了@Transactional注解,并且事务确认已经开启,最后已经生成代理类调用了,它还是失效???
      这个问题可能不常见,一旦遇到,那么除非是spring高高手,对源码熟知又熟,才可以瞬间解之,否则只能像我这种菜鸟,慢慢的跟着源码一路找到底才可能找到一丝光明。
      言归正传,我先来一个上述问题的例子。因为不好拿实际项目中的代码进行演示,所以我将问题抽出来复现一下即可。新建一个项目,按照细说@Transactional用法及原最后小白总结的来即可。最后如图所示
      在这里插入图片描述

      • RecordPointCut
        @Aspect
        @Component
        public class RecordPointCut {
        	@Pointcut("execution(public * com.acme.transactional.demo.service.*Impl.*(..))")
            public void myAnnotationPointcut(){
        
            }
        
            @Before("myAnnotationPointcut()")
            public void before(JoinPoint joinPoint){
                System.out.println(joinPoint.getTarget() + " begin:" + System.currentTimeMillis());
            }
        
            @After("myAnnotationPointcut()")
            public void after(JoinPoint joinPoint){
                System.out.println(joinPoint.getTarget() + " end:" + System.currentTimeMillis());
            }
        }
        
      • DatasourceConfig
         @Configuration
        public class DatasourceConfig {
         	@Bean
        	public DataSource dataSource() {
        		DriverManagerDataSource dataSource = new DriverManagerDataSource("");
        		dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        		return dataSource;
        	}
        }
        
      • MyConfig
        @Configuration
        public class MyConfig {
            /*
            * 看着名字就知道,其它服务的bean,相当于我这个服务使用到了其它服务的bean
            * @Qualifier作用是指定注入哪个bean,这里生成OtherServerBean的时候,就会去创建HelloImpl
            * */
            @Bean
            OtherServerBean otherServerBean(@Qualifier("helloImpl")HelloImpl hello){
                System.out.println(hello);
                return new OtherServerBean();
            }
        }
        
      • Curd
        @Component
        public class Curd {
            public void add(){
                //数据库插入操作
            }
        }
        
      • OtherServerBean
        /*
        * 实现了FactoryBean接口,算是一个特殊的bean
        * */
        public class OtherServerBean implements FactoryBean<Object> {
            @Override
            public OtherServerBean getObject() throws Exception {
                return new OtherServerBean();
            }
        
            @Override
            public Class<OtherServerBean> getObjectType() {
                return OtherServerBean.class;
            }
        }
        
      • HelloImpl
        @Component
        public class HelloImpl{
        
            @Autowired
            private Curd curd;
            
            @Transactional
            public void sayHello(){
                curd.add();
            }
        }
        
      • 测试
        @SpringBootTest
        class DemoApplicationTests {
        	@Autowired
        	HelloImpl hello;
        	@Test
        	void contextLoads() {
        		hello.sayHello();
        	}
        }
        

      先说接下来会发生的现象,hello是一个代理类,但是事务并不生效(没有事务拦截器,可在源码中看到)
      接下来运行代码,如图所示,hello是一个代理类
      在这里插入图片描述
      再单步调试进去,红圈的地方,那三个拦截器,没有一个是事务拦截器的,这就可以反证出开头的那个现象,加了事务注解,是代理类,但是还是没有生效。
      在这里插入图片描述


      现在我们来找原因,首先,我的思路是,为什么hello这个bean没有事务拦截器?拦截器应该说明时候被加进去?是不是bean生成的时候?……(经过我长时间的脑暴+试验,终于找出一条路来,这里我直接说结论,因为路是自己走出来的,很难原样的告诉别人该怎么走)

      • 找到拦截器被加进去的地方
        寻找路线(中间有些会忽略,不然就太长了)
        SpringApplication.run(DemoApplication.class, args) -> refreshContext -> refresh -> finishBeanFactoryInitialization -> preInstantiateSingletons
        好,第一阶段的路线已经找到了,如图,像上面那行注释所说实例化所有非懒加载的bean。
        在这里插入图片描述
        第二阶段,走起,当实例化到demoApplication的时候,路线是这样的
        else中的getBean -> doGetBean -> mbd.isSingleton()分支的createBean ->resolveBeforeInstantiation->applyBeanPostProcessorsBeforeInstantiation->postProcessBeforeInstantiation(当ibp的类型为AnnotationAwareAspectJAutoProxyCreator时单步进去)->shouldSkip->findCandidateAdvisors->findAdvisorBeans
        好,到终点了,我们终于找到增强器(这里还只是增强器,拦截器还要在外面被生成,但是我们的目的已经达到了)被加进去的地方了。如图,看向标1和2的地方,beanNamesForTypeIncludingAncestors这个方法,是去寻找spring工厂中是否有增强器,果不其然,被找到了一个事务增强器,标3所示。
        在这里插入图片描述
        接着往下走,可以看到标1的地方是判断这个bean是否正在创建中,如果是的话,是不会走到标2这个地方的。从图中可以看出,这个bean还没开始创建,所以用beanFactory.getBean去创建它
        在这里插入图片描述

      • 拦截器创建的过程?
        接下来,我们要找出拦截器的创建过程,第一阶段路线
        beanFactory.getBean -> doGetBean -> createBean -> doCreateBean -> createBeanInstance -> instantiateUsingFactoryMethod
        好,第一阶段就走完了,如图,因为org.springframework.transaction.config.internalTransactionAdvisor有工厂方法,所以使用instantiateUsingFactoryMethod这个去实例化它,标2的地方是去创建org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration这个工厂类去了。
        在这里插入图片描述
        创建ProxyTransactionManagementConfiguration的逻辑和之前demoApplication的是一样的,会进入到resolveBeforeInstantiation中,然后试图去找增强器,发现它正在创建中(可不是嘛),直接就返回了,相当于没有找到增强器。
        在这里插入图片描述
        第二阶段,现在时间紧迫,直接开挂,到另一个地方吧,路线如下
        createBeanInstance -> populateBean -> ibp.postProcessProperties(这里需要找到正确的入口,当ibp为AutowiredAnnotationBeanPostProcessor时)->inject->inject->beanFactory.resolveDependency->doResolveDependency -> resolveMultipleBeans -> findAutowireCandidates(else的Collection.class分支)->beanNamesForTypeIncludingAncestors -> getBeanNamesForType -> doGetBeanNamesForType -> isTypeMatch -> getTypeForFactoryBean(在FactoryBean.class.isAssignableFrom(predictedType)下)
        好,路线到这里就结束了,原因我们也快找出来了,我们直接来看看
        getTypeForFactoryBean这个方法,看之前,我们要知道,为什么会进入到这里来,如图,可以看到,居然是otherServerBean这个bean,为什么呢?因为它是一个工厂bean,我们再来看看它的作用是什么,单步进去。
        在这里插入图片描述
        我们可以看到,如果myConfig这个类存在(因为otherServerBean这个是在它下面管理的bean),那么就尝试根据类型去获取otherServerBean,但是发现没有匹配的类型,为什么呢?我们回过头来看,otherServerBean的定义,FactoryBean是Object类型,但是getObjectType返回的却是OtherServerBean类型,所以就匹配不上了。

        /*
        * 实现了FactoryBean接口,算是一个特殊的bean
        * */
        public class OtherServerBean implements FactoryBean<Object> {
            @Override
            public OtherServerBean getObject() throws Exception {
                return new OtherServerBean();
            }
        
            @Override
            public Class<OtherServerBean> getObjectType() {
                return OtherServerBean.class;
            }
        }
        

        在这里插入图片描述

        那么匹配不上会怎么办?继续往下看,因为是单例,所以调的是getSingletonFactoryBeanForTypeCheck这个方法。
        在这里插入图片描述
        它里面会调用createBeanInstance去实例化这个bean,为什么要这样?因为它需要实例化出来才能判断它是啥类型呀。所以问题就在这里。
        在这里插入图片描述

        这里先小结一下,再看最后的结果。因为otherServerBean的创建出了问题

        • otherServerBeanFactoryBean类型是Object的,getObjectType又返回的是OtherServerBean,导致不能直接拿到getObject中的new OtherServerBean(),所以需要去实例化OtherServerBean出来才能去判断类型。
        • 实例化OtherServerBean又会去找MyConfig中注解为@BeanotherServerBean,这时候@Qualifier("helloImpl")HelloImpl hello也被实例化出来了,最终结果就是,hello先生成了,增强器还没创建出来,自然就生成不了拦截器,也就少了这个事务拦截器。

        如图所示,走到这一步后,会去尝试为这个bean获取增强器
        在这里插入图片描述
        但是此时事务增强器还正在创建
        在这里插入图片描述
        所以最后肯定是拿不到它的
        在这里插入图片描述
        最后回到createBean中的doCreateBean,可以看到hello先于org.springframework.transaction.config.internalTransactionAdvisor创建了。
        在这里插入图片描述
        在这里插入图片描述


    • 重要细节分析
      • 为什么preInstantiateSingletons 中,到了demoApplication的加载才开始去创建internalTransactionAdvisor
        有这疑问的,应该都没有实际运行这段代码,跟着调试进去看,那现在我就演示一下,如图,就拿第一个bean来说
        在这里插入图片描述
        这里可以看到,它之前已经被加载过了!所以直接从缓存中拿到,就不会进入到else分支进行bean的创建了!所以也就不会再到internalTransactionAdvisor这一步,同理,demoApplication前面的bean都是这样。
        在这里插入图片描述
      • 为什么会在postProcessBeforeInstantiation 中的shouldSkip去实例化internalTransactionAdvisor
        说到这里,确实是一个坑,postProcessBeforeInstantiation这个方法相当于是bean实例化前,判断一下是否需要走代理?然后它会判断,isInfrastructureClass(beanClass)是否是基础设施类(我直译过来的),判断代码如下,简单讲,如果是这三个类的子类,那么就算是。
        protected boolean isInfrastructureClass(Class<?> beanClass) {
        	boolean retVal = Advice.class.isAssignableFrom(beanClass) ||
        			Pointcut.class.isAssignableFrom(beanClass) ||
        			Advisor.class.isAssignableFrom(beanClass) ||
        			AopInfrastructureBean.class.isAssignableFrom(beanClass);
        	if (retVal && logger.isTraceEnabled()) {
        		logger.trace("Did not attempt to auto-proxy infrastructure class [" + beanClass.getName() + "]");
        	}
        	return retVal;
        }	
        
        然后判断是否应该跳过,代码如下,先去找到所有的增强器,然后再一一判断是否是AspectJPointcutAdvisor类型的,如果有的话就返回true,就可以跳过,不代理了。这里有一个坑,作者也注释了TODO,考虑是否用缓存优化,因为现在每次调用shouldSkip都会去调用findCandidateAdvisors,它去找增强器的时候,如果找到了bean的定义,那么就会去创建它,这一步其实可以用缓存代替,只需要走一次findCandidateAdvisors就行。所以当创建demoApplication的时候,会走到这里,然后找到internalTransactionAdvisor,并创建它,后来又导致一系列的递归创建。
        protected boolean shouldSkip(Class<?> beanClass, String beanName) {
        	// TODO: Consider optimization by caching the list of the aspect names
        	List<Advisor> candidateAdvisors = findCandidateAdvisors();
        	for (Advisor advisor : candidateAdvisors) {
        		if (advisor instanceof AspectJPointcutAdvisor &&
        				((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
        			return true;
        		}
        	}
        	return super.shouldSkip(beanClass, beanName);
        }
        
      • 创建ProxyTransactionManagementConfiguration的时候为什么会创建OtherServerBean
        是的,这两个看起牛马不相及,可是他们就是有关系了!让我们来看看这是怎么回事,从doCreateBeanpopulateBean说起,populateBean的作用就是为bean填充属性,比如A中有b、c属性,那么它就会去创建b、c然后填充进去。ProxyTransactionManagementConfiguration的父类以及自身都有需要填充的属性,如图,先看看父类的,因为肯定会先填充父类的属性。
        在这里插入图片描述
        再看看调试的信息,先为setConfigurers这个方法注入需要的bean
        在这里插入图片描述
        去找到适合这个类型的bean(根据类型查找)
        在这里插入图片描述
        因为毕竟深入,所以我直接贴出最后一层的截图(一步步跟进去即可找到),看方法名就知道,根据type获取bean,继续进去看看
        在这里插入图片描述
        重点来了,当判断到otherServerBean的时候,调用isTypeMatch的时候,问题来了,因为它是一个工厂bean,所以会走到一个比较特殊的地方。
        在这里插入图片描述
        没错,就是这里,如果是工厂bean,那么会调用getTypeForFactoryBean去拿到这个bean的类型。
        在这里插入图片描述
        再深入到里面,可以看到,拿到的getObjectType方法的返回类型是OtherServerBean,但是FactoryBean的类型却是Object
        在这里插入图片描述
        所以类型不匹配,找不到。如果找到的话,就直接返回,不会去实例化otherServerBean
        在这里插入图片描述
        接着就会去实例化otherServerBean这个bean,看看是什么类型,接下来的就没啥可说的了,
        在这里插入图片描述

    • 解决办法
      • 懒加载
        很好理解,既然增强器还没创建出来,我为什么要急着先加载呢?所以在所有使用到HelloImpl的地方,加上@Lazy
        @Bean
        OtherServerBean otherServerBean(@Lazy@Qualifier("helloImpl")HelloImpl hello){
            System.out.println(hello);
            return new OtherServerBean();
        }
        
        但是,这个例子不适用,为什么呢?可以看到,加上了@Lazy,但是当实例化otherServerBean的时候,helloImpl还是被实例化出来了。@Lazy有这么一条规则,如果标记为懒加载的类,需要注入到其它非懒加载的类的时候,懒加载失效!
        在这里插入图片描述
        所以,如果代码改成这样,就可以使用懒加载的方式
        在这里插入图片描述
        @Bean
        OtherServerBean otherServerBean(@Qualifier("THelloImpl")THelloImpl hello){
            System.out.println(hello);
            return new OtherServerBean();
        }
        
        这样,相当于THelloImpl被加载了,但是它的属性HelloImpl却可以懒加载了。
      • 修改OtherServerBeanFactoryBean类型
        这个方法可行性不高,原因是,如果这个是第三方bean,那么你修改不了。但是如果是自己写的话,就很好办了。直接修改类型即可。
        	public class OtherServerBean implements FactoryBean<OtherServerBean> {
            @Override
            public OtherServerBean getObject() throws Exception {
                return new OtherServerBean();
            }
        
            @Override
            public Class<OtherServerBean> getObjectType() {
                return OtherServerBean.class;
            }
        }
        
      • 最直接能避免这种情况的,就是不要使用注入的方式,直接new出对象来
        前提是不需要全局性,也就是单不单例的无所谓那种。但是很明显,这里不行,因为THelloImpl里面用到了spring的自动注入,所以THelloImpl也必须要由spring管理。
        @Bean
        OtherServerBean otherServerBean(){
            System.out.println(new THelloImpl());
            return new OtherServerBean();
        }
        

    • 总结
      • @Transactional失效的根本原因是事务增强器没有被加入到某个bean中,造成这种原因有两种,第一是根本找不到事务增强器,第二是某个bean先于事务增强器加载了,导致事务增强器没被加入。
      • 从表面上看,虽然它是一个代理类,但是里面却没有事务拦截器,有的是其它拦截器,所以当然会失效。
      • FactoryBean的用法,需要注意,<T>泛型参数不要设置为Object,否则就会造成类型检查的时候不匹配,转而去实例化该类获取类型,这又会导致一系列的连锁反应。
      • @Lazy标记的类,需要注入到其它非懒加载的类,那么就会失效。这个很容易理解,本来懒加载就是用的是才会加载,那现在别人需要用到你了,当然需要加载了。
      • 使用到事务注解的类,最好是放在最里层,外面包一层,用@Lazy标记,而且不要在其它地方再直接使用它。例如,A中的方法使用了事务,那么可以用B将A包起来,A在B中标记为懒加载,之后其它地方仅仅引用B,不要直接引用A,就可以避免项目中误使用导致的事务失效。
    展开全文
  • 引言昨天公众号粉丝咨询了一个问题,说自己之前面试被问@Transactional注解哪些场景下会失效,一时语塞致使面试失败。所以今天简单的和大家分享一下@Transactional相关的...

    引言

    昨天公众号粉丝咨询了一个问题,说自己之前面试被问@Transactional注解哪些场景下会失效,一时语塞致使面试失败。所以今天简单的和大家分享一下@Transactional相关的知识。

    @Transactional 注解相信大家并不陌生,平时开发中很常用的一个注解,它能保证方法内多个数据库操作要么同时成功、要么同时失败。使用@Transactional注解时需要注意许多的细节,不然你会发现@Transactional总是莫名其妙的就失效了。

    一、事务

    事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制,主要分为编程式事务声明式事务两种。

    编程式事务:是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强,如下示例:

    1try {
    2    //TODO something
    3     transactionManager.commit(status);
    4} catch (Exception e) {
    5    transactionManager.rollback(status);
    6    throw new InvoiceApplyException("异常失败");
    7}
    

    声明式事务:基于AOP面向切面的,它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。声明式事务也有两种实现方式,一是基于TXAOP的xml配置文件方式,二种就是基于@Transactional注解了。

    1    @Transactional
    2    @GetMapping("/test")
    3    public String test() {
    4
    5        int insert = cityInfoDictMapper.insert(cityInfoDict);
    6    }
    

    二、@Transactional介绍

    1、@Transactional注解可以作用于哪些地方?

    @Transactional 可以作用在接口类方法

    • 作用于类:当把@Transactional 注解放在类上时,表示所有该类的public方法都配置相同的事务属性信息。

    • 作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。

    • 作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效

     1@Transactional
     2@RestController
     3@RequestMapping
     4public class MybatisPlusController {
     5    @Autowired
     6    private CityInfoDictMapper cityInfoDictMapper;
     7
     8    @Transactional(rollbackFor = Exception.class)
     9    @GetMapping("/test")
    10    public String test() throws Exception {
    11        CityInfoDict cityInfoDict = new CityInfoDict();
    12        cityInfoDict.setParentCityId(2);
    13        cityInfoDict.setCityName("2");
    14        cityInfoDict.setCityLevel("2");
    15        cityInfoDict.setCityCode("2");
    16        int insert = cityInfoDictMapper.insert(cityInfoDict);
    17        return insert + "";
    18    }
    19}
    

    2、@Transactional注有哪些属性?

    propagation属性

    propagation 代表事务的传播行为,默认值为 Propagation.REQUIRED,其他的属性信息如下:

    • Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。( 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务 

    • Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。

    • Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。

    • Propagation.REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )

    • Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。

    • Propagation.NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。

    • Propagation.NESTED :和 Propagation.REQUIRED 效果一样。

    isolation 属性

    isolation :事务的隔离级别,默认值为 Isolation.DEFAULT

    • Isolation.DEFAULT:使用底层数据库默认的隔离级别。

    • Isolation.READ_UNCOMMITTED

    • Isolation.READ_COMMITTED

    • Isolation.REPEATABLE_READ

    • Isolation.SERIALIZABLE

    timeout 属性

    timeout :事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

    readOnly 属性

    readOnly :指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。

    rollbackFor 属性

    rollbackFor :用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

    noRollbackFor属性**

    noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。


    二、@Transactional失效场景

    接下来我们结合具体的代码分析一下哪些场景下,@Transactional 注解会失效。

    1、@Transactional 应用在非 public 修饰的方法上

    如果Transactional注解应用在非public 修饰的方法上,Transactional将会失效。

    在这里插入图片描述

    之所以会失效是因为在Spring AOP 代理时,如上图所示 TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute 方法,获取Transactional 注解的事务配置信息。

    1protected TransactionAttribute computeTransactionAttribute(Method method,
    2    Class<?> targetClass) {
    3        // Don't allow no-public methods as required.
    4        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
    5        return null;
    6}
    

    此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。

    注意:protectedprivate 修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。

    2、@Transactional 注解属性 propagation 设置错误

    这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。

    TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
    TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
    TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

    3、@Transactional 注解属性 rollbackFor 设置错误

    rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。

    在这里插入图片描述
    1// 希望自定义的异常可以进行回滚
    2@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class
    

    若在目标方法中抛出的异常是 rollbackFor 指定的异常的子类,事务同样会回滚。Spring源码如下:

     1private int getDepth(Class<?> exceptionClass, int depth) {
     2        if (exceptionClass.getName().contains(this.exceptionName)) {
     3            // Found it!
     4            return depth;
     5}
     6        // If we've gone as far as we can go and haven't found it...
     7        if (exceptionClass == Throwable.class) {
     8            return -1;
     9}
    10return getDepth(exceptionClass.getSuperclass(), depth + 1);
    11}
    

    4、同一个类中方法调用,导致@Transactional失效

    开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。

    那为啥会出现这种情况?其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。

     1//@Transactional
     2    @GetMapping("/test")
     3    private Integer A() throws Exception {
     4        CityInfoDict cityInfoDict = new CityInfoDict();
     5        cityInfoDict.setCityName("2");
     6        /**
     7         * B 插入字段为 3的数据
     8         */
     9        this.insertB();
    10        /**
    11         * A 插入字段为 2的数据
    12         */
    13        int insert = cityInfoDictMapper.insert(cityInfoDict);
    14
    15        return insert;
    16    }
    17
    18    @Transactional()
    19    public Integer insertB() throws Exception {
    20        CityInfoDict cityInfoDict = new CityInfoDict();
    21        cityInfoDict.setCityName("3");
    22        cityInfoDict.setParentCityId(3);
    23
    24        return cityInfoDictMapper.insert(cityInfoDict);
    25    }
    

    5、异常被你的 catch“吃了”导致@Transactional失效

    这种情况是最常见的一种@Transactional注解失效场景

     1    @Transactional
     2    private Integer A() throws Exception {
     3        int insert = 0;
     4        try {
     5            CityInfoDict cityInfoDict = new CityInfoDict();
     6            cityInfoDict.setCityName("2");
     7            cityInfoDict.setParentCityId(2);
     8            /**
     9             * A 插入字段为 2的数据
    10             */
    11            insert = cityInfoDictMapper.insert(cityInfoDict);
    12            /**
    13             * B 插入字段为 3的数据
    14             */
    15            b.insertB();
    16        } catch (Exception e) {
    17            e.printStackTrace();
    18        }
    19    }
    

    如果B方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务还能正常回滚吗?

    答案:不能!

    会抛出异常:

    1org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
    

    因为当ServiceB中抛出了一个异常以后,ServiceB标识当前事务需要rollback。但是ServiceA中由于你手动的捕获这个异常并进行处理,ServiceA认为当前事务应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException异常。

    spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit or rollback,事务是否执行取决于是否抛出runtime异常。如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。

    在业务方法中一般不需要catch异常,如果非要catch一定要抛出throw new RuntimeException(),或者注解中指定抛异常类型@Transactional(rollbackFor=Exception.class),否则会导致事务失效,数据commit造成数据不一致,所以有些时候try catch反倒会画蛇添足。

    6、数据库引擎不支持事务

    这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。

    总结

    @Transactional 注解的看似简单易用,但如果对它的用法一知半解,还是会踩到很多坑的。

     

    展开全文
  • Transactional失效场景第一种 Transactional注解标注方法修饰符为非public时,@Transactional注解将会不起作用。例如以下代码,定义一个错误的@Tr...

     

    Transactional失效场景

    第一种 Transactional注解标注方法修饰符为非public时,@Transactional注解将会不起作用。例如以下代码,定义一个错误的@Transactional标注实现,修饰一个默认访问符的方法:

    /**
     * @author zhoujy
     **/
    @Component
    public class TestServiceImpl {
        @Resource
        TestMapper testMapper;
        
        @Transactional
        void insertTestWrongModifier() {
            int re = testMapper.insert(new Test(10,20,30));
            if (re > 0) {
                throw new NeedToInterceptException("need intercept");
            }
            testMapper.insert(new Test(210,20,30));
        }
    
    }
    

    在同一个包内,新建调用对象,进行访问。

    @Component
    public class InvokcationService {
        @Resource
        private TestServiceImpl testService;
        public void invokeInsertTestWrongModifier(){
            //调用@Transactional标注的默认访问符方法
            testService.insertTestWrongModifier();
        }
    }
    

    测试用例:

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class DemoApplicationTests {
       @Resource
       InvokcationService invokcationService;
    
       @Test
       public void  testInvoke(){
          invokcationService.invokeInsertTestWrongModifier();
       }
    }
    

    以上的访问方式,导致事务没开启,因此在方法抛出异常时,testMapper.insert(new Test(10,20,30));操作不会进行回滚。如果TestServiceImpl#insertTestWrongModifier方法改为public的话将会正常开启事务,testMapper.insert(new Test(10,20,30));将会进行回滚。

    第二种失效场景

    在类内部调用调用类内部@Transactional标注的方法,这种情况下也会导致事务不开启。示例代码如下,设置一个内部调用:

    /**
     * @author zhoujy
     **/
    @Component
    public class TestServiceImpl implements TestService {
        @Resource
        TestMapper testMapper;
    
        @Transactional
        public void insertTestInnerInvoke() {
            //正常public修饰符的事务方法
            int re = testMapper.insert(new Test(10,20,30));
            if (re > 0) {
                throw new NeedToInterceptException("need intercept");
            }
            testMapper.insert(new Test(210,20,30));
        }
    
    
        public void testInnerInvoke(){
            //类内部调用@Transactional标注的方法。
            insertTestInnerInvoke();
        }
    
    }
    

    测试用例:

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class DemoApplicationTests {
    
       @Resource
       TestServiceImpl testService;
    
       /**
        * 测试内部调用@Transactional标注方法
        */
       @Test
       public void  testInnerInvoke(){
           //测试外部调用事务方法是否正常
          //testService.insertTestInnerInvoke();
           //测试内部调用事务方法是否正常
          testService.testInnerInvoke();
       }
    }
    

    上面就是使用的测试代码,运行测试知道,外部调用事务方法能够征程开启事务,testMapper.insert(new Test(10,20,30))操作将会被回滚;

    然后运行另外一个测试用例,调用一个方法在类内部调用内部被@Transactional标注的事务方法,运行结果是事务不会正常开启,testMapper.insert(new Test(10,20,30))操作将会保存到数据库不会进行回滚。

    第三种失效场景

    事务方法内部捕捉了异常,没有抛出新的异常,导致事务操作不会进行回滚。示例代码如下。

    /**
     * @author zhoujy
     **/
    @Component
    public class TestServiceImpl implements TestService {
        @Resource
        TestMapper testMapper;
    
        @Transactional
        public void insertTestCatchException() {
            try {
                int re = testMapper.insert(new Test(10,20,30));
                if (re > 0) {
                    //运行期间抛异常
                    throw new NeedToInterceptException("need intercept");
                }
                testMapper.insert(new Test(210,20,30));
            }catch (Exception e){
                System.out.println("i catch exception");
            }
        }
        
    }
    

    测试用例代码如下。

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class DemoApplicationTests {
    
       @Resource
       TestServiceImpl testService;
    
       @Test
       public void testCatchException(){
          testService.insertTestCatchException();
       }
    }
    

    运行测试用例发现,虽然抛出异常,但是异常被捕捉了,没有抛出到方法 外, testMapper.insert(new Test(210,20,30))操作并没有回滚。

    以上三种就是@Transactional注解不起作用,@Transactional注解失效的主要原因。下面结合spring中对于@Transactional的注解实现源码分析为何导致@Transactional注解不起作用。

    @Transactional注解不起作用原理分析

    第一种场景分析

    @Transactional注解标注方法修饰符为非public时,@Transactional注解将会不起作用。这里分析 的原因是,@Transactional是基于动态代理实现的,@Transactional注解实现原理中分析了实现方法,在bean初始化过程中,对含有@Transactional标注的bean实例创建代理对象,这里就存在一个spring扫描@Transactional注解信息的过程,不幸的是源码中体现,标注@Transactional的方法如果修饰符不是public,那么就默认方法的@Transactional信息为空,那么将不会对bean进行代理对象创建或者不会对方法进行代理调用

    @Transactional注解实现原理中,介绍了如何判定一个bean是否创建代理对象,大概逻辑是。根据spring创建好一个aop切点BeanFactoryTransactionAttributeSourceAdvisor实例,遍历当前bean的class的方法对象,判断方法上面的注解信息是否包含@Transactional,如果bean任何一个方法包含@Transactional注解信息,那么就是适配这个BeanFactoryTransactionAttributeSourceAdvisor切点。则需要创建代理对象,然后代理逻辑为我们管理事务开闭逻辑。

    spring源码中,在拦截bean的创建过程,寻找bean适配的切点时,运用到下面的方法,目的就是寻找方法上面的@Transactional信息,如果有,就表示切点BeanFactoryTransactionAttributeSourceAdvisor能够应用(canApply)到bean中,

    AopUtils#canApply(org.springframework.aop.Pointcut, java.lang.Class<?>, boolean)

    public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
       Assert.notNull(pc, "Pointcut must not be null");
       if (!pc.getClassFilter().matches(targetClass)) {
          return false;
       }
    
       MethodMatcher methodMatcher = pc.getMethodMatcher();
       if (methodMatcher == MethodMatcher.TRUE) {
          // No need to iterate the methods if we're matching any method anyway...
          return true;
       }
    
       IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
       if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
          introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
       }
    
        //遍历class的方法对象
       Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
       classes.add(targetClass);
       for (Class<?> clazz : classes) {
          Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
          for (Method method : methods) {
             if ((introductionAwareMethodMatcher != null &&
                   introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
                 //适配查询方法上的@Transactional注解信息  
                 methodMatcher.matches(method, targetClass)) {
                return true;
             }
          }
       }
    
       return false;
    }
    

    我们可以在上面的方法打断点,一步一步调试跟踪代码,最终上面的代码还会调用如下方法来判断。在下面的方法上断点,回头看看方法调用堆栈也是不错的方式跟踪。

    AbstractFallbackTransactionAttributeSource#getTransactionAttribute

    • AbstractFallbackTransactionAttributeSource#computeTransactionAttribute

    protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
       // Don't allow no-public methods as required.
       //非public 方法,返回@Transactional信息一律是null
       if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
          return null;
       }
       //后面省略.......
     }
    

    不创建代理对象

    所以,如果所有方法上的修饰符都是非public的时候,那么将不会创建代理对象。以一开始的测试代码为例,如果正常的修饰符的testService是下面图片中的,经过cglib创建的代理对象。

    如果class中的方法都是非public的那么将不是代理对象。

    不进行代理调用

    考虑一种情况,如下面代码所示。两个方法都被@Transactional注解标注,但是一个有public修饰符一个没有,那么这种情况我们可以预见的话,一定会创建代理对象,因为至少有一个public修饰符的@Transactional注解标注方法。

    创建了代理对象,insertTestWrongModifier就会开启事务吗?答案是不会。

    /**
     * @author zhoujy
     **/
    @Component
    public class TestServiceImpl implements TestService {
        @Resource
        TestMapper testMapper;
    
        @Override
        @Transactional
        public void insertTest() {
            int re = testMapper.insert(new Test(10,20,30));
            if (re > 0) {
                throw new NeedToInterceptException("need intercept");
            }
            testMapper.insert(new Test(210,20,30));
        }
        
        @Transactional
        void insertTestWrongModifier() {
            int re = testMapper.insert(new Test(10,20,30));
            if (re > 0) {
                throw new NeedToInterceptException("need intercept");
            }
            testMapper.insert(new Test(210,20,30));
        }
    }
    
    

    原因是在动态代理对象进行代理逻辑调用时,在cglib创建的代理对象的拦截函数中CglibAopProxy.DynamicAdvisedInterceptor#intercept,有一个逻辑如下,目的是获取当前被代理对象的当前需要执行的method适配的aop逻辑。

    List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
    

    而针对@Transactional注解查找aop逻辑过程,相似地,也是执行一次

    AbstractFallbackTransactionAttributeSource#getTransactionAttribute

    • AbstractFallbackTransactionAttributeSource#computeTransactionAttribute

    也就是说还需要找一个方法上的@Transactional注解信息,没有的话就不执行代理@Transactional对应的代理逻辑,直接执行方法。没有了@Transactional注解代理逻辑,就无法开启事务,这也是上一篇已经讲到的。

    第二种场景分析

    在类内部调用调用类内部@Transactional标注的方法。这种情况下也会导致事务不开启。

    经过对第一种的详细分析,对这种情况为何不开启事务管理,原因应该也能猜到;

    既然事务管理是基于动态代理对象的代理逻辑实现的,那么如果在类内部调用类内部的事务方法,这个调用事务方法的过程并不是通过代理对象来调用的,而是直接通过this对象来调用方法,绕过的代理对象,肯定就是没有代理逻辑了。

    其实我们可以这样玩,内部调用也能实现开启事务,代码如下。

    /**
     * @author zhoujy
     **/
    @Component
    public class TestServiceImpl implements TestService {
        @Resource
        TestMapper testMapper;
    
        @Resource
        TestServiceImpl testServiceImpl;
    
    
        @Transactional
        public void insertTestInnerInvoke() {
            int re = testMapper.insert(new Test(10,20,30));
            if (re > 0) {
                throw new NeedToInterceptException("need intercept");
            }
            testMapper.insert(new Test(210,20,30));
        }
    
    
        public void testInnerInvoke(){
            //内部调用事务方法
            testServiceImpl.insertTestInnerInvoke();
        }
    
    }
    

    上面就是使用了代理对象进行事务调用,所以能够开启事务管理,但是实际操作中,没人会闲的蛋疼这样子玩~

    第三种场景分析

    事务方法内部捕捉了异常,没有抛出新的异常,导致事务操作不会进行回滚。

    这种的话,可能我们比较常见,问题就出在代理逻辑中,我们先看看源码里卖弄动态代理逻辑是如何为我们管理事务的。

    TransactionAspectSupport#invokeWithinTransaction

    代码如下。

    protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
          throws Throwable {
    
       // If the transaction attribute is null, the method is non-transactional.
       final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
       final PlatformTransactionManager tm = determineTransactionManager(txAttr);
       final String joinpointIdentification = methodIdentification(method, targetClass);
    
       if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
          // Standard transaction demarcation with getTransaction and commit/rollback calls.
           //开启事务
          TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
          Object retVal = null;
          try {
             // This is an around advice: Invoke the next interceptor in the chain.
             // This will normally result in a target object being invoked.
              //反射调用业务方法
             retVal = invocation.proceedWithInvocation();
          }
          catch (Throwable ex) {
             // target invocation exception
              //异常时,在catch逻辑中回滚事务
             completeTransactionAfterThrowing(txInfo, ex);
             throw ex;
          }
          finally {
             cleanupTransactionInfo(txInfo);
          }
           //提交事务
          commitTransactionAfterReturning(txInfo);
          return retVal;
       }
    
       else {
         //....................
       }
    }
    

    所以看了上面的代码就一目了然了,事务想要回滚,必须能够在这里捕捉到异常才行,如果异常中途被捕捉掉,那么事务将不会回滚。

    IT技术分享社区

    个人博客网站:https://programmerblog.xyz

    文章推荐程序员效率:画流程图常用的工具程序员效率:整理常用的在线笔记软件远程办公:常用的远程协助软件,你都知道吗?51单片机程序下载、ISP及串口基础知识硬件:断路器、接触器、继电器基础知识

    展开全文
  • @transactional注解失效

    2020-03-18 16:07:17
    1,一般在service里加@Transactional注解,不建议在接口上添加,加了此注解后此类会纳入spring事务管理中,每个业务方法执行时,都会开启一个事务,不过都是按照相同的管理机制。 2,@Transactional注解只能应用到...
  • SpringBoot@Transactional注解失效

    千次阅读 2019-05-01 17:00:02
    我们用@Transactional注解标注方法开启事务时,测试的时候事务并没有生效。 这是service层的代码,开启事务,并且插入两条数据 @Service public class GirlService { @Autowired private GirlRepository ...
  • SpringBoot @Transactional注解失效排查

    千次阅读 2020-03-15 14:12:33
    问题描述:最近发现一个加了@Transactional注解的事务没有生效 问题分析: 一、注解特性 1、service类标签(一般不建议在接口上)上添加@Transactional,可以将整个类纳入spring事务管理,在每个业务方法执行时都会...
  • 最近公司项目用@Transactional注解时失效了,研究了很久才能正常使用。而注解失效的原因就是获取spring上下文中的bean没有被增强,确切地说,bean被增强了,但是却没有使用增强的bean导致@Transactional失效。下面来...
  • 使用@Transactional注解时需要注意许多的细节,不然你会发现@Transactional总是莫名其妙的就失效了。 一、事务 事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制,主要分为编程式事务和声明...
  • @Transactional注解不生效原因及解决方案 今天再工作中遇到了一个问题:再插入数据前需要进行操作记录,因此再service层调用的保存操作记录的方法,考虑到操作记录不应被后面的操作所影响,因此将事务的传播型为设置...
  • @Transactional注解失效的原因

    万次阅读 2020-06-01 15:22:37
    最近项目上遇到@Transactional注解失效,排查问题发现是由于没有@Transactional注解的方法去调用了有@Transactional的方法,导致@Transactional注解失效。 顺带一提还有当@Transactional放在非public方法时,也会...
  • https://blog.csdn.net/qq_35688140/article/details/102466373 https://blog.csdn.net/canot/article/details/80855439?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control @Order 注解...https://blog.csdn.net/yaomingyang/article/details/86649072
  • 三,解决注解不回滚问题 1,检查方法是不是public 2,检查异常是不是unchecked异常 3,如果是checked异常也想回滚的话,注解上写明异常类型即可 @Transactional(rollbackFor=Exception.class)
  • 引言昨天公众号粉丝咨询了一个问题,说自己之前面试被问@Transactional注解哪些场景下会失效,一时语塞致使面试失败。所以今天简单的和大家分享一下@Transactional相关...
  • 先来了解一下@Transactional注解事务的特性吧,可以更好排查问题 1、service类标签(一般不建议在接口上)上添加@Transactional,可以将整个类纳入spring事务管理,在每个业务方法执行时都会开启一个事务,不过这些...
  • 作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效 @Transactional @RestController @RequestMapping public class ...
  • 一直用@Transactional实现事务管理没有问题,直到今天在一个SpringBoot多数据源前台项目发现@Transactional注解失效了 二、项目代码 第一个数据源的事务管理器配置类 : import ...
  • @TransaTranctional之前一直以为只要在想要加注解的方法上加@Transactional注解就可以开启事务了,而对于其原理是不清楚的,但是其实其中的坑有点多,除了网上各大博主总结的几种情况(详情请看@Transactional失效的6...
  • @Transactional注解相信大家并不陌生,平时开发中常用的一个注解,它能保证方法内多个数据库操作要么同时成功,要么同时失败回滚。但是使用@Transactional有许多需要注意的细节,不然你会发现你的@Transactional总是...
  • @Transactional注解失效场景

    千次阅读 2020-04-16 10:17:01
    作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效 2、@Transactional注有哪些属性? propagation属性 propagation 代表事务的...
  • @Transactional注解失效

    2020-03-29 17:33:11
    作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效 1@Transactional 2@RestController 3@RequestMapping 4public class ...
  • @Transactional注解失效原因 1、在同类中对@Transactional注解的方法进行调用,由于调用不到代理类,@Transactional注解失效
  • @Transactional注解失效场景目 录Spring事务@Transactional介绍合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、...
  • 使用@Transactional注解时需要注意许多的细节,不然你会发现@Transactional总是莫名其妙的就失效了。一、事务事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制,主要分为编程式事务和声明式...
  • 两种解决方案: 1、 调用两次mapper 采用springboot提供的@Transactional注解 使用注解时,第一次进行测试 发现empInfoMapper.delete(id)顺利执行在orgEmpMapper.deleteEmp(id)创造一个异常发现人员表中数据成功删除...
  • 使用@Transactional注解时需要注意许多的细节,不然你会发现@Transactional总是莫名其妙的就失效了。 一、事务 事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制,主要分为编程式事务和声明...
  • Spring 中事务注解@Transactional与trycatch 在项目中 @service层中 我们会经常在做一些增删改操作的方法上看到 spring 的事务注解 @transaction 已知@transaction 是让spring 帮我们实现事务的控制。 但是在项目中...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 7,238
精华内容 2,895
关键字:

transactional注解失效