spring AOP开发和JDBC
今天主要学习AOP的概念,简单地说,AOP将业务逻辑的各个部分进行隔离,采用横向抽取的机制,对模块间进行解耦,能有效提高开发效率。然后学习如何进行手动及spring进行AOP开发,利用spring进行AOP开发时,可以选择基于xml配置的方式或基于注释的方式实现AOP。基于注释的方式实现 AOP 的效果是最方便的方式,所以实际开发中推荐使用注解的方式。最后,基于昨天学习的配置数据源的基础上,学习了spring提供的JDBC Template的配置和基本CRUD操作的使用,JDBC模板可以对不同的数据源进行封装,并为用户提供了统一的大量的查询和更新数据库的方法,如 query()、update() 等,方便开发。
AOP简介
AOP相关概念
- AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,AOP和面向对象编程(OOP)类似,也是一种编程模
- Spring AOP 是基于 AOP 编程模式的一个框架,它的使用有效减少了系统间的重复代码,达到了模块间的松耦合目的。它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率
- AOP 采取横向抽取机制,取代了传统纵向继承体系的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面
- 目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ。Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。AspectJ 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入
- AOP作用及优势
- 在程序运行期间,在不修改代码的情况下,对方法进行功能增强
- 减少重复代码,提高开发效率,并且便于维护
- AOP底层实现
- AOP的底层是通过spring提供的动态代理技术实现的。在运行期间,spring通过动态代理技术动态地生成代理对象,代理对象方法执行时进行功能增强的介入,去调用目标对象的代方法,从而实现功能增强
AOP术语
为了更好地理解 AOP,就需要对 AOP 的相关术语有一些了解,这些专业术语主要包含 Joinpoint(spring中就是指方法)、Pointcut、Advice、Target、Weaving、Proxy 和 Aspect,它们的含义如下表所示:
AOP常用动态代理技术
- JDK代理:基于接口的动态代理技术
- cglib代理:基于父类的动态代理技术
手写JDK动态代理示例
- 创建目标接口并定义实现类
package com.example.proxy.jdk; public interface TargetInterface { public void save(); } // -------------------------------------- package com.example.proxy.jdk; public class TargetInterfaceImpl implements TargetInterface{ @Override public void save() { System.out.println("save running"); } }
- 定义增强类,类的方法为增强目标对象的方法
package com.example.proxy.jdk; public class Advice { public void before(){ System.out.println("前置增强"); } public void after(){ System.out.println("后置增强"); } }
- 定义测试类,调用jdk代理方法,代理目标对象,并进行方法增强
package com.example.proxy.jdk; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JdkTest { public static void main(String[] args) { // 创建目标对象 TargetInterfaceImpl target = new TargetInterfaceImpl(); // 增强对象 Advice advice = new Advice(); // 生成动态代理对象 TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance( target.getClass().getClassLoader(), // 目标对象加载器类 target.getClass().getInterfaces(), // 与目标对象相同的接口字节码对象数组 new InvocationHandler() { // 调用代理对象的任何方法,实质执行都是invoke方法 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 前置增强 advice.before(); Object ret = method.invoke(target, args);// 执行目标方法 // 后置增强 advice.after(); return ret; } } ); // 调用代理对象的方法,实质调用的是invoke,所以具有前置、后置增强效果 proxy.save(); } }
手写cglib动态代理示例
- 直接定义目标类,cglib是基于父类的,不需要像Jdk代理一样定义接口
package com.example.proxy.cglib; public class Target { public void save() { System.out.println("save running"); } }
- 增强类与Jdk代理一致
- 定义测试类,使用cglib代理方法,对目标进行增强
package com.example.proxy.cglib; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CglibTest { public static void main(String[] args) { // 创建目标对象 Target target = new Target(); // 增强对象 Advice advice = new Advice(); // 基于cglib,生成动态代理对象 // 1. 创建增强器 Enhancer enhancer = new Enhancer(); // 2. 设置父类(目标) enhancer.setSuperclass(Target.class); // 3. 设置回调 enhancer.setCallback(new MethodInterceptor() { // 与jdk代理中的invoke类似 @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // 执行前置增强 advice.before(); // 执行目标 Object invoke = method.invoke(target, args); // 执行后置增强 advice.after(); return invoke; } }); // 4. 创建代理对象,cglib基于父类的,代理是Target的子类对象 Target proxy = (Target) enhancer.create(); // 调用增强后的方法 proxy.save(); } }
spring进行AOP开发事项
- 需要编写的内容
- 编写核心业务代码(目标类的目标方法)
- 编写切面类,切面类中有通知(增强功能方法)
- 在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合
- AOP技术实现的内容
- spring框架监控切入点方法的执行,一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能切入,完成完整的代码逻辑运行。
- AOP底层使用哪种代理方式
- spring框架会根据目标类是否实现了接口来决定采用哪种动态代理方式
基于XML的AOP开发
切点表达式
- 切点表达式语法:
execution([修饰符] 返回类型 包名.类名.方法名(参数))
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用*代表任意
- 包名与类名之间一个点.代表当前包下的类,两个点… 代表当前包及其子包下的类
- 参数列表可以使用两个点… 表示任意个数、任意类型的参数列表
通知配置语法及类型
开发步骤
- 导入AOP相关坐标
- 创建目标接口和目标类(内部须有切点)
- 创建切面类(内部有增强方法)
- 将目标类和切面类的对象创建权交给spring
- 在applicationContext.xml文件中配置织入关系
- 测试代码
开发示例
- pom.xml导入AOP相关坐标,使用aspectj开发AOP
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency>
- 创建目标接口和目标类
package com.example.aop; // 目标接口 public interface TargetInterface { public void save(); } // ---------------------------------- package com.example.aop; // 目标类 public class Target{ public void save() { System.out.println("save running"); } }
- 创建切面类(内部有增强方法)
package com.example.aop; // 创建切面类 public class MyAspect { public void before(){ System.out.println("前置增强"); } public void after(){ System.out.println("后置增强"); } }
- 将目标类和切面类的对象创建权交给spring,需要导入aop命名空间,然后进行aop织入关系的配置
<!--命名空间--> xmlns:aop="http://www.springframework.org/schema/aop" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd <!--目标对象--> <bean id="target" class="com.example.aop.Target"></bean> <!--切面对象--> <bean id="myAspect" class="com.example.aop.MyAspect"></bean> <!--配置织入关系,告诉spring框架,哪些方法(切点)需要做哪些增强(通知)--> <aop:config> <!--声明切面--> <aop:aspect ref="myAspect"> <!--配置切点,通配符,指定Target类所有方法都是切点--> <aop:pointcut id="pointCut" expression="execution(* com.example.aop.Target.*(..))"/> <!--切面 = 切点 + 通知--> <aop:before method="before" pointcut-ref="pointCut"/> <!--指定切点--> <!-- <aop:before method="before" pointcut="execution(public void com.example.aop.Target.save())"/> --> <aop:after method="after" pointcut-ref="pointCut"/> </aop:aspect> </aop:config>
- 定义测试类,测试
package com.example.aop; 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 AopTest { // 要测试的目标对象 @Autowired private Target target; @Test public void test(){ target.save(); } } /* 输出 前置增强 save running 后置增强 */
基于注解的AOP开发
- 在 Spring 中,尽管使用 XML 配置文件可以实现 AOP 开发,但是如果所有的相关的配置都集中在配置文件中,势必会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难
- 为此,AspectJ 框架为 AOP 开发提供了另一种开发方式——基于 Annotation 的声明式。AspectJ 允许使用注解定义切面、切入点和增强处理,而 Spring 框架则可以识别并根据这些注解生成 AOP 代理
AspectJ注解类型
开发步骤
- 创建目标接口和目标类(内部有切点)
- 创建切面类(内部有增强方法)
- 将目标类和切面类的对象创建权交给spring
- 在切面类中使用注解配置织入关系
- 在配置文件中开启注解的组件扫描和AOP自动代理
- 测试
开发示例
- 创建目标接口或目标类,并标识@Component注解,将创建权交给spring容器
package com.example.aopanno; import org.springframework.stereotype.Component; // 目标类 @Component("target") public class Target{ public void save() { int i = 1/0; // 测试AOP异常通知 System.out.println("save running"); } }
- 创建切面类,使用@Component和@Aspect注解,并在切面类中使用注解配置织入关系
package com.example.aopanno; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; // 创建切面类 @Component("myAspect") // 加入spring容器 @Aspect // 标示当前类是一个切面类 public class MyAspect { // 定义切点表达式 // 用于取代:<aop:pointcut id="pointCut" expression="execution(* com.example.aopanno.Target.*(..))"/> // 要求:方法必须是private,没有值,名称自定义,没有参数 @Pointcut("execution(* com.example.aopanno.Target.*(..))") private void pointCut() { } // 前置通知 @Before("pointCut()") public void myBefore(JoinPoint joinPoint) { System.out.print("前置通知,目标:"); System.out.print(joinPoint.getTarget() + "方法名称:"); System.out.println(joinPoint.getSignature().getName()); } // 后置通知 @AfterReturning(value = "pointCut()") public void myAfterReturning(JoinPoint joinPoint) { System.out.println("后置通知,方法名称:" + joinPoint.getSignature().getName()); } // 环绕通知 @Around("pointCut()") public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕开始"); // 开始 Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法 System.out.println("环绕结束"); // 结束 return obj; } // 异常通知 @AfterThrowing(value = "pointCut()", throwing = "e") public void myAfterThrowing(JoinPoint joinPoint, Throwable e) { System.out.println("异常通知" + "出错了" + e.getMessage()); } // 最终通知 @After("pointCut()") public void myAfter() { System.out.println("最终通知"); } }
- 在配置文件中开启注解的组件扫描和AOP自动代理
<!--开启组件扫描--> <context:component-scan base-package="com.example.aopanno"/> <!--开启AOP自动代理--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- Junit测试
package com.example.aopanno; 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 AopAnnoTest { // 要测试的目标对象 @Autowired private Target target; @Test public void test(){ target.save(); } } /* 测试结果 环绕开始 前置通知,目标:com.example.aopanno.Target@e4d2696方法名称:save 异常通知出错了/ by zero 最终通知 */
JDBC Template
- JDBC模板是spring框架提供的一个对象,是对原始繁琐的JDBC API对象的简单封装。spring框架为我们提供了很多的操作模板类。例如:操作关系型数据库的JDBC Template和HibernateTemplate,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等
- Spring 框架针对数据库开发中的应用提供了 JDBCTemplate 类,该类是 Spring 对 JDBC 支持的核心,它提供了所有对数据库操作功能的支持
- Spring 框架提供的JDBC支持主要由四个包组成,分别是 core(核心包)、object(对象包)、dataSource(数据源包)和 support(支持包),org.springframework.jdbc.core.JdbcTemplate 类就包含在核心包中。作为 Spring JDBC 的核心,JdbcTemplate 类中包含了所有数据库操作的基本方法
JDBCTemplate开发步骤
- 导入spring-jdbc和spring-tx坐标
- 创建数据库和实体
- 创建JDBC Template对象
- 执行数据库操作
示例
- 导入spring-jdbc和spring-tx坐标
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> </dependency>
- spring xml配置文件配置数据源对象和JDBC Template对象
<!-- 导入properties文件,设置数据源对象 --> <context:property-placeholder location="classpath:jdbc.properties"/> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!--配置JDBC模板--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--默认必须使用数据源,setter注入数据源--> <property name="dataSource" ref="dataSource"/> </bean>
- 定义Acount类,与数据库所要操作的表格字段一致,用来接收查询结果
package com.example.datasource.jdbc; public class Account { private String name; private double money; public String getName() { return name; } public void setName(String name) { this.name = name; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } @Override public String toString() { return "Account{" + "name='" + name + '\'' + ", money=" + money + '}'; } }
- 测试JDBC Template CRUD基本使用
package com.example.datasource.jdbc; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.util.List; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class JdbcTemplateTest { @Autowired private JdbcTemplate jdbcTemplate; @Test public void testUpdate(){ // 更新记录 int row = jdbcTemplate.update("update account set money=? where name=?", 200, "张三"); System.out.println(row); } @Test public void testDelete(){ // 删除记录 int row = jdbcTemplate.update("delete from account where name=?", "李四"); System.out.println(row); } @Test public void testInsert(){ // 插入记录 int row = jdbcTemplate.update("insert into account values(?,?)", "Tony", 123.4); System.out.println(row); } @Test public void testQueryAll(){ // 查询所有记录 List<Account> accountList = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper<Account>(Account.class)); System.out.println(accountList); } @Test public void testQueryOne(){ // 查询单条记录 Account record = jdbcTemplate.queryForObject("select * from account where name=?", new BeanPropertyRowMapper<Account>(Account.class), "Tony"); System.out.println(record); } @Test public void testQueryCount(){ // 聚合查询,查询符合条件的记录数 Long recordNum = jdbcTemplate.queryForObject("select count(*) from account where name=?", Long.class, "Tony"); System.out.println(recordNum); } }
spring声明式事务管理
- spring的声明式事务顾名思义就是采用声明的方式来处理事务。这里所指的声明,就是指在配置文件中声明,用在spring配置文件中声明式的处理事务来代替代码式的处理事务
- Spring 的事务管理是基于 AOP 实现的,而 AOP 是以方法为单位的。Spring 的事务属性分别为传播行为、隔离级别、只读和超时属性,这些属性提供了事务应用的方法和描述策略
- 在 Java EE 开发经常采用的分层模式中,Spring 的事务处理位于业务逻辑层,它提供了针对事务的解决方案
- spring-tx是 Spring 提供的用于事务管理的依赖包,其中包括事务管理的三个核心接口:
PlatformTransactionManager
、TransactionDefinition
和TransactionStatus
- 声明式事务管理的作用
- 事务管理不侵入开发的组件。具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理式属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可
- 在不需要事务管理的时候,只要在设定文件上修改,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便
事务管理相关对象
- PlatformTransactionManager
- PlatformTransactionManager 接口是 Spring 提供的平台事务管理器,用于管理事务。该接口中提供了三个事务操作方法,具体如下
- TransactionStatus getTransaction(TransactionDefinition definition):用于获取事务状态信息
- void commit(TransactionStatus status):用于提交事务
- void rollback(TransactionStatus status):用于回滚事务
- 在项目中,Spring 将 xml 中配置的事务详细信息封装到对象 TransactionDefinition 中,然后通过事务管理器的 getTransaction() 方法获得事务的状态(TransactionStatus),并对事务进行下一步的操作
- PlatformTransactionManager 接口是 Spring 提供的平台事务管理器,用于管理事务。该接口中提供了三个事务操作方法,具体如下
- TransactionDefinition
- TransactionDefinition 接口是事务定义(描述)的对象,它提供了事务相关信息获取的方法,其中包括五个操作,具体如下
- String getName():获取事务对象名称
- int getIsolationLevel():获取事务的隔离级别
- int getPropagationBehavior():获取事务的传播行为
- int getTimeout():获取事务的超时时间
- boolean isReadOnly():获取事务是否只读
- 在上述五个方法的描述中,事务的传播行为是指在同一个方法中,不同操作前后所使用的事务。传播行为的种类如下表所示
在事务管理过程中,传播行为可以控制是否需要创建事务以及如何创建事务。通常情况下,数据的查询不会改变原数据,所以不需要进行事务管理,而对于数据的增加、修改和删除等操作,必须进行事务管理
- TransactionDefinition 接口是事务定义(描述)的对象,它提供了事务相关信息获取的方法,其中包括五个操作,具体如下
- TransactionStatus
- TransactionStatus 接口是事务的状态,它描述了某一时间点上事务的状态信息。其中包含六个操作,具体如下表所示
- TransactionStatus 接口是事务的状态,它描述了某一时间点上事务的状态信息。其中包含六个操作,具体如下表所示
基于XML的声明式事务管理
- 配置要点
- 平台事务管理器配置
- 事务通知配置
- 事务aop织入关系配置
示例
- pom文件导入以下坐标
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.5</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> </dependency>
- 定义Account类,与要操作的数据库表对应
package com.example.tx.domain; public class Account { private String name; private double money; public String getName() { return name; } public void setName(String name) { this.name = name; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } @Override public String toString() { return "Account{" + "name='" + name + '\'' + ", money=" + money + '}'; } }
- 定义dao层接口,并定义其对应实现类,定义方法模拟转账操作中付款,收款操作
package com.example.tx.dao; public interface AccountDao { // 收款 public void in(String inUser, double money); // 付款 public void out(String outUser, double money); } // ----------------------------------------------- package com.example.tx.dao.impl; import com.example.tx.dao.AccountDao; import org.springframework.jdbc.core.JdbcTemplate; public class AccoutDaoImpl implements AccountDao { // 依赖Jdbc模板 private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } // 收款实现方法 @Override public void in(String inUser, double money) { jdbcTemplate.update("update account set money=money+? where name=?", money, inUser); } // 付款实现方法 @Override public void out(String outUser, double money) { jdbcTemplate.update("update account set money=money-? where name=?", money, outUser); } }
- 定义service层接口,定义其对应实现类,定义交易操作方法,同时该方法作为切点,利用AOP进行方法增强(事务管理)
package com.example.tx.service; public interface AccountService { // 转账方法 public void transfer(String inUser, String outUser, double money); } // ----------------------------------------------- package com.example.tx.service.impl; import com.example.tx.dao.AccountDao; import com.example.tx.service.AccountService; public class AccountServiceImpl implements AccountService { private AccountDao accountDao; public void setAccountDao(AccountDao accountDao){ this.accountDao = accountDao; } // 转账方法实现,将此方法看成切点,执行前进行开启事务增强,执行后进行提交事务增强 @Override public void transfer(String inUser, String outUser, double money) { accountDao.out(outUser, money); int i = 1/0; // 模拟断电等异常情况,需进行事务回滚 accountDao.in(inUser, money); } }
- xml配置
- 导入aop和tx命名空间
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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
- 数据源对象和Jdbc模板配置
<!-- 导入properties文件,设置数据源对象 --> <context:property-placeholder location="classpath:jdbc.properties"/> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!--配置JDBC模板--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--默认必须使用数据源,setter注入数据源--> <property name="dataSource" ref="dataSource"/> </bean>
- Bean容器对象配置
<!--配置AccountDao--> <bean id="accountDao" class="com.example.tx.dao.impl.AccoutDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> <!--配置AccountService,注入accountDao依赖--> <bean id="accountService" class="com.example.tx.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean>
- 平台事务管理器配置
<!-- 事务管理器,依赖于数据源 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
- 事务通知配置
<!-- 编写通知:对事务进行增强(通知),需要编写切入点和具体执行事务的细节 --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!-- 给切入点方法添加事务详情,name表示方法名称,*表示任意方法名称,propagation用于设置传播行为,read-only表示隔离级别,是否只读 --> <!--对find开头的切点方法设置事务增强属性--> <!--<tx:method name="find*" propagation="SUPPORTS" rollback-for="Exception" read-only="true"/>--> <!--单独对transfer方法设置事务增强属性--> <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT" rollback-for="Exception" read-only="false"/> <!--配置对所有切点方法事务增强属性--> <!--<tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" />--> </tx:attributes> </tx:advice>
- 事务aop织入关系配置
<!-- aop编写,让Spring自动对目标生成代理,需要使用AspectJ的表达式 --> <aop:config> <!-- 配置切入点为service.impl包下所有类的方法 --> <aop:pointcut expression="execution(* com.example.tx.service.impl.*.*(..))" id="txPointCut" /> <!-- 切面:将切入点与通知整合 --> <aop:advisor pointcut-ref="txPointCut" advice-ref="txAdvice" /> </aop:config>
- 导入aop和tx命名空间
- 定义controller层,模拟事务操作
package com.example.tx.controller; import com.example.tx.service.AccountService; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AccountController { // 模拟控制层,测试转账事务管理 public static void main(String[] args) { ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountService as = app.getBean(AccountService.class); as.transfer("Tony", "tom", 100); } }
基于注解的声明式事务管理
- 对于基于注解的声明式事务管理,与原来的xml配置文件相比,只修改了事务管理器部分,新添加并注册了事务管理器的驱动
<!-- 导入properties文件,设置数据源对象 --> <context:property-placeholder location="classpath:jdbc.properties"/> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!--配置JDBC模板--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--默认必须使用数据源,setter注入数据源--> <property name="dataSource" ref="dataSource"/> </bean> <!--配置AccountDao--> <bean id="accountDao" class="com.example.tx.anno.dao.impl.AccoutDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> <!--配置AccountService,注入accountDao依赖--> <bean id="accountService" class="com.example.tx.anno.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- 事务管理器,依赖于数据源 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!--注册事务管理驱动,使用注解开发时使用--> <tx:annotation-driven transaction-manager="txManager"></tx:annotation-driven>
- 定义基于注解的service实现类
package com.example.tx.anno.service.impl; import com.example.tx.anno.dao.AccountDao; import com.example.tx.anno.service.AccountService; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; //@Transactional(isolation = Isolation.READ_COMMITTED) // 指定整个类的切点方法,事务管理的隔离级别 public class AccountServiceImpl implements AccountService { private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } // 转账方法实现,将此方法看成切点,执行前进行开启事务增强,执行后进行提交事务增强 /* <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!--单独对transfer方法设置事务增强属性--> <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT" rollback-for="Exception" read-only="false"/> </tx:attributes> </tx:advice> */ // 相当于 @Transactional(isolation = Isolation.DEFAULT, rollbackFor = Exception.class, readOnly = false, propagation = Propagation.REQUIRED) @Override public void transfer(String inUser, String outUser, double money) { accountDao.out(outUser, money); int i = 1/0; // 模拟断电等异常情况,需进行事务回滚 accountDao.in(inUser, money); } }
- 测试
package com.example.tx.anno.controller; import com.example.tx.anno.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 AccountController { @Autowired private AccountService accountService; // 模拟控制层,测试转账事务管理 @Test public void test() { accountService.transfer("Tony", "tom", 100); } }