hi,大家好,今天为大家带来Spring事务相关介绍
🧊1.为什么要学事务
情景假设:疯驴子给王麻子转账100万,点击转账以后,界面突然黑屏了,那么疯驴子不知道王麻子收到钱还是没收到,所以转账和收款全部都一起成功或者一起失败,这就是事务的作用,保证了数据的完整性和一致性
🧊2.事务的定义
事务是指一系列操作或事件的集合,通常用于执行一项特定的任务或完成一项特定的工作。在计算机领域,事务通常是指一组数据库操作,这些操作要么全部执行成功,要么全部失败回滚,以保证数据的完整性和一致性。
🧊3.Spring事务的实现
Spring’事务的实现分为两种
1.Spring编程事务 2.Spring声明式任务
👀3.1Mysql事务的使用
事务在MySQL有三个操作:开启事务、提交事务、回滚事务
-- 开启事务
start transaction;
-- 业务执⾏
---
-- 提交事务
commit;
-- 回滚事务
rollback;
👀3.2Spring编程事务(手动)
@Service
public class MyService {@Autowiredprivate MyDao myDao;@Transactionalpublic void doTransactionalOperation(){// some code here}
}@Repository
public class MyDao {@Autowiredprivate JdbcTemplate jdbcTemplate;public void saveData(String data) {String sql = "INSERT INTO my_table (data) VALUES (?)";jdbcTemplate.update(sql, data);}
}
在这个示例中,MyService类是一个Spring的@Service组件,该组件中包含了一个需要进行事务处理的方法doTransactionalOperation()。@Transactional注解指示Spring在该方法执行期间实现事务。
MyDao类是一个Spring的@Repository组件,包含了与数据库相关的操作。在saveData()方法中调用了JdbcTemplate的update()方法将数据插入到数据库中。
通过Spring的事务管理器,当MyService的doTransactionalOperation()方法被调用时,Spring将采取一系列措施,包括开启事务、执行方法、提交或回滚事务。
👀3.3Spring声明式事务(自动)
声明式事务很简单,在方法上或者类上添加@Transactional注解就可以实现
我们来用代码举个例子,
package com.example.demo.model;import lombok.Data;import java.time.LocalDateTime;@Data
public class Userinfo {private int id;private String username;private String password;private String photo;private LocalDateTime createtime;private LocalDateTime updatetime;private int state;
}
package com.example.demo.mapper;import com.example.demo.model.Userinfo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper {@Insert("insert into userinfo(username,password) values(#{username},#{password})")int add(Userinfo userinfo);
}
package com.example.demo.service;import com.example.demo.mapper.UserMapper;
import com.example.demo.model.Userinfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public int add(Userinfo userinfo){return userMapper.add(userinfo);}
}
package com.example.demo.controller;import com.example.demo.model.Userinfo;
import com.example.demo.service.UserService;
import org.apache.catalina.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.pattern.PathPattern;//测试类@RestController@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/add")@Transactionalpublic int add(){Userinfo userinfo=new Userinfo();userinfo.setUsername("疯驴子");userinfo.setPassword("123");int result= userService.add(userinfo);System.out.println("受影响的行数是"+result);int num=10/0;return 0;}
}
运行
不出意外的报错了,然后看看后台
数据库结果:
这个既完成了插入的测试,又没有插入到数据库里面,这就是@Transactional注解的强大力量:当修饰的方法或者类发生异常的时候自动回滚事务
👀3.4@Transactional注解作用范围
修饰⽅法时:只能应⽤到 public ⽅法上,否则不⽣效。
修饰类时:表明该注解对该类中所有的 public ⽅法都⽣效。
👀3.5@Transactional注解参数
参数 | 作用 |
---|---|
isolation | 设置事务的隔离级别,默认为DEFAULT。 |
propagation | 设置事务的传播行为,默认为REQUIRED |
timeout | 设置事务的超时时间,默认为-1,表示不超时 |
readOnly | 设置事务的只读属性,默认为false |
rollbackFor | 设置需要回滚的异常类型,可以设置多个 |
noRollbackFor | 设置不需要回滚的异常类型,可以设置多个 |
value | 作用同于别名name,用于指定事务的名称 |
name | 作用同于别名value,用于指定事务的名称 |
👀3.6@Transactional注意事项
@Transactional在异常被捕获的情况下不会进行事务自动回滚,因为捕获异常,动态代理就感知不到异常了,认为没有异常,自然就不会回滚
解决办法:
1.把异常继续抛出,代理对象就可以感受到异常
从而自动回滚事务
2.⼿动回滚事务,在⽅法中使用TransactionAspectSupport.currentTransactionStatus() 可以得到当前的事务,然后设置回滚⽅法 setRollbackOnly 就可以实现回滚
try{int num=10/0;}catch (Exception e){//throw e;TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}
👀3.7@Transactional工作原理
@Transactional 是基于 AOP 实现的,AOP ⼜是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用CGLIB动态代理
@Transactional 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。如果中途遇到异常,则回滚事务
如果该注解被应用于类级别,则该类中的所有方法都将受到该注解的影响,即所有方法都将在同一个事务中执行。
总之,@Transctional注解的工作原理是通过在方法执行前创建事务,并在方法执行后根据执行结果提交或回滚事务来确保数据的一致性。但需要注意的是,该注解只能用于Spring管理的Bean对象中,否则无效。
🧊4.事务隔离级别
👀4.1事务特性回顾
事务有四大特性:原子性,持久性,一致性,隔离性
原子性(Atomicity)一组操作作为一个整体,要么全部执行成功,要么全部失败回滚。
一致性(Consistency):事务执行前后,数据必须保持一致状态。即,如果事务执行成功,则数据状态必须符合约束条件,否则事务将回滚。
隔离性(Isolation):事务的执行必须与其他事务隔离,以防止并发事务的相互影响。通常有四种隔离级别:读未提交、读已提交、可重复读和串行化。
持久性(Durability):一旦事务执行成功,它对数据库的影响应该是永久性的,即使系统崩溃也不应该丢失数据。
只有隔离性(隔离级别)是可以设置的,为了保证多个并发事务的执行安全,确保数据库的数据完整性、一致性和并发性.
👀4.2Mysql事务隔离级别
MySQL事务隔离级别有4种
1.READ UNCOMMITTED:读未提交
最低级别,允许一个事务读取另一个事务还未提交的数据。
2. READ COMMITTED:读已提交
允许一个事务读取另一个已经提交的事务的数据。
3. REPEATABLE READ:可重复读
可重复读,是 MySQL 的默认事务隔离级别,它能确保同⼀事务多次查询的结果⼀致
4.SERIALIZABLE:序列化
最高级别,所有事务串行执行,保证数据的完整性和一致性,但并发性能较差。
🐶!!!重点来了
1.什么是幻读?
2.RR解决幻读问题了吗?
3.咋样解决的?
什么是幻读
幻读是指在并发访问数据库时,一个事务多次查询同一数据集合时,其他事务插入或删除了数据,导致该事务前后两次查询结果不一致的现象
幻读不是重复读的现象,是查询到了不同的数据
RR解决幻读问题了吗
RR解决了部分幻读问题
咋样解决的?
🤔采用RR+MVCC(多版本并发控制)来解决幻读问题
幻读分为当前读和快照读
当前读:MVCC解决不了,进行当前读时,可能会读取到正在被修改的数据,这样可能会导致读取到不一致的数据.
解决办法:采用锁来解决,当前事务执行完释放锁,其余的事务再执行
快照读:MVCC可以解决,MVCC会为每个事务创建一个版本号,所有对数据的修改都会创建新的版本号。可以在读取数据时返回之前的版本,这个版本是在读取之前提交的,以保证数据的一致性.
🐷彻底解决幻读:
1.使用串行化隔离级别
2.RR+加锁,RR级别下加锁,开启事务就加锁,其他事务在操作此表的相关数据时,就只能等待锁释放(事务一提交或回滚锁自动释放)
当前读:数据库中一种读取数据的方式,它读取最新提交的数据
快照读:快照读是指在读取数据时,读取的是在事务开始前提交的快照数据,而不是当前的数据。如果数据在事务中发生了更新操作,快照读会读取到旧的数据,这会导致数据不一致性问题。
👀4.3Spring设置事务隔离级别
👀4.4Spring事务隔离级别
Spring 事务隔离级别有 5 种
Isolation.DEFAULT(默认):以连接的数据库的事务隔离级别为主
Isolation.READ_UNCOMMITTED(读未提交):最低的隔离级别,事务可以看到其他未提交的事务所做的修改
Isolation.READ_COMMITTED(读已提交):读取已提交的数据,一个事务只能看到其他事务已经提交的数据;
Isolation.REPEATABLE_READ(可重复读):同一个事务中多次读取同一个数据总是相同,不会受其他事务的影响;
Isolation.SERIALIZABLE(串行化):最高的隔离级别,所有事务都像串行执行一样,避免任何并发问题。
今天的讲解就到这里 ,我们下期再见了,886~