当前位置:首页 > 编程笔记 > 正文
已解决

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

来自网友在路上 147847提问 提问时间:2023-09-27 08:25:46阅读次数: 47

最佳答案 问答题库478位专家为你答疑解惑

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项目或者模块

依赖

<dependencies><!--spring依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.10</version></dependency><!--jdbcTemplate依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>6.0.10</version></dependency><!--mysql驱动依赖--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency><!--druid德鲁伊依赖--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.13</version></dependency><!--javaee的注解 @Resource依赖--><dependency><groupId>jakarta.annotation</groupId><artifactId>jakarta.annotation-api</artifactId><version>2.1.1</version></dependency><!--单元测试依赖--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency>
</dependencies>

准备数据库表

项目结构

实体类

package com.example.pojo;import java.util.Objects;/*** @author hrui* @date 2023/9/26 15:02*/
public class Account {private Integer id;private String actno;private Double balance;public Account() {}public Account(Integer id, String actno, Double balance) {this.id = id;this.actno = actno;this.balance = balance;}@Overridepublic String toString() {return "Account{" +"id=" + id +", actno='" + actno + '\'' +", balance=" + balance +'}';}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Account account = (Account) o;return Objects.equals(id, account.id) && Objects.equals(actno, account.actno) && Objects.equals(balance, account.balance);}@Overridepublic int hashCode() {return Objects.hash(id, actno, balance);}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getActno() {return actno;}public void setActno(String actno) {this.actno = actno;}public Double getBalance() {return balance;}public void setBalance(Double balance) {this.balance = balance;}
}

持久层

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

持久层实现类

package com.example.dao.impl;import com.example.dao.AccountDao;
import com.example.pojo.Account;
import jakarta.annotation.Resource;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;/*** @author hrui* @date 2023/9/26 15:04*/
@Repository
public class AccountDaoImpl implements AccountDao {@Resource(name="jdbcTemplate")private JdbcTemplate jdbcTemplate;@Overridepublic Account selectByActNo(Integer id) {String sql="select id,actno,balance from t_act where id=?";Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), id);return account;}@Overridepublic int updateAct(Account account) {String sql="update t_act  set balance=?  where id=?";int count = jdbcTemplate.update(sql, account.getBalance(),account.getId());return count;}
}

业务层接口

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

业务层实现类

package com.example.service.impl;import com.example.dao.AccountDao;
import com.example.pojo.Account;
import com.example.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;/*** @author hrui* @date 2023/9/26 15:57*/
@Service
public class AccountServiceImpl implements AccountService {@Resource(name="accountDaoImpl")private AccountDao accountDao;@Overridepublic void transfer(Integer fid, Integer tid, double balance) {//查询转出账户余额够不够Account fAccount = accountDao.selectByActNo(fid);if(fAccount.getBalance()<balance){throw new RuntimeException("余额不足");}//余额充足Account tAccount = accountDao.selectByActNo(tid);//修改内存中两个对象的值fAccount.setBalance(fAccount.getBalance()-balance);tAccount.setBalance(tAccount.getBalance()+balance);int count = accountDao.updateAct(fAccount);//模拟异常String str=null;System.out.println(str.toString());count+=accountDao.updateAct(tAccount);if(count!=2){System.out.println("转账失败,联系银行");}}
}

Spring配置文件

<?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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!--组件扫描--><context:component-scan base-package="com.example"></context:component-scan><!--配置数据源--><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property><property name="url" value="1:3306/spring6"></property><property name="username" value="1"></property><property name="password" value="1"></property></bean><!--配置JdbcTemplate--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property></bean>
</beans>

测试类

import com.example.service.AccountService;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;/*** @author hrui* @date 2023/9/26 16:14*/
public class Test {@org.junit.Testpublic void transfer(){BeanFactory beanFactory=new ClassPathXmlApplicationContext("spring-config.xml");AccountService accountServiceImpl = beanFactory.getBean("accountServiceImpl", AccountService.class);try {accountServiceImpl.transfer(1, 2, 50);System.out.println("转账成功");} catch (Exception e) {e.printStackTrace();}}
}

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

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

4.有异常则回滚事务

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

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或该类子类异常不回滚,其他异常则回滚

查看全文

99%的人还看了

猜你感兴趣

版权申明

本文"Spring学习笔记13 Spring对事务的支持":http://eshow365.cn/6-14541-0.html 内容来自互联网,请自行判断内容的正确性。如有侵权请联系我们,立即删除!