引入
1、理解AOP的相关概念
2、理解动态代理的原理及使用
3、编写spring AOP中不同通知类型
4、应用spring AOP中的注解
5、熟练spring的事务管理的方式和常用接口
6、理解事务的隔离级别
7、掌握事务的传播行为
8、完成spring声明式事务案例
`xml的本质作用是什么?
做配置文件
动静分离:
动态代码:java代码,一有变动,就重新编译;
静态资源:配置文件(xml,properties文件)
好处:静态资源有修改,无需重新编译java代码,只需重启即可;
`xml文件对于java代码的意义?
编码时:
xml做配置文件,为的是将动态数据与java代码相分离.(松耦合)
执行时:
优先解析xml配置文件,将配置文件中的相关配置转存到java对象中,方便后续java代码直接调用
`直接的使用当前资源属于那一层,就使用那一层的注解
1.@Repository:数据访问层/持久层/数据层
2.@Service:业务逻辑层/服务层
3.@Controller:web层
4.@Configuration:给配置配的注解,等价于xml
5.@Component:组件层(不属于上述任何一层)
以上是我们自定义的资源,可以使用的注解;
如果使用了第三方的资源,建议在配置类中使用
@Bean注解,作用某个方法下,且方法必须有返回值(方法的返回值作为一个bean被ioc容器管理)
一、AOP概述
1、什么是AOP
AOP(Aspect Oriented Programming),即面向切面编程。
通过预编译方式(静态代理)或 运行期(动态代理)实现程序功能的统一维护(增强)
的一种技术。
AOP是OOP的延续,是软件系统开发中的一个热点,也是spring框架的一个重点。
利用AOP可以实现【业务逻辑】各个部分的隔离,从而使得业务逻辑各个部分的耦合性降低,
提高程序的可重用性,同时提高开发效率
简单理解:
AOP是【面向切面编程】,使用【动态代理】技术,实现在【不修改java源代码】的情况下,
运行时实现方法功能的【增强】
具象化理解:【具象化是抽象化的反义词】
理解切面编程,就需要先理解什么是切面。
生活中:
用刀把面包切成数片,切开的每一片面包的切口就是切面==》摸果酱、加蔬菜
菜,锅、炉子共同来完成炒菜,锅与炉子,菜与锅就是切面==》锅有没有热、菜有没有熟、 炉子火力够不够
编程中:
web层==>service层==>dao层,每一层之间也是一个切面,对象与对象之间,
方法与方法之间,模块与模块之间都是一个个切面。
2、AOP的优势及使用场景
我们一般做活动功能的时候,一般对每一个活动接口都会做 (活动是否有效)校验、
是不是需要用户登录校验。
这有个问题就是,有多少活动接口,就要多少次代码copy。对于一个“懒人”,这是不可容忍的。那么,提出一个公共方法,每个接口都来调用这个接口。这里有点切面的味道了。
同样有个问题,我虽然不用每次都copy代码了,但是,每个接口总得要调用这个方法吧。于是就有了切面的概念,我将方法注入到接口调用的某个地方(切点),而红框中就是面向切面编程。
在不修改业务代码的情况下,使用AOP进行切面编程,对原有功能进行【增强】
# 【1】优势
1、非侵入性:使用动态代理技术,不修改java源代码对已有方法功能进行增强
说明:
1、我们开发好了分享活动主业务代码
2、需求人员需要填活动校验的代码
3、我们有不想修改主业务代码掺杂活动校验的代码
4、此时我们可以用AOP的方式开发
2、高内聚:集中处理某一个关注点,方便维护
说明:
AOP的切面只关注活动校验的代码
3、易移植:可以方便的增加、删除、修改某一个关注点的切入业务
说明:
活动校验规则发生改变,我们可以及时修改,而不需要修改主业务代码
# 【2】使用场景
大多情况下,都是适用于非功能性需求中
- 权限控制
- 日志收集
- 缓存设置
- 分布式追踪
- 异常处理
- 事务控制
3、AOP实现原理
AOP的底层实现需要依赖于动态代理技术;
动态代理在程序运行期间,不修改源码对已有方法进行增强。
二、代理模式
1、代理模式概念
我很忙,忙的没空理你,那你要找我呢,就先找我的代理人吧,那代理人总要知道被代理人能做哪些事情不能做哪些事情吧,那就是两个人具备同一个接口,被代理人虽然不想干活,但是代理的人能干活呀。
生活中的房屋中介
2、代理模式分类
静态代理:
【定义】
在程序运行之前,代理类.class文件就已经被创建
【实现】
由程序员创建或特定工具自动生成源代码,在对其编译
动态代理:
【定义】
程序运行时通过反射机制动态创建的,对方法的【增强】,不需要修改源码
【实现】
基于接口:JDK动态代理
基于子类:CGLib动态代理
3、静态代理演示
静态代理:在程序运行前手动创建代理类,代理类和目标类需要实现相同接口
步骤:
1、创建项目;
2、定义接口:HouseAgencyCompany及接口中租房的方法:rentingHouse;
3、定义房主类:HouseOwner 中介类 HouseProxy均实现接口HouseAgencyCompany;
4、租客类Customer调用HouseProxy完成租房
定义HouseAgencyCompany
/**
* @Description:中介公司
*/
public interface HouseAgencyCompany {
/**
* @Description 租房子
*/
void rentingHouse();
}
定义HouseOwner、HouseProxy
/**
* @Description:被代理人(房东)
*/
public class HouseOwner implements HouseAgencyCompany {
@Override
public void rentingHouse() {
System.out.println("房东签合同");
}
}
/**
* @Description:中介(代理人)
*/
public class HouseProxy implements HouseAgencyCompany {
/**
* 被代理人
*/
private HouseOwner houseOwner;
public HouseProxy() {
this.houseOwner = new HouseOwner();
}
@Override
public void rentingHouse() {
System.out.println("中介带看房子");
System.out.println("中介约房东");
houseOwner.rentingHouse();
System.out.println("中介完成租房");
}
}
定义Customer
import org.junit.jupiter.api.Test;
/**
* @Description:租客
*/
public class Customer {
@Test
public void needHouse(){
HouseOwner houseOwner = new HouseOwner();
houseOwner.rentingHouse();
System.out.println("==================================");
HouseAgencyCompany houseAgencyCompany = new HouseProxy();
houseAgencyCompany.rentingHouse();
}
}
4、动态代理演示
# 【1】有什么问题?
如果目标类中有多个方法都需要增强,我们得为每一个服务都得创建代理类,工作量太大,
不易管理。同时接口一旦发生改变,代理类也得相应修改。
# 【2】动态代理概述
代理类在程序运行时创建的方式被成为动态代理。也就是说,代理类并不是在Java代码中定的,而是在运行时根据我们在Java代码中的动态生成的。
相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。
【1】jdk动态代理
必须基于接口的动态代理
1、JDK动态代理:基于接口的;
2、JDK动态代理实现要点:
Proxy类
newProxyInstance静态方法
InvocationHandler增强方法
java.lang.reflect.Proxy:
Java动态代理机制的主类,提供了一组静态方法来为一组接口动态地生成代理类及其实例。
//方法1: 该方法用于获取指定动态代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy)
//方法2:该方法用于获取关联于指定类装载器和一组接口的动态代理对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces)
//方法3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl)
//方法4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理对象:1、类加载器 2、接口数组、调用处理器(增强部分的业务代码)
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
java.lang.reflect.InvocationHandler:
调用处理器接口,它自定义了一个invoke方法,用于集中处理在动态代理对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理对象时都需要指定一个实现了该接口的调用处理器对象。
InvocationHandler的核心方法:
//该方法负责集中处理动态代理类上的所有方法调用。
//第一个参数是代理对象,第二个参数是被调用的方法对象,第三个方法是调用参数。
//调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行。
Object invoke(Object proxy, Method method, Object[] args)
需求:使用jdk动态代理的方式,增强租房方法,使得租房方法前后打印输入日志信息;
删除HouseProxy
修改Customer
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @Description:租客
*/
public class Customer {
HouseOwner houseOwner = new HouseOwner();
@Test
public void needHouse(){
HouseAgencyCompany houseProxy = (HouseAgencyCompany) Proxy.newProxyInstance(
houseOwner.getClass().getClassLoader(), //类加载器
houseOwner.getClass().getInterfaces(), //被代理类实现的接口字节码数组(代理类需要实现这些接口)
//new Class[]{HouseAgencyCompany.class},//被代理类实现的接口字节码数组
new InvocationHandler() { // 对需要增强的方法进行增强,对不需要增强的方法调用原来的逻辑
/**
* 每次调用代理类的方法时,此方法都会执行
* @param proxy : 生成的代理类对象(慎用)
* @param method : 当前所执行的方法的字节码对象
* @param args : 当前执行的方法所传递的参数
*/
@Override
public Object invoke(Object proxy,
Method method,
Object[] args) throws Throwable {
System.out.println("中介公司让中介带客户看房");
Object object = method.invoke(houseOwner, args);
System.out.println("中介公司让中介完成租房业务");
return object;
}
});
houseProxy.rentingHoues();
}
}
【2】cglib动态代理
# 思考
如果目标类没有实现接口呢?
那么就无法使用JDK的动态代理,因此这种方式有其局限性,必须实现一个接口。
可以使用的方案:
使用CGLIB动态代理:基于子类(包含本类)
# Cglib动态代理
1、基于类,无需实现接口;
2、被代理的目标类不能被final修饰
net.sf.cglib.proxy.Enhancer
Enhancer类是CGLib中的一个字节码增强器,作用用于生成代理对象,跟上一章所学的Proxy类相似,常用方式为:
//方法1:该方法用于为指定目标类、回调对象 1、类的类型,2、调用处理器
public static Object create(Class type, Callback callback)
net.sf.cglib.proxy.MethodInterceptor
//方法1:
Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
修改Customer
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.junit.Test;
import java.lang.reflect.Method;
/**
* @Description:
*/
public class Customer {
@Test
public void needHouse(){
System.out.println("=====================");
//代理模式下的租房
HouseOwner houseOwnerProxy = (HouseOwner) Enhancer.create(
HouseOwner.class, // 参数1:被代理对象的字节码
new MethodInterceptor() { // 参数2: 接口,在原方法执行前对方法进行拦截处理
/**
* @param o : 生成的代理类对象(被代理类的子类)
* @param method : 当前执行的方法的字节码对象
* @param objects : 当前执行的方法携带的参数数组
* @param methodProxy : 当前执行的方法的代理对象(不用管)
*/
@Override
public Object intercept(Object o, Method method,
Object[] objects, MethodProxy methodProxy) throws Throwable {
//增强
System.out.println("中介小王:带你看房子");
//返回结果
return methodProxy.invokeSuper(o, objects);
}
});
houseOwnerProxy.renttingHouse();
}
}
5、手写AOP演示
在手写的IOC的基础上手写自己的AOP
这里是手写IOC的文章
# 【1】思考
我们在自定义IOC中我们实例化对象采用反射机制,如果需要对bean增强应该怎么做?
实例化过程不是直接实例化目标类,而是通过代理工厂使用动态代理实例化代理类
# 【2】目标
手写aop
【1】使用动态代理实现增强打印信息(不修改saveAccount())
【2】理解什么时候被代理==>bean初始化的时候,就为其生成代理类
【代码演示】
步骤:
1、添加proxy层,添加CglibProxy实现MethodInterceptor、JdkProxy实现InvocationHandler
2、添加ProxyBeanFactory,根据目标类是否有接口判断选择jdk和cglib
3、修改BeanFactory
1、什么时候加入切面?
在初始化IOC的时候,我们使用动态代理的方式加入切面(AOP)
2、为什么使用AOP?
不需要修改业务,可以达到业务增强的目的
添加proxy层
jdk动态代理
package com.example.spring.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @Description:jdk动态代理
*/
public class JdkProxy implements InvocationHandler {
private Object targClass;
private String joinPoint = "saveAccount";
public JdkProxy(Object targClass) {
this.targClass = targClass;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object object = null;
if (joinPoint.equals(method.getName())){
System.out.println("JDK增强:"+method.getName());
}
object = method.invoke(targClass, args);
return object;
}
}
cglib动态代理
package com.example.spring.proxy;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @Description:cglib动态代理
*/
public class CglibProxy implements MethodInterceptor {
private Object targClass;
private String joinPoint = "saveAccount";
public CglibProxy(Object targClass) {
this.targClass = targClass;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object object = null;
if (joinPoint.equals(method.getName())){
System.out.println("Cglib增强:"+method.getName());
}
object = method.invoke(targClass, objects);
return object;
}
}
添加ProxyBeanFactory
package com.example.spring.factory;
import com.example.spring.proxy.CglibProxy;
import com.example.spring.proxy.JdkProxy;
import net.sf.cglib.proxy.Enhancer;
import java.lang.reflect.Proxy;
/**
* @Description:工厂bean的动态代理
*/
public class BeanProxyFactory {
//我为谁干活
private Object targClass;
//传入目标类
public BeanProxyFactory(Object targClass) {
this.targClass = targClass;
}
public Object getBean() {
//获得对应类的接口
Class<?>[] interfaces = targClass.getClass().getInterfaces();
Object object = null;
//接口数目大于0
if (interfaces.length>0){
//jdk代理
JdkProxy jdkProxy = new JdkProxy(targClass);
object = Proxy.newProxyInstance(targClass.getClass().getClassLoader(), interfaces, jdkProxy);
}else {
//cglib代理
CglibProxy cglibProxy = new CglibProxy(targClass);
object = Enhancer.create(targClass.getClass(), cglibProxy);
}
return object;
}
}
修改MyBeanFactory
package com.example.spring.factory;
import javafx.beans.binding.ObjectExpression;
import java.io.IOException;
import java.util.*;
/**
* @Description:bean工厂
*/
public class BeanFactory {
//1、事先存储容器
private static Map<String, Object> map = new HashMap<>();
//2、加载配置文件
static {
Properties properties = new Properties();
try {
properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("db.properties"));
Enumeration<?> enumeration = properties.propertyNames();
while (enumeration.hasMoreElements()) {
String key = (String) enumeration.nextElement();
String value = (String) properties.get(key);
//3、实例化bean
//Object beanObject = Class.forName(value).newInstance();
BeanProxyFactory beanProxyFactory = new BeanProxyFactory(Class.forName(value).newInstance());
Object beanObject = beanProxyFactory.getBean();
//4、放入容器
map.put(key,beanObject);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//5、公共获得bean
public static Object getBean(String calssName){
return map.get(calssName);
}
}
可以看出:在调用service、dao层业务,我们在不改动代码的情况下,达到了增强的目标
三、Spring中的AOP【重点】
通过前面课程我们手写了一个自己的AOP,下面我们对照自己的AOP去理解一下AOP相关术语
1、AOP相关术语
#Target(目标对象):
被代理的对象。
#Proxy(代理对象):
一个类被AOP织入增强后,即产生一个结果代理类。
#Joinpoint(连接点):
在spring中,连接点指的都是方法(指的是那些要被增强功能的候选方法)
#Pointcut(切入点):
切入点是指我们要对哪些Joinpoint进行拦截的定义。
#Advice(通知):
通知指的是拦截到Joinpoint之后要做的事情。即增强的功能
通知类型:前置通知、后置通知、异常通知、最终通知、环绕通知
#Weaving(织入):
织入指的是把增强用于目标对象。创建代理对象的过程
#Aspect(切面):
切面指的是切入点和通知的结合 , 切入点+通知=切面 ,目标方法和增强方法合到在一起叫做切面
Spring AOP底层实现技术就是动态代理
spring底层会自动抉择使用Jdk Proxy或Enhancer(cglib)
2、Spring-AOP中xml与注解
【1】xml的标签
属性 | 说明 |
---|---|
< aop:config > | 声明AOP |
<aop:aspect id=“唯一标识” ref=“通知bean的ID”> | 声明切面 |
<aop:pointcut id=“连接点ID” expression=“连接点表达式”>< /aop:pointcut > | 声明连接点 |
<aop:before method=“通知方法” pointcut-ref=“连接点ID”>< /aop:before > | 前置通知 |
<aop:after-returning method=“通知方法” pointcut-ref=“连接点ID”>< /aop:after-returning > | 后置通知 |
<aop:after-throwing method=“通知方法” pointcut-ref=“连接点ID”>< /aop:after-throwing > | 异常通知 |
<aop:after method=“通知方法” pointcut-ref=“连接点ID”>< /aop:after > | 最终通知 |
<aop:around method=“通知方法” pointcut-ref=“连接点ID”>< /aop:around > | 环绕通知 |
【2】JoinPoint 对象
JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,
就可以获取到封装了该方法信息的JoinPoint对象
方法名 | 功能 |
---|---|
Signature getSignature(); | 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,Class等信息 |
Object[] getArgs(); | 获取传入目标方法的参数对象 |
Object getTarget(); | 获取被代理的对象 |
Object getThis(); | 获取代理对象 |
【3】切点表达式
切入点表达式:
作用: 找到那些被增强的方法,类似正则表达式
格式:
修饰符 返回值 方法的全限定名(参数列表)
public void com.example.service.impl.AccountServiceImpl.delete()
说明:修饰符可省略不写,默认public
支持通配符的写法:
* : 表示任意字符串
.. : 任意重复次数
execution表达式
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
注:
通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
* com.example.spring.service.impl.*.*(..)
对象匹配
语法:
bean(accountService) //匹配名为accountService的bean
bean(*Service) //匹配Spring容器中所有以Service结尾的bean
例如:
<aop:pointcut id="logPointcut" expression="bean(accountController)"></aop:pointcut>
阅读练习:
execution(* *(..)) //说明:第一个*表示任意返回值类型,第一个*匹配任意类,..匹配任意参数,该表达式表示匹配任意类的任意方法(简化书写方式,很少用到)
execution(* *..*(..))//同上
execution(* *..*.*(..))//同上,更符合书写习惯,第一个*表示匹配任意返回值类型,*..表示匹配任意包名称及其任意子包,第三个*匹配任意类,第四个匹配任意方法
execution(public * *..*.*(..))//表示匹配任意public修饰符的方法
execution(public int *..*.*(..))//表示陪陪任意返回值是int类型且修饰符是public的方法
execution(public void *..*.*(..))//匹配任意返回值是void且修饰符是public的方法
execution(public void com..*.*(..))//匹配com包及其任意子包下的被pulic修饰的返回值是void的方法
execution(public void com..service.*.*(..))//匹配com包下任意子包下存在service包下的任意类的任意方法,且方法为void 被public修饰
execution(public void com.example.service.*.*(..))
execution(public void com.example.service.User*.*(..))//侧重点:任意一User开头的类下的任意void且被public修饰的方法
execution(public void com.example.service.*Service.*(..))//侧重点:任意一Service结尾的类下的任意方法
execution(public void com.example.service.UserService.*(..))//匹配UserService类下的任意方法...
execution(public User com.example.service.UserService.find*(..))//侧重点:匹配UserService类下的任意以find开头的任意方法...
execution(public User com.example.service.UserService.*Id(..))//侧点:匹配UserService类下的任意以id结尾的任意方法...
execution(public User com.example.service.UserService.findById(..))//侧重点:匹配UserService类下方法名为findById的任意参数的方法....
execution(public User com.example.service.UserService.findById(int))//精准匹配,方法入参只有一个int类型
execution(public User com.example.service.UserService.findById(int,int))//精准匹配,方法入参包含2个int类型
execution(public User com.example.service.UserService.findById(int,*))//2个入参,第一个必须int,第二个任意类型
execution(public User com.example.service.UserService.findById(*,int))//2个入参,第一个任意类型,第二个必须int类型
execution(public User com.example.service.UserService.findById())//精准匹配无参
execution(List com.example.service.*Service+.findAll(..))//匹配任意以Service结尾的接口或者实现类下方法名为findAll的任意入参方法
【4】通知类型
# 5种通知类型
1、前置通知:
在目标方法执行前执行
2、后置正常通知:
在目标方法正常返回后执行。它和异常通知只能执行一个
3、异常通知:
在目标方法发生异常后执行。它和后置通知只能执行一个
4、最终通知:
无论目标方法正常返回,还是发生异常都会执行
5、环绕通知:
在目标方法执行前后执行该增强方法
xml配置
xml配置 | 说明 |
---|---|
<aop:before method=“通知方法” pointcut-ref=“连接点ID”>< /aop:before > | 前置通知 |
<aop:after-returning method=“通知方法” pointcut-ref=“连接点ID”>< /aop:after-returning > | 后置通知 |
<aop:after-throwing method=“通知方法” pointcut-ref=“连接点ID”>< /aop:after-throwing > | 异常通知 |
<aop:after method=“通知方法” pointcut-ref=“连接点ID”>< /aop:after > | 最终通知 |
<aop:around method=“通知方法” pointcut-ref=“连接点ID”>< /aop:around > | 环绕通知 |
注解配置
1、开启包扫描
<!-- 开启需要扫描的包 -->
<context:component-scan base-package="com.example"></context:component-scan>
<!-- 声明当前项目可以使用AOP的注解 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2、在切面类中添加相关注解
注解配置:
@Aspect : 声明切面类
@PonitCut:定义公共的切入点
配置到空方法上,value:切入点表达式
引用:方法名()
配置通知类型:
@Before() : 前置通知
@AfterReturning() : 后置通知
@AfterThrowing() :异常通知
@After() :最终通知
@Around() :环绕通知
注解XML对照表
xml | 注解 | 说明 |
---|---|---|
<aop:aspect > | @Aspect | 声明切面 |
<aop:before > | @Before | 前置通知 |
<aop:after-returning > | @AfterReturning | 后置正常通知 |
<aop:after-throwing > | @AfterThrowing | 后置异常通知 |
<aop:after > | @After | 最终通知 |
<aop:around > | @Around | 环绕通知 |
@Before、@AfterReturning、@AfterThrowing、@After、@Around可以直接配置切入点
@Before(value = "execution(* *..AccountServiceImpl.queryAll())")
【注解生效】
在xml中配置:
<!--开启AOP注解-->
<aop:aspectj-autoproxy />
XML | 注解 | 说明 |
---|---|---|
<aop:aspectj-autoproxy /> | @EnableAspectJAutoProxy | 开启注解AOP |
全注解
@Configuration // 声明此类为Spring的配置类
@ComponentScan("com.itheima") // 设置需要扫描的包(开启对Spring注解的支持)
@EnableAspectJAutoProxy // 开启对AOP注解的支持
public class SpringConfig {
}
/**
* 此类我们称之为切面类,在切面类中存放有各种通知
* 切面 = 切入点 + 通知
* @Aspect : 声明此类为切面类
*/
@Aspect
@Component
public class MyAspect {
}
3、案例演示【方法增强】
- XML方式
- XML+注解方式
- 注解方式
案例: 对AccountServiceImpl下的方法进行增强
步骤如下:
1.导入相关jar坐标;
2.自己封装要增强方法,并保存在某个专用类中【切面类/通知类/增强类】
3.将所有进行AOP操作的资源【切面类+目标对象】加载到IOC容器中;
3.编写配置文件 / 注解
告诉spring【切面类中的增强方法】在哪个对象的哪个方法上增强使用;
【1】xml方式实现AOP
具体步骤
1、导入jar包坐标
pom.xml
<dependencies>
<!-- spring核心jar包,已经依赖的AOP的jar -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- spring整合单元测试的jar -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- 单元测试的jar -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- 切入点表达式 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8</version>
</dependency>
</dependencies>
2、applicationContext.xml配置
<!-- 将目标类(被增强的类)存放到IOC容器中 -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!-- 将切面类加载到IOC容器中 -->
<bean id="myAspect" class="com.itheima.aspect.MyAspect"></bean>
<!-- 面向切面编程====织入 -->
<aop:config>
<!-- 切入点表达式 -->
<!-- 指定切面类 -->
<aop:aspect ref="myAspect">
<aop:before method="before"
pointcut="execution(public * com.itheima.service.impl.AccountServiceImpl.*(..))"></aop:before>
<aop:after-returning method="afterReturning"
pointcut="execution(public * com.itheima.service.impl.AccountServiceImpl.*(..))"></aop:after-returning>
<aop:after-throwing method="afterThrowing"
pointcut="execution(public * com.itheima.service.impl.AccountServiceImpl.*(..))"></aop:after-throwing>
<aop:after method="after"
pointcut="execution(public * com.itheima.service.impl.AccountServiceImpl.*(..))"></aop:after>
<aop:around method="around" pointcut="execution(public * com.itheima.service.impl.AccountServiceImpl.*(..))"></aop:around>
</aop:aspect>
</aop:config>
3、搭建目标对象
package com.example.service.impl;
import com.example.service.AccountService;
public class AccountServiceImpl implements AccountService {
@Override
public String findAll() {
System.out.println("findAll....");
return "findAll";
}
@Override
public void save(String str) {
System.out.println("save...."+str);
}
@Override
public void update() {
System.out.println("update....");
}
@Override
public void delete() {
System.out.println("delete....");
System.out.println(1/0);
}
}
4、编写通知类(切面类)
package com.example.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 此类我们称之为切面类,在切面类中存放有各种通知
* 切面 = 切入点 + 通知
*/
public class MyAspect {
/**
* 前置通知调用的方法
*/
public void before(){
System.out.println("前置通知:11111111111111");
}
/**
* 后置通知调用的方法
*/
public void afterReturning(){
System.out.println("后置通知:22222222222222");
}
/**
* 异常通知调用的方法
*/
public void afterThrowing(){
System.out.println("异常通知:33333333333333");
}
/**
* 最终通知调用的方法
*/
public void after(){
System.out.println("最终通知:44444444444444");
}
/**
* 环绕通知
* @param joinPoint 连接点 ---> 切入点
* ProceedingJoinPoint: method
* @return
*/
public Object around(ProceedingJoinPoint joinPoint){
Object result = null;
try {
System.out.println("前置增强啊--111111111111");
// 让原方法执行 method.invoke()
result = joinPoint.proceed();
System.out.println("后置增强啊--222222222222");
} catch (Throwable throwable) {
//throwable.printStackTrace();
System.out.println("异常增强啊--333333333333");
}finally {
System.out.println("最终增强啊--444444444444");
}
return result;
}
}
5、编写测试类
package com.example.test;
import com.example.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Test01 {
@Autowired
private AccountService service;
@Test
public void test(){
service.delete();
}
}
【2】xml+注解实现AOP
xml: jar包中的类使用xml
注解: 我们自己写的类使用注解
使用步骤:
1、导入jar包坐标
<dependencies>
<!-- spring核心jar包,已经依赖的AOP的jar -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- spring整合单元测试的jar -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- 单元测试的jar -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- 切入点表达式 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8</version>
</dependency>
</dependencies>
2、applicationContext.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"
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">
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!-- 开启对Spring-AOP注解的支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
2.搭建环境
接口
package com.example.service;
public interface AccountService {
String findAll();
void save(String str);
void update();
void delete();
}
实现类
package com.example.service.impl;
import com.example.service.AccountService;
import org.springframework.stereotype.Service;
@Service
public class AccountServiceImpl implements AccountService {
@Override
public String findAll() {
System.out.println("findAll....");
return "findAll";
}
@Override
public void save(String str) {
System.out.println("save...."+str);
}
@Override
public void update() {
System.out.println("update....");
}
@Override
public void delete() {
System.out.println("delete....");
//System.out.println(1/0);
}
}
3.编写通知类(切面类)
package com.example.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 此类我们称之为切面类,在切面类中存放有各种通知
* 切面 = 切入点 + 通知
* @Aspect : 声明此类为切面类
*/
@Aspect
@Component
public class MyAspect {
/**
* 配置切入点表达式
* 调用切入点表单时,直接使用 方法名称()
* pt()
*/
@Pointcut("execution(* com.itheima.service.*.AccountServiceImpl.*(..))")
public void pt(){}
/**
* 前置通知调用的方法
*/
//@Before("pt()")
public void before(){
System.out.println("前置通知:11111111111111");
}
/**
* 后置通知调用的方法
*/
//@AfterReturning("pt()")
public void afterReturning(){
System.out.println("后置通知:22222222222222");
}
/**
* 异常通知调用的方法
*/
//@AfterThrowing("pt()")
public void afterThrowing(){
System.out.println("异常通知:33333333333333");
}
/**
* 最终通知调用的方法
*/
//@After("pt()")
public void after(){
System.out.println("最终通知:44444444444444");
}
/**
* 环绕通知
* @param joinPoint 连接点 ---> 切入点
* ProceedingJoinPoint: method
* @return
*/
@Around("pt()")
public Object around(ProceedingJoinPoint joinPoint){
Object result = null;
try {
System.out.println("前置增强啊--111111111111");
// 让原方法执行 method.invoke()
result = joinPoint.proceed();
System.out.println("后置增强啊--222222222222");
} catch (Throwable throwable) {
//throwable.printStackTrace();
System.out.println("异常增强啊--333333333333");
}finally {
System.out.println("最终增强啊--444444444444");
}
return result;
}
}
5.编写测试类
package com.example.web;
import com.example.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class WebClient {
@Autowired
private AccountService service;
@Test
public void test01(){
service.delete();
// service.update();
}
}
【3】全注解方式实现AOP
1、新建配置类
@Configuration // 声明此类为Spring的配置类
@ComponentScan("com.itheima") // 设置需要扫描的包(开启对Spring注解的支持)
@EnableAspectJAutoProxy // 开启对AOP注解的支持
public class SpringConfig {
}
2、切面类
package com.example.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 此类我们称之为切面类,在切面类中存放有各种通知
* 切面 = 切入点 + 通知
* @Aspect : 声明此类为切面类
*/
@Aspect
@Component
public class MyAspect {
/**
* 配置切入点表达式
* 调用切入点表单时,直接使用 方法名称()
* pt()
*/
@Pointcut("execution(* com.itheima.service.*.AccountServiceImpl.*(..))")
public void pt(){}
/**
* 前置通知调用的方法
*/
//@Before("pt()")
public void before(){
System.out.println("前置通知:11111111111111");
}
/**
* 后置通知调用的方法
*/
//@AfterReturning("pt()")
public void afterReturning(){
System.out.println("后置通知:22222222222222");
}
/**
* 异常通知调用的方法
*/
//@AfterThrowing("pt()")
public void afterThrowing(){
System.out.println("异常通知:33333333333333");
}
/**
* 最终通知调用的方法
*/
//@After("pt()")
public void after(){
System.out.println("最终通知:44444444444444");
}
/**
* 环绕通知
* @param joinPoint 连接点 ---> 切入点
* ProceedingJoinPoint: method
* @return
*/
@Around("pt()")
public Object around(ProceedingJoinPoint joinPoint){
Object result = null;
try {
System.out.println("前置增强啊--111111111111");
// 让原方法执行 method.invoke()
result = joinPoint.proceed();
System.out.println("后置增强啊--222222222222");
} catch (Throwable throwable) {
//throwable.printStackTrace();
System.out.println("异常增强啊--333333333333");
}finally {
System.out.println("最终增强啊--444444444444");
}
return result;
}
}
3、测试类
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author cfl
* @description
* @date 2021-05-23
*/
@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(locations = "classpath:applicationContext.xml.bak")
@ContextConfiguration(classes = SpringConfig.class)
public class AopTest {
@Autowired
private AccountService accountService;
/**
* 测试前置增强
*/
@Test
public void test1() {
accountService.delete();
accountService.save("张三");
}
}
四、Spring的事务管理【重点】
1、事务概念
#【1】事务的定义
是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作,这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合
#### 【2】事务的ACID原则
事务具有4个基本特性:原子性、一致性、隔离性、持久性。也就是我们常说的ACID原则
`原子性(Atomicity):
一个事务已经是一个不可再分割的工作单位。事务中的全部操作要么都做;要么都不做
例如:A和B两个人一共1000元,A给B转账100元,A付款100元,B收款100元,
A的付款行为和B的收款行为要么都成功,要么都失败。
`一致性(Consistency):
事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。
例如:A和B两个人一共1000元,无论A,B两人互相转账多少次,A和B两个人总额都应该是1000元
`隔离性(Isolation):
事务允许多个用户对同一个数据进行并发访问,而不破坏数据的正确性 和完整性。同时,并行事务的修改必须与其他并行事务的修改相互独立。
例如:万达影院有《叶问4》电影票100张,允许所有人同时去淘票票上购票,当第100张电影票被A,B,C这三人同时购买,如果A拿到第100张电影票,但是还在犹豫要不要付钱,则B,C必须等待A的决定,如果A决定付钱,B.C就无法抢到票,如果A超时不付钱,则这第100张电影票回归票池,从新分配。
`持久性(Durability):
一个事务一旦提交,它对数据库中数据的改变会永久存储起来。其他操作不会对它产生影响
例如:万达影院有《叶问4》电影票100张,100张电影票销售完毕,对于每个购买者来说,他的购买记录已经产生,即使退票,他之前的购买记录也不会消失。
2、事务隔离级别
## 为什么要事务隔离?
`事务隔离就是帮助我们解决:脏读、不可重复读、幻读(虚读)
`脏读:
在一个事务中读取到了另外一个事务修改的【未提交的数据】,而导致多次读取同一个数据返回的结果不一致 (必须要解决的)
例如:
1.事务1,小明的原工资为1000, 财务人员将小明的工资改为了8000【但未提交事务】
2.事务2,小明读取自己的工资 ,发现自己的工资变为了8000,欢天喜地!
3.事务1,财务发现操作有误,回滚了操作,小明的工资又变为了1000
像这样,小明读取的工资数8000是一个脏数据
`不可重复读:
在一个事务中读取到了另外一个事务修改的【已提交的数据】,而导致多次读取同一个数据返回的结果不一致
例如:
1.事务1,小明读取了自己的工资为1000,操作还没有完成
2.事务2,这时财务人员修改了小明的工资为2000,并提交了事务.
3.事务1,小明再次读取自己的工资时,工资变为了2000
`幻读(虚读):
一个事务读取了几行记录后,另一个事务插入一些记录,幻读就发生了。再后来的查询中,第一个事务就会发现有些原来没有的记录
例如:
1.事务1,财务统计所有工资为5000的员工有10人。
2.事务2,人事向user表插入了一条员工记录,工资也为5000
3.事务1,财务再次读取所有工资为5000的员工 共读取到了11条记录,明明刚刚是10人啊?产生幻觉了?
3、Spring事务隔离级别
隔离级别 | 说明 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
ISOLATION_DEFAULT | spring默认数据库的隔离级别 | – | – | – |
ISOLATION_READ_UNCOMMITTED | 读未提交 | √ | √ | √ |
ISOLATION_READ_COMMITTED | 读已提交 | × | √ | √ |
ISOLATION_REPEATABLE_READ | 可重复读 | × | × | √ |
ISOLATION_SERIALIZABLE | 序列化操作 | × | × | × |
隔离级别由低到高【读未提交】=>【读已提交】=>【可重复读】=>【序列化操作】
1、对大多数数据库来说采用:READ_COMMITTED(读已提交)
2、MySQL默认采用:REPEATABLE_READ(可重复读),
3、Oracle采用READ__COMMITTED()
4、Spring的隔离级别默认数据库的隔离级别:ISOLATION_DEFAULT
4、事务传播行为
事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
# 事务传播行为的配置
只要增删改: 加事务 REQUIRED
只要查询: 不加事务 SUPPORTS
非主要业务不对主业务造成影响:REQUIRES_NEW
必须在有事务的环境运行:MANDATORY
# 事务超时时间
int TIMEOUT_DEFAULT = -1
以秒为单位进行设置。如果设置为-1(默认值),表示没有超时限制。在企业项目中使用默认值即可
# 是否只读
boolean isReadOnly():
true为只读,
false为读写。
设置规则
增删改事务:false
查询事务:true
传播行为 | 说明 |
---|---|
REQUIRED | 当前如果有事务,Spring就会使用该事务;否则会开始一个新事务(增、删、改) |
SUPPORTS | 当前如果有事务,Spring就会使用该事务;否则不会开始一个新事务(查询) |
MANDATORY | 当前如果有事务,Spring就会使用该事务;否则会抛出异常 |
REQUIRES_NEW | 当前如果有事务,把当前事务挂起,新建事务 |
NOT_SUPPORTED | 当前有事务,Spring也会在非事务环境下执行。如果当前有事务,则该事务挂起 |
NEVER | 当前有事务,Spring也会在非事务环境下执行。如果当前有事务,则抛出异常 |
NESTED | 当前有事务,则在嵌套事务中执行。如果没有,那么执行情况与REQUIRED一样 |
【默认】REQUIRED:当前如果有事务,Spring就会使用该事务;否则会开始一个新事务【有事务】
SUPPORTS:当前如果有事务,Spring就会使用该事
MANDATORY:当前如果有事务,Spring就会使用该事务;否则会抛出异常
REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
NOT_SUPPORTED:Spring不会执行事务中的代码。代码总是在非事务环境下执行,如果当前有事务,则该事务挂起
NEVER:即使当前有事务,Spring也会在非事务环境下执行。如果当前有事务,则抛出异常
NESTED: 如果当前存在事务,则在嵌套事务内执行,如果当前不存在事务,则创建一个新的事务,同REQUIRED
嵌套事务:
使用数据库中的保存点来实现,即嵌套事务回滚不影响外部事务,但外部事务回滚将导致嵌套事务回滚
5、Spring事务API介绍
PlatformTransactionManager接口
作用:PlatformTransactionManager【事务平台管理器】:是一个接口,
定义了获取事务、提交事务、回滚事务的接口
public interface PlatformTransactionManager {
//根据事务定义TransactionDefinition,获取事务
TransactionStatus getTransaction(TransactionDefinition definition);
//提交事务
void commit(TransactionStatus status);
//回滚事务
void rollback(TransactionStatus status);
}
PlatformTransactionManager实现类
作用:事务平台管理器PlatformTransactionManager定义了标准,他有如下经常使用的实现
实现类 | 说明 |
---|---|
org.springframework.jdbc.datasource.DataSourceTransactionManager | DataSource 数据源的事务 |
org.springframework.orm.hibernateX.HibernateTransactionManager | Hibernate 事务管理器 |
org.springframework.orm.jpa.JpaTransactionManager | JPA 事务管理器 |
org.springframework.transaction.jta.JtaTransactionManager | 多个数据源的全局事务管理器 |
org.springframework.orm.jdo.JdoTransactionManager | JDO 事务管理器 |
TransactionDefinition接口
作用:TransactionDefinition【事务定义信息】,是一个接口,
定义了事务隔离级别、事务传播行为、事务超时时间、事务是否只读
①事务隔离级别
事务:逻辑上的一组操作要么同时成功,要么同时失败
特性:ACID 原子性,一致性,隔离性,持久性
在不考虑隔离性的前提下:
脏读: 一个事务读取到了另一个事务中尚未提交的数据
不可重复度: 一个事务中两次读取的数据内容不一致,要求的是一个事务中多次读取时数据是一致的,这是事务update时引发的问题
虚读,幻读: 一个事务中两次读取的数据的数量不一致,要求在一个事务多次读取的数据的数量是一致的,这是insert或delete时引发的问题
---------
解决:设置隔离级别
Read unCommitted :什么都解决不了
Read committed :解决脏读 Oracle
Repeatable Read :解决脏读和不可重复读 Mysql
Serializable :解决所有但是不用
一般使用默认值
②超时时间
使用默认值 -1, 意思为,什么时候执行完,什么时候提交或回滚事务
③事务的传播行为
在一个业务中事务是可以相互传播的
REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务,加入到这个事务中。一般的选择(默认值) 针对增删改
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务) 针对查询
④是否是只读事务
只读事务 :对查询有效(查询)
非只读事务 :对增删改有效(增删改)
readOnly :true(只读事务)或 false(非只读事务)
重要★★
增删改操作配置:
传播行为:REQUIRED
是否只读:false
查询操作配置:
传播行为:SUPPORTS
是否只读:true
public interface TransactionDefinition {
/**********************事务传播行为类型常量***********************************/
//事务传播行为类型:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。
int PROPAGATION_REQUIRED = 0;
//事务传播行为类型:支持当前事务,如果当前没有事务,就以非事务方式执行。
int PROPAGATION_SUPPORTS = 1;
//事务传播行为类型:当前如果有事务,Spring就会使用该事务;否则会抛出异常
int PROPAGATION_MANDATORY = 2;
//事务传播行为类型:新建事务,如果当前存在事务,把当前事务挂起。
int PROPAGATION_REQUIRES_NEW = 3;
//事务传播行为类型:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
int PROPAGATION_NOT_SUPPORTED = 4;
//事务传播行为类型:即使当前有事务,Spring也会在非事务环境下执行。如果当前有事务,则抛出异常
int PROPAGATION_NEVER = 5;
//事务传播行为类型:如果当前存在事务,则在嵌套事务内执行。
int PROPAGATION_NESTED = 6;
/**********************事务隔离级别常量***********************************/
//MySQL默认采用ISOLATION_REPEATABLE_READ,Oracle采用READ__COMMITTED级别。)
//隔离级别:默认的隔离级别()
int ISOLATION_DEFAULT = -1;
//隔离级别:读未提交(最低)
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
//隔离级别:读提交
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
//隔离级别:可重复度
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
//隔离级别:序列化操作(最高)
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
//默认事务的超时时间
int TIMEOUT_DEFAULT = -1;
----------------------------事务方法--------------------------------
//获取事务的传播行为
int getPropagationBehavior();
//获取事务的隔离级别
int getIsolationLevel();
//获取超时时间
int getTimeout();
//是否只读
boolean isReadOnly();
//事务名称
String getName();
}
TransactionStatus接口
作用: TransactionStatus 用于保存当前事物状态 ,
定义了一个简单的控制事务执行和查询事务状态的方法
public interface TransactionStatus extends SavepointManager, Flushable {
//是否一个新的事务
boolean isNewTransaction();
//是否有存储点(存储过程中用到)
boolean hasSavepoint();//
//将事务设置为只能回滚,不允许提交
void setRollbackOnly();
//查询事务是否已有回滚标志
boolean isRollbackOnly();
//刷新事务
void flush();
//查询事务是否结束
boolean isCompleted();
}
6、Spring声明事务的实现
# Spring声明式事务
是建立在AOP之上的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,
在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务最大的优点就是不需要通过编程的方式管理事务,
这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明即可。
# 编程式事务(很少使用)
相比声明式事务的非侵入式的开发方式,编程式事务需要自己通过编程式事务管理使用TransactionTemplate
或者直接使用底层的PlatformTransactionManager来实现事务
# 思考
1、声明式事务是依赖于AOP的,他如何依赖的?
2、在J2EE的三层模式中,我们的事务管理放在那一层更合适?
# 分析
【dao层】
事务加在dao层,那么只要与数据库做增删改,就需要提交一次事务;
如此做事务的特性就发挥不出来,特别是事务的一致性,当出现并发问题时,从数据库查到的数据都有可能存在偏差。
【service层】
一般情况下,service层调用多个dao层的方法,在service添加事务,使用一个事务处理多次数据库的请求,事务的特性也会充分的发挥出来。
【controller层】
此层主要是控制层,负责的是请求响应,组装数据,返回响应结果,而并不处理具体业务逻辑
【画外音】
记住一句话:千千万万不要在controller中直接使用dao,要不你会掉大坑里面!!!
Spring事务工作流程
1、Spring框架进行事务的管理,首先使用TransactionDefinition对事务进行定义。
2、PlatformTransactionManager根据TransactionDefinition的定义信息进行事务的管理。
3、在事务管理过程中产生一系列的状态:保存到TransactionStatus中。
配置前先看一下要用到哪些标签。
spring-tx标签说明
# 建立事务通知与切点关系
<aop:advisor advice-ref="txAdvice" pointcut-ref="切点ID"></aop:advisor >
# 配置通知
<tx:advice id="txAdvice" transaction-manager="transactionManager">
# 声明事务属性
<tx:attributes> </tx:attributes>
# 执行对应的方法时,采用此处的事务配置(策略)
name:方法名称可以使用通配符
isolation:事务隔离级别
propagation:事务传播行为
read-only:是否只读事务
timeout:配置事务超时
rollback-for:发生异常时,回滚
no-rollback-for:发生异常时不回滚
<tx:method name="do*" propagation="REQUIRED" read-only="false"/>
案例演示
# 案例需求
模拟 姓名:tom 向 姓名:rose 转账100元,分别查看在正常转账下的和异常转账下的数据库中的数据结果。
# 三种方式实现
xml
xml+注解
注解
# 事务须知
事务:逻辑上的一组操作要么同时成功,要么同时失败
javaEE采用分层思想,我们一般将事务放在service层(业务层)
在业务开始之前开启手动事务,在业务执行完毕后提交或回滚事务,最终归还连接到连接池
Spring框架为我们提供了一组事务操作方法,存放这些方法的类,我们称之为切面类.
MyAspect: 我们自己定义的增强类---切面类
在Spring的事务控制中,切面类Spring已经为我们提供好了,
我们直接调用即可
1.什么是事务?
逻辑上的一组操作要么同时成功,要么同时失败
2.事务在java代码中使用在service层
3.AOP事务控制时,Spring提供了切面类,在切面类中存放有事务的相关通知
【1】xml方式实现
1、导入jar包坐标
pom.xml
<dependencies>
<!-- spring核心jar包,已经依赖的AOP的jar -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- jdbcTemplate -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.14</version>
</dependency>
<!-- spring整合单元测试的jar -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- 单元测试的jar -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- 切入点表达式 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
</dependencies>
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.username=root
jdbc.password=root
2、xml配置
applicationContext.xml
<!-- 目标对象: 创建Service层对象 -->
<bean id="AccountService" class="com.example.service.impl.AccountServiceImpl">
<property name="accountDao" ref="AccountDao"></property>
</bean>
<!-- 创建dao层对象 -->
<bean id="AccountDao" class="com.example.dao.impl.AccountDaoImpl">
<property name="template" ref="JdbcTemplate"></property>
</bean>
<!-- 创建JdbcTemplate对象 -->
<bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 创建连接池 -->
<!-- 解析propries配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- ============配置声明式事务=============== -->
<!-- TODO:配置事务管理平台,在事务管理平台对象中存放有各种通知 -->
<!-- 开启手动事务,提交事务,回滚事务.... -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- TODO:配置事务管理平台的 顾问 -->
<!-- 注意: 约束别导错了 -->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 在执行对应的方法时,采用此处的配置 -->
<!--
read-only: 设置是否为只读事务
false
timeout: 设置超时时间(秒)
-1
propagation: 传播行为
REQUIRED
isolation: 隔离级别
REPEATABLE_READ
-->
<tx:method name="*" read-only="false" propagation="REQUIRED" />
<tx:method name="find*" read-only="true" timeout="-1" propagation="SUPPORTS" isolation="REPEATABLE_READ"/>
<tx:method name="query*" read-only="true" propagation="SUPPORTS" />
</tx:attributes>
</tx:advice>
<!-- TODO:配置织入 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut id="pt" expression="execution(* com.example.service.impl.*.*(..))"></aop:pointcut>
<!-- 方法增强时找顾问 -->
<aop:advisor advice-ref="myAdvice" pointcut-ref="pt"></aop:advisor>
</aop:config>
3、代码演示
pojo类 【数据库表–》根据Account类属性建立相应字段】
package com.example.pojo;
import lombok.*;
import java.util.AbstractCollection;
@Data//setter getter tostring
@AllArgsConstructor
@NoArgsConstructor
public class Account {
private Integer id;
private String name;
private Float money;
public static void main(String[] args) {
Account account = new Account(1,"张三",100f);
System.out.println(account.toString());
}
// 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 Float getMoney() {
// return money;
// }
//
// public void setMoney(Float money) {
// this.money = money;
// }
//
// @Override
// public String toString() {
// return "Account{" +
// "id=" + id +
// ", name='" + name + '\'' +
// ", money=" + money +
// '}';
// }
}
----------dao接口-----------
package com.example.dao
public interface AccountDao {
Account findByName(String name);
/**
* 转出金额
* @param from 姓名
* @param money 钱
* @return
*/
Integer transferTo(String from,Float money);
/**
* 转入金额
* @param to 姓名
* @param money 钱
* @return
*/
Integer transferIn(String to,Float money);
}
----------dao实现类-----------
package com.example.dao.impl;
import com.example.dao.AccountDao;
import com.example.pojo.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
public class AccountDaoImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public Account findByName(String name) {
String sql="select * from account where name=?";
Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Account>(Account.class), name);
return account;
}
public Integer transferTo(String from, Float money) {
String sql="update account set money=money-? where name=?";
int count = jdbcTemplate.update(sql, money, from);
return count;
}
public Integer transferIn(String to, Float money) {
String sql="update account set money=money+? where name=?";
int count = jdbcTemplate.update(sql, money, to);
return count;
}
}
---------service接口------------
package com.example.service;
public interface AccountService {
boolean transfer(String from,String to,Float money);
}
----------service实现类-----------
package com.example.service.impl;
import com.example.dao.AccountDao;
import com.example.pojo.Account;
import com.example.service.AccountService;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public boolean transfer(String from, String to, Float money) {
//查询转账相关人员的信息
Account fromAccount = accountDao.findByName(from);
Account toAccount = accountDao.findByName(to);
System.out.println("(from"+fromAccount+"),(to:"+toAccount);
//转出
Integer toCount = accountDao.transferTo(from, money);
int a=100/0;
//转入
Integer inCount = accountDao.transferIn(to, money);
return (toCount>0&&inCount>0);
}
}
测试类
package com.example;
import com.example.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
ringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class TestTransactionDemo {
@Autowired
private AccountService accountService;
/**
* 测试转账
*/
@Test
public void test1() {
boolean isSuccess = accountService.transfer("tom", "rose", 100f);
System.out.println(isSuccess?"转正成功":"转账失败");
}
}
【2】xml+注解实现
相比全xml方式,只修改了配置文件,和service层实现类
将配置文件中我们自己的类使用注解,其他不变,需要在我们的类上添加相关注解
修改applicationContext.xml配置
<!-- 开启包扫描 -->
<context:component-scan base-package="com.example"></context:component-scan>
<!-- 创建JdbcTemplate对象 -->
<bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 创建连接池 -->
<!-- 解析propries配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- ============配置声明式事务=============== -->
<!-- TODO:配置事务管理平台,在事务管理平台对象中存放有各种通知 -->
<!-- 开启手动事务,提交事务,回滚事务.... -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启AOP自动代理支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- 开启事务注解支持 -->
<tx:annotation-driven></tx:annotation-driven>
代码演示
修改service层实现类
package com.example.service.impl;
import com.example.dao.AccountDao;
import com.example.pojo.Account;
import com.example.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("accountService")
//注解作用于类中,那么类下的所有方法默认采取指定的事务策略
@Transactional(propagation = Propagation.REQUIRED,readOnly = false)
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
/**
* @Transactional: 可以使用在类上或方法上
* 类上: 当前类中所有的方法都需要事务控制,并遵循类上的相关配置
* 方法上: 当前方法需要事务控制,并遵循方法上的相关配置
* 注意: 如果类上和方法上都有,则听方法的.
*/
@Transactional(propagation = Propagation.REQUIRED,readOnly = false)
public boolean transfer(String from, String to, Float money) {
//查询转账相关人员的信息
Account fromAccount = accountDao.findByName(from);
Account toAccount = accountDao.findByName(to);
System.out.println("(from"+fromAccount+"),(to:"+toAccount);
//转出
Integer toCount = accountDao.transferTo(from, money);
int a=100/0;
//转入
Integer inCount = accountDao.transferIn(to, money);
return (toCount>0&&inCount>0);
}
}
测试类和上面代码相同。
【3】全注解实现
需要用到的相关注解:
@Configuration // 声明此类为Spring的核心配置类
@ComponentScan("com.itheima") // 开启包扫描
@PropertySource("classpath:jdbc.properties") // 加载properties配置文件
@EnableTransactionManagement // 开启事务注解支持
设置哪个方法需要事务增强
@Transactional(propagation =Propagation.REQUIRED,readOnly = false)
添加SpringConfig配置类
package com.example.config;
import org.junit.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.lang.annotation.Target;
import java.sql.DriverManager;
@Configuration // 声明配置类
@ComponentScan("com.example") // 配置注解扫描
@PropertySource("classpath:jdbc.properties") // 加载外部的properties资源
@EnableAspectJAutoProxy //等价于 <aop:aspectj-autoproxy/> 开启aop支持
@EnableTransactionManagement //等价于 <tx:annotation-driven/> 开启事务的注解驱动
public class SpringConfig {
@Value("${jdbc.userName}")
private String userName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.driverClass}")
private String driverClass;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource") // 配置dataSource存入IOC容器中
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setPassword(password);
dataSource.setUsername(userName);
dataSource.setDriverClassName(driverClass);
dataSource.setUrl(url);
return dataSource;
}
@Bean("jdbcTemplate") // 配置jdbcTemplate存入IOC容器中
public JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
return jdbcTemplate;
}
/**
* 配置事务管理器 aspect bean存入IOC容器值
* 注意事项:
* bean的名称强烈建议为:transactionManager
* @param dataSource
* @return
*/
@Bean("transactionManager")
public DataSourceTransactionManager dataSourceTransactionManager(@Autowired DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
测试类
package com.example;
import com.example.config.SpringConfig;
import com.example.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class) // 声明spring提供的单元测试环境
//@ContextConfiguration(locations = "classpath:applicationContext.xml.bak")
@ContextConfiguration(classes = SpringConfig.class) // 声明spring的配置信息
public class TestTransactionDemo {
@Autowired
private AccountService accountService;
/**
* 测试转账
*/
@Test
public void test1() {
boolean isSuccess = accountService.transfer("tom", "rose", 100f);
System.out.println(isSuccess?"转正成功":"转账失败");
}
}
总结
动态代理:
作用: 在不改变源码的基础上,对方法增强
实现方式:
基于接口: jdk提供的
Proxy
基于子类: cglib
Enhancer
方法增强:
方法增强有哪些方式?
继承
装饰者模式
动态代理
Proxy : 基于接口
Cglib : 基于子类.第三方
设计模式: 23种
java代码编写的固定格式,每一种格式都可以解决一类问题.
Spring-AOP:
AOP: 面向切面编程
作用: 在不改变源码的基础上,对方法增强
优势:
降低代码冗余
提高开发效率
方便维护
名词解释:
目标对象: 被代理的类对象
代理对象: 生成的代理类
连接点: 被代理类中所有的方法
切入点: 正在被增强的方法
通知/增强: 增强的代码或方法
前置增强
后置增强
异常增强
最终增强
环绕通知
切面: = 切入点 + 通知/增强
织入: 织入是一个过程.
将连接点转换成切入点加入通知的过程叫做---织入
底层实现:
AOP底层就是动态代理,AOP分别使用了Proxy和Cglib,Spring在方法增强时,会自动选择所使用的动态代理方式.
如果被代理类实现接口,则使用Proxy代理,
如果被代理类没有实现接口,则使用Cglib
使用Spring的AOP实现方法增强:
注: 在这种方式实现时,增强的内容是由我们自己定义的.
纯XML方式实现
半XML半注解
纯注解方式实现
目标:使用Spring的AOP实现自动事务控制
注: 切面类不再由我们自己提供,Spring已经封装好了一套管理事务的切面类
纯XML方式实现
半XML半注解
纯注解方式实现