spring事务代码
很多同学在进行编程学习时缺乏系统学习的资料。本页面基于spring事务代码内容,从基础理论到综合实战,通过实用的知识类文章,标准的编程教程,丰富的视频课程,为您在spring事务代码相关知识领域提供全面立体的资料补充。同时还包含 safari浏览器、samba、SAMP 的知识内容,欢迎查阅!
spring事务代码相关知识
-
Spring源码剖析9:Spring事务源码剖析转自:http://www.linkedkeeper.com/detail/blog.action?bid=1045 <!-- more --> 声明式事务使用 Spring事务是我们日常工作中经常使用的一项技术,Spring提供了编程、注解、aop切面三种方式供我们使用Spring事务,其中编程式事务因为对代码入侵较大所以不被推荐使用,注解和aop切面的方式可以基于需求自行选择,我们以注解的方式为例来分析Spring事务的原理和源码实现。 首先我们简单看一下Spring事务的使用方式,配置: <tx:annotation-driven transaction-manager="transactionManager
-
spring事务详解(二)源码详解一、引子在Spring中,事务有两种实现方式:编程式事务管理: 编程式事务管理使用TransactionTemplate可实现更细粒度的事务控制。 申明式事务管理: 基于Spring AOP实现。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。申明式事务管理不需要入侵代码,通过@Transactional就可以进行事务操作,更快捷而且简单(尤其是配合spring boot自动配置,可以说是精简至极!),且大部分业务都可以满足,推荐使用。其实不管是编程式事务还是申明式事务,最终调用的底层核心代码是一致的。本章分别从编程式、申明式入手,再进入核心源码贯穿式讲解。 回到顶部二、事务源码2.1 编程式事务TransactionTemplate全路径名是:org.springframework.transaction.support.TransactionTemplate。看包名也知道了这是spring对事务的模板类。(spring动
-
spring 事务深入 -事务失效事务失效的几种原因:1.spring的事务注解@Transactional只能放在public修饰的方法上才起作用,如果放在其他非public(private,protected)方法上,虽然不报错,但是事务不起作用2.如使用mysql且引擎是MyISAM,则事务会不起作用,原因是MyISAM不支持事务,可以改成InnoDB引擎。 3.默认情况下,Spring会对unchecked异常进行事务回滚;如果是checked异常则不回滚。什么是unchecked异常,什么是checked异常?java里面将派生于Error或者RuntimeException(比如空指针,1/0)的异常成为unchecked异常,其他继承自java.lang.Exception的异常统称为checked Exception,如IOException、TimeOutException等 通俗点讲就是,写的代码出现了空指针异常,会被回滚,而文件读写,网络出问题,spring就没法回滚了。 写代码的时候,有些IOException我们的
-
Spring事务原理分析在经历的几轮面试中,每一轮都问到了事务相关的内容,让我越发感到事务的重要性。 如: MySQL事务隔离级别?分别解释下他们的含义,默认的事务隔离级别是什么,Oracle的呢? Spring事务传播级别?分别代表什么含义 Spring事务是如何处理的?自己能写出来吗? 那么今天一起看一下Spring的事务处理方式。我自己想手写事务的时候,发现还是太依赖Spring框架提供的功能了,自己写对我来说还是有一定的难度,在此分析一下Spring的实现方式。 整体结构 Spring初始化概览 Spring整个框架包含很
spring事务代码相关课程
spring事务代码相关教程
- 2.2 代码实现 1. 创建事务管理器类package com.offcn.transaction;/** * @Auther: wyan * @Date: 2020-05-26 21:20 * @Description: */import com.offcn.utils.ConnectionUtils;/** * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接 */public class TransactionManager { //获取数据库连接的工具类 private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } /** * 开启事务 */ public void beginTransaction(){ try { connectionUtils.getThreadConnection().setAutoCommit(false); }catch (Exception e){ e.printStackTrace(); } } /** * 提交事务 */ public void commit(){ try { connectionUtils.getThreadConnection().commit(); }catch (Exception e){ e.printStackTrace(); } } /** * 回滚事务 */ public void rollback(){ try { connectionUtils.getThreadConnection().rollback(); }catch (Exception e){ e.printStackTrace(); } } /** * 释放连接 */ public void release(){ try { connectionUtils.getThreadConnection().close();//还回连接池中 connectionUtils.removeConnection(); }catch (Exception e){ e.printStackTrace(); } }}代码解释:此工具类主要作用是对数据库连接实现事务的开启,提交以及回滚。至于何时开启事务,何时提交事务,何时回滚事务,那就根据业务场景需要调用该类的方法即可。2. 创建动态处理器package com.offcn.utils;import com.offcn.service.IAccountService;import com.offcn.transaction.TransactionManager;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;/** * @Auther: wyan * @Date: 2020-05-26 21:08 * @Description: */public class TransactionProxyFactory { //被代理的业务类接口 private IAccountService accountService; //提供事务管理的工具类 private TransactionManager txManager; public void setTxManager(TransactionManager txManager) { this.txManager = txManager; } public final void setAccountService(IAccountService accountService) { this.accountService = accountService; } /** * 获取Service代理对象 * @return */ public IAccountService getAccountService() { return (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() { /** * 添加事务的支持 * * @param proxy * @param method * @param args * @return * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Object rtValue = null; try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 rtValue = method.invoke(accountService, args); //3.提交事务 txManager.commit(); //4.返回结果 return rtValue; } catch (Exception e) { //5.回滚操作 txManager.rollback(); throw new RuntimeException(e); } finally { //6.释放连接 txManager.release(); } } }); }}代码解释:此类的核心代码就是 getAccountService 方法,该方法返回代理业务类示例,而在代理对象的 invoke 方法内部,实现对原始被代理对象的增强。方法的参数解释如下:proxy: 该参数就是被代理的对象实例本身;method: 该参数是被代理对象正在执行的方法对象;args: 该参数是正在访问的方法参数对象。在方法内部,method.invoke() 的方法调用,即表示被代理业务类的方法执行,我们调用 txManager 的开启事务方法。在 method.invoke() 方法执行之后,调用提交事务的方法。一旦执行过程出现异常,在 catch 代码块中调用事务回滚的方法。这样就保证了事务的原子性,执行的任务,要么全部成功,要么全部失败。最终在 finally 的代码块中,调用释放连接的方法。3. 配置文件的修改:添加事务管理的相关配置,完整配置文件如下:<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" 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"> <!-- 配置Service --> <bean id="accountService" class="com.offcn.service.impl.AccountServiceImpl"> <!-- 注入dao --> <property name="accountDao" ref="accountDao"></property> </bean> <!--配置Dao对象--> <bean id="accountDao" class="com.offcn.dao.impl.AccountDaoImpl"> <!-- 注入QueryRunner --> <property name="runner" ref="runner"></property> <!-- 注入ConnectionUtils --> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <!--配置QueryRunner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/transmoney"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> <!-- 配置Connection的工具类 ConnectionUtils --> <bean id="connectionUtils" class="com.offcn.utils.ConnectionUtils"> <!-- 注入数据源--> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置事务管理器--> <bean id="txManager" class="com.offcn.transaction.TransactionManager"> <!-- 注入ConnectionUtils --> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <!--配置beanfactory--> <bean id="beanFactory" class="com.offcn.utils.TransactionProxyFactory"> <!-- 注入service --> <property name="accountService" ref="accountService"></property> <!-- 注入事务管理器 --> <property name="txManager" ref="txManager"></property> </bean> <!--配置代理的service--> <bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean></beans>4. 测试类代码代码解释:本测试代码发生一个小变化,第 23 行的位置,多了一个注解 @Qualifier 。此注解的作用不知各位是否还记得,如果在 Spring 的容器中,出现多种同类型的 bean ,可以通过此注解指定引入的实例,所以这里的 注解内的字符串 proxyAccountService 表示本 IAccountService 接口引入的实例为代理对象。那么为什么要引入代理对象呢?因为代理对象的方法内部已经做了增强逻辑,通过 TransactionManager 类实现对事务的开启,提交和回滚。5. 测试结果:为了测试效果更明显,我们先把数据库的数据还原为每人各 1000,如图:执行代码后结果:当然还会继续报错,但是数据库呢?上次是一个账号减去了 100 块钱,另外一个账号却没有增加钱,这次我们来看看:可以看到:账号的金钱依然是原样,这就说明事务的控制已经生效了,保证了数据的一致性。
- 6.2 一个事务方法调用另一个事务方法时失效 先看下面的实例,我们修改下 OrderService 类,通过一个事务方法调用 createOrder 方法。实例:/** * 订单服务类 */@Service // 注册为服务类public class OrderService { @Autowired private GoodsDao goodsDao; @Autowired private OrderDao orderDao; @Transactional // 开启事务 public int startCreateOrder(Long goodsId, Long count) throws Exception { return this.createOrder(goodsId, count); } /** * 下单 * * @param goodsId 购买商品id * @param count 购买商品数量 * @return 生成订单数 */ @Transactional(rollbackFor = Exception.class) // 抛出异常即回滚 public int createOrder(Long goodsId, Long count) throws Exception { // 锁定商品库存 GoodsDo goods = goodsDao.selectForUpdate(goodsId); // 扣减库存 Long newNum = goods.getNum() - count; goods.setNum(newNum); goodsDao.update(goods); if (count > goods.getNum()) { // 非受检查异常抛出时,会回滚 throw new Exception(); } // 生成订单 OrderDo order = new OrderDo(); order.setGoodsId(goodsId); order.setCount(count); int affectRows = orderDao.insert(order); return affectRows; }}此时我们在测试类中通过 startCreateOrder 方法再去调用 createOrder 方法,代码如下:实例:/** * 订单测试 */@SpringBootTestclass OrderTest { @Autowired private OrderService orderService; /** * 创建订单测试 */ @Test void testCreateOrder() throws Exception { // 购买id为1的商品1份 int affectRows = orderService.startCreateOrder(1L, 100L); assertEquals(1, affectRows); }}startCreateOrder 和 createOrder 方法都是事务方法,且这两个方法事务特性不同 (一个没有 rollbackFor=Exception.class),如果我们调用 startTransaction 方法,则 createOrder 中的事务并不会生效。也就是说,如果在同一个类中,一个事务方法调用另一个事务方法,可能会导致被调用的事务方法的事务失效!这是因为 Spring 的声明式事务使用了代理,具体机制此处不再探讨,但是一定要注意规避这种事务失效的场景。
- 2.2 代码实现 1. 创建 maven 工程:pom 文件的 jar 包坐标如下:<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency></dependencies>2. 连接数据库的工具类:@Componentpublic class ConnectionUtils { private ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); @Autowired private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * 获取当前线程上的连接 * @return */ public Connection getThreadConnection() { try{ //1.先从ThreadLocal上获取 Connection conn = tl.get(); //2.判断当前线程上是否有连接 if (conn == null) { //3.从数据源中获取一个连接,并且存入ThreadLocal中 conn = dataSource.getConnection(); conn.setAutoCommit(false); tl.set(conn); } //4.返回当前线程上的连接 return conn; }catch (Exception e){ throw new RuntimeException(e); } } /** * 把连接和线程解绑 */ public void removeConnection(){ tl.remove(); }}3. 实体类 Account:public class Account implements Serializable { //数据id private Integer id; //账号编码 private String accountNum; //账号金额 private Float money; //省略get 和set 方法}4. 持久层 dao 和 dao 的 实现类://dao的接口public interface IAccountDao { /** * 更新 * @param account */ void updateAccount(Account account); /** * 根据编号查询账户 */ Account findAccountByNum(String accountNum);}//dao的实现类@Repositorypublic class AccountDaoImpl implements IAccountDao { //dbutil的查询工具类 @Autowired private QueryRunner runner; //连接的工具类 @Autowired private ConnectionUtils connectionUtils; public void setRunner(QueryRunner runner) { this.runner = runner; } public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } //修改账号 public void updateAccount(Account account) { try{ runner.update(connectionUtils.getThreadConnection(),"update account set accountNum=?,money=? where id=?",account.getAccountNum(),account.getMoney(),account.getId()); }catch (Exception e) { throw new RuntimeException(e); } } //根据账号查询 public Account findAccountByNum(String accountNum) { try{ List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where accountNum = ? ",new BeanListHandler<Account>(Account.class),accountNum); if(accounts == null || accounts.size() == 0){ return null; } if(accounts.size() > 1){ throw new RuntimeException("结果集不唯一,数据有问题"); } return accounts.get(0); }catch (Exception e) { throw new RuntimeException(e); } }}代码解释: AccountDaoImpl 类上面的注解 @Repository 表示使用注解实例化此类,并交给 Spring 的容器管理。5. 业务类 Service 和 Service 的实现类://业务接口public interface IAccountService { /** * 转账 * @param sourceAccount 转出账户名称 * @param targetAccount 转入账户名称 * @param money 转账金额 */ void transfer(String sourceAccount, String targetAccount, Integer money);}//业务实现类@Servicepublic class AccountServiceImpl implements IAccountService { @Autowired private IAccountDao accountDao; public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } public void transfer(String sourceAccount, String targetAccount, Integer money) { Account source = accountDao.findAccountByNum(sourceAccount); Account target = accountDao.findAccountByNum(targetAccount); source.setMoney(source.getMoney()-money); target.setMoney(target.getMoney()+money); accountDao.updateAccount(source); accountDao.updateAccount(target); System.out.println("转账完毕"); }}代码解释:AccountServiceImpl 类上面的注解 @Service 表示使用注解实例化此类,并交给 Spring 的容器管理。6. 事务管理器类@Component@Aspectpublic class TransactionManager { @Autowired private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } @Pointcut("execution(* com.offcn.service.impl.*.*(..))") private void pt1() {} /** * 开启事务 */ @Before("pt1()") public void beginTransaction(){ try { System.out.println("开启事务"); connectionUtils.getThreadConnection().setAutoCommit(false); }catch (Exception e){ e.printStackTrace(); } } /** * 提交事务 */ @AfterReturning("pt1()") public void commit(){ try { System.out.println("提交事务"); connectionUtils.getThreadConnection().commit(); }catch (Exception e){ e.printStackTrace(); } } /** * 回滚事务 */ @AfterThrowing("pt1()") public void rollback(){ try { System.out.println("回滚事务"); connectionUtils.getThreadConnection().rollback(); }catch (Exception e){ e.printStackTrace(); } } /** * 释放连接 */ @After("pt1()") public void release(){ try { System.out.println("释放连接"); connectionUtils.getThreadConnection().close();//还回连接池中 connectionUtils.removeConnection(); }catch (Exception e){ e.printStackTrace(); } }}代码解释:此类通过注解 @Componet 实例化,并且交由 Spring 容器管理,@Aspect 表明它是一个切面类。而下面的注解 @Pointcut 和其余的方法上的各个通知注解,在上面也已经介绍过,这里不做赘述了。主要专注点在于每个注解的通知方法内部引入切入点的表达式方式。7. 配置文件:<?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: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/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"> <!--配置QueryRunner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/transmoney"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> <!-- 注解扫描工程下的包路径--> <context:component-scan base-package="com.offcn"></context:component-scan> <!-- 注解代理模式 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>配置文件说明:dataSource: 采用 c3p0 数据源,大家一定要注意数据库的名称与账号名和密码;queryRunner: dbutils 第三方框架提供用于执行 sql 语句,操作数据库的一个工具类;context:component-scan: 此注解表示注解方式初始化容器扫描的包路径;aop:aspectj-autoproxy: 此注解表示开启代理模式8. 测试类代码@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations = "classpath:applicationContext.xml")public class AccountServiceTest { @Autowired private IAccountService accountService; @Test public void testTransfer(){ accountService.transfer("622200009999","622200001111",100); }}测试结果:执行代码后结果:可以看到,我们通过注解方式配置 Spring 的 AOP 相关配置,同样实现了对于数据的操作。
- 2. 事务 什么是事务?一讲到事务,就有一个典型的案例:转账。转账业务涉及到 2 个账号的变更。例如 A 现在要把自己账户上仅有的 100 元大钞转给自己的好朋友 B。转账过程中至少要涉及到两条更新语句:A 账面上的钱减少,使用更新语句实现:Update 账号表 set 我的钱=我的钱-100 where 账号拥有人=AB 账户上的钱增多,使用更新语句实现:Update 账号表 set 我的钱=我的钱+100 where 账号拥有人=B如果、万一这两条更新语句中有一条没有执行成功,会发生什么情况了?发生在 A 身上的更新语句没有成功,B 的更新语句成功了。也就是说 A 账面上的钱没有减少,B 账面上的钱却增加了。天呀!这是何等好事,关键是这钱是从哪里来的,左想右想,看来只能是银行里来的,但是,你觉得银行会做这种傻事吗?不会!那银行又是如何保证不让这种事情发生了。发生在 B 身上的更新语句没有成功,A 的更新语句成功了。也就是说 A 的钱减少了,但是 B 没有增加。傻眼了吧,莫名其妙的钱就不见了。钱去哪儿了?你会让这种事情发生吗?也不会,你会投诉银行的。当然,如果这 2 条 SQL 语句执行成功或者失败,则不会发生任何损失,可见,咱们必须控制这两条 SQL 语句要么都成功,要么都失败。对!这就是事务。所谓事务,就是把一个业务逻辑当成一个逻辑整体单元,其中的执行代码要么一起成功,但凡执行过程中出现了某些错误,就恢复到最原始的状态。转账就是一个业务逻辑,整个业务逻辑中至少包括 2 条 SQL 语句,这 2 条 SQL 语句互为依靠,彼此脱单对业务逻辑没有任何意义。所以,必须当成一个整体看待。一个事务单元有 4 个特性,也就是事务的 ACID 特性:原子性(atomicity): 表示一个事务内的所有操作是一个整体,要么全部成功,要么全部失败;一致性(consistency): 表示一个事务内有一个操作失败时,所有的更改过的数据都必须回滚到修改前的状态;如前面转账案例,转账前 A 和 B 加起来有多少钱,无论转账是否成功,最后 A 和 B 加起来的钱应该和前面相等;隔离性(isolation): 事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据;持久性(durability): 事务完成之后,它对于系统的影响是永久性的。事务结束后,就没有什么后悔药了。4 个特性中除了隔离性都比较好理解。所以,剩下的篇幅中,咱们好好聊一聊隔离性。要讲透隔离性,肯定就离不开并发概念。什么是并发?并发是计算机中的一个概念,但是在现实生活中还是能找到一个能类比的例子。火车上,2个人同时去一间洗手间,这个过程可以称其为并发。换成计算机概念就是说两段逻辑代码同时使用同一个资源。当然,这里只是从宏观上理解并发。老公和老婆共用一个账号,大家需要钱时都从这个账号上取,可假设老公和老婆就是两个事务,这 2 个事务如果同时取钱时,就必须隔离,否则就会出现麻烦。2 个事务同时进入这个账号,一查看,很高兴,账面上有钱,于是都想取出这 1000 块钱。银行肯定只能让一个事务成功,要不然银行就亏大了。这便是事务的隔离机制,当一个事务对一个资源进行操作时,必须隔离另一个事务对其进行相关操作。但是,如果隔离的太严格了,事务之间就如同排队,需要一个一个来,将会降低系统的响应时间,使用者会认为,切!这系统设计的够糟糕的,睡了一觉,还没有响应。如果隔离的太宽松了,受事务之间的影响,会发生数据的异常。所以在 JDBC 中一般会提供多种隔离机制,让开发者根据需要进行选择。事务隔离级别从低到高:读取未提交(Read Uncommitted);读取已提交 (Read Committed);可重复读(Repeatable Read);序列化(serializable)。如何选择,当然是需要根据业务需要进行设定。不同的隔离机制下,并发的事务之间会发生一些什么样的事情?
- 9.1 事务管理器 transactionManager 在 xml 文件中对应 <transactionManager type="JDBC"/>,其中 type 属性对应了事务管理器的两种类型,分别是JDBC和MANAGED。JDBC :直接使用了 JDBC 的提交和回滚机制。MANAGED:让容器来管理事务的整个生命周期,例如 spring 容器。提示: 如果你使用 spring 作为容器,那么 transactionManager 会被自动配置且可用。
- 2.2 代码实现 1. 创建 maven 工程:pom 文件的 jar 包坐标如下:<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency></dependencies>2. 实体类 Accountpublic class Account implements Serializable { //数据id private Integer id; //账号编码 private String accountNum; //账号金额 private Float money; //省略get 和set 方法}3. 数据库连接工具类public class ConnectionUtils { private ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * 获取当前线程上的连接 * @return */ public Connection getThreadConnection() { try{ //1.先从ThreadLocal上获取 Connection conn = tl.get(); //2.判断当前线程上是否有连接 if (conn == null) { //3.从数据源中获取一个连接,并且存入ThreadLocal中 conn = dataSource.getConnection(); tl.set(conn); } //4.返回当前线程上的连接 return conn; }catch (Exception e){ throw new RuntimeException(e); } } /** * 把连接和线程解绑 */ public void removeConnection(){ tl.remove(); }}4. 持久层 dao 和 dao 的 实现类://dao的接口public interface IAccountDao { /** * 更新 * @param account */ void updateAccount(Account account); /** * 根据编号查询账户 */ Account findAccountByNum(String accountNum);}//dao的实现类public class AccountDaoImpl implements IAccountDao { //dbutil的查询工具类 private QueryRunner runner; //连接的工具类 private ConnectionUtils connectionUtils; public void setRunner(QueryRunner runner) { this.runner = runner; } public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } //修改账号 public void updateAccount(Account account) { try{ runner.update(connectionUtils.getThreadConnection(),"update account set accountNum=?,money=? where id=?",account.getAccountNum(),account.getMoney(),account.getId()); }catch (Exception e) { throw new RuntimeException(e); } } //根据账号查询 public Account findAccountByNum(String accountNum) { try{ List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where accountNum = ? ",new BeanListHandler<Account>(Account.class),accountNum); if(accounts == null || accounts.size() == 0){ return null; } if(accounts.size() > 1){ throw new RuntimeException("结果集不唯一,数据有问题"); } return accounts.get(0); }catch (Exception e) { throw new RuntimeException(e); } }}5. 业务类 Service 和 Service 的实现类//业务接口public interface IAccountService { /** * 转账 * @param sourceAccount 转出账户名称 * @param targetAccount 转入账户名称 * @param money 转账金额 */ void transfer(String sourceAccount, String targetAccount, Integer money);}//业务实现类public class AccountServiceImpl implements IAccountService { //持久层对象 private IAccountDao accountDao; //省略 set 和 get 方法 //转账的方法 public void transfer(String sourceAccount, String targetAccount, Integer money) { //查询原始账户 Account source = accountDao.findAccountByNum(sourceAccount); //查询目标账户 Account target = accountDao.findAccountByNum(targetAccount); //原始账号减钱 source.setMoney(source.getMoney()-money); //目标账号加钱 target.setMoney(target.getMoney()+money); //更新原始账号 accountDao.updateAccount(source); //更新目标账号 accountDao.updateAccount(target); System.out.println("转账完毕"); }}6. 事务管理器类package com.offcn.transaction;/** * @Auther: wyan * @Date: 2020-05-26 21:20 * @Description: */import com.offcn.utils.ConnectionUtils;/** * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接 */public class TransactionManager { private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } /** * 开启事务 */ public void beginTransaction(){ try { System.out.println("开启事务"); connectionUtils.getThreadConnection().setAutoCommit(false); }catch (Exception e){ e.printStackTrace(); } } /** * 提交事务 */ public void commit(){ try { System.out.println("提交事务"); connectionUtils.getThreadConnection().commit(); }catch (Exception e){ e.printStackTrace(); } } /** * 回滚事务 */ public void rollback(){ try { System.out.println("回滚事务"); connectionUtils.getThreadConnection().rollback(); }catch (Exception e){ e.printStackTrace(); } } /** * 释放连接 */ public void release(){ try { System.out.println("释放连接"); connectionUtils.getThreadConnection().close();//还回连接池中 connectionUtils.removeConnection(); }catch (Exception e){ e.printStackTrace(); } }}代码解释:此工具类就作为 Spring 使用 AOP 管理事务的通知类,里面的各个方法用于配置 Spring 的通知使用。为了测试效果,在每个通知方法内,我们输出打印了测试语句。7. 配置文件中添加 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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 配置Service --> <bean id="accountService" class="com.offcn.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!--配置Dao对象--> <bean id="accountDao" class="com.offcn.dao.impl.AccountDaoImpl"> <property name="runner" ref="runner"></property> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <!--配置QueryRunner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/transmoney"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> <!-- 配置Connection的工具类 ConnectionUtils --> <bean id="connectionUtils" class="com.offcn.utils.ConnectionUtils"> <!-- 注入数据源--> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置事务管理器--> <bean id="txManager" class="com.offcn.transaction.TransactionManager"> <!-- 注入ConnectionUtils --> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <!-- aop相关的节点配置 --> <aop:config> <aop:pointcut expression="execution ( * com.offcn.service.*.*(..))" id="pc"/> <aop:aspect ref="txManager"> <aop:before method="beginTransaction" pointcut-ref="pc"/> <aop:after-returning method="commit" pointcut-ref="pc"/> <aop:after method="release" pointcut-ref="pc"/> <aop:after-throwing method="rollback" pointcut-ref="pc"/> </aop:aspect> </aop:config></beans>配置文件说明:connectionUtils: 是获取数据库连接的工具类;dataSource: 采用 c3p0 数据源,大家一定要注意数据库的名称与账号名和密码;queryRunner: dbutils 第三方框架提供用于执行 SQL 语句,操作数据库的一个工具类;accountDao 和 accountService: 是我们自定义的业务层实现类和持久层实现类;aop:config: 此节点是新增加 AOP 配置,AOP 相关信息都在这;aop:pointcut: 此节点是切入点,表示哪些类的哪些方法在执行的时候会应用 Spring 配置的通知进行增强;aop:aspect: 此节点是配置切面类的节点,在 AOP 介绍的小节解释过,它的作用主要就是整合通知和切入点。null 前置、后置、异常、和最终。可以看得出来 before 前置通知执行的方法是开启事务, after-returning 成功执行的方法是提交事务,after 最终执行的方法是释放连接,after-throwing 出现异常执行的方法是回滚。8. 测试类代码@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations = "classpath:applicationContext.xml")public class AccountServiceTest { @Autowired private IAccountService accountService; @Test public void testTransfer(){ accountService.transfer("622200009999","622200001111",100); }}测试结果:执行代码后结果:可以看到,我们通过在 xml 文件中配置 Spring 的 AOP 相关配置,就可以实现对我们业务类中的方法实现了增强,无需自定义对业务类做代理实现。
spring事务代码相关搜索
-
s line
safari浏览器
samba
SAMP
samplerate
sandbox
sanitize
saper
sas
sass
save
smarty模板
smil
smtp
snapshot
snd
snmptrap
soap
soapclient
soap协议