个人认为,要了解事务之前应该先了解AOP,因为spring运用aop完成的事务控制
AOP
简介
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。主要目标还是致力于解耦,我们可以看到解耦这一理念贯穿于我们的整个编码工作中。我们通过各种设计模式或者设计原则来使得对象之间解耦。通过Spring IOC容器中利用依赖注入使得对象之间的耦合度更低。而AOP的思想解耦得更彻底, 通过动态的添加功能来增强实现,并且做到毫无代码的侵入性 。利用AOP可以对业务逻辑和非业务逻辑的部分进行隔离,可以提取非业务逻辑的部分,提高程序的可重用性,同时提高了开发的效率。
为什么使用AOP?
核心业务代码与切面代码解耦,切面代码对核心业务代码完全无侵入,遵守单一职责原则,完全隔离核心业务代码与切面代码。
低耦合带来可维护性高,修改或者新增一个切面代码仅需集中在一处进行更改。低耦合也意味着切面代码可复用性高。
Spring IOC容器天然地为AOP的实现提供了便利,IOC和AOP的结合使得Spring的解耦能力更强。
实际应用
使用 注解配置
开启了上述配置之后,所有在容器中,被 注解的 bean 都会被 Spring 当做是 AOP 配置类,称为一个 。
配置 (增强的切入点)
代表要拦截哪些方法
// 指定的方法
@Pointcut("execution(* testExecution(..))")
publicvoidanyTestMethod() {}
:匹配方法签名
这个最简单的方式就是上面的例子," "表示的是匹配名为 的方法,*代表任意返回值,(..)表示零个或多个任意参数。
:指定所在类或所在包下面的方法(Spring AOP 独有)
// service 层
// ".." 代表包及其子包
@Pointcut("within(ric.study.demo.aop.svc..*)")
publicvoidinSvcLayer() {}
:方法上具有特定的注解
// 指定注解
@Pointcut("@annotation(ric.study.demo.aop.HaveAop)")
publicvoidwithAnnotation() {}
:匹配 bean 的名字(Spring AOP 独有)
// controller 层
@Pointcut("bean(testController)")
publicvoidinControllerLayer() {}
如果你是在开发企业级应用,Spring 建议你使用 这种切面配置方式,即将一些公共的 配置全部写在这个一个类里面维护
packagecom.xyz.someapp;
importorg.aspectj.lang.annotation.Aspect;
importorg.aspectj.lang.annotation.Pointcut;
@Aspect
publicclassSystemArchitecture{
@Pointcut("within(com.xyz.someapp.web..*)")
public void inWebLayer() {}
@Pointcut("within(com.xyz.someapp.service..*)")
public void inServiceLayer() {}
@Pointcut("within(com.xyz.someapp.dao..*)")
public void inDataAccessLayer() {}
@Pointcut("execution(* com.xyz.someapp.service.*.*(..))")
public void businessService() {}
@Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
public void dataAccessOperation() {}
}
上面这个 很好理解,该 Aspect 定义了一堆的 Pointcut,随后在任何需要 Pointcut 的地方都可以直接引用。
配置Advice(对程序进行怎么样的增强)
Advice注解
Advice注解一共有五种,分别是:
@Before前置通知
前置通知在切入点运行前执行,不会影响切入点的逻辑
@After后置通知
后置通知在切入点正常运行结束后执行,如果切入点抛出异常,则在抛出异常前执行
@AfterThrowing异常通知
异常通知在切入点抛出异常前执行,如果切入点正常运行(未抛出异常),则不执行
@AfterReturning返回通知
返回通知在切入点正常运行结束后执行,如果切入点抛出异常,则不执行
@Around环绕通知
环绕通知是功能最强大的通知,可以在切入点执行前后自定义一些操作。环绕通知需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行
/**
* 注:实际开发过程当中,Advice应遵循单一职责,不应混在一起
*/
@Aspect
@Component
publicclassGlobalAopAdvice{
@Before("ric.study.demo.aop.SystemArchitecture.dataAccessOperation()")
publicvoiddoAccessCheck() {
// ... 实现代码
}
// 实际使用过程当中 可以像这样把Advice 和 Pointcut 合在一起,直接在Advice上面定义切入点
@Before("execution(* ric.study.demo.dao.*.*(..))")
publicvoiddoAccessCheck() {
// ... 实现代码
}
// 在方法
@AfterReturning("ric.study.demo.aop.SystemArchitecture.dataAccessOperation()")
publicvoiddoAccessCheck() {
// ... 实现代码
}
// returnVal 就是相应方法的返回值
@AfterReturning(
pointcut="ric.study.demo.aop.SystemArchitecture.dataAccessOperation()",
returning="returnVal")
publicvoiddoAccessCheck(Object returnVal) {
// ... 实现代码
}
// 异常返回的时候
@AfterThrowing("ric.study.demo.aop.SystemArchitecture.dataAccessOperation()")
publicvoiddoRecoveryActions() {
// ... 实现代码
}
// 注意理解它和 @AfterReturning 之间的区别,这里会拦截正常返回和异常的情况
@After("ric.study.demo.aop.SystemArchitecture.dataAccessOperation()")
publicvoiddoReleaseLock() {
// 通常就像 finally 块一样使用,用来释放资源。
// 无论正常返回还是异常退出,都会被拦截到
}
// 这种最灵活,既能做 @Before 的事情,也可以做 @AfterReturning 的事情
@Around("ric.study.demo.aop.SystemArchitecture.businessService()")
publicObjectdoBasicProfiling(ProceedingJoinPoint pjp)throwsThrowable {
// target 方法执行前... 实现代码
Object retVal = pjp.proceed();
// target 方法执行后... 实现代码
returnretVal;
}
}
实际配置例子
配置一个注解配置的aop需要以下几点:
首先aop需要基于ioc,需要它的扫描bean
开启注解配置方式需要用到的注解 ,当然,要保证这个注解的类被扫描到:
一个目标类,该类就是被切入的方法对象,需要被扫描到
publicinterfaceAopSvc{
voiddoSomething();
}
一个 ,也就是 和 的集合, 定义了它切入哪个方法, 定义了它什么时候进行切入,这些类都需要 注解
@Component
@Aspect
public class ServiceLogAspect {
// 拦截,打印日志,并且通过JoinPoint 获取方法参数
@Before("com.hyx.aop.PointCutConfig.inSvcLayer()")
public void logBeforeSvc(JoinPoint joinPoint) {
System.out.println("在service 方法执行前 打印第 1 次日志");
System.out.println("拦截的service 方法的方法签名: "+ joinPoint.getSignature());
System.out.println("拦截的service 方法的方法入参: "+ Arrays.toString(joinPoint.getArgs()));
}
// 这里是Advice和Pointcut 合在一起配置的方式,使用该方式无需另写Pointcut方法
@Before("within(com.hyx.aop..*)")
publicvoidlogBeforeSvc2() {
System.out.println("在service的方法执行前 打印第 2 次日志");
}
}
注解配置aop
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
什么是事务?
事务是逻辑上的一组操作,要么都执行,要么都不执行。
事务能否生效数据库引擎是否支持事务是关键。比如常用的 MySQL 数据库默认使用支持事务的 引擎。但是,如果把数据库引擎变为 ,那么程序也就不再支持事务了!
spring中的事务
spring只提供如PlatformTransactionManager事务管理接口,具体的实现交给了如jdbc等平台,根据连接数据库的工具不同,其内部实现也有所不同,spring的事务在代码逻辑中,是使用aop进行实现的,具体的在aop操作中的开启事务,回滚事务等操作需要调用jdbc等工具的实现,也需要看数据库引擎是否支持事务操作(mysql的innodb引擎支持操作)
事物的特性(ACID)
原子性( ): 一个事务( )中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚( )到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。事务是一个整体,要么全完成,要么全不完成
一致性( ): 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
隔离性( ): 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读( )、提交读( )、可重复读( )和串行化( )。
持久性( ): 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
在 MySQL 中,恢复机制是通过 回滚日志(undo log) 实现的,所有事务进行的修改都会先先记录到这个回滚日志中,然后再执行相关的操作。如果执行过程中遇到异常的话,我们直接利用 回滚日志 中的信息将数据回滚到修改之前的样子即可!并且,回滚日志会先于数据持久化到磁盘上。这样就保证了即使遇到数据库突然宕机等情况,当用户再次启动数据库的时候,数据库还能够通过查询回滚日志来回滚将之前未完成的事务。
Spring 编程式事务管理
编程式事务管理是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。
Spring 声明式事务管理
代码侵入性最小,实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)。
事务的传播机制
事务的传播性一般用在 事务嵌套的场景,比如一个事务方法里面调用了另外一个事务方法,那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交 ,这就是需要事务传播机制的配置来确定怎么样执行。
常用的事务传播机制如下:
Spring默认的传播机制,能满足绝大部分业务需求,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,新建一个事务执行
该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可
如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务
该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码
该传播机制不支持外层事务,即如果外层有事务就抛出异常
与NEVER相反,如果外层没有事务,则抛出异常
该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。 传播规则回答了这样一个问题:一个新的事务应该被启动还是被挂起,或者是一个方法是否应该在事务性上下文中运行。
事务的隔离级别
在多事务并发处理数据时有时会出现下面的问题:
脏读(Dirty read)
脏读发生在一个事务读取了被另一个事务改写但尚未提交的数据时。如果这些改变在稍后被回滚了,那么第一个事务读取的数据就会是无效的。
不可重复读(Nonrepeatable read)
不可重复读发生在一个事务执行相同的查询两次或两次以上,但每次查询结果都不相同时。这通常是由于另一个并发事务在两次查询之间更新了数据。
幻读(Phantom reads)
幻读和不可重复读相似。当一个事务(T1)读取几行记录后,另一个并发事务(T2)插入了一些记录时,幻读就发生了。在后来的查询中,第一个事务(T1)就会发现一些原来没有的额外记录。
在理想状态下,事务之间将完全隔离,从而可以防止这些问题发生。然而,完全隔离会影响性能,因为隔离经常涉及到锁定在数据库中的记录(甚至有时是锁表)。完全隔离要求事务相互等待来完成工作,会阻碍并发。因此,可以根据业务场景选择不同的隔离级别。
事务设置只读
如果一个事务只对数据库执行读操作,那么该数据库就可能利用那个事务的只读特性,采取某些优化措施。通过把一个事务声明为只读,可以给后端数据库一个机会来应用那些它认为合适的优化措施。
事务超时回滚
为了使一个应用程序很好地执行,它的事务不能运行太长时间。因此,声明式事务的下一个特性就是它的超时。
假设事务的运行时间变得格外的长,由于事务可能涉及对数据库的锁定,所以长时间运行的事务会不必要地占用数据库资源。这时就可以声明一个事务在特定秒数后自动回滚,不必等它自己结束。
回滚规则
在默认设置下,事务只在出现运行时异常(runtime exception)时回滚,而在出现受检查异常(checked exception)时不回滚。 不过,可以声明在出现特定受检查异常时像运行时异常一样回滚。同样,也可以声明一个事务在出现特定的异常时不回滚,即使特定的异常是运行时异常。
spring中基于xml的事务配置
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
导入事务的约束,需要tx的名称空间与约束,也需要aop的
3. 配置AOP的切入点表达式
4. 建立事务通知和切入点表达式对应关系
5. 配置事务的属性
是在aop:advisor内进行配置
-->
isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
-->
spring中基于注解的配置
修改xml配置文件
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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/tx
http://www.springframework.org/schema/tx/spring-tx.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">
1、配置事务管理器
2、开启spring对注解事务的支持
3、在需要事务支持的地方使用@Transactional注解
-->
于目标类上添加注解 ,其属性值与xml属性值一致
如果你觉得文章不错,文末的赞 又回来啦,记得给我「点赞」和「在看」哦~
领取专属 10元无门槛券
私享最新 技术干货