Spring事务在SpringMVC中失效(已解决)

背景

在运行B站挨踢黑马的整合SSM的项目时,其事务配置如下(未改动):
ApplicationContext.xml:使用声明式事务管理。

    <!--配置Spring框架声明式事务管理-->
    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!--配置事务通知--><!--声明式事务管理-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="*" isolation="DEFAULT"/>
        </tx:attributes>
    </tx:advice>

    <!--配置AOP增强-->
    <aop:config>
        <aop:advisor advice-ref="txAdvice" pointcut="execution(* cn.itcast.service.impl.*ServiceImpl.*(..))"/>
    </aop:config>

springmvc.xml配置如下:

    <!--开启注解扫描,只扫描Controller注解-->
    <context:component-scan base-package="cn.itcast">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>

    <!--配置的视图解析器对象-->
    <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <!--过滤静态资源-->
<!--    <mvc:resources location="/css/" mapping="/css/**" />-->
<!--    <mvc:resources location="/images/" mapping="/images/**" />-->
<!--    <mvc:resources location="/js/" mapping="/js/**" />-->

    <!--开启SpringMVC注解的支持-->
    <mvc:annotation-driven/>

为了测试事务,我在Service里加了一个很好理解的小改动(运行时异常):

@Service("accountService")
public class AccountServiceImpl implements AccountService{
    
    

    @Autowired
    private AccountDao accountDao;

    @Override
    public List<Account> findAll() {
    
    
        System.out.println("业务层:查询所有账户...");
//        return null;// 单独测试spring
        return accountDao.findAll();
    }

    @Override
    public void saveAccount(Account account) {
    
    
        System.out.println("业务层:保存帐户...");
        accountDao.saveAccount(account);
        System.out.println("wait...");  //小改动
        int i = 10/0;   
    }
}

然后运行程序,页面报了被零除的错误。然后查看数据库,发现数据直接加入数据库了!
what?

于是debug了一下,发现执行被零除之后并没有回滚,而是被抛给了前端控制器DispatcherServlet…

于是我在测试类里调用service方法进行junit测试,debug发现执行被零除之后会跳转到AopUtils.class,然后执行TranasctionAspectSupport.class,事务回滚了
在这里插入图片描述
在这里插入图片描述
emmmm那看来Spring配置的事务是没问题的,问题在于使用了SpringMVC之后,Spring事务失效了

然后百度了一下浅谈spring事务失效之谜,发现

spring事务失效七种大的原因

  • 如使用mysql且引擎是MyISAM,则事务会不起作用,原因是MyISAM不支持事务,可以改成InnoDB。
  • 如果使用了spring+mvc,则context:component-scan重复扫描问题可能会引起事务失败。
  • @Transactional
    注解开启配置,必须放到listener里加载,如果放到DispatcherServlet的配置里,事务也是不起作用的。
  • @Transactional 注解只能应用到 public 可见度的方法上。 如果你在 protectedprivate 或者 的方法上使用 @Transactional 注解,它也不会报错,事务也会失效。
  • Spring团队建议在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(但是放在接口上面也是可以实现事务滴,我还没搞清)。
  • 一个事务方法调用同一个类里面的另一个事务方法,被调用的方法的事务失效。(使用了原生对象,使得代理失效)
  • 将异常代码使用throw new Exception(“xxxxxxxxxxxx”)进行捕获

看完大神的总结,因为我没有使用注解配置,所以忽略后面的几条。而且我的是运行时异常,跟配置Exception没啥关系,正常throw了就是要回滚的。所以主要关注了

context:component-scan重复扫描问题

原因

Spring的父子容器(放到DispatcherServlet配置事务失效)
父容器:Root WebAplicationContext
子容器:Servlet WebAplicationContext
在这里插入图片描述

这张图也解释了为什么我们在SpringMVC中配置了事务扫描却不生效------》一般做事务处理都是在Service层,我们把事务扫描配置在SpringMVC中,我们的父容器是无法得到的,这样就导致了事务失效。。。。。

如图,可以看到子容器有什么需要的是可以向父容器要的,但是父容器是没法向子容器要的。所以当我们是使用Spring+SpringMVC进行web应用开发的时候,Spring负责扫描DAO和Service层,SpringMVC负责扫描controller就行了。

解决

SpringMVC的DispatcherServlet导致配置事务失效,那就不要让它扫描Service包了:
加了个exclude-filter。

    <!--开启注解扫描,只扫描Controller注解-->
    <context:component-scan base-package="cn.itcast">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    </context:component-scan>

重新启动,在页面上运行,事务回滚,成功。


那为什么必须写个exclude-filter才能排除这个包呢?
因为另一个大神在这里说了:

use-default-filters是一个不容忽视的属性,默认值为true,表示默认会对@Component、@Controller、@Service、@Reposity标注的bean进行扫描
context:component-scan先根据context:exclude-filter列出需要排除的黑名单,再通过context:include-filter列出需要包含的白名单。

下面的反例中,不但会扫描@Controller的bean,还会扫描@Component@Service@Repository的bean

<context:component-scan base-package="com.xxx">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> 
</context:component-scan>

看来是use-default-filters的原因啊…所以要么把它设成false,要么使用exclude-filter排除扫描的包,这样才能正确配置事务啊!

    <context:component-scan base-package="cn.itcast" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>

事务的问题解决了,那如果不想让用户看见满屏幕的错误呢?

在这里插入图片描述
这就需要使用@ControllerAdvice了(不过已经是另一个问题了)

在这里插入图片描述

おすすめ

転載: blog.csdn.net/qq_36937684/article/details/116034263