前言
Spring在TransactionDefinition接口中规定了7种类型的事务传播行为。事务传播行为是Spring框架独有的事务增强(通知)特性,是Spring为我们提供的强大的工具箱,使用事务传播行可以为我们的开发工作提供许多便利。但是人们对他的误解也颇多,你一定也听过“service方法事务最好不要嵌套”的传言。要想正确的使用工具首先需要了解工具。本文对七种事务传播行为做详细介绍,内容主要代码示例的方式呈现。
基础概念
1.什么是事务传播行为?
事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的事务如何传播。也就是说:当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
2.Spring中7种事务传播行为
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务;如果已经存在一个事务,就加入该事务中(使用该事务)。这是最常见的选择 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,则把当前事务挂起 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作 |
PROPAGATION_SUPPORTS | 支持当前事务。如果当前没有事务,则以非事务方式执行 |
PROPAGATION_MANDATORY | 使用当前事务。如果当前没有事务,则抛出异常 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作。如果当前存在事务,则把当前事务挂起 |
PROPAGATION_NEVER | 以非事务方式执行。如果当前存在事务,则抛出异常 |
上述对七种传播行为的说明,简单易懂,但有很多细节值得我们去深究,下面我们将对required、requires_new、nested三个重点且常用的传播行为进行分析。
代码验证
文中代码以传统三层结构中两层呈现,即Service和Dao层,由Spring负责依赖注入和注解式事务管理,DAO层由Mybatis实现,你也可以使用任何喜欢的方式,例如,Hibernate,JPA,JDBCTemplate等。数据库使用的是MySQL数据库,你也可以使用任何支持事务的数据库,并不会影响验证结果。
首先我们在数据库中创建两张表:
user1
CREATE TABLE `user1` (
`id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL DEFAULT '',
PRIMARY KEY(`id`)
)
ENGINE = InnoDB;
user2
CREATE TABLE `user2` (
`id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL DEFAULT '',
PRIMARY KEY(`id`)
)
ENGINE = InnoDB;
然后编写相应的Bean和DAO层代码:
User1
package com.bruce.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User1 {
private Integer id;
private String name;
}
User2
package com.bruce.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User2 {
private Integer id;
private String name;
}
User1Mapper
package com.bruce.mapper;
import com.bruce.pojo.User1;
public interface User1Mapper {
int insert(User1 u);
}
User1Mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bruce.mapper.User1Mapper">
<insert id="insert">
insert into tb_user1 values(null,#{name})
</insert>
</mapper>
User2Mapper
package com.bruce.mapper;
import com.bruce.pojo.User1;
import com.bruce.pojo.User2;
public interface User2Mapper {
int insert(User2 u);
}
User2Mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bruce.mapper.User2Mapper">
<insert id="insert">
insert into tb_user2 values(null,#{name})
</insert>
</mapper>
最后也是具体验证的代码由service层实现,下面我们分情况列举。
1.PROPAGATION_REQUIRED
如果当前没有事务,就新建一个事务;如果已经存在一个事务,就加入该事务中(使用该事务)。
我们为User1Service和User2Service相应方法加上PROPAGATION_REQUIRED属性。
1.1 场景1
外围方法没有开启事务,外围方法有异常。
User1ServiceImpl
package com.bruce.service.impl;
import com.bruce.mapper.User1Mapper;
import com.bruce.pojo.User1;
import com.bruce.service.User1Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class User1ServiceImpl implements User1Service {
@Autowired
User1Mapper user1Mapper;
@Override
@Transactional(propagation = Propagation.REQUIRED)
public int insert(User1 u) {
return user1Mapper.insert(u);
}
}
User2ServiceImpl
package com.bruce.service.impl;
import com.bruce.mapper.User2Mapper;
import com.bruce.pojo.User2;
import com.bruce.service.User2Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class User2ServiceImpl implements User2Service {
@Autowired
User2Mapper user2Mapper;
@Override
@Transactional(propagation = Propagation.REQUIRED)
public int insert(User2 u) {
return user2Mapper.insert(u);
}
}
UserServiceImpl:外围方法
package com.bruce.service.impl;
import com.bruce.pojo.User1;
import com.bruce.pojo.User2;
import com.bruce.service.User1Service;
import com.bruce.service.User2Service;
import com.bruce.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
User1Service user1Service;
@Autowired
User2Service user2Service;
@Override
public int addUser1AndUser2(User1 u1, User2 u2) {
int count1 = user1Service.insert(u1);
System.out.println("count1="+count1);
System.out.println(100/0);//异常
int count2 = user2Service.insert(u2);
System.out.println("count2="+count2);
return count1+count2;
}
}
TestUser
package com.bruce.test;
import com.bruce.pojo.User1;
import com.bruce.pojo.User2;
import com.bruce.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestUser {
@Autowired
UserService userService;
@Test
public void testUU(){
int count = userService.addUser1AndUser2(new User1(null, "小舞"), new User2(null, "唐三"));
System.out.println(count);
}
}
结果:数据库中,唐三和小舞都插入
结果分析:当外围方法未开启事务时,外围方法中的异常不会影响内部方法,内部方法都在自己的事务中独立运行。
1.2 场景2
外围方法没有开启事务,内部方法有异常。
User1ServiceImpl
package com.bruce.service.impl;
import com.bruce.mapper.User1Mapper;
import com.bruce.pojo.User1;
import com.bruce.service.User1Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class User1ServiceImpl implements User1Service {
@Autowired
User1Mapper user1Mapper;
@Override
@Transactional(propagation = Propagation.REQUIRED)
public int insert(User1 u) {
return user1Mapper.insert(u);
}
}
User2ServiceImpl
package com.bruce.service.impl;
import com.bruce.mapper.User2Mapper;
import com.bruce.pojo.User2;
import com.bruce.service.User2Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class User2ServiceImpl implements User2Service {
@Autowired
User2Mapper user2Mapper;
@Override
@Transactional(propagation = Propagation.REQUIRED)
public int insert(User2 u) {
System.out.println(100/0);//异常
return user2Mapper.insert(u);
}
}
UserServiceImpl:外围方法
package com.bruce.service.impl;
import com.bruce.pojo.User1;
import com.bruce.pojo.User2;
import com.bruce.service.User1Service;
import com.bruce.service.User2Service;
import com.bruce.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
User1Service user1Service;
@Autowired
User2Service user2Service;
@Override
public int addUser1AndUser2(User1 u1, User2 u2) {
int count1 = user1Service.insert(u1);
System.out.println("count1="+count1);
int count2 = user2Service.insert(u2);
System.out.println("count2="+count2);
return count1+count2;
}
}
TestUser
package com.bruce.test;
import com.bruce.pojo.User1;
import com.bruce.pojo.User2;
import com.bruce.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestUser {
@Autowired
UserService userService;
@Test
public void testUU(){
int count = userService.addUser1AndUser2(new User1(null, "小舞"), new User2(null, "唐三"));
System.out.println(count);
}
}
结果:数据库中,唐三没有插入,小舞插入
结果分析:当外围方法未开启事务时,内部方法中的异常只会回滚到自己方法上,其他方法不受影响。
结论1
通过以上两个场景我们证明了在外围方法未开启事务的情况下,Propagation.REQUIRED
修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
1.3 场景3
外围方法开启事务,外围方法有异常。(常用)
User1ServiceImpl
package com.bruce.service.impl;
import com.bruce.mapper.User1Mapper;
import com.bruce.pojo.User1;
import com.bruce.service.User1Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class User1ServiceImpl implements User1Service {
@Autowired
User1Mapper user1Mapper;
@Override
@Transactional(propagation = Propagation.REQUIRED)
public int insert(User1 u) {
return user1Mapper.insert(u);
}
}
User2ServiceImpl
package com.bruce.service.impl;
import com.bruce.mapper.User2Mapper;
import com.bruce.pojo.User2;
import com.bruce.service.User2Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class User2ServiceImpl implements User2Service {
@Autowired
User2Mapper user2Mapper;
@Override
@Transactional(propagation = Propagation.REQUIRED)
public int insert(User2 u) {
return user2Mapper.insert(u);
}
}
UserServiceImpl:外围方法
package com.bruce.service.impl;
import com.bruce.pojo.User1;
import com.bruce.pojo.User2;
import com.bruce.service.User1Service;
import com.bruce.service.User2Service;
import com.bruce.service.UserService;
import