还是用一个老生常谈的例子来说
张三给李四转账 :
张三账户-100;
李四账户+100;
如果在张三账户-100的时候发生异常,此时李四账户+100的操作还没有完成,张三账户的钱不就平白无故的消失了,而如果使用事务就可以解决这个问题,让这一组操作要么一起成功,要么一起失败。
--开启事务start transaction;-- 业务执行-- 提交事务commit;-- 回滚事务rollback;
-- 创建数据库
drop database if exists mycnblog;
create database mycnblog default character set utf8mb4;
-- 使⽤数据数据
use mycnblog;
-- 创建表[⽤户表]
drop table if exists userinfo;
create table userinfo(
id int primary key auto_increment,
username varchar(100) not null,
password varchar(32) not null,
photo varchar(500) default '',
createtime datetime default now(),
updatetime datetime default now(),
`state` int default 1
) default charset 'utf8mb4';
-- 添加⼀个⽤户信息
insert into `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`,
`createtime`, `updatetime`, `state`) values
(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1)
;
-- 添加⼀个用户日志表
DROP TABLE IF EXISTS userlog;
CREATE TABLE userlog(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(100) NOT NULL,
createtime DATETIME DEFAULT NOW( ),
updatetime DATETIME DEFAULT NOW()
) DEFAULT CHARSET 'utf8mb4';
@Data
public class User {
private Integer id;
private String username;
private String password;
private String photo;
private Date createtime;
private Date updatetime;
}
@Data
public class UserLog {
private Integer id;
private String username;
private Date cratetime;
private Date updatetime;
public UserLog() {
}
public UserLog(String username) {
this.username = username;
}
}
@Mapper
public interface UserMapper {
@Select("select * from userinfo")
List selectAll();
@Insert("insert into userinfo(username,password) values (#{username},#{password}))")
Integer insert(User user);
}
@Mapper
public interface UserLogMapper {
@Insert("insert into userlog(username) values (#{username})")
Integer insert(UserLog userLog);
}
@Service
public class UserService {
@Autowired
UserMapper userMapper;
public List selectAll() {
return userMapper.selectAll();
}
public Integer addUser(User user) {
return userMapper.insert(user);
}
}
@Service
public class UserLogService {
@Autowired
UserLogMapper userLogMapper;
public Integer addUser(UserLog userLog) {
return userLogMapper.insert(userLog);
}
}
@RestController
@Slf4j
@RequestMapping("/trans")
public class TransactionalController {
@Autowired
UserService userService;
@RequestMapping("/add")
public Integer addUser() {
User user = new User();
user.setName("zxn123");
user.setPwd("123456");
return userService.addUser(user);
}
}
加入事务之后进行rollback
@RestController
@Slf4j
@RequestMapping("/trans")
public class TransactionalController {
@Autowired
private UserService userService;
//数据库事务管理器
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
//事务属性
@Autowired
private TransactionDefinition transactionDefinition;
@RequestMapping("/add")
public Integer addUser(String username, String password) {
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
User user = new User();
user.setName(username);
user.setPwd(password);
Integer insert = userService.addUser(user);
log.info("影响了" + insert + "行");
dataSourceTransactionManager.rollback(transaction);
return insert;
}
}
仍然执行上面的操作.数据库没有发生改变,说明数据进行了回滚
加入事务之后进行rollback日志
加入@Transactional注解
@RestController
@Slf4j
@RequestMapping("/trans2")
public class TransactionalController2 {
@Autowired
private UserService userService;
@Transactional
@RequestMapping("/add")
public Integer addUser(String username, String password) {
User user = new User();
user.setName(username);
user.setPwd(password);
Integer insert = userService.addUser(user);
log.info("影响了" + insert + "行");
return insert;
}
}
可以看到没有提交操作.
参数 | 作用 |
value | 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器. |
transactionManager | 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器. |
propagation | 事务的传播行为,默认值Propagation.REQUIRED |
isolation | 事务的隔离级别,默认值为lsolation.DEFAULT |
timeout | 事务的超时时间,默认值为-1.如果超过该时间限制但事务还没有完成,则自动回滚事务. |
readOnly |
指定事务是否为只读事务,默认值为false;为了忽略那些不需要事务的方法,比如读取数据,可以设置read-only为 true. |
rollbackFor | 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型. |
rollbackForClassName | 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型. |
noRollbackFor | 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型. |
noRollbackForClassName |
抛出指定的异常类型,不回滚事务,也可以指定多个异常类型. |
演示noRollbackFor
@Transactional(noRollbackFor = ArithmeticException.class)
@RequestMapping("/add")
public Integer addUser(String username, String password) {
User user = new User();
user.setName(username);
user.setPwd(password);
Integer insert = userService.addUser(user);
log.info("影响了" + insert + "行");
int a = 10 / 0;
return insert;
}
此时可以看到是有ArithmeticException错误的,我们添加数据
@Transactional默认只在遇到运行时异常和Error时才会回滚,非运行时异常不回滚
即Exception的子类中,除了RuntimeException及其子类
@Transactional(rollbackFor = Exception.class)
@Transactional 在异常被捕获的情况下,不会进行事务自动回滚
@RequestMapping("/add2")
@Transactional
public Integer add2(String username, String password) {
User user = new User();
user.setName(username);
user.setPwd(password);
// 插⼊数据库
int result = userService.addUser(user);
try {
// 执⾏了异常代码(0不能做除数)
int i = 10 / 0;
} catch (Exception e) {
log.info(e.getMessage());
}
return result;
}
原子性(Atomicity) 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency) 事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
隔离性(Isolation) 事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。(具体见事务的隔离级别)
持久性(Durability) 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交(READ UNCOMMITTED) | √ | √ | √ |
读已提交(READ COMMITTED) | x | √ | √ |
可重复读(REPEATABLE READ) | x | x | √ |
串行化(SERIALIZABLE) | x | x | x |
@Transactional(isolation = Isolation.READ_COMMITTED)
以上事务传播机制,可分为三类
没有错误的模拟
@Transactional(propagation = Propagation.REQUIRED)
public Integer addUser(User user) {
return userMapper.insert(user);
}
@Transactional(propagation = Propagation.REQUIRED)
public Integer addUserLog(UserLog userLog) {
return userLogMapper.insert(userLog);
}
@RestController
@Slf4j
@RequestMapping("/trans3")
public class TransactionalController3 {
@Autowired
private UserService userService;
@Autowired
private UserLogService userLogService;
@Transactional
@RequestMapping("/addUser")
public boolean addUser(String username, String password) {
User user = new User();
user.setName(username);
user.setPwd(password);
//插入用户表
userService.addUser(user);
UserLog userLog = new UserLog(username);
//插入日志表
userLogService.addUserLog(userLog);
return true;
}
}
错误发生时的模拟
@Transactional(propagation = Propagation.REQUIRED)
public Integer addUserLog(UserLog userLog) {
int i = 10 / 0;
return userLogMapper.insert(userLog);
}
修改隔离级别
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Integer addUser(User user) {
return userMapper.insert(user);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Integer addUserLog(UserLog userLog) {
int i = 10 / 0;
return userLogMapper.insert(userLog);
}
@Transactional(propagation = Propagation.NEVER)
public Integer addUser(User user) {
return userMapper.insert(user);
}
@Transactional(propagation = Propagation.NEVER)
public Integer addUserLog(UserLog userLog) {
int i = 10 / 0;
return userLogMapper.insert(userLog);
}
执行,会出现以下报错日志
@Transactional(propagation = Propagation.NESTED)
public Integer addUser(User user) {
return userMapper.insert(user);
}
@Transactional(propagation = Propagation.NESTED)
public Integer addUserLog(UserLog userLog) {
int i = 10 / 0;
return userLogMapper.insert(userLog);
}
@Transactional(propagation = Propagation.NESTED)
public Integer addUserLog(UserLog userLog) {
userLogMapper.insert(userLog);
try {
int i = 10 / 0;
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return 1;
}
可以看到userinfo表示添加成功,而userlog表示添加失败的了.
这个只是回滚了部分事务,如果是required的话,会回滚所有的事务.
嵌套事务(NESTED)和加入事务(REQUIRED )的区别: