AOP
AOP简介
AOP(Aspect-Oriented Programming, 面向切面编程):是一种新的方法论, 是对传统 OOP(Object-OrientedProgramming, 面向对象编程) 的补充.;
AOP 的主要编程对象是切面(aspect), 而切面模块化横切关注点.
在应用 AOP 编程时, 仍然需要定义公共功能, 但可以明确的定义这个功能在哪里, 以什么方式应用, 并且不必修改受影响的类. 这样一来横切关注点就被模块化到特殊的对象(切面)里.
AOP术语
切面(Aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
通知(Advice): 切面必须要完成的工作
目标(Target): 被通知的对象
代理(Proxy): 向目标对象应用通知之后创建的对象
连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、 调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如 ArithmethicCalculator#add() 方法执行前的连接点,执行点为 ArithmethicCalculator#add(); 方位为该方法执行 前的位置 ;
切点(pointcut):每个类都拥有多个连接点:例如ArithmethicCalculator 的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。
Spring AOP
AspectJ:Java 社区里最完整最流行的 AOP 框架.
在 Spring2.0 以上版本中, 可以使用基于 AspectJ 注解或基于 XML 配置的 AOP
用AspectJ注解声明切面
要在 Spring 应用中使用 AspectJ 注解, 必须在classpath 下包含 AspectJ 类库: aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar
将 aop Schema 添加到 根元素中.
要在 Spring IOC 容器中启用 AspectJ 注解支持, 只要 在 Bean 配置文件中定义一个空的 XML 元素
当 Spring IOC 容器侦测到 Bean 配置文件中的 元素时, 会自动为与 AspectJ 切面匹配的 Bean 创建代理.
要在 Spring 中声明 AspectJ 切面, 只需要在 IOC 容 器中将切面声明为 Bean 实例. 当在 Spring IOC 容器中初始化 AspectJ 切面之后, Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理.
在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类.
通知是标注有某种注解的简单的 Java 方法.
AspectJ 支持 5 种类型的通知注解:
– @Before: 前置通知, 在方法执行之前执行
– @After: 后置通知, 在方法执行之后执行
– @AfterRunning: 返回通知, 在方法返回结果之后执行
– @AfterThrowing: 异常通知, 在方法抛出异常之后
– @Around: 环绕通知, 围绕着方法执行
利用方法签名编写AspectJ切入点表达式
最典型的切入点表达式时根据方法的签名来匹配各种方法:
– execution * com.atguigu.spring.ArithmeticCalculator.*(..):匹配 ArithmeticCalculator 中声明的所有方法,第一个 * 代表任意修饰符及任意返回值. 第二个 * 代表任意方法. .. 匹配任意数量的参数. 若目标类与接口与该切面在同一个包中, 可以省 略包名.
– execution public * ArithmeticCalculator.*(..): 匹配ArithmeticCalculator 接口的所有公有方法.
– execution public double ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 中返回 double 类型数值的方法
– execution public double ArithmeticCalculator.*(double, ..): 匹配第一个参数为double 类型的方法, .. 匹配任意数量任意类型的参数
– execution public double ArithmeticCalculator.*(double, double): 匹配参数类型为 double, double 类型的方法.
合并切入点表达式
可以声明一个没有方法体的方法,作为切入点;
在 AspectJ 中, 切入点表达式可以通过操作符 &&, ||, ! 结合起来.
前置通知
让通知访问当前连接点的细节
可以在通知方法中声明一个类型为 JoinPoint 的参数.然后就能访问链接细节. 如方法名称和参数值.如上图;
后置通知:
一个切面可以包括一个或者多个通知。
后置通知是在连接点完成之后执行的, 即连接点返回结果或者抛出异常的时候
/**
* 定义一个方法, 用于声明切入点表达式. 一般地, 该方法中再不需要添入其他的代码.
* 使用 @Pointcut 来声明切入点表达式.
* 后面的其他通知直接使用方法名来引用当前的切入点表达式.
*/
@Pointcut("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(..))")
public void declareJointPointExpression(){}
/**
* 在 com.atguigu.spring.aop.ArithmeticCalculator 接口的每一个实现类的每一个方法开始之前执行一段代码
*/
@Before("declareJointPointExpression()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
Object [] args = joinPoint.getArgs();
System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
}
/**
* 在方法执行之后执行的代码. 无论该方法是否出现异常
*/
@After("declareJointPointExpression()")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName + " ends");
}
返回通知
无论连接点是正常犯规还是抛出异常,后置通知都会执行,如果只想在连接点返回的时候打印记录日志,应使用返回通知代替后置通知.
/**
* 在方法法正常结束受执行的代码
* 返回通知是可以访问到方法的返回值的!
*/
@AfterReturning(value="declareJointPointExpression()",
returning="result")
public void afterReturning(JoinPoint joinPoint, Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName + " ends with " + result);
}
在返回通知中访问连接点的返回值
在返回通知中,只要经returning属性添加到@AfterReturning注解中,就可以访问连接点的返回值,该属性的值即为用来传入返回值的参数名称.
必须在通知方法的签名中添加一个同名的参数,在运行时,Spring AOP会通过这个参数传递返回值.
原始的切点表达式需要出现在pointcut(value)属性中
异常通知
只在连接点跑出异常时才执行异常通知
jiangthrowing属性添加到@AfterThrowing注解中,也可以访问连接点抛出的异常.Throwable是所有错误和异常类的超类.所以在异常通知方法可以捕获到任何错误和异常.
如果只对某种特殊的异常类型感兴趣,可以将参数生命为其他异常的参数类型,然后通知就只在跑出这个类型及其子类的异常时才执行
/**
* 在目标方法出现异常时会执行的代码.
* 可以访问到异常对象; 且可以指定在出现特定异常时在执行通知代码
*/
@AfterThrowing(value="declareJointPointExpression()",
throwing="e")
public void afterThrowing(JoinPoint joinPoint, Exception e){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName + " occurs excetion:" + e);
}
环绕通知
环绕通知是所有通知类型中最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点.
对于环绕通知来说,连接点的参数类型必须是ProceedingJoinPoint.他是JoinPoint的子接口,允许控制何时执行,是否执行连接点.
在环绕通知中需要明确调用ProccedingJoinPoint的proceed()方法来执行被代理的方法,如果忘记这样做就会导致通知被执行了,但目标方法没有执行.
注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用joinPoint.procced()的返回值,否则会出现空指针异常;
/**
* 环绕通知需要携带 ProceedingJoinPoint 类型的参数.
* 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法.
* 且环绕通知必须有返回值, 返回值即为目标方法的返回值
*/
@Around("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(..))")
public Object aroundMethod(ProceedingJoinPoint pjd){
Object result = null;
String methodName = pjd.getSignature().getName();
try {
//前置通知
System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
//执行目标方法
result = pjd.proceed();
//返回通知
System.out.println("The method " + methodName + " ends with " + result);
} catch (Throwable e) {
//异常通知
System.out.println("The method " + methodName + " occurs exception:" + e);
throw new RuntimeException(e);
}
//后置通知
System.out.println("The method " + methodName + " ends");
return result;
}
指定切面的优先级
在同一个连接点上应用不止一个切面时,除非明确指定,否则他们的优先级是不确定的.
切面的优先级可以通过实现Ordered接口或利用@Order注解指定
实现Ordered接口,getOrder()方法的返回值越小,优先级越高.
若使用@order注解,需要出现在注解中
@Order(1)
@Aspect
@Component
public class VlidationAspect {
@Before("com.atguigu.spring.aop.LoggingAspect.declareJointPointExpression()")
public void validateArgs(JoinPoint joinPoint){
System.out.println("-->validate:" + Arrays.asList(joinPoint.getArgs()));
}
}
重用切入点定义
在编写AspectJ切面时,可以直接在通知注解中书写切入点表达式,带同一个切点表达式可能会在多个通知中重复出现.
在AspectJ切面中,可以通过@Pointcut注解将一个切入点声明成简单的方法,切入点的方法体通常是空的额,因为将切入点定义与应用程序逻辑混在一起时不合理的;
切入点方法的访问控制符同时也控制着这个切入点的可见性. 如果切入点要在多个切面中共用, 最好将它们集中在一个公共的类中. 在这种情况下, 它们必须被声明为 public. 在引入这个切入点时, 必须将类名也包括在内. 如果类没有与这个切面放在同一个包中, 还必须包含包名.
其他通知可以通过方法名称引入该切入点.
重用切入点定义示例代码
@Order(1)
@Aspect
@Component
public class VlidationAspect {
@Before("com.atguigu.spring.aop.LoggingAspect.declareJointPointExpression()")
public void validateArgs(JoinPoint joinPoint){
System.out.println("-->validate:" + Arrays.asList(joinPoint.getArgs()));
}
}
@Order(2)
@Aspect
@Component
public class LoggingAspect {
/**
* 定义一个方法, 用于声明切入点表达式. 一般地, 该方法中再不需要添入其他的代码.
* 使用 @Pointcut 来声明切入点表达式.
* 后面的其他通知直接使用方法名来引用当前的切入点表达式.
*/
@Pointcut("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(..))")
public void declareJointPointExpression(){}
/**
* 在 com.atguigu.spring.aop.ArithmeticCalculator 接口的每一个实现类的每一个方法开始之前执行一段代码
*/
@Before("declareJointPointExpression()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
Object [] args = joinPoint.getArgs();
System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
}
/**
* 在方法执行之后执行的代码. 无论该方法是否出现异常
*/
@After("declareJointPointExpression()")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName + " ends");
}
/**
* 在方法法正常结束受执行的代码
* 返回通知是可以访问到方法的返回值的!
*/
@AfterReturning(value="declareJointPointExpression()",
returning="result")
public void afterReturning(JoinPoint joinPoint, Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName + " ends with " + result);
}
/**
* 在目标方法出现异常时会执行的代码.
* 可以访问到异常对象; 且可以指定在出现特定异常时在执行通知代码
*/
@AfterThrowing(value="declareJointPointExpression()",
throwing="e")
public void afterThrowing(JoinPoint joinPoint, Exception e){
String methodName = joinPoint.getSignature().getName();
System.out.println("The method " + methodName + " occurs excetion:" + e);
}
/**
* 环绕通知需要携带 ProceedingJoinPoint 类型的参数.
* 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法.
* 且环绕通知必须有返回值, 返回值即为目标方法的返回值
*/
@Around("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(..))")
public Object aroundMethod(ProceedingJoinPoint pjd){
Object result = null;
String methodName = pjd.getSignature().getName();
try {
//前置通知
System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
//执行目标方法
result = pjd.proceed();
//返回通知
System.out.println("The method " + methodName + " ends with " + result);
} catch (Throwable e) {
//异常通知
System.out.println("The method " + methodName + " occurs exception:" + e);
throw new RuntimeException(e);
}
//后置通知
System.out.println("The method " + methodName + " ends");
return result;
}
}
基于XML的配置声明切面
除了ASpectJ注解声明切面,Spring也支持在Bean配置文件中声明切面,这种声明是通过aop schema中的xml元素完成的
正常情况下,基于注解的声明要优先于基于xml的声明,通过AspectJ注解,切面可以与AspectJ兼容,而基于xml的配置则是Spring专有的,由于AspectJ得到越来越多的AOP框架支持,所有以AspectJ注解风格编写的切面将会有更多重用的机会.
基于xml--声明切面
当使用xml声明切面时,需要在<beans>根元素中导入 aop Schema
在bean配置文件中,所有的Spring aop配置都必须定义在<aop:config>元素内部,对于每个切面而言,都要常见一个<aop:aspect>元素来为具体的切面实现引用后端Bean实例;
qiemianBean必须有一个标识符,供<aop:aspect>元素引用
声明切面的实例代码
<!-- 配置切面的 bean. -->
<bean id="loggingAspect"
class="com.atguigu.spring.aop.xml.LoggingAspect"></bean>
<bean id="vlidationAspect"
class="com.atguigu.spring.aop.xml.VlidationAspect"></bean>
<!-- 配置 AOP -->
<aop:config>
<!-- 配置切点表达式 -->
<aop:pointcut expression="execution(* com.atguigu.spring.aop.xml.ArithmeticCalculator.*(int, int))"
id="pointcut"/>
<!-- 配置切面及通知 -->
<aop:aspect ref="loggingAspect" order="2">
<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
<aop:after method="afterMethod" pointcut-ref="pointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
<!--
<aop:around method="aroundMethod" pointcut-ref="pointcut"/>
-->
</aop:aspect>
<aop:aspect ref="vlidationAspect" order="1">
<aop:before method="validateArgs" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
基于xml--声明切入点
切入点使用<aop:pointcut>元素声明
切入点必须定义在<aop:aspect>元素下,或者直接定义在<aop:config>元素下
-定义在<aop:aspect>元素下,只对当前切面有效
-定义在<aop:config>元素下:对所有切面都生效
基于xml的aop配置不允许在切入点表达式中用名称引用其他的切入点
基于xml--声明通知
在aopSchema中,每种通知类型都对应一个特定的xml元素.
通知元素需要使用<pointcut-ref>来引用切入点,或用<pointcut>直接切入切入点表达式,method属性指定切面中通知方法的名称;
Spirng中的事务管理
事务简介
事务管理是企业级应用程序开发中必不可少的技术, 用来确保数据的完整性和一致性.
事务就是一系列的动作, 它们被当做一个单独的工作单元.这些动作要么全部完成, 要么全部不起作用
事务的四个关键属性(ACID)
-原子性(atomicity): 事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用.
-一致性(consistency): 一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中.
-隔离性(isolation): 可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏.
-持久性(durability): 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中.
spring中的事务管理
作为企业级应用程序框架, Spring 在不同的事务管理 API之上定义了一个抽象层. 而应用程序开发人员不必了解底层的事务管理 API, 就可以使用 Spring 的事务管理机制.
Spring 既支持编程式事务管理, 也支持声明式的事务管理.
编程式事务管理: 将事务管理代码嵌入到业务方法中来控制事务的提交和回滚. 在编程式管理事务时, 必须在每个事务操作中包含额外的事务管理代码.
声明式事务管理: 大多数情况下比编程式事务管理更好用. 它将事务管理代码从业务方法中分离出来, 以声明的方式来实将事务管理代码从业务方法中分离出来, 以声明的方式来实方法模块化. Spring 通过 Spring AOP 框架支持声明式事务管理.
Spring中的事务管理器
Spring 从不同的事务管理 API 中抽象了一整套的事务机制. 开发人员不必了解底层的事务 API, 就可以利用这些事务机制. 有了这些事务机制, 事务管理代码就能独立于特定的事务技术了.
Spring 的核心事务管理抽象是PlatformTransactionManager,它为事务管理封装了一组独立于技术的方法,无论使用Spring的那种事务声明策略,事务管理器都是必要的.
spring中的事务管理器的不同实现
DataSourceTransactionmanger:在应用程序中只需要处理一个数据源,而且通过JDBC获取;
JtaTransactionManager:在JavaEE应用服务器上用JTA(Java Transaction API)进行事务管理
HibernateTransactionManager用于Hibernate框架存取数据库
事务管理器以普通的bean形式出现在SpringIOC容器中
<!-- 1. 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
用事务通知声明式地管理事务
事务管理是一种横切关注点
为了在Spring3.0找那个启用声明式事务管理,可以通过tx Schema中定义的<tx:advice>元素声明事务通知,为此必须事先将这个Schema定义添加到<beans>根元素中去
声明了事务通知后,就需要将它与切入点关联起来,由于事务通知实在<aop:config>元素外部声明的,所以它无法直接与切入点产生关联,所以必须在<aop:config>元素中声明一个增强器<aop:advisor>通知与切入点关联起来;
由于Spring AOP是基于代理的方法,所以只能增强公共方法,一次只有公共方法才能通过Spring AOP进行事务管理;
用事务通知声明式地管理事务示例代码
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
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-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="com.atguigu.spring"></context:component-scan>
<!-- 导入资源文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置 C3P0 数据源 -->
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
</bean>
<!-- 配置 Spirng 的 JdbcTemplate -->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置 bean -->
<bean id="bookShopDao" class="com.atguigu.spring.tx.xml.BookShopDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="bookShopService" class="com.atguigu.spring.tx.xml.service.impl.BookShopServiceImpl">
<property name="bookShopDao" ref="bookShopDao"></property>
</bean>
<bean id="cashier" class="com.atguigu.spring.tx.xml.service.impl.CashierImpl">
<property name="bookShopService" ref="bookShopService"></property>
</bean>
<!-- 1. 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2. 配置事务属性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 根据方法名指定事务的属性 -->
<tx:method name="purchase" propagation="REQUIRES_NEW"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 3. 配置事务切入点, 以及把事务切入点和事务属性关联起来 -->
<aop:config>
<aop:pointcut expression="execution(* com.atguigu.spring.tx.xml.service.*.*(..))"
id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
</beans>
用@Transactional注解声明式地管理事务
除了在带有切入点,通知和增强器的Bean配置文件中声明事务外,Spring还允许简单得使用@Transactional注解来标注事务方法.
为了讲方法定义为支持事务处理的,可以为方法添加@Transactional主键,根据Spring AOP基于代理机制,只能标注共有方法.
可以在方法或者类级别上添加@Transactional注解.当把这个注解应用到类上时,这个类中所有的公共方法都会被定义成支持事务处理的.
在bean配置文件中只需要启动<tx:annotation-driven>元素,并为之指定事务管理器就可以了.
如果事务处理器名称是transactionManager,就可以在<tx:annotation-driven>元素中省略transaction-manager属性,之歌元素就会自动监测该名称的事务处理器.
使用@Transactional注解声明式地管理事务配置文件示例代码
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="com.atguigu.spring"></context:component-scan>
<!-- 导入资源文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置 C3P0 数据源 -->
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
</bean>
<!-- 配置 Spirng 的 JdbcTemplate -->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置 NamedParameterJdbcTemplate, 该对象可以使用具名参数, 其没有无参数的构造器, 所以必须为其构造器指定参数 -->
<bean id="namedParameterJdbcTemplate"
class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
事务传播属性
当事务方法被另一个事务方法调用时,必须制定事务应该如何传播.例如:方法可能继续出现在现有的事务中执行,也可能开启一个新事务,并在自己的事务中运行.
事务的传播行为可以由传播属性指定,Spring定义了7中传播行为
Spring支持的事务传播行为:
配置在@Transactional注解中,或者<tx:method>中
传播属性 | 描述 |
REQUIRED(默认) | 如果有事务在运行,当前的方法就在这个事务内运行,否者,就启动一个新的事物,并在自己的事务内运行 |
REQUIRED_NEW | 当前的方法必须启动新事务,并在它自己的事务内运行。如果有事务在运行,应该将它挂起; |
SUPPORTS | 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务内 |
NOT_SUPPORTED | 当前的方法不应该运行在事务中,如果有运行的事务,将它挂起 |
MANDATORY | 当前的方法必须运行在事物内部,如果没有正在运行的事务,就抛出异常 |
NEVER | 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常 |
NESTED | 如果有事务晕高兴,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在自己的事务内运行 |
并发事务所导致的问题
当同一个应用程序或者不同应用程序中的多个事务在同一个数据集上并发执行时,可能会出现许多意外的问题;
并发事务所导致的问题可以分为下面三种类型:
- 脏读:也就是当数据库的一个事务A正在使用一个数据但还没有提交,另外一个事务B也访问到了这个数据,还使用了这个数据,这就会导致事务B使用了事务A没有提交之前的数据。
- 不可重复读:在一个事务A中多次操作一个数据,在这两次或多次访问这个数据的中间,事务B也操作此数据,并使其值发生了改变,这就导致同一个事务A在两次操作这个数据的时候值不一样,这就是不可重复读。
- 幻读:对于两个事务A,B,A从一个表中读取了一个字段,然后B在该表中插入了一些新的行,之后,如果A再次读取同一个表,就会多出几行。
因此要用事务的隔离级别来保证事务的正常发生
Spring支持的事务隔离级别
隔离级别 | 描述 |
DEFAULT | 使用底层数据库的默认隔离级别,对于大多数数据库来说,默认隔离级别都是READ_COMMITED |
READ_OMMMITED | 只允许事务读取已经被其他事务提交的变更,可以避免脏读,但不可重复读和幻读问题仍然存在 |
READ_UNCOMMITED | 允许事务读取未被事务提交的变更,脏读,不可重复读和幻读的问题都会出现 |
REPEATABLE_READ | 确保事务可以多次从一个字段中读取相同的值。在这个事务持续期间。禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,但幻读的问题仍然存在 |
SERIALIZABLE | 确保事务可以从一个表中读取相同的行,在这个事务期间,禁止其他事务对该表执行插入,更新和删除操作,所有的并发问题都可以避免,但性能十分低下。 |
事务的隔离级别要得到底层数据库引擎的支持,而不是应用程序或者框架的支持。
Oracle支持的2种事务隔离级别:READ_COMMITED,SERIALIZABLE
MySQL支持4种事务隔离级别;
设置事务的隔离属性
用@Transactional注解声明式地管理事务时可以在@Transactional地Isolation属性中设置隔离级别
在<xml>事务通知时,可以在<tx:method>元素种指定隔离级别
设置回滚事务属性
事务的回滚规则可以通过@Transactionnal注解的rollbackFor和noRollbackFor属性来定义。这两个属性被声明为Class[]类型的,因此可以为这两个属性指定多个异常类;
-rollbackFor:遇到时必须进行回滚
-noRollbackFor:一组异常类,遇到时必须不会滚;
设置:同事务的隔离属性一致;
超时和只读属性
由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。
超时事务属性:事务在强制回滚之前可以保持多久,这样可以防止长期运行的事务占用资源
只读事务属性:表示事务只读数据但不更新数据,这样可以可以帮助数据库引擎优化事务