Spring | 事务没有生效导致数据没有持久化的探究

今天遇到一个很诡异的问题,在使用 Hibernate 持久化数据时,数据没有写入到数据库,其他正常的查询数据都是没有问题的。

1 事务配置

spring-context.xml 中事务的配置

<!-- 配置事务管理器 -->
<bean name="transactionManager" 
        class="org.springframework.orm.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

<!-- 基于 <tx> 和 <aop> 命名空间的声明式事务管理 -->
<tx:advice id="transactionAdvice">
    <tx:attributes>
        <tx:method name="add*" rollback-for="Exception" propagation="REQUIRED"/>
        <tx:method name="save*" rollback-for="Exception" propagation="REQUIRED"/>
        <tx:method name="insert*" rollback-for="Exception" propagation="REQUIRED"/>
        <tx:method name="update*" rollback-for="Exception" propagation="REQUIRED"/>
        <tx:method name="edit*" rollback-for="Exception" propagation="REQUIRED"/>
        <tx:method name="modify*" rollback-for="Exception" propagation="REQUIRED"/>
        <tx:method name="delete*" rollback-for="Exception" propagation="REQUIRED"/>
        <tx:method name="remove*" rollback-for="Exception" propagation="REQUIRED"/>

        <tx:method name="get*" propagation="NOT_SUPPORTED" read-only="true"/>
        <tx:method name="find*" propagation="NOT_SUPPORTED" read-only="true"/>
        <tx:method name="list*" propagation="NOT_SUPPORTED" read-only="true"/>
        <tx:method name="select*" propagation="NOT_SUPPORTED" read-only="true"/>
    </tx:attributes>
</tx:advice>

<!-- 开启注解式事务的支持,即使用注解 Transactional -->
<tx:annotation-driven transaction-manager="transactionManager"/>

<!-- 配置事务切面 -->
<aop:config>
    <aop:pointcut id="transactionPointcut"
                  expression="execution(* com.fairy.springmvc.apps.*.service.*.*(..))"/>
    <aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice"/>
</aop:config>
复制代码

配置中同时使用了两种风格的事务支持,一种是切面式的一种是注解式的

经过仔细的 Debug 和排查,发现 Service 中的持久化操作没有被包裹在事务中执行。至于其他的异常和数据库的原因统统被排除掉。经过一步步的调试,发现是配置的问题(spring-servlet.xml)。

2 spring-servlet.xml 的配置

原来的配置:

<!--扫描 controller 包,并将其生命周期纳入 Spring 管理-->
<!--如果需要扫描多个位置,可以指定多路径 例如  -->
<!--<context:component-scan base-package="x.y.z.service, x.y.z.controller, x.y.z.dao" />-->
<context:component-scan base-package="com.fairy.springmvc">
    <!--只扫描 @Controller 注解的类-->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.RestController"/>
</context:component-scan>
复制代码

3 原因探究

其实核心问题还是 spring-context.xmlspring-servlet.xml 的关系没有完全理清导致出现的问题。

因为 Spring 应用中 spring-context.xmlspring-servlet.xml 对应的容器是父子容器,spring-context.xml 对应的是父容器,spring-servlet.xml对应的是子容器。

子容器 Controller 进行扫描装配 Service 时装配了 @Service 注解的 Bean(没有被事务包裹),子容器此时得到的是原样的Service(没有经过事务加强处理,故而没有事务处理能力)。

其实正常情况下 Bean 应该由父容器进行初始化以保证事务的增强处理(因为事务的配置在 spring-context.xml 中)。

关系理清之后,问题的解决方式其实很简单,核心就一条,保证 spring-servlet.xml 在扫描的时候尽量只扫描控制器,如果是单个 Servlet 内部使用的 bean,可以根据业务自行调整。

所以上文简单列举了两种修改方式。类推,不仅仅是事务包裹的 Service,其他类似的业务也是如此。

下面列举几种修改方式,配置修改成下面的情形都可正常持久化

第一种情况:排除使用 Service 注解的类

重点关注 context:exclude-filter 标签

<context:component-scan base-package="com.fairy.springmvc">
    <!--只扫描 Controller 注解的类-->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.RestController"/>
    <!--排除 Service 注解的类-->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
复制代码

第二种情况:只专注扫描 controller

<context:component-scan base-package="com.fairy.springmvc.apps.*.controller">
    <!--只扫描 Controller 注解的类-->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.RestController"/>
</context:component-scan>
复制代码

第三种情况:设置 use-default-filters="false"

注意 use-default-filters="false" 这个属性:默认为 true,会扫描包含Service, Component, Repository, Controller 注解修饰的类,配置成 false,只扫描指定的注解。

<context:component-scan base-package="com.fairy.springmvc" use-default-filters="false">
    <!--只扫描 Controller 注解的类-->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.RestController"/>
</context:component-scan>
复制代码

猜你喜欢

转载自juejin.im/post/7132713117263331365