本文在spring + spring mvc + mybatis中使用
第一步配置xml:注意xml最前面tx名称空间一定要配置
在这里,由于org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致,所以MyBatis自动参与到spring事务管理中,无需额外配置。
第二步:
在接口、接口方法、类以及类方法( public方法)上直接添加@Transactional即可。
注意:不要导错包,使用org.springframework.transaction.annotation.Transactional包,而不是javax.transaction.Transactional
当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性。在方法上使用该注解会覆盖类上的定义。
注意:虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常
Spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
默认配置下,Spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException及其子类以及Errors,抛出checked异常则不会导致事务回滚。
可以明确的配置在抛出哪些异常时回滚事务,包括checked异常。例如:使用@Transactional(rollbackFor=IOException.class)
则抛出IOException异常时也可以回滚事务。也可以明确定义哪些异常抛出时不回滚事务,例如@Transactional(noRollbackFor = IOException.class)
还可以编程性的通过TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()方法来手动地指示一个事务必须回滚。
Controller层:
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(value = "jsp1",method = RequestMethod.GET)
public String getJsp( ){
return "test";
}
@RequestMapping(value = "insertUser", method = RequestMethod.POST)
public void insertUser(@RequestBody User user)throws Exception {
userService.insertUser(user);
}
}
Service层:
public interface UserService {
void insertUser(User user)throws Exception;
}
@Transactional
@Service
public class UserServiceImpl implements UserService {
private static final Logger logger = Logger.getLogger(UserServiceImpl.class);
@Autowired
private UserServiceTestTransactionImpl userServiceTestTransaction;
@Autowired
private UserMapper userMapper;
public void insertUser(User user) throws Exception{
// try {
userMapper.inserUser(new User(11,"格格A"));
userServiceTestTransaction.insertUser(user);
// } catch (Exception e) {
// e.printStackTrace();
// }
}
}
@Transactional(RollbackFor = IOException.class)
@Service
public class UserServiceTestTransactionImpl {
@Autowired
private UserMapper userMapper;
public void insertUser(User user) throws Exception {
userMapper.inserUser(user);
throw new IOException();
//throw new RuntimeException();
//throw new Error();
}
}
dao层不再展示。
注意:@Transactional注解默认的事务传播行为是REQUIRED。可以通过propagation 属性指定具体的传播行为。例如:@Transactional(rollbackFor = IOException.class ,propagation = Propagation.SUPPORTS)
这里简单介绍一下:PROPAGATION_REQUIRED。解释:在ServiceA.methodA中调用了ServiceB.methodB时,若ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED。如果ServiceA.methodA已经起了事务(有@Transactional注解),这时ServiceB.methodB就加入ServiceA.methodA的事务中,就不再起新的事务。而如果ServiceA.methodA运行的时候发现自己没有在事务中,ServiceA.methodA会为自己创建一个事务。这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被提交,但是ServiceA.methodA在接下来fail要回滚时,ServiceB.methodB也要回滚。
本文验证了一下:UserServiceImpl(A)作为ServiceA,UserServiceTestTransactionImpl(B)作为ServiceB。
当UserServiceTestTransactionImpl(B)中的public方法抛出RuntimeException异常时。
若在UserServiceImpl(A)的方法中Try catch住异常
若没有在UserServiceImpl(A)的方法中Try catch住异常
可以发现,只要我们在Service层每一个类上都加上@Transtractional注解,即:每一个类均开启事务,不论是否捕获B抛出的异常,事务都会回滚。
当B由于自己没有事务而加入A的事务时(此时AB都在A的事务中),若A中没有捕获B抛出的异常,则AB都会回滚(这里证明了事务的传播规则REQUIRED的特性);此时若A中捕获了B抛出的异常,则事务失效,AB都不会回滚。
当A没有事务,而B存在事务时,由于A无法为自己开启事务,所以A会因为没有事务而不会回滚。B只存在自己的事务中,无论在A中是否被捕获异常,都会回滚,因为B在自己的事务中并没有捕获异常,而是抛出异常。
因为,Spring的声明式事务管理是基于AOP实现的。
具体原理如下:在应用系统调用声明@Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据@Transactional 的属性配置信息,这个代理对象决定该声明@Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截,在 TransactionInterceptor 拦截时,会在在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务。Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 两种。
而Spring aop 异常捕获原理是被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到方法的异常,才能进行回滚。捕获异常后,aop就发现不了异常,进而不能回滚。
方案一:保证所有涉及到的service类均加上事务
方案二:保证A有事务,在A中不要捕获异常,异常继续向上抛出
方案三:保证A有事务,并在其捕获异常后的cathch语句后面加上throw new RuntimeException()语句
方案四:保证A有事务,并在其捕获异常后的cathch语句后面加上TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()语句