• Spring学习笔记13 Spring对事务的支持


    Spring学习笔记12 面向切面编程AOP-CSDN博客

    什么是事务:在一个业务流程当中,通常需要多条DML(insert delete update)语句共同联合才能完成,这多条DML语句必须同时成功,或者同时失败,这样才能保证数据的安全.

    多条DML要么同时成功,要么同时失败,叫做事务(Transaction)

    事务四个处理过程:

    1.开启事务(start transaction)

    2.执行核心业务代码

    3.提交事务(如果核心业务处理过程中没有出现异常)(commit transaction)

    4.回滚事务(如果核心业务处理过程中出现异常)(rollabck transaction)

    事务的四个特性:

    A.原子性:事务是最小的工作单元,不可再分

    C.一致性:事务要求要么同时成功,要么同时失败.事务前和事务后的总量不变.

    I.隔离性:事务和事务之间因为有隔离,才可以保证互不干扰

    D.持久性:持久性是事务结束的标志.

    引入事务场景:

    以银行账户转账为例学习事务.两个账户act-01和act-02.

    act-01向act-02转账10000.

    一个账户减10000,一个账户加10000,必须同时成功,或者同时失败

    连接数据库的技术采用Spring框架的JdbcTemplate

    新建maven项目或者模块

    依赖

    
        
        
            org.springframework
            spring-context
            6.0.10
        
        
        
            org.springframework
            spring-jdbc
            6.0.10
        
        
        
            mysql
            mysql-connector-java
            8.0.30
        
        
        
            com.alibaba
            druid
            1.2.13
        
        
        
            jakarta.annotation
            jakarta.annotation-api
            2.1.1
        
        
        
            junit
            junit
            4.13.2
            test
        
    

    准备数据库表

    项目结构

    实体类

    1. package com.example.pojo;
    2. import java.util.Objects;
    3. /**
    4. * @author hrui
    5. * @date 2023/9/26 15:02
    6. */
    7. public class Account {
    8. private Integer id;
    9. private String actno;
    10. private Double balance;
    11. public Account() {
    12. }
    13. public Account(Integer id, String actno, Double balance) {
    14. this.id = id;
    15. this.actno = actno;
    16. this.balance = balance;
    17. }
    18. @Override
    19. public String toString() {
    20. return "Account{" +
    21. "id=" + id +
    22. ", actno='" + actno + '\'' +
    23. ", balance=" + balance +
    24. '}';
    25. }
    26. @Override
    27. public boolean equals(Object o) {
    28. if (this == o) return true;
    29. if (o == null || getClass() != o.getClass()) return false;
    30. Account account = (Account) o;
    31. return Objects.equals(id, account.id) && Objects.equals(actno, account.actno) && Objects.equals(balance, account.balance);
    32. }
    33. @Override
    34. public int hashCode() {
    35. return Objects.hash(id, actno, balance);
    36. }
    37. public Integer getId() {
    38. return id;
    39. }
    40. public void setId(Integer id) {
    41. this.id = id;
    42. }
    43. public String getActno() {
    44. return actno;
    45. }
    46. public void setActno(String actno) {
    47. this.actno = actno;
    48. }
    49. public Double getBalance() {
    50. return balance;
    51. }
    52. public void setBalance(Double balance) {
    53. this.balance = balance;
    54. }
    55. }

    持久层

    1. package com.example.dao;
    2. import com.example.pojo.Account;
    3. /**
    4. * 专门负责账户信息的CRUD操作
    5. * DAO中只执行SQL语句,没有任何业务逻辑.
    6. * 也就是说DAO不和业务挂钩
    7. * @author hrui
    8. * @date 2023/9/26 15:00
    9. */
    10. public interface AccountDao {
    11. Account selectByActNo(Integer id);
    12. int updateAct(Account account);
    13. }

    持久层实现类

    1. package com.example.dao.impl;
    2. import com.example.dao.AccountDao;
    3. import com.example.pojo.Account;
    4. import jakarta.annotation.Resource;
    5. import org.springframework.jdbc.core.BeanPropertyRowMapper;
    6. import org.springframework.jdbc.core.JdbcTemplate;
    7. import org.springframework.stereotype.Repository;
    8. /**
    9. * @author hrui
    10. * @date 2023/9/26 15:04
    11. */
    12. @Repository
    13. public class AccountDaoImpl implements AccountDao {
    14. @Resource(name="jdbcTemplate")
    15. private JdbcTemplate jdbcTemplate;
    16. @Override
    17. public Account selectByActNo(Integer id) {
    18. String sql="select id,actno,balance from t_act where id=?";
    19. Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), id);
    20. return account;
    21. }
    22. @Override
    23. public int updateAct(Account account) {
    24. String sql="update t_act set balance=? where id=?";
    25. int count = jdbcTemplate.update(sql, account.getBalance(),account.getId());
    26. return count;
    27. }
    28. }

    业务层接口

    1. package com.example.service;
    2. /**
    3. * @author hrui
    4. * @date 2023/9/26 15:55
    5. */
    6. public interface AccountService {
    7. void transfer(Integer fid,Integer tid,double balance);
    8. }

    业务层实现类

    1. package com.example.service.impl;
    2. import com.example.dao.AccountDao;
    3. import com.example.pojo.Account;
    4. import com.example.service.AccountService;
    5. import jakarta.annotation.Resource;
    6. import org.springframework.stereotype.Service;
    7. /**
    8. * @author hrui
    9. * @date 2023/9/26 15:57
    10. */
    11. @Service
    12. public class AccountServiceImpl implements AccountService {
    13. @Resource(name="accountDaoImpl")
    14. private AccountDao accountDao;
    15. @Override
    16. public void transfer(Integer fid, Integer tid, double balance) {
    17. //查询转出账户余额够不够
    18. Account fAccount = accountDao.selectByActNo(fid);
    19. if(fAccount.getBalance()<balance){
    20. throw new RuntimeException("余额不足");
    21. }
    22. //余额充足
    23. Account tAccount = accountDao.selectByActNo(tid);
    24. //修改内存中两个对象的值
    25. fAccount.setBalance(fAccount.getBalance()-balance);
    26. tAccount.setBalance(tAccount.getBalance()+balance);
    27. int count = accountDao.updateAct(fAccount);
    28. //模拟异常
    29. String str=null;
    30. System.out.println(str.toString());
    31. count+=accountDao.updateAct(tAccount);
    32. if(count!=2){
    33. System.out.println("转账失败,联系银行");
    34. }
    35. }
    36. }

    Spring配置文件

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:context="http://www.springframework.org/schema/context"
    5. xmlns:aop="http://www.springframework.org/schema/aop"
    6. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    7. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    8. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    9. <!--组件扫描-->
    10. <context:component-scan base-package="com.example"></context:component-scan>
    11. <!--配置数据源-->
    12. <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    13. <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
    14. <property name="url" value="1:3306/spring6"></property>
    15. <property name="username" value="1"></property>
    16. <property name="password" value="1"></property>
    17. </bean>
    18. <!--配置JdbcTemplate-->
    19. <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    20. <property name="dataSource" ref="dataSource"></property>
    21. </bean>
    22. </beans>

    测试类

    1. import com.example.service.AccountService;
    2. import org.springframework.beans.factory.BeanFactory;
    3. import org.springframework.context.support.ClassPathXmlApplicationContext;
    4. /**
    5. * @author hrui
    6. * @date 2023/9/26 16:14
    7. */
    8. public class Test {
    9. @org.junit.Test
    10. public void transfer(){
    11. BeanFactory beanFactory=new ClassPathXmlApplicationContext("spring-config.xml");
    12. AccountService accountServiceImpl = beanFactory.getBean("accountServiceImpl", AccountService.class);
    13. try {
    14. accountServiceImpl.transfer(1, 2, 50);
    15. System.out.println("转账成功");
    16. } catch (Exception e) {
    17. e.printStackTrace();
    18. }
    19. }
    20. }

    上面代码中间抛出异常就会导致,一遍转账了,而另一边没收到的情况

    以代码逻辑的方式  需要在下面代码中执行1.开启事务  2.执行核心业务逻辑  3.无异常则提交事务

    4.有异常则回滚事务

    1. @Override
    2. public void transfer(Integer fid, Integer tid, double balance) {
    3. //1.开启事务
    4. //2.执行核心业务逻辑
    5. //查询转出账户余额够不够
    6. Account fAccount = accountDao.selectByActNo(fid);
    7. if(fAccount.getBalance()<balance){
    8. throw new RuntimeException("余额不足");
    9. }
    10. //余额充足
    11. Account tAccount = accountDao.selectByActNo(tid);
    12. //修改内存中两个对象的值
    13. fAccount.setBalance(fAccount.getBalance()-balance);
    14. tAccount.setBalance(tAccount.getBalance()+balance);
    15. int count = accountDao.updateAct(fAccount);
    16. //模拟异常
    17. String str=null;
    18. System.out.println(str.toString());
    19. count+=accountDao.updateAct(tAccount);
    20. if(count!=2){
    21. System.out.println("转账失败,联系银行");
    22. }
    23. //3.如果执行业务流程过程中,没有异常.提交事务
    24. //4.如果执行业务流程过程中,有异常,回滚事务
    25. }

    Spring对事务的支持

    Spring实现事务的两种方式

    编程式事务:通过编写代码的方式来实现事务的管理

    声明式事务:1.基于注解方式   2.基于XML配置方式

    Spring事务管理API

    Spring对事务的管理底层实现方式是基于AOP实现的.采用AOP的方式进行了封装.所以Spring专门针对事务开发了一套API,API的核心接口如下

    PlatformTransactionManager接口:Spring事务管理器的核心接口.在Spring6中它有两个实现

    1.DataSourceTransactionManager:支持JdbcTemplate,Mybatis,Hibernate等事务管理

    2.JtaTransactionManager:支持分布式事务管理

    如果要在Spring6中使用JdbcTemplate,就要使用DataSourceTransactionManager来管理事务.(Srping内置写好了,可以直接使用)

    声明式事务基于注解的实现方式

    Spring配置文件里配置事务管理器,让SpringIOC容器管理  设置dataSource属性为druid的DataSource实现类

    然后在方法上加@Transactional即可

    好比有了1 2 3 4的步骤

    写在类上,类里面所有方法都有事务控制

    写在方法上,单个方法有事务控制

    @Transactional注解

    事务的传播行为

    在a()方法中调用了b()方法,比如a()方法有事务,b()方法也有事务,那么事务是如何传递的?是合并到一个事务?还是另开启一个事务?这就是事务的传播行为

    事务一共有七种传播行为:

    REQUIRED:支持当前事务,如果不存在就新建一个(默认)[没有就新建,有就加入]

    SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行[有就加入,没有就不管了]

    MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常[有就加入,没有就抛异常]

    REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起[不管有没有事务,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起]

    NOT_SUPPORTED:以非事务方式运行,如果有事务在,挂起当前事务[不支持事务,存在就挂起]

    NEVER:以事务方式运行,如果有事务存在,挂起当前事务[不支持事务,存在就挂起]

    NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套事务中.被嵌套的事务可以独立于外层事务进行提交或回滚.如果外层事务不存在,行为就像REQUIRED一样[有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚,没有事务就和REQUIRED一样]

    默认是@Transactional(Propagation=Propagation.REQUIRED)

    下面两个是常用的:

    REQUIRED:支持当前事务,如果不存在就新建一个(默认)[没有就新建,有就加入]

    REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起[不管有没有事务,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起]

    举例:    A方法和B方法的方法上都有@Transactional(Propagation=Propagation.REQUIRED)默认

    在A方法中调用B方法

    A(){

            保存操作XXXX;

            B();

    }

    B方法

    B(){

            保存操作

    }

    那么A方法和B方法在同一个事务中, 即使你在A方法里对B方法进行try catch,无论哪个方法里报错,都会回滚

    举例:    A方法是@Transactional(Propagation=Propagation.REQUIRED)默认,B方法的方法上是@Transactional(Propagation=Propagation.REQUIRES_NEW)

    A方法内进行保存操作且调用了B方法,假如B方法报错了,且A方法没有对B方法进行try catch那么两个都会回滚,假如B方法报错了,但是A方法内对B方法进行了try catch那么B方法会回滚,而A方法不会回滚,因为是两个事务

    事务隔离级别:

    数据库中读取数据的三大问题:(三大读问题)

    脏读:读取到没有提交的数据,叫脏读(读的是缓存(内存里的东西))

    不可重复读:在同一个事务当中,第一次和第二次读到的数据不一样

    幻读:督导的数据是假的

    事务隔离级别包括四个级别:

    读未提交:READ_UNCOMMITTED.这种隔离级别,存在脏读问题.所谓脏读(dirty read)表示能够读取到其他事务还未提交的数据

    读提交:READ_COMMITTED.解决了脏读问题,其他事务提交之后才能督导,但存在不可重复读问题.(Oracle数据库的默认级别)

    可重复读:REPEATABLE_READ.解决了不可重复读,可以达到可重复读的效果,只要当前事务不结束,读取到的数据一直都是一样的.但存在幻读问题(Mysql数据库的默认级别)

    序列化:SERIALIZABLE.解决了幻读问题,事务排队执行.不支持并发

    读未提交,读已提交,可重复读都是多线程并发问题引起的,序列化就排队

    事务超时问题     @Transactional(timeout=10)

    以上代码表示设置事务的超时时间为10秒

    表示超过10秒如果该事务中所有的DML(增删改)语句还没有执行完毕,最终结果会选择回滚

    默认值-1,表示没有时间限制

    这里有个坑,事务的超时时间指的是哪段时间?

    在当前事务当中,最后一条DML(增删改)语句执行之前的时间,如果最后一条DML语句后面还有很多业务逻辑,这些业务代码执行的时间不会被计入超时时间

    只读事务   代码   @Transactional(readOnly=true)

    将当前事务设置为只读事务,在该事务执行过程中只允许select语句执行,delete insert update均不可以执行.

    该特性的作用:启动Spring的优化策略,提高select语句执行效率.

    如果该事务中确实没有增删改操作,建议设置为只读事务.

    异常回滚事务:

    代码 例 @Transactional(rollbackFor=NumberFormatException.class)

    表示只有发生NumberFormatException异常或该异常的子类异常时才回滚

    设置哪些异常不回滚事务:

    代码 例 @Transactional(noRollbackFor=NullPointerException.class)

    表示发生NullPointerException或该类子类异常不回滚,其他异常则回滚

  • 相关阅读:
    linux内核工作延迟机制
    JZM-D30室温探针台技术参数
    AJAX: 对话框大全
    C语言中 -a++ 、-++a运算顺序解析
    React 第五章 表单
    二进制安装Kubernetes(k8s)v1.30.1
    v-if的使用
    使用 KubeSkoop exporter 监测和定位容器网络抖动问题
    HTML期末大作业(HTML+CSS+JavaScript响应式游戏资讯网站bootstrap网页)
    GoLand 2023:为Go开发者打造的智能IDE mac/win版
  • 原文地址:https://blog.csdn.net/tiantiantbtb/article/details/133306440