Spring-day03
01_Spring的AOP概述
目标
- 了解AOP存在的意义及方式
路径
- AOP介绍
- AOP作用
- AOP优势
- Spring中AOP的实现方式
在前面我们提到:Spring技术的两大核心就是Ioc(控制反转)和AOP
AOP介绍
AOP(Aspect Oriented Programing) 面向切面编程,一种编程范式,指导开发者如何组织程序结构
- OOP(Object Oriented Programing) 面向对象编程
AOP作用
在程序运行期间,不修改源码的基础上对已有方法进行增强(无侵入性: 解耦)
AOP优势
- 减少重复代码
- 提高开发效率
- 维护方便
Spring中AOP的实现方式
spring的AOP有两种实现方式:
-
基于JDK官方的动态代理(优先使用)
- 当bean实现接口时,spring就会用JDK的动态代理
-
基于第三方的cglib的动态代理
- 当bean没有实现接口时,spring使用CGLib来实现(JDK8后, JDK动态代理效率高于CGlib)
备注: 开发者可以在spring中强制使用CGLib (了解) 在Spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>
02_Spring前置知识点
目标
- 清楚动态代理的代码书写方式 (仅做了解)
路径
- 动态代理:Proxy
- 动态代理:CGLIb
需求:统计service层AccountServiceImpl类(有父接口)中所有方法的运行时间。
- 要求:不修改源码的基础上, 对每个方法进行增强
动态代理:Proxy
JDK的Proxy动态代理是针对对象做代理,要求原始对象具有接口实现,并对接口方法进行增强。
代码示例:
- 业务层
@Service("accountService")
public class AccountServiceImpl implements IAccountService { //类有实现接口
@Override
public void saveAccount(Account account) {
System.out.println("AccountServiceImpl => saveAccount方法");
}
@Override
public void updateAccount(Account account) {
System.out.println("AccountServiceImpl => updateAccount方法");
}
@Override
public void deleteAccount(Integer id) {
System.out.println("AccountServiceImpl => deleteAccount方法");
}
@Override
public Account queryAccountById(Integer id) {
System.out.println("AccountServiceImpl => queryAccountById方法");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
public List<Account> queryAllAccount() {
System.out.println("AccountServiceImpl => queryAllAccount方法");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
- 测试类
public class JdkProxyTest {
@Test
public void testProxy() {
//创建对象
AccountServiceImpl accountService = new AccountServiceImpl();
//类加载器
ClassLoader classLoader = JdkProxyTest.class.getClassLoader();
//父接口
Class[] interfaces = AccountServiceImpl.class.getInterfaces();
//处理器
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;//方法执行完后的返回值
long beginTime = System.currentTimeMillis();//开时时间
result = method.invoke(accountService, args);//执行方法
long endTime = System.currentTimeMillis();//结束时间
System.out.println("执行时间:" + (endTime - beginTime) + "毫秒");
return result;
}
};
//使用jdk的Proxy获取代理对象
IAccountService accountServiceProxy = (IAccountService) Proxy.newProxyInstance(classLoader, interfaces, handler);
//使用代理对象调用方法
List<Account> accountList = accountServiceProxy.queryAllAccount();
}
}
//输出结果:
AccountServiceImpl => queryAllAccount方法
执行时间:3007毫秒
动态代理:CGLIB
CGLIB(Code Generation Library),Code生成类库 (第三方的库,不是JDK)
- CGLIB动态代理不限定被代理类是否具有接口,可以对任意操作进行增强
- 底层实现原理:继承
- CGLIB动态代理继承于被代理类,动态创建出新的代理对象
代码示例:
使用cglib需要导入cglib的jar包
在Spring中已经整合了cglib,所以导入Spring-context包即可
- 业务类
@Service("accountService2")
public class AccountServiceClass { //类没有实现接口
public Account queryAccountById(Integer id) {
System.out.println("AccountServiceClass => queryAccountById方法");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
public List<Account> queryAllAccount() {
System.out.println("AccountServiceClass => queryAllAccount方法");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
- 测试类
import com.itheima.pojo.Account;
import com.itheima.service.AccountServiceClass;
import org.junit.Test;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.List;
public class CglibTest {
@Test
public void testCglib() {
//1. 创建被代理类的对象
AccountServiceClass accountService = new AccountServiceClass();
//2. 创建代理类对象
Callback callback = new MethodInterceptor() {
/*
intercept方法: 代理类对象执行任意方法,都会调用此方法
1. proxy :表示代理类对象本身 (没什么用)
2. method : 表示代理类对象当前调用的方法
3. args : 表示代理类对象当前调用方法传入的参数
4. methodProxy : 方法代理 (没什么用)
返回值Object : 表示代理类对象当前调用方法产生的返回值
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object result = null;//方法执行完后的返回值
long beginTime = System.currentTimeMillis();//开时时间
result = method.invoke(accountService, args);//执行方法
long endTime = System.currentTimeMillis();//结束时间
System.out.println("执行时间:" + (endTime - beginTime) + "毫秒");
return result;
}
};
Class type = accountService.getClass();
AccountServiceClass proxy = (AccountServiceClass) Enhancer.create(type,callback);
//3. 使用代理类对象调用方法
List<Account> accountList = proxy.queryAllAccount();
Account account = proxy.queryAccountById(1);
}
}
//输出结果:
AccountServiceClass => queryAllAccount方法
执行时间:3008毫秒
AccountServiceClass => queryAccountById方法
执行时间:2010毫秒
03_Spring的AOP基础
目标
- 了解Spring中AOP开发基础概念
路径
- AOP编程用到的概念
- AOP的开发过程
- AOP的开发方式
AOP编程用到的概念
面向切面编程中的相关概念:
- Target(目标对象)
- 要被增强的对象(被代理类的对象)
- Proxy(代理对象)
- 对目标对象的增强对象(生成的代理类对象)
- Joinpoint(连接点)
- 目标对象中的可被增强的方法(被代理类中的方法)
- 不可被增强的方法:
- Proxy中被代理类不可被增强方法(父接口中没有的方法)
- CGlib中被代理类不可被增强方法(final修饰的方法)
- 不可被增强的方法:
- 目标对象中的可被增强的方法(被代理类中的方法)
- Pointcut(切入点)
- 要被增强的方法(被代理类中要增强的方法)
- 切入点一定是连接点;但连接点不一定是切入点
- 切入点是要增强的方法,而该方法必须是连接点
- 连接点,不一定要被增强
- 切入点一定是连接点;但连接点不一定是切入点
- 要被增强的方法(被代理类中要增强的方法)
- Advice(通知)
- 通知是增强的那段代码形成的方法
- 通知的分类:
- 前置通知 在方法之前进行增强
- 后置通知 在方法之后进行增强
- 异常通知 在方法异常进行增强
- 最终通知 最终执行的方法进行增强
- 环绕通知 单独使用(以上所有通知)
- Aspect(切面)
- 切面 = 切入点+通知
- 目标方法和增强代码合到一起叫做切面
- 切面 = 切入点+通知
- Weaving(织入)
- 在运行过程中spring底层将通知和切入点进行整合的过程称为织入
AOP的开发过程
开发阶段(开发者完成)
- 正常的制作程序
- 将非共性功能开发到对应的目标对象类中,并制作成切入点方法
- 将共性功能独立开发出来,制作成通知
- 在配置类中,声明切入点
- 在配置类中,声明切入点与通知间的关系(含通知类型),即切面
运行阶段(AOP完成)
- Spring容器加载配置文件时, 使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置将通知对应的功能织入,形成完整的代码逻辑
- 当切入点方法被运行,将会调用代理对象的方法,达到增强目标对象的效果
AOP的开发方式
AOP有两种开发方式:
- XML方式
- 注解方式(课程中学习这个)
04_Spring的AOP入门
目标
- 能够使用Spring中的AOP配置增强类中方法
路径
- Spring中AOP开发步骤
- Spring的AOP注解
- 使用Spring的AOP方式统计业务类中方法执行时间
Spring中AOP开发步骤
在spring中使用aop开发的步骤:
-
导入相关坐标(Spring、切入点表达式)
-
开启aop注解支持
-
编写切面类
-
配置切入点
-
配置通知类型
-
-
运行程序(测试)
Spring的AOP注解
开启aop注解支持:
@EnableAspectJAutoProxy
public class SpringConfig{
}
切面类:
@Aspect //配置当前类为切面类(切入点+通知)
public class ClassName{
//配置切入点 : @Pointcut("切入点表达式")
//定义切入点表达式:com.itheima包及子包下的AccountServiceImpl类中所有方法
/*
举例1:针对AccountServiceImpl类中的queryAllAccount()方法进行增强
切入点表达式: com.itheima.service.impl.AccountServiceImpl.queryAllAccount()
举例2:针对AccountServiceImpl类中的queryAccountById(Integer id)方法进行增强
切入点表达式: com.itheima.service.impl.AccountServiceImpl.queryAccountById(Integer)
举例3:针对AccountServiceImpl类中的所有方法进行增强
切入点表达式:com.itheima.service.impl.AccountServiceImpl.*(..)
*/
@Pointcut("execution(* com.itheima..AccountServiceImpl.*(..))")
public void pt(){
//切入点,必须配置在三无方法上(无参、无返回值、空方法体)
}
//配置通知类型
/* @Before: 前置通知
@AfterReturning:后置通知
@AfterThrowing :异常通知
@After :最终通知
@Around:环绕通知
*/
@Before("pt()") //指定切点入(原因:明确要对哪个方法进行前置增强)
public void 方法名(){
//增强功能的代码
}
@AfterReturning("pt()")
public void 方法名(){
//增强功能的代码
}
}
使用Spring的AOP方式统计业务类中方法执行时间
代码示例:
- 导入坐标
<!-- spring核心jar包,已存在依赖的AOP的jar -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!-- 切入点表达式 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
- 配置类(开启aop注解支持)
@Configurable
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy //开启aop注解支持
public class SpringConfig {
}
- 业务层
//接口
public interface IAccountService {
public void saveAccount(Account account);
public void updateAccount(Account account);
public void deleteAccount(Integer id);
public Account queryAccountById(Integer id);
public List<Account> queryAllAccount();
}
//实现类
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Override
public void saveAccount(Account account) {
System.out.println("AccountServiceImpl => saveAccount方法");
}
@Override
public void updateAccount(Account account) {
System.out.println("AccountServiceImpl => updateAccount方法");
}
@Override
public void deleteAccount(Integer id) {
System.out.println("AccountServiceImpl => deleteAccount方法");
}
@Override
public Account queryAccountById(Integer id) {
System.out.println("AccountServiceImpl => queryAccountById方法");
try {
Thread.sleep(2000);//睡眠2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
public List<Account> queryAllAccount() {
System.out.println("AccountServiceImpl => queryAllAccount方法");
try {
Thread.sleep(3000);//睡眠3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
- 切面类
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component //当前bean需要加载到ioc容器中
@Aspect //切面类(切入点+通知)
public class AccountServiceAdvice {
//配置切入点 切入点表达式=com.itheima.service.impl.AccountServiceImpl类中所有方法
@Pointcut("execution(* com.itheima.service.impl.AccountServiceImpl.*(..))")
public void pt() {
//切入点配置在无参无返回值的空方法上
}
long beginTime; //起始时间
long endTime;//终止时间
//配置通知
@Before("pt()") //前置通知,在目标方法执行前运行
public void beginTime() {
System.out.println("开始计时...");
beginTime = System.currentTimeMillis();
}
@AfterReturning("pt()") //后置通知,在目标方法执行后运行
public void endTime() {
endTime = System.currentTimeMillis();
System.out.println("执行时间:" + (endTime - beginTime) + "毫秒");
}
}
- 测试类
@RunWith(SpringJUnit4ClassRunner.class) //spring整合junit
@ContextConfiguration(classes = SpringConfig.class) //加载注解配置类
public class AspectTest {
@Autowired
IAccountService accountService;
@Test
public void testAspect(){
List<Account> accountList = accountService.queryAllAccount();
Account account = accountService.queryAccountById(1);
}
}
//输出结果:
开始计时...
AccountServiceImpl => queryAllAccount方法
执行时间:3002毫秒
开始计时...
AccountServiceImpl => queryAccountById方法
执行时间:2001毫秒
05_Spring的AOP详解-切入点
目标
- 能够书写SpringAOP的切入点表达式
路径
- 案例分析
- 切入点表达式语法
- 切入点表达式示例
案例分析
spring的aop入门案例:
- 导入依赖
- 编写service层(接口、实现类[目标对象])
- 编写切面类(切入点 、通知)
aop入门案例执行机制:
-
创建目标对象:AccoucntServiceImpl (使用IoC配置)
-
创建代理对象
2.1、基于目标对象 => 代理类
2.2、基于AccountServiceAdvice => 代理类方法体
- 切面 = 切入点 + 通知
2.3、Weaving(织入)
- 在运行过程中spring底层将通知和切入点进行整合
-
使用代理对象调用方法
@Autowired IAccountService accountService; //代理对象 //调用方法 Account account = accountService.queryAccountById(1);
切入点表达式
通过切入点表达式可以让spring找到要增强的目标方法
要使用切入点表达式,需要添加依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
切入点表达式的语法:
-
格式:
execution(方法的修饰符 方法的返回值 类的全限定名.方法名(参数))
示例: execution(public void com.itheima.service.impl.AccountServiceImpl.deleteAccount(Integer))
-
通配符
- 任意字符串:
*
- 任意重复次数:
..
示例: execution(public * com.itheima..AccountServiceImpl.*(..))
- 任意字符串:
-
规则
- 方法的修饰符可以省略
- 返回值可以使用
*
号代替(任意返回值类型) - 包名可以使用
*
号代替,代表任意包(一层包使用一个*
) - 使用
..
配置包名,标识此包以及此包下的所有子包 - 类名可以使用
*
号代替,标识任意类 - 方法名可以使用
*
号代替,表示任意方法 - 可以使用
..
配置参数,任意参数
切入点表达式示例
-
省略方法的修饰符号
execution(void com.itheima.service.impl.AccountServiceImpl.deleteAccount(Integer))
-
使用
*
代替返回值类型execution(* com.itheima.service.impl.AccountServiceImpl.deleteAccount(Integer))
-
使用
*
代替包名(一层包使用一个*
)execution(* com.itheima.*.*.AccountServiceImpl.deleteAccount(Integer))
-
使用
..
省略包名execution(* com..AccountServiceImpl.deleteAccount(Integer))
-
使用
*
代替类名execution(* com..*.deleteAccount(Integer))
-
使用
*
代替方法名execution(* com..*.*(Integer))
-
使用
..
省略参数execution(* com..*.*(..))
说明:在需求范围越具体越好(效率高)
//service包下的所有方法,均为切入点
execution(* com.itheima.service..*.*(..))
//service包下以Service结尾的业务类(如:IUserService、IAccountService、...)中所有方法
execution(* com.itheima.service.*Service.*(..))
06_Spring的AOP详解-通知
目标
- 能够配置Spring中AOP的通知
路径
- AOP中通知介绍
- AOP中环绕通知的使用
AOP中通知介绍
在AOP中有5种类型通知:
-
前置通知:@Before
- 原始方法(切入点)执行前执行,如果通知中抛出异常,阻止原始方法运行
- 应用场景:数据校验
@Before("配置了切入点的方法名()") public void beforeMethod(){ //前增强功能(在目标方法执行前运行) }
-
后置通知:@AfterReturning
- 原始方法执行后执行,原始方法中出现异常,不再执行
- 应用场景:返回值相关数据处理
@AfterReturning("配置了切入点的方法名()") public void afterMethod(){ //后增强功能(在目标方法执行后运行) }
-
抛出异常后通知:@AfterThrowing
- 原始方法抛出异常后执行,如果原始方法没有抛出异常,无法执行
- 应用场景:对原始方法中出现的异常信息进行处理
@AfterThrowing("配置了切入点的方法名()") public void throwMethod(){ //在目标方法执行有异常时运行 }
-
最终通知:@After
- 无论如何最终都执行(不论是否有异常发生,都会执行)
- 应用场景:清理资源
@After("配置了切入点的方法名()") public void finallyMethod(){ //不论是否有异常,在目标方法执行完后都会运行 }
-
环绕通知:@Around
- 在原始方法执行前后均有对应执行,还可以阻止原始方法的执行
- 应用场景:可以做以上四种类型通知的所有事情(十分强大)
AOP中环绕通知的使用
环绕通知的开发方式:
-
环绕通知是在原始方法的前后添加功能,在环绕通知中,存在对原始方法的显式调用
@Around("配置了切入点的方法名()") public Object around(ProceedingJoinPoint pjp) throws Throwable { Object ret = pjp.proceed();//调用原始方法 return ret; }
-
环绕通知方法相关说明:
- 方法须设定Object类型的返回值,否则会拦截原始方法的返回。如果原始方法返回值类型为void,通知方也可以设定返回值类型为void,最终返回null
- 方法需在第一个参数位置设定ProceedingJoinPoint对象(代表切入点),通过该对象调用proceed()方法,实现对原始方法的调用。如省略该参数,原始方法将无法执行。
- ProceedingJoinPoint相当于Method,但是比Method的封装度更高
- 使用proceed()方法调用原始方法时,因无法预知原始方法运行过程中是否会出现异常,强制抛出Throwable对象,封装原始方法中可能出现的异常信息
代码示例:
- 切面类
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AccountServiceAdvice2 {
//配置切入点
@Pointcut("execution(* com.itheima.service.impl.AccountServiceImpl.*(..))")
public void pt() {
//切入点配置在无参无返回值的空方法上
}
@Around("pt()") // ProceedingJoinPoint 相当于:Method
public Object aroundMethod(ProceedingJoinPoint pjp) {
Object result =null ;//返回结果
try {
//前置增强
System.out.println("开始计时...");
long beginTime = System.currentTimeMillis();
//执行原始方法(被增强的原方法)
result = pjp.proceed();
//模拟异常:
//int num = 10/0;
//后置增强
long endTime = System.currentTimeMillis();
Signature signature = pjp.getSignature();//获取切入点方法
System.out.println(signature.getName()+" 执行时间:" + (endTime - beginTime) + "毫秒");
} catch (Throwable throwable) {
System.out.println("异常通知");
} finally {
System.out.println("最终通知");
}
return result;//返回执行结果
}
}
- 测试类
@RunWith(SpringJUnit4ClassRunner.class) //spring整合junit
@ContextConfiguration(classes = SpringConfig.class) //加载注解配置类
public class AspectTest {
@Autowired
IAccountService accountService;
@Test
public void testAspect(){
List<Account> accountList = accountService.queryAllAccount();
Account account = accountService.queryAccountById(1);
accountService.deleteAccount(1);
accountService.saveAccount(null);
}
}
//输出结果:
开始计时...
AccountServiceImpl => queryAllAccount方法
queryAllAccount 执行时间:3001毫秒
最终通知
开始计时...
AccountServiceImpl => queryAccountById方法
queryAccountById 执行时间:2002毫秒
最终通知
开始计时...
AccountServiceImpl => deleteAccount方法
deleteAccount 执行时间:0毫秒
最终通知
开始计时...
AccountServiceImpl => saveAccount方法
saveAccount 执行时间:0毫秒
最终通知
07_事务案例-转账
目标
- 为学习后续的Spring事务管理做铺垫
路径
- 事务介绍
- 案例:转账业务
事务介绍
事务:transaction
- 一组SQL操作,要么全部成功,要么全部失败
事务操作步骤:
- 开启事务
- 业务操作(多条sql语句)
- 提交事务/回滚事务
事务的特性:(ACID)
- 原子性(Atomicity)
- 指事务是一个不可分割的整体,其中的操作要么全执行或全不执行
- 一致性(Consistency)
- 事务前后数据的完整性必须保持一致
- 隔离性(Isolation)
- 事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离
- 持久性(Durability)
- 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
事务的隔离级别:
隔离级别 | 说明 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
READ_UNCOMMITTED | 读未提交 | √ | √ | √ |
READ_COMMITTED | 读已提交 | × | √ | √ |
REPEATABLE_READ | 可重复读 | × | × | √ |
SERIALIZABLE | 串行化操作 | × | × | × |
- 隔离级别由低到高:
- READ_UNCOMMITTED => READ_COMMITTED => REPEATABLE_READ => SERIALIZABLE
- 数据库默认隔离级别:
- MySQL采用:REPEATABLE_READ(可重复读)
- Oracle采用:READ__COMMITTED(读已提交)
案例:转账业务
需求:jack给rose转账500元
数据准备:
create database spring_db;
use spring_db;
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
insert into account values(null,'jack',1000),(null,'rose',1000);
导入素材中的代码:day03-transfer
代码示例:
- 数据库配置文件:jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=itheima
- Pojo类
public class Account {
private Integer id;
private String name;
private Double money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
}
- Mybatis配置类
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean getSqlSessionFactoryBean(DataSource ds){
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
//设置pojo的包扫描
factoryBean.setTypeAliasesPackage("com.itheima.pojo");
//设置连接池
factoryBean.setDataSource(ds);
return factoryBean;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
//设置dao层的接口扫描
msc.setBasePackage("com.itheima.dao");
return msc;
}
}
- Spring配置类
@Configuration
@ComponentScan("com.itheima")//把bean加载到IOC容器
@PropertySource("classpath:jdbc.properties")
@Import(MybatisConfig.class)
@EnableAspectJAutoProxy //开启aop注解支持
public class SpringConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource getDataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
- Dao层
public interface AccountDao {
//转出
@Update("update account set money = money - #{money} where id = #{outId}")
public abstract int outMoney(@Param("outId") int outId,
@Param("money")double money);
//转入
@Update("update account set money = money + #{money} where id = #{inId}")
public abstract int inMoney(@Param("inId") int inId,
@Param("money")double money);
}
- 业务层
//接口
public interface AccountService {
//转账业务
public abstract void transfer(int outId,int inId,double money);
}
//实现类
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao dao;
@Override
public void transfer(int outId, int inId, double money) {
try {
//转出
dao.outMoney(outId, money);
//可能在转账过程中发生意外: 转出执行,转入还未执行
//int i = 1/0;
//转入
dao.inMoney(inId, money);
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class WebApp {
@Autowired
private AccountService service;
@Test
public void test01(){
service.transfer(1, 2, 500);
}
}
08_Spring事务管理对象
目标
- 能够写出Spring中事务管理对象API的基础代码
路径
- Spring事务核心对象介绍
- 事务管理器对象
- 事务定义对象
- 事务状态对象
Spring事务核心对象介绍
-
JavaEE开发使用分层设计的思想进行
一般dao层只做数据库增删改查实现,当业务中包含多个dao层的调用时,需要在service层开启事务,对数据层中多个操作进行组合并归属于同一个事务进行处理
-
Spring为业务层提供了整套的事务解决方案
- PlatformTransactionManager //平台事务管理器
- TransactionDefinition //事务定义
- TransactionStatus //事务状态
事务管理器对象
PlatformTransactionManager接口(平台事务管理器)
- DataSourceTransactionManager类
- 适用于SpringJDBC或MyBatis
- HibernateTransactionManager类
- 适用于Hibernate3.0及以上版本
- JpaTransactionManager类
- 适用于JPA (Java EE 标准之一,为POJO提供持久化标准规范,并规范了持久化开发的统一API,符合JPA规范的开发可以在不同的JPA框架下运行)
PlatformTransactionManager接口定义了事务的基本操作:
-
获取事务
TransactionStatus getTransaction(TransactionDefinition definition)
-
提交事务
void commit(TransactionStatus status)
-
回滚事务
void rollback(TransactionStatus status)
代码示例:
//1. 创建事务管理器
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
//2. 为事务管理器设置与数据层相同的数据源
dstm.setDataSource(dataSource);
事务定义对象
TransactionDefinition接口(事务定义)
-
DefaultTransactionDefinition类
DefaultTransactionDefinition td = new DefaultTransactionDefinition();
TransactionDefinition接口定义了事务的基本信息:
-
事务隔离级别
- spring默认使用和数据库一样的隔离级别
//设置事务的隔离级别 td.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT); //mysql默认隔离级别是REPEATABLE_READ //oracle默认隔离级别是READ_COMMITTED
-
事务是否只读
//设置是否为只读事务 td.setReadOnly(false); //false, 表示可读可写(适合增删改操作)【默认设置】 //true, 表示只读(适合查,效率高)
-
事务超时时间(单位:秒)
//设置超时时间 td.setTimeout(10);//超时时间为10秒 //默认值是-1, 表示永不超时
-
事务传播行为
//设置事务传播行为 (这个比较复杂,后续详解) td.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
示例代码:
//创建事务定义对象
DefaultTransactionDefinition td = new DefaultTransactionDefinition();
//设置事务隔离级别
td.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
//设置是否为只读事务
td.setReadOnly(false);
//设置超时时间
td.setTimeout(10);
//设置事务传播行为
td.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
事务状态对象
TransactionStatus接口(事务状态)
- 定义了事务在执行过程中某个时间点上事务对象的状态信息
获取事务是否处于新开启事务状态
boolean isNewTransaction();
获取事务是否处于完成状态
boolean isCompleted();
//获取事务是否处于回滚状态
boolean isRollbackOnly();
//获取事务是否具备回滚存储点
boolean hasSavepoint();
//设置事务处于回滚状态
void setRollbackOnly()
//刷新事务状态
void flush();
完整代码示例:
/* Spring提供的事务管理对象:
平台事务管理器对象 :PlatformTransactionManager接口
事务定义对象:TransactionDefinition接口
事务状态对象:TransactionStatus接口
*/
//1、创建事务管理器对象(Mybatis专用)
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
//为事务管理器设置数据源
dstm.setDataSource(dataSource);//数据库连接池对象和事务管理器绑定了
//2、创建事务定义对象
DefaultTransactionDefinition td = new DefaultTransactionDefinition();
//设置事务隔离级别
td.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
//设置是否为只读事务
td.setReadOnly(false);
//设置超时时间
td.setTimeout(10);
//设置事务传播行为
td.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
//3、创建事务状态对象 (用于控制事务执行[相当于开启事务])
TransactionStatus ts = dstm.getTransaction(td);
//省略:业务层调用dao层功能代码
// 提交事务
dstm.commit(ts);
// 回滚事务
dstm.rollback(ts);
简化:
//1、创建事务管理器对象
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
//2、把事务器对象和数据库连接池进行绑定
dstm.setDataSource(dataSource);
//3、开启事务
TransactionStatus ts = dstm.getTransaction(new DefaultTransactionDefinition());
//4、事务操作(业务)
业务层中的代码.....
//5、提交事务|回滚事务
dstm.commit(ts);
09_Spring事务-编程式事务
目标
- 了解Spring中编程式事务的使用
路径
- 使用编程式事务改造转账业务
spring事务管理有两种方式:
- 编程式(了解)
- 声明式(重点)
使用编程式事务改造转账业务
使用spring提供的api进行事务管理,对转账业务代码进行修改:
/*
TODO : Spring编程式事务
1. 解释: 用Spring中的事务相关API用编码的方式来实现事务
2. 但是这样实现不好: 耦合严重
1). 事务管理代码跟业务代码耦合在一起
2). 如果后续还有其他业务方法需要事务, 还得一个个编写事务代码,很冗余
3. 解决方案:
AOP
1). 特点: 在不惊动原始设计的前提下,增强方法
2). 概述: 无侵入性, 解耦
*/
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Autowired
private DataSource dataSource;
/**
* 功能: 转账
*
* @param outId 出款人id
* @param inId 收款人id
* @param money 转账金额
*/
@Override
public void transfer(int outId, int inId, double money) {
//1. 创建事务管理器
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
//为事务管理器设置与数据层相同的数据源!!!
dstm.setDataSource(dataSource);
//2. 创建事务定义对象 : 隔离级别/传播特性/超时时间...
DefaultTransactionDefinition td = new DefaultTransactionDefinition();
/*
设置事务隔离级别
0). spring默认隔离级别是跟数据库软件一致 (ISOLATION_DEFAULT)
1). mysql默认是REPEATABLE_READ
2). oracle默认是READ_COMMITTED
*/
td.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
/**
* 设置是否为只读事务
* 1). false,表示读写均可(默认设置,适合增删改操作)
* 2). true,表示只读(适合查,效率高)
*/
td.setReadOnly(false);
/**
* 设置超时时间
* 1). 默认值是-1, 表示永不超时
* 2). 单位是秒
*/
td.setTimeout(10);
/**
* 设置事务传播行为 (这个比较复杂,后续详解)
* 1. 一般增删改:REQUIRED (默认值)
* 2. 一般查询 SUPPORTS
*/
td.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
//3.创建事务状态对象,用于控制事务执行(了解) -> 相当于开启事务
TransactionStatus ts = dstm.getTransaction(td);
try {
//转出
accountDao.outMoney(outId, money);
//可能在转账过程中发生意外: 转出执行,转入还未执行
int i = 1 / 0;
//转入
accountDao.inMoney(inId, money);
//成功:事务提交
dstm.commit(ts);
} catch (Exception e) {
//e.printStackTrace();
//失败:事务回滚
dstm.rollback(ts);
}
}
}
以上代码中存在的问题:
- 事务管理代码跟业务代码耦合在一起(耦合严重)
- 如果后续还有其他业务方法需要事务, 还得一个个编写事务代码(过于冗余)
解决方案:AOP
- 无侵入性, 解耦(在不惊动原始代码设计的前提下添加事务管理)
10_Spring事务-AOP改造编程式事务
目标
- 了解使用Spring的AOP改造编程式事务的方式
路径
- 使用Spring的AOP改造编程式事务
使用Spring的AOP改造编程式事务
使用spring的aop改造案例中的事务管理代码:
- Spring配置类
@Configurable
@ComponentScan("com.itheima")
@PropertySource("classpath:db.properties")
@Import(MybatisConfig.class)
//开启AOP支持
@EnableAspectJAutoProxy
public class SpringConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource getDataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
- 修改业务类
- 注意:不要在转账业务代码中添加异常处理代码
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
/**
* 功能: 转账
*
* @param outId 出款人id
* @param inId 收款人id
* @param money 转账金额
*/
@Override
public void transfer(int outId, int inId, double money) {
/**
* 注意: 在aop使用中,切入点方法千万不能自己catch异常
* 原因: 如果切入点自己catch了异常,那么通知中是调用切入点的地方是不会感知到异常,就不会执行catch了
* (相当于异常通知失效)
* 解决方案:
* 方案1: 有异常直接抛出,不要catch
* 方案2: 可以catch,但是再new一个异常抛出
*/
//转出
accountDao.outMoney(outId, money);
//可能在转账过程中发生意外: 转出执行,转入还未执行
int i = 1 / 0;
//转入
accountDao.inMoney(inId, money);
}
}
- 切面类:事务管理
@Component
@Aspect
public class TransactionAdvice { //事务通知类
@Autowired
private DataSource dataSource;
//切入点
@Pointcut("execution(* com.itheima.service.impl.AccountServiceImpl.transfer(..))")
public void pt(){
}
//环绕通知
@Around("pt()")
public Object around(ProceedingJoinPoint pjp){
Object result = null;
//1. 创建事务管理器
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
//为事务管理器设置与数据层相同的数据源!!!
dstm.setDataSource(dataSource);
//2. 创建事务定义对象 : 隔离级别/传播特性/超时时间...
DefaultTransactionDefinition td = new DefaultTransactionDefinition();
/*
设置事务隔离级别
0). spring默认隔离级别是跟数据库软件一致 (ISOLATION_DEFAULT)
1). mysql默认是REPEATABLE_READ
2). oracle默认是READ_COMMITTED
*/
td.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
/**
* 设置是否为只读事务
* 1). false,表示读写均可(默认设置,适合增删改操作)
* 2). true,表示只读(适合查,效率高)
* /
td.setReadOnly(false);
/**
* 设置超时时间
* 1). 默认值是-1, 表示永不超时
* 2). 单位是秒
*/
td.setTimeout(10);
/**
* 设置事务传播行为 (这个比较复杂,后续详解)
* 1. 一般增删改:REQUIRED (默认值)
* 2. 一般查询 SUPPORTS
*/
td.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
//3.创建事务状态对象,用于控制事务执行 -> 相当于开启事务
TransactionStatus ts = dstm.getTransaction(td);
try {
//执行原始方式
result = pjp.proceed();
//成功:事务提交
dstm.commit(ts);
} catch (Throwable throwable) {
//throwable.printStackTrace();
System.out.println("转账业务异常!");
//失败:事务回滚
dstm.rollback(ts);
}
//返回值
return result;
}
}
11_Spring事务-声明式事务
目标
- 使用声明式事务解决转账业务
路径
- 声明式事务前言
- 声明式事务开发步骤
- 使用声明式事务实现转账业务
声明式事务前言
AOP配置事务不具备特例性(通俗的说:任何业务添加事务都是一样的操作)
Spring底层封装了事务通知类,直接配置即可用。称为:声明式事务
- 也就说, 之前案例中的TransactionAdvice类+相应的AOP配置可以不写了
声明式事务的开发步骤
声明式事务的开发步骤:
-
第1步:开启事务管理支持
@EnableTransactionManagement
-
第2步:配置事务管理器
@Bean public DataSourceTransactionManager getTxManager(DataSource dataSource){ //创建事务管理器 DataSourceTransactionManager manager = new DataSourceTransactionManager(); manager.setDataSource(dataSource);//给事务管理器设置数据源 return manager; }
-
第3步:配置需要事务支持的切入点
@Transactional( isolation = Isolation.DEFAULT, //使用数据库默认的隔离级别 readOnly = false, //不是只读事务(增删改查都可以) timeout = 10, //超时时间 propagation = Propagation.REQUIRED //设置事务传播行为 ) void transfer(int outId,int inId,double money);
使用声明式事务实现转账业务
代码示例:
- Spring配置类
@Configurable
@ComponentScan("com.itheima")
@PropertySource("classpath:db.properties")
@Import(MybatisConfig.class)
//@EnableAspectJAutoProxy //开启AOP支持
//声明式事务第1步:开启事务管理支持
@EnableTransactionManagement
public class SpringConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource getDataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
//声明式事务第2步:配置事务管理器
@Bean
public DataSourceTransactionManager getTxManager(DataSource dataSource){
DataSourceTransactionManager manager = new DataSourceTransactionManager();
manager.setDataSource(dataSource);
return manager;
}
}
- 业务层
//接口
public interface AccountService {
//声明式事务第3步:配置需要事务支持的切入点
@Transactional(
isolation = Isolation.DEFAULT,
readOnly = false,
timeout = 10,
propagation = Propagation.REQUIRED
)
//转账业务
public abstract void transfer(int outId, int inId, double money);
}
- @Transactional注解书写位置
- 可以直接放在接口上,表示该接口的所有方法都是切入点 (推荐)
- 可以放在接口方法上,表示该方法的所有重写方法都是切入点 (推荐)
- 放在实现类上,表示该实现类所有方法都是切入点
- 放在实现类的方法上,表示该方法是切入点
12_Spring事务传播行为
目标
- 能够讲出Spring中常用的事务传播行为
路径
- 什么是事务传播行为
- 事务传播行为的设置
- 事务传播行为的应用场景
- 事务传播行为示例
什么是事务传播行为
事务传播行为:
- 在一个业务流程(有事务的业务流)中,通常会调用多个方法执行,而每个方法对于事务的处理态度
事务传播行为的设置
@Transactional(
propagation = Propagation.REQUIRED //事务传播行为
)
public abstract void transfer(int outId, int inId, money money);
事务传播行为Propagation的取值:
- REQUIRED (默认的传播行为)
- 支持当前事务,如果不存在,就新建一个
- SUPPORTS
- 支持当前事务,如果不存在,就不使用事务
- REQUIRES_NEW
- 不论当前事务是否存在,都创建一个新的事务
- NOT_SUPPORTED
- 以非事务方式运行,如果有事务存在,挂起当前事务
- MANDATORY
- 支持当前事务,如果不存在,抛出异常
- NEVER
- 以非事务方式运行,如果有事务存在,抛出异常
- NESTED
- 如果当前事务存在,则嵌套事务执行(一个事务, 在A事务调用B过程中,B失败了,回滚事务到之前SavePoint ,用户可以选择提交事务或者回滚事务)
常用事务传播行为:
一般增删改:加事务 REQUIRED (默认取值)
一般查询:不加事务 SUPPORTS
非主要业务不对主业务造成影响:REQUIRES_NEW
事务传播行为的应用场景
应用场景举例:转账操作
转账业务:transfer()
- 转出转入 inAndOut() :X用户执行转出操作,修改表 , Y用户执行转入操作,修改表
- 记录日志 saveLog() :银行记录转账日志到数据库日志表中
业务逻辑:
1). 如果"转账"操作失败了, "转出转入"需要回滚
如果"转账"有事务,"转出转入"跟随即可, 如果"转账"没有事务,"转出转入"需要自己创建事务
所以"转出转入"适合设置传播行为属性为:REQUIRED
2). 如果"转账"操作失败了, "记录日志"不需要回滚,必须要执行
如果"转账"有事务, "记录日志"不跟随(创建新事务)
如果"转账"没有事务, "记录日志"创建事务
所以"记录日志"适合设置传播行为属性为:REQUIRES_NEW
事务传播行为示例
事务传播行为是发生在两个Bean之间的
代码示例:
- 数据表:
#日志表
create table db_log(
id int primary key auto_increment,
message varchar(20)
);
insert into db_log values(null,"测试");
- Mybatis配置类
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean getSqlSessionFactoryBean(DataSource ds){
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
//设置pojo的包扫描
factoryBean.setTypeAliasesPackage("com.itheima.pojo");
//设置连接池
factoryBean.setDataSource(ds);
return factoryBean;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
//设置dao层的接口扫描
msc.setBasePackage("com.itheima.dao");
return msc;
}
}
- Spring配置类
@Configurable
@ComponentScan("com.itheima")
@PropertySource("classpath:db.properties")
@Import(MybatisConfig.class)
//声明式事务第1步:开启事务管理支持
@EnableTransactionManagement
public class SpringConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource getDataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
//声明式事务第2步:配置事务管理器
@Bean
public DataSourceTransactionManager getTxManager(DataSource dataSource){
DataSourceTransactionManager manager = new DataSourceTransactionManager();
manager.setDataSource(dataSource);
return manager;
}
}
- 业务层
//接口
public interface AccountService {
//转账业务
public abstract void transfer(int outId, int inId, double money);
}
//实现类
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountServiceClass accountService;
/**
* 功能: 转账
*
* @param outId 出款人id
* @param inId 收款人id
* @param money 转账金额
*/
@Override
//声明式事务第3步:配置需要事务支持的切入点
@Transactional
public void transfer(int outId, int inId, double money) {
try{
//转出转入
accountService.inAndOut(outId,inId,money);
}finally {
//记录日志
accountService.saveLog();
}
}
}
//业务类
@Service
public class AccountServiceClass {
@Autowired
private AccountDao accountDao;
//转出转入
@Transactional(
propagation = Propagation.REQUIRED //默认值
)
public void inAndOut(int outId, int inId, float money){
//转出
accountDao.outMoney(outId, money);
//可能在转账过程中发生意外: 转出执行,转入还未执行
int i = 1 / 0;
//转入
accountDao.inMoney(inId, money);
}
//日志记录
@Transactional(
propagation = Propagation.REQUIRES_NEW //开启新事务
)
public void saveLog() {
accountDao.insertLog("转账日志");
}
}
- 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class WebApp {
@Autowired
private AccountService accountService;
@Test
public void test01(){
accountService.transfer(1, 2, 500);
}
}
标签:事务,day03,Spring,切入点,void,AOP,方法,public
From: https://www.cnblogs.com/-turing/p/17206358.html