Spring AOP声明式事务(详细+案例)
文章目录
1、事务一般分为两类:
编程式事务:在代码中直接加入处理事务的逻辑,可能需要在代码中显式调用beginTransaction()、commit()、rollback()等事务管理相关的方法
声明式事务:在方法的外部添加注解或者直接在配置文件中定义,将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。spring的AOP恰好可以完成此功能:事务管理代码的固定模式作为一种横切关注点,通过AOP方法模块化,进而实现声明式事务。
2、声明式事务的简单配置
Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。
Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
-
导入坐标(直接导入即可)pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>mashibing_spring_oap_02</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <dependencies> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency> <!-- https://mvnrepository.com/artifact/aopalliance/aopalliance --> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.3.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.3.0</version> </dependency> <!--springioc的配置--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.3.RELEASE</version> </dependency> <!--添加数据库连接池--> <!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.1</version> </dependency> <!--添加数据库支持--> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--添加JdbcTemplate支持--> <!-- https://mvnrepository.com/artifact/org.springframework/spring-orm --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.2.3.RELEASE</version> </dependency> </dependencies> </project>
-
添加配置文件(最重要的是添加事务管理器)applictionContext.xml
<?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!--开启注解扫描包--> <context:component-scan base-package="com.aop"></context:component-scan> <!--开启加载properties配置文件--> <context:property-placeholder location="dbconfig.properties"></context:property-placeholder> <!--开启基于注解的事务控制模式,依赖tx名称空间--> <tx:annotation-driven transaction-manager="TransactionManager"></tx:annotation-driven> <!--注入数据源--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${password}"></property> <property name="url" value="${url}"></property> <property name="driverClassName" value="${driverClassName}"></property> </bean> <!--配置JdbcTemplate--> <bean id="template" class="org.springframework.jdbc.core.JdbcTemplate"> <!--注入数据源--> <property name="dataSource" ref="dataSource"></property> </bean> <!--添加事务管理--> <bean id="TransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入数据源--> <property name="dataSource" ref="dataSource"></property> </bean> </beans>
-
添加properties文件,存放数据库登录信息。
作用:数据库登录信息不用在appliction.xml中直写死,增加灵活性。
jdbc.username=root password=root url=jdbc:mysql://localhost:3306/hm driverClassName=com.mysql.jdbc
3、环境准备(模拟转账)
- 类目录
-
数据库准备
创建account数据库
-
创建实体类对象(Account,java实体类)
package com.aop.bean; public class Account { private String name; private int money; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getMoney() { return money; } public void setMoney(int money) { this.money = money; } @Override public String toString() { return "AccountDaoImpl{" + "name='" + name + '\'' + ", money=" + money + '}'; } }
-
创建持久层接口(accountDao.java)
package com.aop.dao; public interface accountDao { //查询余额 public Integer selectBalance(String name); //账户加钱 public void addmoney(String name,int money); //账户减钱 public void lessmoney(String name,int money); }
-
实现持久层接口(AccountDaoImpl.java)
package com.aop.dao.Impl; import com.aop.dao.accountDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class AccountDaoImpl implements accountDao { @Autowired JdbcTemplate template; //查询账户余额 @Override public Integer selectBalance(String name) { String sql = "SELECT money FROM account WHERE NAME=?"; Integer integer = template.queryForObject(sql, Integer.class, name); return integer; } //账户加钱 @Override public void addmoney(String name, int money) { String sql = "UPDATE account SET money=money+? WHERE NAME=?"; template.update(sql, money, name); } //账户减钱 @Override public void lessmoney(String name, int money) { String sql = "UPDATE account SET money=money-? WHERE NAME=?"; template.update(sql, money, name); } }
-
创建业务层(accountservice.java)
package com.aop.service; import com.aop.dao.Impl.AccountDaoImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class accountservice { @Autowired AccountDaoImpl dao; //模拟转账 public void Transfer(String addname,String lessname,int money){ //账户加钱 dao.lessmoney(lessname,money); //账户加钱 dao.addmoney(addname,money); } }
-
进行测试(测试成功)
4、事务配置的属性
@Transactional:
isolation:设置事务的隔离级别
propagation:事务的传播行为
noRollbackFor:那些异常事务可以不回滚
noRollbackForClassName:填写的参数是全类名
rollbackFor:哪些异常事务需要回滚
rollbackForClassName:填写的参数是全类名
readOnly:设置事务是否为只读事务
timeout:事务超出指定执行时长后自动终止并回滚,单位是秒
5、测试超时属性:
- @Transactional(timeout = 3)
accountservice.java
package com.aop.service;
import com.aop.dao.Impl.AccountDaoImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class accountservice {
@Autowired
AccountDaoImpl dao;
//方法执行超过3秒则回滚
@Transactional(timeout = 3)
public void Transfer(String addname,String lessname,int money) throws InterruptedException {
//账户减钱
dao.lessmoney(lessname,money);
//等待3秒
Thread.sleep(3000);
//账户加钱
dao.addmoney(addname,money);
}
}
执行结果为账户余额都不改变,因为方法执行时间超过3秒,事务回滚。
6、设置事只读:
- @Transactional(readOnly= true)
如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;
如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。
对于只读查询,可以指定事务类型为readonly,即只读事务。由于只读事务不存在数据的修改,因此数据库将会为只读事务提供一些优化手段
**测试:**accountservice.java
package com.aop.service;
import com.aop.dao.Impl.AccountDaoImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class accountservice {
@Autowired
AccountDaoImpl dao;
//方法执行超过3秒则回滚
@Transactional(readOnly= true)
public void Transfer(String addname,String lessname,int money) throws InterruptedException {
//账户减钱
dao.lessmoney(lessname,money);
//账户加钱
dao.addmoney(addname,money);
}
}
7、设置哪些异常不回滚
-
noRollbackFor:哪些异常事务可以不回滚
-
noRollbac出现orClassName:填写的参数是全类名
当方法中发现异常,要求即使方法中出现异常也不要回滚
**测试:**accountservice.java
package com.aop.service;
import com.aop.dao.Impl.AccountDaoImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class accountservice {
@Autowired
AccountDaoImpl dao;
//方法执行超过3秒则回滚
@Transactional(noRollbackFor = {ArithmeticException.class, NullPointerException.class})
public void Transfer(String addname, String lessname, int money) throws InterruptedException {
//账户减钱
dao.lessmoney(lessname, money);
//账户加钱
dao.addmoney(addname, money);
int i = 9 / 0;
}
}
---
package com.aop.service;
import com.aop.dao.Impl.AccountDaoImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class accountservice {
@Autowired
AccountDaoImpl dao;
//方法执行超过3秒则回滚
@Transactional(noRollbackForClassName = {"java.lang.ArithmeticException")
public void Transfer(String addname, String lessname, int money) throws InterruptedException {
//账户减钱
dao.lessmoney(lessname, money);
//账户加钱
dao.addmoney(addname, money);
int i = 9 / 0;
}
}
- 注意:
- 不会回滚指的是走到异常的语句后,发现异常,提交事务,并不会继续往下执行语句,比如将异常放在dao.lessmoney(lessname, money);和 dao.addmoney(addname, money);中间,上方会执行,而下面不会方法执行
- 若把异常捕获,则两个方法都可以执行
8、设置异常回滚
-
rollbackFor:哪些异常事务需要回滚
-
rollbackForClassName:填写的参数是全类名
**测试:**accountservice.java
package com.aop.service; import com.aop.dao.Impl.AccountDaoImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.io.FileNotFoundException; @Service public class accountservice { @Autowired AccountDaoImpl dao; //方法执行超过3秒则回滚 @Transactional(rollbackFor = {FileNotFoundException.class}) public void Transfer(String addname, String lessname, int money) throws InterruptedException { //账户减钱 dao.lessmoney(lessname, money); //账户加钱 dao.addmoney(addname, money); int i = 9 / 0; } }
9、设置隔离级别
10、设置传播特性
隔离级别和传播特性比较复杂需要讲的比较详细,在下一篇进行详细总结!
总结:
-
事务分为两类:编程式事务和声明式事务
-
事务的属性
isolation:设置事务的隔离级别
propagation:事务的传播行为
noRollbackFor:那些异常事务可以不回滚
noRollbackForClassName:填写的参数是全类名
rollbackFor:哪些异常事务需要回滚
rollbackForClassName:填写的参数是全类名
readOnly:设置事务是否为只读事务
timeout:事务超出指定执行时长后自动终止并回滚,单位是秒
-
对事务的属性进行测试,利用转账案例进行测试。