什么是事务的传播机制
指的是一个事务方法被另一个事务方法调用的时候,这个事务方法应该如何进行
举例
@Autowried
public Person person;
@Service
public class Person{
@Transactional
public void laoda(){
System.out.println("老大的方法");
person.xiaodi();
}
@Transactional
public void xiaodi(){
System.out.println("这是小弟");
}
}
【情况一】:当laoda方法调用xiaodi方法的时候,如果laoda方法出现异常,xiaodi方法会怎么做,是否要回滚
【情况二】:当laoda方法调用xiaodi方法的时候,如果xiaodi方法出现异常,laoda方法要怎么做,是否要回滚
我们在方法里面的参数要配置传播特性,使用它来控制是否要回滚等别的行为规范
spring的事务的传播机制
死活不要事务的
PROPAGATION_NOT_SUPPORTED:
总是非事务地执行,并挂起任何存在的事务。
PROPAGATION_NEVER:
总是非事务地执行,如果存在一个活动事务,则抛出异常。
可有可无的
PROPAGATION_SUPPORTS:
如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。
必须要有事务的
PROPAGATION_REQUIRES_NEW:
肯定会创建子事务,父子互不影响;
PROPAGATION_NESTED:(嵌套)
肯定会创建子事务,父影响子,子不影响父;
PROPAGATION_REQUIRED:
没有事务就新建一个事务,有就加入当前事务。
PROPAGATION_MANDATORY:(强制)
没有事务就抛出异常,有事务就加入当前事务。
如果想实现:子影响父,但父不影响子的效果,我觉得可以把传播级别设置 PROPAGATION_REQUEIRES_NEW,然后在父中捕获子的异常,然后再抛出异常来实现。
事务的代码说明
下面我们将使用代码来说明一下spring的事务的各种情况
先写基本代码
controller层
@RestController
@RequestMapping("/money")
public class MoneyVoController {
@Autowired
private UpdateByMoneyEmployServiveImpl updateByMoneyEmployServive;
@PostMapping("/change")
public void change(@RequestBody MoneyVo moneyVo){
updateByMoneyEmployServive.selectByEmploy(moneyVo);
}
}
Mapper层
@Mapper
public interface MoneyMapper {
void UpdateByMoneyEmploy(Money money);
int selectByMoneyEmploy(String employFrom);
}
Service层
@Service
@Slf4j
public class UpdateByMoneyEmployServiveImpl {
@Autowired
private MoneyMapper moneyMapper;
@Autowired
private UpdateByMoneyEmployServiveImpl updateByMoneyEmployServive;
@Transactional
public synchronized void selectByEmploy(MoneyVo moneyVo) {
int EmployeeFromMoeny = moneyMapper.selectByMoneyEmploy(moneyVo.getEmployeeFrom());
int EmployeeToMoeny= moneyMapper.selectByMoneyEmploy(moneyVo.getEmployeeTo());
if(moneyVo.getEmployeeMoney()< EmployeeFromMoeny){
int leave= EmployeeFromMoeny-moneyVo.getEmployeeMoney();
String emloy = moneyVo.getEmployeeFrom();
Money money = new Money();
money.setEmployMoney(leave);
money.setEmploy(emloy);
updateByMoneyEmployServive.updateByMoney(money);
log.info("本次扣减的:{}"+money);
int add=EmployeeToMoeny+moneyVo.getEmployeeMoney();
String employeeTo= moneyVo.getEmployeeTo();
Money money2 = new Money();
money2.setEmploy(employeeTo);
money2.setEmployMoney(add);
updateByMoneyEmployServive.updateByMoney(money2);
log.info("本次增加金钱的是:{}"+money2);
return;
} throw new RuntimeException("钱不够");
}
@Transactional
public void updateByMoney(Money money){
moneyMapper.UpdateByMoneyEmploy(money);
}
}
实体类和Vo
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Money {
public String employ;
public Integer employMoney;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MoneyVo {
private String employeeFrom;
private String employeeTo;
private Integer employeeMoney;
}
单元测试
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringBootApplicationMybatis.class)
public class TestMoneyVoController {
@Autowired
private MoneyVoController moneyVoController;
@Test
public void Testchange(){
MoneyVo moneyVo = new MoneyVo("小红","小蓝",100);
moneyVoController.change(moneyVo);
}
}
不要事务的
改变server的方法为
@Transactional(propagation = Propagation.NEVER)
public void updateByMoney(Money money){
moneyMapper.UpdateByMoneyEmploy(money);
}
报错
Existing transaction found for transaction marked with propagation 'never'
改service的方法为
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void updateByMoney(Money money){
moneyMapper.UpdateByMoneyEmploy(money);
}
我这没有得到我要的那个报错,报错是:update的时候,where后面没有走索引,会导致锁表,因为我这是把update语句放在后面执行了,没有一开始的时候就执行update语句,一开始先执行update语句,再执行一次update语句,会因为update导致的锁表问题,导致超时问题,不能事务执行。
别的不写了,以后如果记得再说
什么是事务的隔离级别
1.ISOLATION_DEFAULT:
这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.另外四个与JDBC的隔离级别相对应。
2.ISOLATION_READ_UNCOMMITTED:
这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
3.ISOLATION_READ_COMMITTED:
保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
4.ISOLATION_REPEATABLE_READ:
这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
5.ISOLATION_SERIALIZABLE:
这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。
其中的一些概念的说明:
脏读: 指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据, 那么另外一 个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
不可重复读: 指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。 那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
幻觉读: 指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及 到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
如何处理事务的回滚
要处理事务的回滚机制,首先我们需要保证事务是生效的
。
自动回滚
只有在开启事务的方法中出现异常,才会自动回滚,需要在service的public方法上面加上@Transactional(rollbackFor = Exception.class),一旦程序出现异常,事务会自动回滚,注意在方法中不能catch以后不throw异常出去,这样会导致异常被吞,不会抛出异常
手动回滚
如果在try-catch语句中对可能出现的异常进行了处理,没有再手动throw异常,spring认为该方法成功执行,不会进行回滚;
需要在catch以后再throw出去异常
//第一种方式:注解事务回滚
@Override
@Transactional(rollbackFor = Exception.class)//第一种方式
public Boolean insert(String param) throws Exception {
try {
int i = testDao.insert(test);
if(i!=1){
throw new exception("新增失败");
}
return true;
} catch (Exception e) {
log.error("新增异常",e);
return false;
}
}
//第二种方式:手动事务回滚
@Override
@Transactional
public Boolean insert2(String param) {
try {
int i = testDao.insert(test);
if(i!=1){
throw new exception("新增失败");
}
return true;
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();//第二种方式
log.error("新增异常",e);
return false;
}
}