在Spring Boot
中,配置一套使用MySQL
的环境非常简单,而且当我们使用了spring-boot-starter-jdbc
的时候,Spring Boot
会自动注入DataSourceTransactionManager
启用帮助配置数据库事务相关的类。
Maven配置
在pom.xml
中添加
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-tomcat
${spring-boot.version}
org.springframework.boot
spring-boot-starter-undertow
${spring-boot.version}
org.springframework.boot
spring-boot-starter-jdbc
${spring-boot.version}
mysql
mysql-connector-java
org.springframework.boot
spring-boot-starter-test
test
${spring-boot.version}
然后构建一个主类Application.java
:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder().sources(Application.class).run(args);
}
}
application.properties
中添加mysql
相关的配置
spring.datasource.url=jdbc:mysql://localhost:3306/spring_demo
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
这个时候,Spring Boot
已经默认添加好了事务相关的配置,@Transactional
注解标记的方法或类就会被加上事务。
事务名词
在解Spring
中事务的概念主要是4个隔离级别和7个传播行为。
隔离级别
隔离级别
是指若干个并发的事务之间的隔离程度。
我们开发时候主要相关的场景包括:
- 脏读取:指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
- 重复读:是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。在第一个事务中的两 次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
- 幻读:是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。
所以在Spring
中提供了这四种隔离级别:
public enum Isolation {
// 底层数据库默认的隔离等级
DEFAULT(-1),
// 一个事务可以读取另一个事务修改但还没有提交的数据。
READ_UNCOMMITTED(1),
// 该隔离级别表示一个事务只能读取另一个事务已经提交的数据。
READ_COMMITTED(2),
// 该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。
REPEATABLE_READ(4),
// 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
SERIALIZABLE(8);
private final int value;
private Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
传播行为
传播行为指的是如果在开始当前事务之前,一个事务上下文(Context)已经存在,此时有若干选项可以指定一个事务性方法的执行行为。
这里打一个比方:
@Transactional(propagation = Propagation[A])
class ServiceA{
public void insertOne(){
// code statement
}
}
@Transactional(propagation = Propagation[B])
class ServiceB{
public void insertMany(list){
// 循环调用
for(int i;i
在ServiceB
的insertMany()
方法中已经存在Propagation[B]
事务的上下文,ServiceA
的insertOne()
方法在insertMany()
中被执行,那么它需要如何处理自己的方法(也就是insertOne()
方法)中的事务问题。
在Spring中,提供了以下几种事务传播行为:
public enum Propagation {
// 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
// @Transactional 注解默认采用这个方案
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
// 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
// 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
// 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
// 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
// 以非事务方式运行,如果当前存在事务,则抛出异常。
NEVER(TransactionDefinition.PROPAGATION_NEVER),
// 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED
// 并非所有的TransactionManager都能支持这个传播级别
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
Propagation(int value) { this.value = value; }
public int value() { return this.value; }
}
一般而言,被调用方法上定义的传播行为是高于调用方法的传播行为的,也就是上面例子中,insertOne()
在这个例子中的传播行为高于insertMany()
构建测试方法
通过构建一个简单的应用,可以比较清晰地了解以上所说的是4个隔离级别和7个传播行为。
MySQL
中定义一张用户表:
CREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(11) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
java中对应构建一个实体类(UserEntity
):
public class UserEntity {
private Integer id;
private String name;
private Integer age;
public UserEntity(String name, Integer age) {
this.name = name;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
定义一个UserService
,然后在insertMany()
和insertOne()
方法上分别定义事务的级别:
@Service
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 添加多条数据
*
* @param entities
*/
@Transactional(propagation = Propagation.REQUIRED)
public void insertMany(List entities) {
String sql = "insert into user (name, age) values(?, ?)";
final List
测试代码:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class UserServiceTest extends ApplicationTest {
@Autowired
private UserService userService;
@Test
public void insertTest() {
List userEntityList = new ArrayList<>(10);
userEntityList.add(new UserEntity("AAA",20));
userEntityList.add(new UserEntity("BBB",21));
userEntityList.add(new UserEntity("CCC",22));
userEntityList.add(new UserEntity("DDD",23));
userEntityList.add(new UserEntity("EEE",24));
userEntityList.add(new UserEntity("FFF",25));
userService.insertManyOneByOne(userEntityList);
}
}
通过更个UserService
中方法的事务级别,不断地换跑测试方法,就能测试Spring
中的事务配置了。