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;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", actno='" + actno + '\'' +
", balance=" + balance +
'}';
}
@Override
public 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);
}
@Override
public 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;
@Override
public 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;
}
@Override
public 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;
@Override
public 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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://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.Test
public 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.有异常则回滚事务
@Override
public 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或该类子类异常不回滚,其他异常则回滚