spring中的事务之通俗易懂版
一、认识事务
什么是事务?
事务是我们进行数据库操作时最小的单位,在逻辑上的原则是要么都成功要么都失败。
需求: 用户转账操作
业务逻辑的实现: (1) 需要先从
A
账户中减去100
元,然后从B
账户中增加100
元,
此时就需要 事务的支持,而这个转账操作需要2条sql语句的支持, 这两条sql语句就是一个事务。
二、事务的特性
事务有4大特性:
- 隔离性: 隔离性指的是一个事务的执行不能被其他事务干扰。多个事务同时执行时,它们之间应该相互隔离,一个事务的操作不应该对其他事务产生影响。隔离性问题可能导致诸如脏读(Dirty Read)、不可重复读(Non-Repeatable Read)、幻读(Phantom Read)等问题。
- 一致性: 一致性要求事务将数据库从一个一致性状态转移到另一个一致性状态。事务执行前后,数据库应该保持一致性。如果事务执行过程中发生了错误,数据库状态应该回滚到事务开始之前的状态,以保持数据的一致性。
- 原子性: 这指的是事务应该被视为一个不可分割的单元。如果事务包含多个操作,要么全部执行成功,要么全部失败回滚。原子性问题出现时,可能会导致部分操作成功而部分失败,这可能会使数据库处于不一致的状态。
- 持久性: 持久性要求一旦事务被提交,其所做的修改应该永久保存在数据库中,即使系统崩溃或发生故障也不会丢失。持久性问题可能会导致提交的事务数据丢失,从而违反了事务的持久性特性。
三、事务的隔离级别
事务的隔离级别:
- 读未提交: 最低的隔离级别,允许一个事务读取另一个事务未提交的数据。这可能导致脏读(Dirty Read)问题,即一个事务读取到了其他事务尚未提交的数据。解决了问题: 并发性高,但数据的一致性和可靠性差。
- 读已提交: 允许一个事务只能读取已经提交的数据。其他事务的数据对当前事务不可见,直到其他事务提交。解决了脏读问题,但可能会导致不可重复读(Non-Repeatable Read)问题,即同一事务内多次读取同一数据,得到的结果不同。
- 可重复读: 保证一个事务在执行期间多次读取同一数据得到的结果是一致的,不受其他事务影响。即使其他事务对数据进行修改,当前事务内多次读取同一数据时,也只会看到事务开始时的一致性数据。这可以解决不可重复读问题,但可能导致幻读(Phantom Read)问题,即在一个事务中执行同样的查询,结果集合不一致。
- 串行化: 最高的隔离级别,确保事务之间完全隔离。每个事务按照顺序依次执行,就好像事务是串行执行的一样。这种级别可以解决幻读问题,但并发性最低,可能影响性能。
四、事务处理时会遇到的问题
- 脏读: 读未提交级别可能导致,解决方法是提高隔离级别。
- 不可重复读: 读提交和可重复读级别可能导致,在可重复读级别中,可能出现对同一数据多次读取结果不一致的情况。
- 幻读: 可能在可重复读和读提交级别出现,当一个事务在读取一个范围内的记录时,另一个事务插入了新的记录,导致第一个事务之前读取的记录集合和之后读取的记录集合不一致。
五、Spring中如何配置事务
第一步: 引入依赖
<dependences>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--spring 的 核心包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--lombok 依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<!--面向切面编程-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<!--Spring的声明式事务管理-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<!--Spring测试包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!--Mysql 驱动连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!-- 阿里巴巴的 druid 连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.22</version>
</dependency>
</dependences>
第二步: 去配置文件中,开启事务的管理
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启包扫描-->
<context:component-scan base-package="com.atguigu"></context:component-scan>
<!--加载 jdbc 配置文件-->
<context:property-placeholder location="classpath*:jdbc.properties"></context:property-placeholder>
<!--注册数据源连接-->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!--这个驱动可写可不写-->
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--注册 JDBC模板-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<!--开启声明式事务-->
<tx:annotation-driven proxy-target-class="false" transaction-manager="transactionManager"></tx:annotation-driven>
<!--注册 事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入dataSources-->
<property name="dataSource" ref="druidDataSource"/>
</bean>
</beans>
第三步: 编写配置文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url= 自己的数据库地址
jdbc.username= 自己的用户名
jdbc.password= 自己的数据库密码
第四步: 创建业务层代码
public interface UserService {
void addUser();
}
package com.atguigu.service.impl;
import com.atguigu.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
// 开启事务注解
@Transactional
public void addUser(){
String sql = "INSERT INTO user values(null,?,?,?)";
jdbcTemplate.update(sql,"鸭小架",28,"女");
deleteUser();
}
@Transactional
public void deleteUser(){
String sql = "DELETE FROM user WHERE id = ?";
jdbcTemplate.update(sql,45);
// int a = 2/0;
}
}
第五步: 测试
// 自动加载配置文件
@ContextConfiguration("classpath:/spring.xml")
// 运行单元测试
@RunWith(SpringJUnit4ClassRunner.class)
public class TransactionalTest {
@Autowired
private UserService userService;
@Test
public void testTransactional(){
userService.addUser();
}
}
六、事务的传播性
这个注解上加
@Transactional(propagation = Propagation.REQUIRED)
@Transactional(propagation = Propagation.REQUIRES_NEW)
■ 传播属性:
● REQUIRED :如果有事务在运行,当前的方法就在这个事务内运行,否则启动一个新的事务,并在自己的事务内运行。
● REQUIRES_NEW: 当前的方法必须启动新事务,并在它自己的事务内运行,如果有事务正在运行,应该将它挂起