一、什么是事务?
我们在开发企业应用时,对于业务人员的一个操作实际是对数据读写的多步操作的结合。由于数据操作在顺序执行的过程中,任何一步操作都有可能发生异常,异常会导致后续操作无法完成,此时由于业务逻辑并未正确的完成,之前成功操作数据的并不可靠,需要在这种情况下进行回退。
事务的作用就是为了保证用户的每一个操作都是可靠的,事务中的每一步操作都必须成功执行,只要有发生异常就回退到事务开始未进行操作的状态。
事务管理是Spring框架中最为常用的功能之一,我们在使用Spring Boot开发应用时,大部分情况下也都需要使用事务。
事务我们都知道分为编程式事务与声明式事务,我们之前都知道在Spring中的区别,但在SpringBoot项目中是啥区别呢?我们通过两个例子来分析一下。
二、编程式事务
编程式事务是代码级别的,类似于JDBC的事务管理,Spring管理使用TransactionTemplate事务。
1、配置事务模板
package com.riemann.springbootdemo.transaction;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import javax.sql.DataSource;
class MyTransactionTemplate {
/**
* 数据源加入事务管理
* @param masterDataSource
* @return
*/
@Bean(name = "transactionManager")
@Primary
public DataSourceTransactionManager masterDataSourceTransactionManager(@Qualifier("masterDataSource") DataSource masterDataSource) {
final DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(masterDataSource);
return dataSourceTransactionManager;
}
@Bean(name = "transactionTemplate")
@Primary
public TransactionTemplate masterTransactionTemplate (@Qualifier("transactionManager") DataSourceTransactionManager transactionManager) {
final TransactionTemplate transactionTemplate = new TransactionTemplate();
transactionTemplate.setTransactionManager(transactionManager);
transactionTemplate.setIsolationLevelName("ISOLATION_DEFAULT");
transactionTemplate.setPropagationBehaviorName("PROPAGATION_REQUIRED");
return transactionTemplate;
}
}
2、测试类
package com.riemann.springbootdemo.transaction;
import com.riemann.springbootdemo.model.UserDomain;
import com.riemann.springbootdemo.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
public class MyTransactionTemplateTest {
private static final Logger LOGGER = LoggerFactory.getLogger(MyTransactionTemplateTest.class);
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private UserService userService;
@GetMapping(value = "/addUser",produces = "application/json;charset=UTF-8")
public void addUser() {
UserDomain user1 = new UserDomain();
user1.setUserId(2);
user1.setUserName("andy");
user1.setPassword("andy");
user1.setPhone("15875571889");
UserDomain user2 = new UserDomain();
user2.setUserId(1);
user2.setUserName("edgar");
user2.setPassword("root");
user2.setPhone("13607961855");
List userDomainList = new ArrayList<>();
userDomainList.add(user1);
userDomainList.add(user2);
TransactionCallback transactionCallback = new TransactionCallback() {
@Nullable
@Override
public Object doInTransaction(TransactionStatus transactionStatus) {
try {
for (UserDomain user : userDomainList) {
int count = userService.addUser(user);
if (count == 1) {
LOGGER.info("userId 为:" + user.getUserId() + " 添加成功");
} else {
LOGGER.info("userId 为:" + user.getUserId() + " 添加失败");
}
}
} catch (Exception e) {
LOGGER.error("出现异常", e);
// 设置事务回滚
transactionStatus.setRollbackOnly();
}
return null;
}
};
transactionTemplate.execute(transactionCallback);
}
}
3、事先创建好表
事先插入一条 userId 为1的数据,并且 userId 为主键。
在这里插入图片描述
在这里插入图片描述
相信小伙伴们一看这报错就明白了什么意思了。
我们数据库有了 userId 为1的数据,插入两条数据,第一条插入为2的数据显示添加成功,第二条为1的数据跟数据库已有主键为1的userId冲突了,导致异常。然后我们再去数据库看,表里还是刚开始的那一条数据。
在这里插入图片描述
由结果得出结论,操作两条数据,只要有一条操作失败,那么编程式事务就会回滚。
三、声明式事务
管理建立在AOP基础上,本质是对方法前后进行拦截,所以声明式事务是方法级别的,使用的时候只需要在方法前面加上@Transactional注解。
声明式事务的代码如下:
package com.riemann.springbootdemo.transaction;
import com.riemann.springbootdemo.model.UserDomain;
import com.riemann.springbootdemo.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.ArrayList;
import java.util.List;
public class MyTransactionTemplateTest2 {
private static final Logger LOGGER = LoggerFactory.getLogger(MyTransactionTemplateTest.class);
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private UserService userService;
@GetMapping(value = "/addUser",produces = "application/json;charset=UTF-8")
@Transactional(rollbackFor = Exception.class)
public void addUser() {
UserDomain user1 = new UserDomain();
user1.setUserId(2);
user1.setUserName("andy");
user1.setPassword("andy");
user1.setPhone("15875571889");
UserDomain user2 = new UserDomain();
user2.setUserId(1);
user2.setUserName("edgar");
user2.setPassword("root");
user2.setPhone("13607961855");
List userDomainList = new ArrayList<>();
userDomainList.add(user1);
userDomainList.add(user2);
for (UserDomain user : userDomainList) {
int count = userService.addUser(user);
if (count == 1) {
LOGGER.info("userId 为:" + user.getUserId() + " 添加成功");
} else {
LOGGER.info("userId 为:" + user.getUserId() + " 添加失败");
}
}
}
}
测试效果跟编程式事务的结果是一样的。
四、小结
1、编程式事务需要你在代码中直接加入处理事务的逻辑,可能需要在代码中显式调用beginTransaction()、commit()、rollback()等事务管理相关的方法,如在执行a方法时候需要事务处理,你需要在a方法开始时候开启事务,处理完后。在方法结束时候,关闭事务。
2、声明式的事务的做法是在a方法外围添加注解或者直接在配置文件中定义,a方法需要事务处理,在spring中会通过配置文件在a方法前后拦截,并添加事务。其实使用的AOP面向切面的思想。
3、编程式事务侵入性比较强,但处理粒度更细。