Spring5 框架 笔记(四)事务操作 详解 手把手教学

五、事务操作

1. 事务概念

1、什么是事务

(1)事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败

(2)典型场景:A 转账给 B 100 元

要么转账成功:A 少 100 元,B 多 100 元

要么转账失败:A 不少, B 不多

不能 A 少 100 元而 B 没得到 100元

2、事务四个特性(ACID)

(1)原子性(Atomicity)

​ 一个事务要么都成功,要么全部失败回滚,不能只执行其中的一部分操作

(2)一致性(Consistenc)

​ 事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行前后,数据库必须处于一致性状态

(3)隔离性(Isolation)

​ 事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能相互干扰

(4)持久性(Durabili)

​ 持久性是指一个事务一旦被提交,它对数据库中数据的改变是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响

2. 搭建事务操作环境

截屏2021-02-26 19.37.14

1、创建数据库表,添加记录

截屏2021-02-26 19.45.44

2、创建 service,搭建 dao,完成对象创建和注入关系

@Repository
public class UserDaoImpl implements UserDao{
    @Autowired
    private JdbcTemplate jdbcTemplate;
}

@Service
public class UserService {
    @Autowired
    private UserDao userDao;
}

3、在 dao 创建两个方法:多钱和少钱的方法,在 service 创建方法(转账方法)

@Repository
public class UserDaoImpl implements UserDao{
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void reduceMoney() {
        String sql = "UPDATE t_bank SET money = money - ? WHERE user_name = ?";
        jdbcTemplate.update(sql, 100, "A");
    }

    @Override
    public void addMoney() {
        String sql = "UPDATE t_bank SET money = money + ? WHERE user_name = ?";
        jdbcTemplate.update(sql, 100, "B");
    }
}
@Service
public class UserService {
    @Autowired
    private UserDao userDao;

    public void accountMoney() {
        userDao.reduceMoney();
        userDao.addMoney();
    }
}

4、上面代码,如果正常执行是没有问题的,但是如果代码执行过程中出现异常,有问题

@Service
public class UserService {
    @Autowired
    private UserDao userDao;

    public void accountMoney() {
        userDao.reduceMoney();
        
        // 模拟异常
        int a = 1 / 0;
        
        userDao.addMoney();
    }
}

运行上面代码

截屏2021-02-26 19.59.13

可以知道 reduceMoney 方法执行后,出现错误,则 A 少了 100 元,而 B 并没有得到 100 元

在现实生活中,这种结果是万万不能接受的

截屏2021-02-26 20.00.07

比起 A 少钱而 B 没有得到而言,如果中途出错,A 既不少钱而 B 不多钱的结果是可以接受的

所以我们需要事务来进行数据库操作,要么成功,要么都失败

事务过程

public void accountMoney() {
    try {
        // 第一步 开启事务
        
        // 第二步 进行业务操作
        userDao.reduceMoney();
        
        int a = 1/ 0;
        
        userDao.addMoney();
    	
        // 第三步 没有发生异常,提交事务
    } catch(Exception e) {
        // 第四步 出现异常,事务回滚
    }
}

3.事务操作

3.1 Spring 事务管理介绍

1、事务添加到 JavaEE 三层结构里面 Service 层(业务逻辑层)

2、在 Spring 进行事务管理操作

(1)编程式事务管理,在代码中硬编码。(不推荐使用)

(2)声明式事务管理,在配置文件中配置(推荐使用)

3、声明式事务管理

(1)基于注解方式

(2)基于 xml 配置文件方式

4、在 Spring 进行声明式事务管理,底层使用 AOP 原理

5、Spring 事务管理 API

​ 提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类

截屏2021-02-26 20.27.31

3.2 注解声明式事务管理

1、在 Spring 配置文件配置事务管理器

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

2、在 Spring 配置文件,开启事务注解

(1)在 Spring 配置文件引入名称空间 tx

截屏2021-02-26 20.31.36

(2) 开启事务注解

<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

3、在 service 类上面(或者 service 类方法上面)添加事务注解

(1)@Transactional注解可以加到类上面也可以加到方法上面

(2)如果把这个注解添加类上面,这个类里面所有的方法都添加事务

(3)如果把这个注解添加到方法上面,只为这个方法添加事务

截屏2021-02-26 20.42.05
3.3 声明式事务管理参数配置

1、在 service 类上面添加注解 @Transactional,在这个注解里面可以配置事务相关参数

截屏2021-02-26 20.59.55

2、propagation:事务传播行为

(1)多事务方法进行直接进行调用,这个过程事务是如何进行管理的

截屏2021-02-26 21.12.57

(2)Spring 定义 7 种传播行为

支持当前事务的情况:

  • TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)

不支持当前事务的情况:

  • TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

其他情况:

  • TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED
截屏2021-02-26 21.20.46

3、ioslation:事务隔离级别

(1)对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题

(2)有三个读问题:脏读、不可重复读、虚(幻)读

  • 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
  • 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
  • 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。

(3)数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题

(4)一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。

隔离级别描述
READ UNCOMMITTED
(读未提交数据)
允许事务读取未被其他事务提交的变更,脏读、不可重复读、幻读的问题均会出现
READ COMMITTED
(读已提交数据)
只允许事务读取已经被其他事务提交的变更,可以避免脏读,但不可重复读、幻读的问题仍会出现
REPEATABLE READ
(可重复读)
确保事务可以多次从一个字段中读取到相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,但幻读仍存在
SERIALIZABLE
(串行化)
确保事务可以从一个表中读取中读取相同行,在这个事务持续期间,禁止其他事务对该表进行插入、更新和删除操作,所有并发问题都可以避免,但性能十分低下

截屏2021-02-26 21.46.56

4、timeout:超时时间

(1)事务需要在一定时间内进行提交,如果不提交进行回滚

(2)默认值是 -1,设置时间以秒单位进行计算

5、readOnly:是否只读

(1)读:查询操作,写:添加修改删除操作

(2)readOnly 默认值 false,表示可以查询,可以添加修改删除操作

(3)设置 readOnly 值是 true,只能进行查询操作

6、rollbackFor:回滚

设置出现哪些异常进行事务回滚

7、noRollbackFor:不回滚

设置出现哪些异常不进行回滚

3.4 xml 声明事务管理

1、在 Spring 配置文件中进行配置

第一步 配置事务管理器

第二步 配置通知

第三步 配置切入点和切面

<!-- 1 创建事务管理器  -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--  注入数据源  -->
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 2 配置通知  -->
<tx:advice id="txadvice">
    <!--  配置事务参数  -->
    <tx:attributes>
        <!--  指定那种规则的方法上面添加事务  -->
        <tx:method name="accountMoney" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<!-- 3 配置切入点和切面  -->
<aop:config>
    <!--  配置切入点  -->
    <aop:pointcut id="pt" expression="execution(* com.spring5.demo.service.UserService.*(..))"/>
    <!--  配置切面 -->
    <aop:advisor advice-ref="txadvice" pointcut-ref="pt"></aop:advisor>
</aop:config>
3.5 完全注解声明式事务管理

1、创建配置类,使用配置类替代 xml 配置文件

@Configuration // 配置类
@ComponentScan(basePackages = "com.spring5.demo") // 组件扫描 
@EnableTransactionManagement // 开启事务
public class TxConfig {
    // 创建数据库连接池
    @Bean
    public DruidDataSource getDruidDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=utf8&?rewriteBatchedStatements=true");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }
    // 创建 JdbcTemplate 对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
    // 创建事务管理器
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值