背景
在运行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 可见度的方法上。 如果你在protected
、private
或者的方法上使用
@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
了(不过已经是另一个问题了)