1. AOP
1.1 AOP概述
- AOP为Aspect Oriented Programming,面向切面编程,通过预编译方式和运行期动态代理实现程序功能的同一维护的一种技术。
- AOP可以对业务逻辑的各个部分进行隔离, 从而使得业务逻辑各部分之间的耦合度降低, 提高程序的可重用性, 同时提高了开发的效率.
- AOP作用:在程序运行期间,不修改任何相关源码对已有方法进行增强。优势是:减少重复代码、提高开发效率、维护方便。
- SpringAOP使用代理技术两种:ava官方的JDK动态代理技术和CGLIB 第三方代理技术。
1.2 传统开发模型(纵向的编程)和面向切面编程(纵横配合的编程)
纵向开发模型
面向切面编程
1.3 AOP的作用、优势和实现方式
- 作用:在程序运行期间,不修改任何相关源码对已有方法进行增强。
- 优势:减少重复代码、提高开发效率、维护方便
2. 动态代理技术
Java中的动态代理,就是使用者使用的不是真实的对象,而是使用的一个代理对象,而这个代理对象中包含的就是真实的对象,代理对象就是不改变原有对象的功能方法的基础之上封装新的功能(功能增强)
动态代理不是在编译阶段产生的代理对象(开发者不同编写源代码),而是在JVM运行中动态产生的
SpringAOP使用代理技术两种,Java官方的JDK动态代理技术和CGLIB 第三方代理技术。
2.1 JDK动态代理技术
JDK动态代理技术的优缺点和创建方式
** JDK动态代理是Java官方的代理 **
- 需要通过JDK官方的Proxy类创建代理对象
- 创建代理对象必须要一个代理处理类(实现了接口InvocationHandler的类)
** JDK动态代理的不足 ** - JDK动态代理的对象必须要实现一个接口;-因为JDK动态代理是基于接口代理的;
- 需要为每个对象创建代理对象;
- 动态代的最小单位是类(所有类中的方法都会被处理),查询方法不需要事务,可能不需要被代理
案例
@Component
public class JdkDynamicProxyHandle {
//注入真实对象
@Autowired
private UserServiceImpl target;
//注入要增强的功能
@Autowired
private TransactionManagerHandler01 tsManager;
/**
* 获取动态代理对象的方法
* 返回一个代理对象,代理对象就做了方法的增强,(事务管理,日志控制,权限管理等)
* @param clz
* @param <T>
* @return
*/
public <T> T getProxyObject (Class<T> clz) {
/**
* ClassLoder loder
* 类加载器:从当前项目加载相关资源(配置文件等),类加载器在项目运行JVM会为每一个项目创建一个字节码
* 一个项目运行期间有且只有一个类加载器,开发者使用的时候只要获取即可
* 获取的方法
* 1. 使用当前线程获取
* ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
* 2. 任意一个类的字节码获取
* target.getClass().getClassLoader()
*
* Class<?>[] interfaces
* 被代理对象的接口
*
* reflect.InvocationHandler h
* 代理执行处理器,真正做代码增强的地方
*
*/
ClassLoader loder = Thread.currentThread().getContextClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
Object newProxyInstance = Proxy.newProxyInstance(loder, interfaces, new InvocationHandler() {
/**
* 代理类的增强方法,正常增强的地方
* @param proxy 代理对象
* @param method 被代理对象的方法
* @param args 被代理对象方法的参数
* @return 被代理对象执行的结果
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//执行被代理对象的方法
Object result = null;
try {
//方法之前执行操作:开始事务
tsManager.begin();
//执行被代理对象的方法
result = method.invoke(target, args);
//方法之后执行的操作:提交事务
tsManager.commit();
} catch (Exception e) {
//回滚事物
e.printStackTrace();
} finally {
//关闭事务
tsManager.close();
}
return result;
}
});
//返回代理对象
return (T)newProxyInstance;
}
}
2.2 CGLIB第三方代理
CGLIB动态代理技术的优缺点和创建方式
** CGLIB(Code Generation Library)是一个开源项目 **
- CGLIB和JDK动态代理一样都是动态代理,但是CGLIB代理没有接口可以进行
- CGLIB代理没有接口的类,在JVM动态的为这个代理创建一个子类,子类重写父类方法,并且调用父类方法,在父类方法执行之前,之后,异常,最终做增强。
** CGLIB代理的不足 ** - CGLIB可以生成目标类的子类,并重写父类非final修饰符的方法。因此要求类不能是final的,要代理的方法要是非final、非static、非private的。
- 动态代理的最小单位是类(所有类中的方法都会被处理)
案例
@Component
public class CglibDynamicProxyHandle {
//注入真实对象
@Autowired
private UserServiceImpl02 target;
//注入要增强的功能
@Autowired
private TransactionManagerHandler02 tsManager;
/**
* 获取动态代理对象的方法
* 返回一个代理对象,代理对象就做了方法的增强,(事务管理,日志控制,权限管理等)
* @param clz
* @param <T>
*/
public <T> T getProxyObject (Class<T> clz) {
//1. 创建CGLIB的Enhancer对象(用户创建代理的对象)
Enhancer enhancer = new Enhancer();
//2. 设置加载器
enhancer.setClassLoader(Thread.currentThread().getContextClassLoader());
//3. 设置被代理对象的字节码(CGLIB直接在JVM中创建被代理对象的子类)
enhancer.setSuperclass(target.getClass());
//4. 代理的具体实现
enhancer.setCallback(new InvocationHandler() {
/**
* 代理类的增强方法,正常增强的地方
* @param proxy 代理对象
* @param method 被代理对象的方法
* @param args 被代理对象方法的参数
* @return 被代理对象执行的结果
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//执行被代理对象的方法
Object result = null;
try {
//方法之前执行操作:开始事务
tsManager.begin();
//执行被代理对象的方法
result = method.invoke(target, args);
//方法之后执行的操作:提交事务
tsManager.commit();
} catch (Exception e) {
//回滚事物
e.printStackTrace();
} finally {
//关闭事务
tsManager.close();
}
return result;
}
});
//创建代理对象
Object proxy = enhancer.create();
//返回代理对象
return (T)proxy;
}
}
2.3 小结
解决重复代码
- 在Spring中,若目标对象实现了若干接口,Spring就会使用JDK动态代理。
- 若目标对象没有实现任何接口,Spring就使用CGLIB库生成目标对象的子类。
直接使用代理的缺陷
- 如果直接使用代理解决代码重复问题,每一个类都要配置代理类,非常的麻烦。
- 必须需要实现类必须要有一个接口 -JDK动态代理。
- 无法通过规则制定拦截的方法。
- 如何解决这个问题:Spring提供了AOP的实现。
3. SpringAOP
Spring通过动态代理模式的实现后,我们可以定义AOP其实就是用于通过规则设置来拦截方法,加入可以统一处理的代码。
3.1 相关概念
规则
** 可以指定哪些方法要做增强,哪些方法不做增强,由Spring的AOP 统一配置即可,底层如果被代理的类有接口使用JDK动态代理,没有接口自动CGLIB第三方代理,开发者只要配置AOP即可,不需要再编写底层动态代理的相关代码。**
AOP相关术语
- Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。就是根据规则,可以指定拦截的方法,我们将每一个被拦截的方法称为连接点。
- Pointcut(切入点):所谓的切入点,就是拦截方法设置的规则。所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
Advice(通知/增强):就是可以设置在方法之前拦截或者方法执行之后拦截或者方法出异常后拦截,或者方法之前和之后都拦截。我们将这些拦截场景称为通知。所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。 - Aspect(切面):所谓的切面就是我们的拦截处理类。是切入点和通知的结合。
- Weaving(织入):把切面加入到对象,并创建出代理对象的过程。(该过程由Spring来完成)
AOP相关jar包
** AOP 思想必须使用AspectJ语法,而AOP思想不是Spring框架设计出来的,而是叫一个AOP联盟组织提出这种思想,所以开发者需要导入 AOP联盟提供的 aspectjweaver.jar(aspectweaver织入包) **
切入点表达式说明:(execution:)
匹配方法的执行(常用)
execution(表达式)
表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
写法说明:
全匹配方式:
public void cn.zj.service.impl.CustomerServiceImpl.saveCustomer()
访问修饰符可以省略
void com.zj.service.impl.CustomerServiceImpl.saveCustomer()
返回值可以使用*号,表示任意返回值
* com.zj.service.impl.CustomerServiceImpl.saveCustomer()
包名可以使用*号,表示任意包,但是有几级包,需要写几个*
* *.*.*.*.CustomerServiceImpl.saveCustomer()
使用..来表示当前包,及其子包
* com..CustomerServiceImpl.saveCustomer()
类名可以使用*号,表示任意类
* com..*.saveCustomer()
方法名可以使用*号,表示任意方法
* com..*.*()
参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
* com..*.*(*)
参数列表可以使用..表示有无参数均可,有参数可以是任意类型
* com..*.*(..)
全通配方式:
* *..*.*(..)
3.2 基于xml配置AOP
ApplicationContext.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<!-- 配置包扫描
cn.zj.spring : 此包以及子包全部被扫描
-->
<context:component-scan base-package="cn.xiaoxiao01"/>
<!--配置模拟事务管理器-->
<!--what?-->
<bean id="tsManager" class="cn.xiaoxiao01.utils.TransactionManagerHandler"/>
<!--
配置AOP有一个WWW原则
W:what?干什么(事务增强)
W:where?地点(业务层)
W:when?时机(所有方法执行之前?之后?方法前后?)
-->
<!--开启aop配置-->
<aop:config proxy-target-class="true">
<!--what?做什么-->
<aop:aspect ref="tsManager">
<!--where?切入点(Pointcut):在哪些类,哪些方法上切入(where);-->
<aop:pointcut id="pt" expression="execution(* cn.xiaoxiao01.service..*.*(..))"/>
<!--when?时机-->
<!--前置增强-->
<aop:before method="begin" pointcut-ref="pt"/>
<!--最终增强-->
<aop:after method="close" pointcut-ref="pt"/>
<!--后置增强-->
<aop:after-returning method="commit" pointcut-ref="pt"/>
<!--异常增强:抛出的异常是Throwable-->
<aop:after-throwing method="rollback" pointcut-ref="pt"/>
<!--环绕增强:将多个增加集中到一起-->
<aop:around method="allInOne" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
<!--出现问题?Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException:
Bean named ’ iCustomerService ’ is expected to be of type ’ service.ICustomerServiceImpl ’
but was actually of type ‘com.sun.proxy.$Proxy13’
原因分析:
spring默认使用jdk方式创建代理对象,而不是cglib方式
二者区别主要在于,jdk方式只能代理接口实现类,cglib既可以代理接口实现类又可以代理一般类
ICustomerServiceImpl经过代理之后变成了iCustomerService接口类型
方案一:使用cglib代理方式,配置文件
<aop:config proxy-target-class=“true”>
方案二:使用接口类型调用方法
iCustomerService.service();
方案三:不做增强
-->
</beans>
TransactionManagerHandler.java
public class TransactionManagerHandler {
public void begin () {
System.out.println("开启事务...");
}
public void commit () {
System.out.println("提交事务...");
}
public void rollback () {
System.out.println("回滚事务...");
}
public void close () {
System.out.println("关闭session...");
}
/**
* 环绕增强的方法
* ProceedingJoinPoint 连接点
* @param pjp
*/
public void allInOne(ProceedingJoinPoint pjp){
try {
//获取被代理对象的相关信息
beginPjp (pjp);
System.out.println("开启事务--环绕通知");
//执行方法,此方法在环绕方法中可以调用正在的业务方法
pjp.proceed();
System.out.println("提交事务--环绕通知");
} catch (Throwable e) {
System.out.println("回滚事务--环绕通知" + e.getMessage());
} finally {
System.out.println("关闭seeeion--环绕通知");
}
System.out.println("");
}
/**
* 获取被代理对象的相关信息
* JoinPoint : 连接点
* @param jp
*/
private void beginPjp(JoinPoint jp) {
//获取被代理对象的类型
Class<?> clz = jp.getTarget().getClass();
System.out.println(clz);
//获取被代理对象执行方法对应的参数
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
}
}
UserServiceImplTest.java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:ApplicationContext.xml")
public class UserServiceImplTest {
@Autowired
private UserServiceImpl userServiceImpl;
@Test
public void insert() {
User user = new User("xiaoxiao", "xiaoxiao", 17);
userServiceImpl.insert(user);
}
@Test
public void update() {
User user = new User("xiaoxiao", "xiaoxiao", 17);
userServiceImpl.update(user);
}
}
3.3 基于注解配置AOP
SpringConfig.java
@Configuration
@ComponentScan("cn.xiaoxiao02")
//开启aop注解配置
@EnableAspectJAutoProx
y
public class SpringConfig {
}
TransactionManagerHandler
@Component
@Aspect() //在此类中可以使用注解进行aop的相关配置:相当于<aop:aspect>
public class TransactionManagerHandler {
//相当于
//<aop:pointcut expression="execution( * cn.zj.spring.service..*.*(..))" id="当前方法名就是id"/>
@Pointcut("execution( * cn.xiaoxiao02.service..*.*(..))")
public void pointcut(){}
@Before("pointcut()")
public void begin () {
System.out.println("开启事务...");
}
@AfterReturning("pointcut()")
public void commit () {
System.out.println("提交事务...");
}
@AfterThrowing(pointcut = "pointcut()", throwing = "ex")
public void rollback (Throwable ex) {
System.out.println("回滚事务..." + ex.getMessage());
}
@After("pointcut()")
public void close () {
System.out.println("关闭session...");
}
/**
* 环绕增强的方法
* ProceedingJoinPoint 连接点
* @param pjp
*/
@Around("pointcut()")
public void allInOne(ProceedingJoinPoint pjp){
try {
//获取被代理对象的相关信息
beginPjp (pjp);
System.out.println("开启事务--环绕通知");
//执行方法,此方法在环绕方法中可以调用正在的业务方法
pjp.proceed();
System.out.println("提交事务--环绕通知");
} catch (Throwable e) {
System.out.println("回滚事务--环绕通知" + e.getMessage());
} finally {
System.out.println("关闭seeeion--环绕通知");
}
System.out.println("");
}
/**
* 获取被代理对象的相关信息
* JoinPoint : 连接点
* @param jp
*/
private void beginPjp(JoinPoint jp) {
//获取被代理对象的类型
Class<?> clz = jp.getTarget().getClass();
System.out.println(clz);
//获取被代理对象执行方法对应的参数
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
}
}
4. Spring的事务管理
4.1 事务
** 事务:指数据库单个逻辑操作单元的集合。
在操作数据库时(增删改),如果同时操作多次数据,我们从业务希望,要不全部成功,要不全部失败。这种情况称为事务处理。 **
Spring事务
- JavaEE体系进行分层开发,事务处理位于业务层,所以,一般情况下我们使用事务代理(事务管理器),一般放在分层设计业务层。
- spring框架为我们提供了一组事务控制的应用程序接口(API)。Spring框架已经将不同持久层框架的事务管理实现了,开发者只需要将事务配置到项目中即可。
- spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现。
转账案例分析
如:A转账给B。
第一步,扣除A君账号要转的金额
第二步,增加B君账号的金额
如下图可得结论: 根据上述案例分析, 事务管理应该是在Service层处理
事务的ACID
- 原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。-一荣俱荣,一损俱损
- 一致性(Consistency):事务前后数据的完整性必须保持一致。
- 隔离性(Isolation):事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
- 持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
4.2 数据库并发问题
** 数据库并发问题?多个客户端同时访问数据库中某一条数据(秒杀),访问指有修改操作。数据库可以拥有多个访问客户端,若多个客户端并发地访问数据库中相同的资源,如果没有采取必要的隔离措施,则会导致各种并发问题,破坏数据的完整性。**
** 这些问题归结为5类 ->包括3类数据读问题(脏读,不可重复读,幻读)和2类数据更新问题(第一类丢失更新,第二类丢失更新)。 **
第一类数据丢失
两个事务更新相同数据,如果一个事务提交,另一个事务回滚,第一个事务的更新会被回滚。
第二类数据丢失
多个事务同时读取相同数据,并完成各自的事务提交,导致最后一个事务提交会覆盖前面所有事务对数据的改变。
脏读
第二个事务查询到第一个事务未提交的更新数据,第二个事务根据该数据执行,但第一个事务回滚,第二个事务操作脏数据。Oracle 不会出现脏读,数据库 已经把理论会出现问题解决了。
幻读/虚读
一个事务查询到了另一个事务已经提交的新数据,导致多次查询数据不一致
MySQL默认不会出现幻读。
不可重复读
一个事务查询到另一个事务已经修改的数据,导致多次查询数据不一致。
数据库的事务隔离级别
数据库提供了不同的事务隔离级别来处理不同的事务并发问题,事务隔离级别定义如下:
隔离级别 | 说明 |
---|---|
READ_UNCOMMITED | 允许你读取还未提交的改变了的数据。可能导致脏、幻、不可重复读(相当于没有做任何事务隔离) |
READ_COMMITTED | 允许在并发事务已经提交后读取。可防止脏读,但幻读和 不可重复读仍可发生(ORACLE默认级别) |
REPEATABLE_READ | 对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏、不可重复读,但幻读仍可能发生。(MYSQL默认级别) |
SERIALIZABLE | 完全服从ACID的隔离级别,确保不发生脏、幻、不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的。(ORACLE支持) |
针对不同隔离级别可以解决的的如下五类问题
解决丢失更新的方案
解决丢失更新的方案:数据库表加锁。
悲观锁
- 在操作当前数据的事务开启事务就使用for update 锁住当前数据
- Hibernate和MyBatis都有悲观锁对应的解决方案
乐观锁
- 为表添加一个version字段。当前事务操作的时候都会比对当前事务情况的多次操作的版本号是否一致,如果不一致认为数据已经被更新
- Hibernate和MyBatis都有乐观锁对应的解决方案
4.3 Spring对事务的支持
Spring是事务代理(事务管理器),已经将事务的具体代码封装好,只需要在spring配置文件中配置一次,不用重复编写事务处理代码!!
** Spring框架针对事务处理提供专门的解决方案。Spring的事务管理主要包括3个接口。**
Spring事务处理接口 | 描述 |
---|---|
TransactionDefinition | 封装事务的隔离级别超时时间,是否为只读事务和事务的隔离级别和传播规则等事务属性,可通过XML配置具体信息1,事务的隔离级别2,事务的超时时间管理3,事务的传播规则4,设置是否是只读事务(DQL是只读事务,DML是非只读事务) |
PlatformTransactionManager | 根据TransactionDefinition提供的事务属性配置信息,创建事务。事物管理器 |
TransactionStatus | 封装了事务的具体运行状态。比如,是否是新开启事务,是否已经提交事务,设置当前事务为rollback-only等 |
TransactionDefinition
** 该接口主要定义了:事务的传播行为(规则),事务的隔离级别,获得事务信息的方法。所以在配置事务的传播行为,事务的隔离级别已经需要获得事务信息时,可以通过查阅该类的代码获得相关信息。事务的传播规则如下: **
事务传播规则类型 | 描述 |
---|---|
PROPAGATION_REQUIRED | 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务(最常用操作)-Spring默认使用的就是此规则 |
PROPAGATION_REQUIRES_NEW | 创建一个新的事务,如果当前存在事务,则把当前事务挂起 |
PROPAGATION_SUPPORTS | 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式运行,如果当前存在事务,则把当前事务挂起 |
PROPAGATION_NEVER | 以非事务方式运行,如果当前存在事务,则抛出异常 |
PROPAGATION_MANDATORY | 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常 |
PROPAGATION_NESTED | 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED |
PlatformTransactionManager事务管理器
** PlatformTransactionManager接口主要定义事务的处理方法,获取事务,开启事务,提交事务,回滚事务,在实际开发中不同的框架有不同的实现类。**
Spring的事务管理
- TransactionStatus getTransaction(TransactionDefinition definition):根据事务定义信息从事务环境中返回一个已存在的事务,或者创建一个新的事务,并用TransactionStatus描述该事务的状态。
- void commit(TransactionStatus status):根据事务的状态提交事务,如果事务状态已经标识为rollback-only,该方法执行回滚事务的操作。
- void rollback(TransactionStatus status):将事务回滚,当commit方法抛出异常时,rollback会被隐式调用
在使用spring管理事务的时候,首先得告诉spring使用哪一个事务管理器,使用不同的框架(JdbcTemplate,MyBatis,Hibernate/JPA )使用事务管理器都不同。
事物管理器的继承体系图
** 常用的事务管理器:DataSourceTransactionManager:使用JDBC,MyBatis的事务管理器。 **
4.4 Spring事务的配置
** Spring支持编程式事务管理和声明式事务管理。**
** 编程式事务管理:事务代码直接编写在Java源码中(不推荐)**
- 事务的隔离级别,传播规则,都需要编写在Java代码中
- 开启事务,提交事务,回滚事务都要编写到Java代码中
** 声明式事务管理,直接将事务配置到xml文件,使用注解的方式配置 **
声明式事务管理-xml方式配置
ApplicationContext.xml配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
">
<!--引入命名空间tx-->
<!--xmlns:tx="http://www.springframework.org/schema/tx-->
<!-- http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd-->
<!-- 声明扫描包以及子包的类。如果发现有组件注解的类,就创建对象,并加入到容器 -->
<context:component-scan base-package="cn.xiaoxiao"></context:component-scan>
<!--读取配置文件-->
<context:property-placeholder location="classpath:db.properties"/>
<!--连接池配置-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
</bean>
<!-- 配置jdbcTemplate 模板类 -->
<bean id="jdbcTemplate" scope="prototype" class="org.springframework.jdbc.core.JdbcTemplate">
<!--构造器注入-->
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<!--
配置事务
1. 配置事务管理器?what
2. 配置事务通知、增强(时机)when?
3. 使用AOP将事务切入到Service层(地点)where?
-->
<!--1. 配置事务管理器?what-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--2.配置事务通知/增强(时机)when?-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!--
<tx:advice
id="txAdvice" :事务通知的唯一标识(AOP配置需要使用)
transaction-manager="txManager">:要通知/增强的功能,事务管理器的引用
-->
<!--配置通知属性-->
<tx:attributes>
<!--配置事务要通知/增强的方法-->
<!--
<tx:method
name="" :方法名
事务最终是切入到Service层,Service有很多的方法,会对方法分类
DQL:查询方法,以select,get,find,query等开头的方法都是查询方法
*通配符:select*,get*,find*,query*
DML:增删改方法,一般业务层除了查询,剩下的基本都是增删改
isolation="DEFAULT" :隔离级别,默认就是当前连接数据库默认的隔离级别,不同数据库隔离级别不同(可以不配置)
propagation="REQUIRED":事务的传播规则,默认使用REQUIRED
timeout="-1":事务超时时间配置,-1就是数据库默认超时时间(可省略)
read-only="false":是否只读,默认false(非只读事务)
DQL:必须是只读书屋(效率高)
DML:必须是非只读事务(数据库数据发生更改)
/>
-->
<!--查询方法:DQL操作-->
<tx:method name="select*" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="true"/>
<!-- <tx:method name="get*" read-only="true"/>-->
<!-- <tx:method name="find*" read-only="true"/>-->
<!-- <tx:method name="query*" read-only="true"/>-->
<!--非查询方法:DML操作,需要事务管理-->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--3. 使用AOP将事务切入到Service层(地点)where?-->
<aop:config>
<!--切入点-->
<aop:pointcut id="pt" expression="execution(* cn.xiaoxiao.spring01.service..*.*(..))"/>
<!--
配置切面 切面=切入点+通知
advice-ref:通知的引用
pointcut-ref:切入点的引用
-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
<!-- 织入(Weaving):把切面加入到对象,并创建出代理对象的过程。(该过程由Spring来完成) -->
</aop:config>
</beans>
声明式事务管理-基于注解配置
ApplicationContext.xml配置文件
<!-- 1,配置事务管理器(应根据情况使用合适的事务管理器) -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入dataSource -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启注解驱动配置事务
编写此标签:Spring底层就支持注解配置事物,并且已经配置好事物管理器
-->
<tx:annotation-driven transaction-manager="txManager"/>
业务层代码
@Service
/* @Transactional
* 贴上此当前类已经被Spring事务管理
* 注意: @Transactional 只能对当前贴的Service类有效
* 常用属性 :
* isolation=Isolation.REPEATABLE_READ, 隔离级别
propagation=Propagation.REQUIRED,传播规则
readOnly=true 是否只读事务
*
*/@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService{
@Autowired
private AccountDao dao;
public void trans(Integer transOutId,
Integer transInId, Integer money) {
dao.tranOut(transOutId, money);
System.out.println(1 / 0);// 模拟断电
dao.tranIn(transInId, money);
}
//单独为某一个方法配置具体的事物细节:如查询方法,事物是只读的
@Transactional(readOnly=true)
public Object getUser() {
//查询操作
return null;
}
@Transactional(readOnly=true)
public List<Object> getUsers() {
//查询操作
return null;
}
}
全注解配置
@Configuration
@ComponentScan("cn.xiaoxiao02")
@PropertySource("db.properties")
//开启事务注解支持
@EnableTransactionManagement
//使用包扫描创建XxxMapper接口代理对象
@MapperScan("cn.xiaoxiao02.mapper")
public class SpringConfig {
@Value("${jdbc.driverClassName}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Value("${jdbc.maxActive}")
private Integer maxActive;
@Bean(name = "dataSource")
public DataSource getDataSource () {
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(this.driverClassName);
ds.setUrl(this.url);
ds.setUsername(this.username);
ds.setPassword(this.password);
ds.setMaxActive(this.maxActive);
return ds;
}
//创建SqlSessionFactory工厂对象
@Bean
public SqlSessionFactory getFactory ()throws Exception {
System.out.println("welcome to SpringConfig.getFacory...");
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
//设置数据源
factoryBean.setDataSource(getDataSource());
//设置包扫描配置别名
factoryBean.setTypeAliasesPackage("cn.xiaoxiao02.bean");
return factoryBean.getObject();
}
//配置事务
@Bean
public PlatformTransactionManager getTxManager() {
System.out.println("welcome to SpringConfig.getTxManager...");
return new DataSourceTransactionManager(getDataSource());
}
}
xml配置和注解配置差异
- xml配置 : 代码清晰,配置量少,容易维护
- 注解配置 : 侵入代码,每个Service层类都得配置,针对不同的方法还得配置事务细节:如查询方法配置只读操作,不容易维护。
- 建议选择xml配置。