Spring二:AOP和事务
1 AOP
1.1 概述
什么是 AOP
- AOP全称是Aspect-Oriented Programming,即面向切面编程。
- AOP采取横向抽取机制,将分散在各个方法中的代码提取出来,然后在程序编译或运行时,再将这些提取取来的代码用到需要执行的地方。
- AOP是一种新的编程思想,也是spring框架中的一个重要内容。它是OOP的延伸和补充,而不是其替代品。
- 利用AOP可以对业务逻辑的各个部分进行分离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提升开发效率。
- 目前流行的AOP框架有两个,分别为 Spring AOP 和 AspectJ【重点介绍】。
AOP的作用和优势
- 作用:在程序运行期间,不修改源码对已有方法进行增强处理。
- 优势:减少重复代码;提高开发效率;方便维护等。
AOP相关术语
- Joinpoint(连接点):指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
- Pointcut(切入点):指我们要对哪些Joinpoint进行拦截的定义。
- Advice(通知/增强):指拦截到Joinpoint之后所要做的事情就是通知。
- 通知的类型有:前置通知,后置通知,异常通知,最终通知,环绕通知。
- Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field。
- Target(目标对象):代理的目标对象。
- Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。
- 补充:spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。[了解即可]
- Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
- Aspect(切面):是切入点和通知(引介)的结合。
AOP的实现方式
- AOP的实现方式为动态代理技术。动态代理简介如下
- 特点:字节码随用随创建,随用随加载。
- 作用:不修改源码的基础上对方法增强。
- 分类:基于接口的动态代理(JDK动态代理)和基于子类的动态代理(CGLIB动态代理)。
说明
- 新版本Spring建议使用AspectJ来开发AOP,Spring AOP 了解即可,下面的代码演示均基于 AspectJ 开发。
1.2 xml方式
-
普通maven工程,结构图如下
-
依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.18.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency>
-
模拟持久层
package cn.dao; public interface UserDao { public void addUser(); public void deleteUser(); }
package cn.dao.impl; import cn.dao.UserDao; public class UserDaoImpl implements UserDao { public void addUser() { // 模拟异常(用于异常通知) // int i = 10 / 0; System.out.println("*** userDao操作:添加用户 ***"); } public void deleteUser() { System.out.println("*** userDao操作:删除用户 ***"); } }
-
切面
package cn.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; public class MyAspect { // 前置通知 public void myBefore(JoinPoint joinPoint) { System.out.print("前置通知 :模拟执行权限检查...,"); System.out.print("目标类是:" + joinPoint.getTarget()); System.out.println(",被织入增强处理的目标方法为:" + joinPoint.getSignature().getName()); } // 后置通知 public void myAfterReturning(JoinPoint joinPoint) { System.out.print("后置通知:模拟记录日志...,"); System.out.println("被织入增强处理的目标方法为:" + joinPoint.getSignature().getName()); } /** * 环绕通知 * ProceedingJoinPoint 是JoinPoint子接口,表示可以执行目标方法 * 1.必须是Object类型的返回值 * 2.必须接收一个参数,类型为ProceedingJoinPoint * 3.必须throws Throwable */ public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 开始 System.out.println("环绕开始:执行目标方法之前,模拟开启事务..."); // 执行当前目标方法 Object obj = proceedingJoinPoint.proceed(); // 结束 System.out.println("环绕结束:执行目标方法之后,模拟关闭事务..."); return obj; } // 异常通知 public void myAfterThrowing(JoinPoint joinPoint, Throwable e) { System.out.println("异常通知:" + "出错了" + e.getMessage()); } // 最终通知 public void myAfter() { System.out.println("最终通知:模拟方法结束后的释放资源..."); } }
-
配置
<?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" 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"> <!-- 1 目标类 --> <bean id="userDao" class="cn.dao.impl.UserDaoImpl"/> <!-- 2 切面 --> <bean id="myAspect" class="cn.aop.MyAspect"/> <!-- 3 aop编程 --> <aop:config> <!-- 配置切面 --> <aop:aspect ref="myAspect"> <!-- 3.1 配置切入点,通知最后增强哪些方法 --> <!-- 含义:返回类型 包名.类名.方法名.(方法的参数) --> <!-- * 表示任意(注:返回类型与包名之间有一个空格),(..)表示任意参数 --> <aop:pointcut id="myPointCut" expression="execution(* cn.dao.*.*(..))"/> <!-- 3.2 关联通知Advice和切入点pointCut --> <!-- 3.2.1 前置通知 --> <aop:before method="myBefore" pointcut-ref="myPointCut"/> <!-- 3.2.2 后置通知,在方法返回之后执行,就可以获得返回值;returning属性:用于设置后置通知的第二个参数的名称,类型是Object --> <aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="returnVal"/> <!-- 3.2.3 环绕通知 --> <aop:around method="myAround" pointcut-ref="myPointCut"/> <!-- 3.2.4 抛出通知:用于处理程序发生异常--> <!-- * 注:如果程序没有异常,将不会执行增强;throwing属性:用于设置通知第二个参数的名称,类型Throwable --> <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/> <!-- 3.2.5 最终通知:无论程序发生任何事情,都将执行 --> <aop:after method="myAfter" pointcut-ref="myPointCut"/> </aop:aspect> </aop:config> </beans>
-
运行
package cn; import cn.dao.UserDao; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); UserDao userDao = (UserDao) applicationContext.getBean("userDao"); userDao.addUser(); /** * 结果 * 前置通知 :模拟执行权限检查...,目标类是:cn.dao.impl.UserDaoImpl@1722011b,被织入增强处理的目标方法为:addUser * 环绕开始:执行目标方法之前,模拟开启事务... * *** userDao操作:添加用户 *** * 最终通知:模拟方法结束后的释放资源... * 环绕结束:执行目标方法之后,模拟关闭事务... * 后置通知:模拟记录日志...,被织入增强处理的目标方法为:addUser */ /** * 异常结果 (开启 cn.dao.impl.UserDaoImpl#addUser() 中的 int i = 10 / 0;) * 前置通知 :模拟执行权限检查...,目标类是:cn.dao.impl.UserDaoImpl@1722011b,被织入增强处理的目标方法为:addUser * 环绕开始:执行目标方法之前,模拟开启事务... * 最终通知:模拟方法结束后的释放资源... * 异常通知:出错了/ by zero */ } }
1.3 annotation方式
-
普通maven工程,结构图如下
-
依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.18.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency>
-
模拟持久层
package cn.dao; public interface UserDao { public void addUser(); public void deleteUser(); }
package cn.dao.impl; import cn.dao.UserDao; import org.springframework.stereotype.Repository; @Repository("userDao") public class UserDaoImpl implements UserDao { public void addUser() { // 模拟异常(用于异常通知) // int i = 10 / 0; System.out.println("*** userDao操作:添加用户 ***"); } public void deleteUser() { System.out.println("*** userDao操作:删除用户 ***"); } }
-
切面
package cn.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { // 定义切入点表达式 @Pointcut("execution(* cn.dao.*.*(..))") // 使用一个返回值为void、方法体为空的方法来命名切入点 private void myPointCut() { } // 前置通知 @Before("myPointCut()") //不要忘记写括号 public void myBefore(JoinPoint joinPoint) { System.out.print("前置通知 :模拟执行权限检查...,"); System.out.print("目标类是:" + joinPoint.getTarget()); System.out.println(",被织入增强处理的目标方法为:" + joinPoint.getSignature().getName()); } // 后置通知 @AfterReturning(value = "myPointCut()") public void myAfterReturning(JoinPoint joinPoint) { System.out.print("后置通知:模拟记录日志...,"); System.out.println("被织入增强处理的目标方法为:" + joinPoint.getSignature().getName()); } // 环绕通知 @Around("myPointCut()") public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // 开始 System.out.println("环绕开始:执行目标方法之前,模拟开启事务..."); // 执行当前目标方法 Object obj = proceedingJoinPoint.proceed(); // 结束 System.out.println("环绕结束:执行目标方法之后,模拟关闭事务..."); return obj; } // 异常通知 @AfterThrowing(value = "myPointCut()", throwing = "e") public void myAfterThrowing(JoinPoint joinPoint, Throwable e) { System.out.println("异常通知:" + "出错了" + e.getMessage()); } // 最终通知 @After("myPointCut()") public void myAfter() { System.out.println("最终通知:模拟方法结束后的释放资源..."); } }
-
配置
package cn.conf; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration @ComponentScan("cn") @EnableAspectJAutoProxy //对应xml中<aop:aspectj-autoproxy/>,即配置spring开启注解AOP的支持 public class SpringConf { }
-
运行
package cn; import cn.conf.SpringConf; import cn.dao.UserDao; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class App { public static void main(String[] args) { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConf.class); UserDao userDao = (UserDao) applicationContext.getBean("userDao"); userDao.addUser(); /** * 结果 * 环绕开始:执行目标方法之前,模拟开启事务... * 前置通知 :模拟执行权限检查...,目标类是:cn.dao.impl.UserDaoImpl@22f31dec,被织入增强处理的目标方法为:addUser * *** userDao操作:添加用户 *** * 后置通知:模拟记录日志...,被织入增强处理的目标方法为:addUser * 最终通知:模拟方法结束后的释放资源... * 环绕结束:执行目标方法之后,模拟关闭事务... */ /** * 异常结果 (开启 cn.dao.impl.UserDaoImpl#addUser() 中的 int i = 10 / 0;) * 环绕开始:执行目标方法之前,模拟开启事务... * 前置通知 :模拟执行权限检查...,目标类是:cn.dao.impl.UserDaoImpl@22f31dec,被织入增强处理的目标方法为:addUser * 异常通知:出错了/ by zero * 最终通知:模拟方法结束后的释放资源... */ } }
2 事务
2.1 概述
简介
- 在Spring的所有JAR包中,包含一个名为spring-tx-4.3.6.RELEASE的JAR包(版本则需而定),该包就是Spring提供的用于事务管理的依赖包。
- 在该JAR包的org.springframework. transaction包中,我们可以找到3个接口文件PlatformTransactionManager、TransactionDefinition 和 TransactionStatus。接下来将对这三个核心接口类做详细介绍。
事务的核心接口类
-
PlatformTransactionManager(平台事务管理器)
- 该接口中提供了3个事务操作的方法,具体如下:
- TransactionStatus getTransaction ( TransactionDefinition definition ):用于获取事务状态信息。
- void commit ( TransactionStatus status ):用于提交事务。
- void rollback ( TransactionStatus status ):用于回滚事务。
-
TransactionDefinìtion (事务定义)
- 对象中定义了事务规则,并提供了获取事务相关信息的方法,具体如下。
- String getName():获取事务对象名称。
- int getlsolationLevel():获取事务的隔离级别。
- int getPropagationBehavior():获取事务的传播行为。
- int getTimeout():获取事务的超时时间。
- boolean isReadOnly():获取事务是否只读。
-
TransactionStatus (事务的状态)
- 它描述了某一时间点上事务的状态信息。
- void flush():刷新事务。
- boolean hasSavepoint():获取是否存在保存点。
- boolean isCompleted():获取事务是否完成。
- boolean isNewTransaction():获取是否是新事务。
- boolean isRollbackOnly():获取是否回滚。
- void setRollbackOnly():设置事务回滚。
2.2 xml方式
SQL脚本
-- 同上一篇文章增删改查的SQL脚本
create table account(
id int primary key auto_increment,
name varchar(40),
money double
)character set utf8 collate utf8_general_ci;
insert into account(name,money) values('张三',500.0);
insert into account(name,money) values('李四',1000.0);
insert into account(name,money) values('王五',2000.0);
代码
-
普通maven工程即可,结构图如下
-
依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.18.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.18.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.18.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.18.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.21</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> </dependency>
-
实体类
package cn.pojo; import lombok.Data; @Data public class Account { private Integer id; private String name; private Double money; }
-
持久层
package cn.dao; import cn.pojo.Account; public interface AccountDao { Account findAccountById(Integer accountId); Account findAccountByName(String accountName); void updateAccount(Account account); }
package cn.dao.impl; import cn.dao.AccountDao; import cn.pojo.Account; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.support.JdbcDaoSupport; import java.util.List; public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { @Override public Account findAccountById(Integer accountId) { List<Account> accounts = super.getJdbcTemplate().query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), accountId); return accounts.isEmpty() ? null : accounts.get(0); } @Override public Account findAccountByName(String accountName) { List<Account> accounts = super.getJdbcTemplate().query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), accountName); if (accounts.isEmpty()) { return null; } if (accounts.size() > 1) { throw new RuntimeException("结果集不唯一"); } return accounts.get(0); } @Override public void updateAccount(Account account) { super.getJdbcTemplate().update("update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId()); } }
-
业务层
package cn.service; import cn.pojo.Account; public interface AccountService { Account findAccountById(Integer accountId); void transfer(String sourceName, String targetName, Float money); }
package cn.service.impl; import cn.dao.AccountDao; import cn.pojo.Account; import cn.service.AccountService; public class AccountServiceImpl implements AccountService { private AccountDao accountDao; // 提供set方法用于依赖注入 public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public Account findAccountById(Integer accountId) { return accountDao.findAccountById(accountId); } @Override public void transfer(String sourceName, String targetName, Float money) { //2.1根据名称查询转出账户 Account source = accountDao.findAccountByName(sourceName); //2.2根据名称查询转入账户 Account target = accountDao.findAccountByName(targetName); //2.3转出账户减钱 source.setMoney(source.getMoney() - money); //2.4转入账户加钱 target.setMoney(target.getMoney() + money); //2.5更新转出账户 accountDao.updateAccount(source); // 模拟当第一个用户转出钱,但第二个用户没收到钱时发生异常 int i = 1 / 0; //2.6更新转入账户 accountDao.updateAccount(target); } }
-
配置
<?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: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"> <!-- 配置数据源--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://192.168.192.130:3306/test?serverTimezone=CTT"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean> <!-- 配置账户的持久层--> <bean id="accountDao" class="cn.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置业务层--> <bean id="accountService" class="cn.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!-- spring中基于XML的声明式事务控制配置步骤 1、配置事务管理器 2、配置事务的通知 此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的 使用tx:advice标签配置事务通知 属性: id:给事务通知起一个唯一标识 transaction-manager:给事务通知提供一个事务管理器引用 3、配置AOP中的通用切入点表达式 4、建立事务通知和切入点表达式的对应关系 5、配置事务的属性 是在事务的通知tx:advice标签的内部 --> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置事务的通知--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- 配置事务的属性 isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。 propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。 read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。 timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。 rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。 no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。 --> <tx:attributes> <tx:method name="*" propagation="REQUIRED" read-only="false"/> <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method> </tx:attributes> </tx:advice> <!-- 配置aop--> <aop:config> <!-- 配置切入点表达式--> <aop:pointcut id="pt1" expression="execution(* cn.service.impl.*.*(..))"></aop:pointcut> <!--建立切入点表达式和事务通知的对应关系 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor> </aop:config> </beans>
-
测试
package cn.service; import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AccountServiceTest { private AccountService accountService; @Before public void init() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); accountService = (AccountService) applicationContext.getBean("accountService"); } @Test public void transfer() { accountService.transfer("张三", "李四", 100.0f); } }
2.3 annotation方式
-
普通maven工程,结构图如下
-
依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.18.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.18.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.18.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.18.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.21</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> </dependency>
-
实体类
package cn.pojo; import lombok.Data; @Data public class Account { private Integer id; private String name; private Double money; }
-
持久层
package cn.dao; import cn.pojo.Account; public interface AccountDao { Account findAccountById(Integer accountId); Account findAccountByName(String accountName); void updateAccount(Account account); }
package cn.dao.impl; import cn.dao.AccountDao; import cn.pojo.Account; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import java.util.List; @Repository("accountDao") public class AccountDaoImpl implements AccountDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public Account findAccountById(Integer accountId) { List<Account> accounts = jdbcTemplate.query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), accountId); return accounts.isEmpty() ? null : accounts.get(0); } @Override public Account findAccountByName(String accountName) { List<Account> accounts = jdbcTemplate.query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), accountName); if (accounts.isEmpty()) { return null; } if (accounts.size() > 1) { throw new RuntimeException("结果集不唯一"); } return accounts.get(0); } @Override public void updateAccount(Account account) { jdbcTemplate.update("update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId()); } }
-
业务层
package cn.service; import cn.pojo.Account; public interface AccountService { Account findAccountById(Integer accountId); void transfer(String sourceName, String targetName, Float money); }
package cn.service.impl; import cn.dao.AccountDao; import cn.pojo.Account; import cn.service.AccountService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service("userService") public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; @Override public Account findAccountById(Integer accountId) { return accountDao.findAccountById(accountId); } @Override @Transactional public void transfer(String sourceName, String targetName, Float money) { //2.1根据名称查询转出账户 Account source = accountDao.findAccountByName(sourceName); //2.2根据名称查询转入账户 Account target = accountDao.findAccountByName(targetName); //2.3转出账户减钱 source.setMoney(source.getMoney() - money); //2.4转入账户加钱 target.setMoney(target.getMoney() + money); //2.5更新转出账户 accountDao.updateAccount(source); // 模拟当第一个用户转出钱,但第二个用户没收到钱时发生异常 int i = 1 / 0; //2.6更新转入账户 accountDao.updateAccount(target); } }
-
配置
# 数据源配置,路径为:resources/dataSource.properties datasource.driver=com.mysql.cj.jdbc.Driver datasource.url=jdbc:mysql://192.168.192.130:3306/test?serverTimezone=CTT datasource.username=root datasource.password=root
package cn.conf; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DriverManagerDataSource; import javax.sql.DataSource; public class DataSourceConf { @Value("${datasource.driver}") private String driver; @Value("${datasource.url}") private String url; @Value("${datasource.username}") private String username; @Value("${datasource.password}") private String password; @Bean(name = "jdbcTemplate") public JdbcTemplate createJdbcTemplate(DataSource dataSource) { return new JdbcTemplate(dataSource); } @Bean(name = "dataSource") public DataSource createDataSource() { DriverManagerDataSource ds = new DriverManagerDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(username); ds.setPassword(password); return ds; } }
package cn.conf; import org.springframework.context.annotation.Bean; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; public class TransactionConf { // 用于创建事务管理器对象 @Bean(name = "transactionManager") public PlatformTransactionManager createTransactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }
package cn.conf; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.PropertySource; import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration @ComponentScan({"cn.dao", "cn.service"}) @Import({DataSourceConf.class, TransactionConf.class}) @PropertySource("dataSource.properties") @EnableTransactionManagement public class SpringConf { }
-
测试
package cn.service; import cn.conf.SpringConf; 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(classes = SpringConf.class) public class AccountServiceTest { @Autowired private AccountService accountService; @Test public void transfer() { accountService.transfer("李四", "张三", 100.0f); } }
- 源码链接:blogs-spring