Controller调用saveOrUpdate时Transaction Interceptor未触发问题求助
看起来你遇到了一个典型的Spring事务在Web环境中不生效,但JUnit测试正常的问题。我帮你梳理一下可能的原因和对应的解决办法:
最可能的原因:Spring上下文加载冲突
JUnit测试时直接加载了根上下文(application-main-context.xml),里面包含了事务配置和Service层的Bean定义,所以事务代理正常生效。但在Web环境中,如果你没有正确分离根上下文和DispatcherServlet上下文的职责,就会导致Service层的Bean被重复创建,且没有被事务代理。
解决步骤:
分离上下文扫描范围
- 根上下文(由
ContextLoaderListener加载的application-main-context.xml)负责扫描非Web层组件(Service、Dao、Repository等),同时包含事务配置:<context:component-scan base-package="com.example"> <!-- 排除Controller,交给DispatcherServlet上下文处理 --> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- 保留你的事务配置(tx:annotation-driven、transactionManager、tx:advice、aop:config) --> - DispatcherServlet的配置文件(比如
spring-mvc.xml)只负责扫描Controller层,避免重复创建Service Bean:<context:component-scan base-package="com.example"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <!-- 明确排除Service和Repository,防止重复实例化 --> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/> </context:component-scan> <!-- 这里只放MVC相关配置:视图解析器、RequestMappingHandlerMapping等 -->
- 根上下文(由
确认Web.xml中配置了ContextLoaderListener
确保根上下文被正确加载,否则事务配置和Service Bean都不会初始化:<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:application-main-context.xml</param-value> </context-param> <!-- 你的DispatcherServlet配置 --> <servlet> <servlet-name>spring-mvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring-mvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
其他排查点
验证Service是否被事务代理
在Controller中添加一行代码打印Service的类名:System.out.println("Service类名:" + entityService.getClass().getName());如果输出的是原始类名(比如
com.example.service.UserHistoryServiceImpl),说明没有被代理;正常应该是代理类名(比如com.example.service.UserHistoryServiceImpl$$EnhancerBySpringCGLIB$$xxxx或者JDK代理的类名)。检查AOP切入点表达式是否匹配
你的AOP切入点是execution(* com.example.service..*Service.*(..)),确认你的UserHistoryService是否在com.example.service包或子包下,且类名以Service结尾(比如UserHistoryServiceImpl实现UserHistoryService接口,这个是符合的,但要确保包路径正确)。避免事务配置重复
你同时使用了<tx:annotation-driven>和基于AOP的<tx:advice>,虽然两者可以共存,但建议统一配置方式:要么全用注解式事务(在Service类/方法上加@Transactional),要么全用AOP的XML配置,避免潜在冲突。
额外提示
你的OpenSessionInViewFilter配置没问题,但要确保它在web.xml的Filter顺序中是第一个,这样能保证Session在请求开始时就被创建,避免懒加载问题。
内容的提问来源于stack exchange,提问作者Horn Daneth

