目录
一:Spring中事务的实现
二:Spring事务的操作
2.1:Spring编程式实现事务
2.1.1:User表
2.1.2:Mapper接口声明的方法
2.1.3:Service层
2.1.4:Controller层
2.2:Spring声明式事务(自动)
2.2.1:实现案例
2.2.2: @Transactional注意事项
三:事务的隔离级别
3.1:原子性
3.2:一致性
3.3:持久性
3.4:隔离性
3.4.1:脏读
3.4.2:不可重复读
3.4.3:幻读
3.4.4:Spring事务隔离级别
四:Spring事务传播机制
4.1:什么是事务传播机制?
4.2:事务传播机制有哪些?
编辑
Spring中的事务操作分为两类:
1:编程式事务(手动写代码操作事务)
2:声明式事务(利用注解自动开启和提交事务)
在spring操作中,也分为三个步骤 ,开启事务,回滚事务,提交事务。
下面,我们通过案例来进行操作。
#创建用户表
create table user(
userId int primary key auto_increment,
#约定用户名是独一无二的
username varchar(64) unique ,
#密码
password varchar(64)
);
在mapper中声明的方法,我们用的是添加一个用户信息的操作。返回的数字代表影响的行数。
@Mapper
public interface UserMapper {
//返回int的数据
int addUserInfo(UserInfo userInfo);
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public Integer addUserInfo(UserInfo userInfo){
return userMapper.addUserInfo(userInfo);
}
}
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
//添加这个注解,将事务管理器引入进来
@Autowired
private DataSourceTransactionManager transactionManager;
//设置事务的属性
@Autowired
private TransactionDefinition transactionDefinition;
//返回受影响的行数
@RequestMapping("/add")
public Integer add(UserInfo userInfo){
//第一步:非空校验
if(userInfo==null||!StringUtils.hasLength(userInfo.getUsername())
||!StringUtils.hasLength(userInfo.getPassword())){
return 0;
}
//1.开启事务,得到并开启事务
TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
Integer integer = userService.addUserInfo(userInfo);
System.out.println(integer);
//2:回滚事务
transactionManager.rollback(transaction);
return integer;
}
}
此时,由于我们对添加用户信息这一条进行了回滚,所以,新的用户信息添加不到数据库当中,但是返回的数值仍旧是1.
但当我们将回滚事务转变成提交事务,我们看数据库里面能不能得到添加的用户信息。
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
//添加这个注解,将事务管理器引入进来
@Autowired
private DataSourceTransactionManager transactionManager;
//设置事务的属性
@Autowired
private TransactionDefinition transactionDefinition;
//返回受影响的行数
@RequestMapping("/add")
public Integer add(UserInfo userInfo){
//第一步:非空校验
if(userInfo==null||!StringUtils.hasLength(userInfo.getUsername())
||!StringUtils.hasLength(userInfo.getPassword())){
return 0;
}
//1.开启事务,得到并开启事务
TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
Integer integer = userService.addUserInfo(userInfo);
System.out.println(integer);
//2:回滚事务
transactionManager.commit(transaction);
return integer;
}
}
运行结果:
在controller层添加@Transactional这个注解,就可以开启事务了。
特点:
1:@Transaction这个注解可以加在类和方法上,加在类上代表这个类里面的public方法,都具有事务,加在方法上,代表此处的方法具有事务。
2:@Transactional,在方法执行前开启事务,在方法执行后提交事务,如若在方法执行过程中,出现异常,则回滚事务。
@Transactional
@RequestMapping("/insert")
public Integer insert(UserInfo userInfo){
if(userInfo==null||!StringUtils.hasLength(userInfo.getUsername())
||!StringUtils.hasLength(userInfo.getPassword())){
return 0;
}
Integer integer = userService.addUserInfo(userInfo);
return integer;
}
上述代码中,在方法运行过程中,并没有出现异常,那么,就会自动提交事务。倘若,我们给这个方法中添加算数异常,看看会发生什么?
@Transactional @RequestMapping("/insert") public Integer insert(UserInfo userInfo){ if(userInfo==null||!StringUtils.hasLength(userInfo.getUsername()) ||!StringUtils.hasLength(userInfo.getPassword())){ return 0; } Integer integer = userService.addUserInfo(userInfo); System.out.println(integer); //搞一个算数异常 int num=10/0; return integer; }
此时就会报出算数异常,在方法中出现算数异常,那么,此时,就会出现事务回滚,即不添加新的用户信息。
当程序中有try-catch语句时,即使程序中有异常,事务也不会回滚。
@Transactional
@RequestMapping("/insert")
public Integer insert(UserInfo userInfo){
if(userInfo==null||!StringUtils.hasLength(userInfo.getUsername())
||!StringUtils.hasLength(userInfo.getPassword())){
return 0;
}
Integer integer = userService.addUserInfo(userInfo);
System.out.println(integer);
//搞一个算数异常
try {
int num=10/0;
} catch (Exception e) {
System.out.println(e.getMessage());
}
return integer;
}
当在程序中,出现算数异常时,我们添加用户名wangwu和密码12345。
控制台代码:
数据库表:
从上述结果来看,发现添加上了,此时,事务并没有进行回滚。
如何解决?
解决方案一:将异常继续抛出,交给spring
解决方案二:
@Transactional @RequestMapping("/insert") public Integer insert(UserInfo userInfo){ if(userInfo==null||!StringUtils.hasLength(userInfo.getUsername()) ||!StringUtils.hasLength(userInfo.getPassword())){ return 0; } Integer integer = userService.addUserInfo(userInfo); System.out.println(integer); //搞一个算数异常 try { int num=10/0; } catch (Exception e) { System.out.println(e.getMessage()); //使用代码,手动回滚事务 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } return integer; }
多个操作被打包成一个整体,要么全部都能够正确执行,要么就一个都不执行。
事务之前和事务之后,数据都能对上。
事务执行的各种操作,都是持久化存储在硬盘上的。(一旦事务执行成功,这里所有的操作,都是写在硬盘上的)
并发执行事务的时候,隔离性,会在执行效率和数据可靠之间做出权衡。(“隔离"描述的是同时执行的事务之间,相互的影响。隔离性越高,并发性越低,数据越可靠)
如果这两个事务,修改的是同一张表,这个时候存在麻烦。
由于上面的执行顺序可能存在差异,可能导致执行结果不同。
脏数据,是临时的,并非是最终的数据。
如何解决脏读?
在同一个读取事务中,可能会涉及到读操作,多个读操作,读到的数据不一样。
如何解决不可重复读?
一个事务在多次读的过程中,虽然读到的数据值是一样的,但是结果集不一样。
eg:第一次的记录是10条,第二次读的是11条记录。前十次记录是一样的,但是多出来一条。
如何解决幻读?
Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。
Spring事务传播机制定义了多个包含事务的方法,相互调用时,事务是如何传递的。
当三个方法用同一个模式时,任何一个方法出问题了,都会进行事务的回滚操作。
三个方法用三种不同的模式,各个之间的方式不影响。