结构

domain类

package com.itheima.domain;import java.io.Serializable;/*** 账户的实体类*/
public class Account implements Serializable {private Integer id;private String name;private Float money;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Float getMoney() {return money;}public void setMoney(Float money) {this.money = money;}@Overridepublic String toString() {return "Account{" +"id=" + id +", name='" + name + '\'' +", money=" + money +'}';}
}

业务层接口

package com.itheima.service;import com.itheima.domain.Account;import java.util.List;/*** 账户的业务层接口*/
public interface IAccountService {/*** 查询所有* @return*/List<Account> findAllAccount();/*** 查询一个* @return*/Account findAccountById(Integer accountId);/*** 保存* @param account*/void saveAccount(Account account);/*** 更新* @param account*/void updateAccount(Account account);/*** 删除* @param accountId*/void deleteAccount(Integer accountId);/*** 转账* @param sourceName 转出账户名称* @param targetName 转入账户名称* @param money      转账金额*/void transfer(String sourceName, String targetName, Float money);
}

业务层实现

package com.itheima.service.impl;import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import com.itheima.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;/*** 账户的业务成层实现类* 事务控制应该都是在业务层*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService {@Autowiredprivate IAccountDao accountDao;/* private TransactionManager txManager;public void setTxManager(TransactionManager txManager) {this.txManager = txManager;}*/@Overridepublic List<Account> findAllAccount() {return accountDao.findAllAccount();}@Overridepublic Account findAccountById(Integer accountId) {return accountDao.findAccountById(accountId);}@Overridepublic void saveAccount(Account account) {accountDao.findAllAccount();}@Overridepublic void updateAccount(Account account) {accountDao.findAllAccount();}@Overridepublic void deleteAccount(Integer accountId) {accountDao.findAllAccount();}@Overridepublic void transfer(String sourceName, String targetName, Float money){//1.根据名称查询转出账户Account source=accountDao.findAccountByName(sourceName); //获取一个数据库连接//2.根据名称查询转入账户Account target=accountDao.findAccountByName(targetName); //获取一个数据库连接//3.转出账户减钱source.setMoney(source.getMoney()-money);//4.转入账户加钱target.setMoney(target.getMoney()+money);//5.更新转出账户accountDao.updateAccount(source); //获取一个数据库连接int i=1/0;//6.更新转入账户accountDao.updateAccount(target); //获取一个数据库连接}
}

持久层接口

package com.itheima.dao;import com.itheima.domain.Account;import java.util.List;/*** 账户的持久层接口*/
public interface IAccountDao {/*** 查询所有* @return*/List<Account> findAllAccount();/*** 查询一个* @return*/Account findAccountById(Integer accountId);/*** 保存* @param account*/void saveAccount(Account account);/*** 更新* @param account*/void updateAccount(Account account);/*** 删除* @param accountId*/void deleteAccount(Integer accountId);/*** 根据名称查询账户* @param accountName* @return 如果有唯一的结果就返回,如果没有唯一的结果就返回null*         如果结果集超过一个,就抛异常*/Account findAccountByName(String accountName);
}

持久层实现

package com.itheima.dao.impl;import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import com.itheima.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;import java.util.List;/*** 账户的持久层实现类*/
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {@Autowiredprivate QueryRunner runner;@Autowiredprivate ConnectionUtils connectionUtils;@Overridepublic List<Account> findAllAccount() {try {return runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));} catch (Exception e) {throw new RuntimeException();}}@Overridepublic Account findAccountById(Integer accountId) {try {return runner.query(connectionUtils.getThreadConnection(),"select * from account where id = ?",new BeanHandler<Account>(Account.class),accountId);} catch (Exception e) {throw new RuntimeException();}}@Overridepublic void saveAccount(Account account) {try {runner.update(connectionUtils.getThreadConnection(),"insert into account(name,money)values(?,?)",account.getName(),account.getMoney());} catch (Exception e) {throw new RuntimeException();}}@Overridepublic void updateAccount(Account account) {try {runner.update(connectionUtils.getThreadConnection(),"update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());} catch (Exception e) {throw new RuntimeException();}}@Overridepublic void deleteAccount(Integer accountId) {try {runner.update(connectionUtils.getThreadConnection(),"delete from account where id=?",accountId);} catch (Exception e) {throw new RuntimeException();}}@Overridepublic Account findAccountByName(String accountName) {try {List<Account> accounts=runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ?",new BeanListHandler<Account>(Account.class),accountName);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();}}
}

线程和数据源绑定工具类

package com.itheima.utils;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;/*** 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定*/
@Component("connectionUtils")
public class ConnectionUtils {private ThreadLocal<Connection> tl=new ThreadLocal<Connection>();@Autowiredprivate DataSource dataSource;public ConnectionUtils(DataSource dataSource) {this.dataSource = dataSource;}public Connection getThreadConnection(){//1.先从ThreadLocal上获取Connection conn= tl.get();//2.判断当前线程上是否有连接if(conn==null){//3.从数据源中获取一个连接,并且存入ThreadLocal中try {conn=dataSource.getConnection();} catch (SQLException e) {e.printStackTrace();}tl.set(conn);}//4.返回当前线程上的连接return conn;}/*** 把连接和线程解绑*/public void removeConnection(){tl.remove();}
}

事务控制工具类

package com.itheima.utils;import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.sql.SQLException;/*** 和事务相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接*/
@Component("txManager")
@Aspect
public class TransactionManager {@Autowiredprivate ConnectionUtils connectionUtils;@Pointcut("execution(* com.itheima.service.impl.*.*(..))")private void pt1(){}/*** 开启事务*/@Before("pt1()")public void beginTransaction(){try {connectionUtils.getThreadConnection().setAutoCommit(false);} catch (SQLException e) {e.printStackTrace();}}/*** 提交事务*/@AfterReturning("pt1()")public void CommitTransaction(){try {connectionUtils.getThreadConnection().commit();} catch (SQLException e) {e.printStackTrace();}}/*** 回滚事务*/@AfterThrowing("pt1()")public void rollbackTransaction(){try {connectionUtils.getThreadConnection().rollback();} catch (SQLException e) {e.printStackTrace();}}/*** 释放连接*/@After("pt1()")public void releaseTransaction(){try {connectionUtils.getThreadConnection().close();  //还回连接池中connectionUtils.removeConnection();} catch (SQLException e) {e.printStackTrace();}}
}

bean.xml

<?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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><!--配置Spring创建容器时要扫描的包--><context:component-scan base-package="com.itheima"></context:component-scan><!--配置QueryBean--><bean id="runner" class="org.apache.commons.dbutils.QueryRunner"><!--&lt;!&ndash;配置数据源&ndash;&gt;<constructor-arg name="ds" ref="dataSource"></constructor-arg>--></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/eesy"></property><property name="user" value="root"></property><property name="password" value="root"></property></bean><!--开启spring对注解AOP的支持--><aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

测试

package com.itheima.test;import com.itheima.domain.Account;
import com.itheima.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import java.util.List;/*** 使用Junited单元测试,测试我们的配置*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {@Autowiredprivate IAccountService as;@Testpublic void testTransfer(){as.transfer("aaa","bbb",100f);}}

出现问题

  1. 在测试类AccountServiceTest执行testTransger()方法时
  2. 会调用业务层实现类AccountServiceImpl的transfer()方法
  3. 在执行业务层实现类方法时会调用持久层实现类的方法
  4. 每个持久层实现类的方法都会获取当前连接connectionUtils.getThreadConnection()
  5. 就会调用工具类ConnectionUtils的方法
  6. 执行工具类ConnectionUtils方法时就会判断当前线程上是否有连接
  7. 而基于注解配置的AOP的通知顺序是有问题的 1–开启事务 2–释放连接 3–提交事务/回滚事务
  8. 在后置通知中释放连接里,会执行removeConnection方法,此时线程就解绑
  9. 而紧接着提交事务/回滚事务又重新从线程池中获取了一个线程
  10. 但此时前一个线程已经结束了,所以就会出现控制不了事务

解决办法:使用环绕通知

/*** @program: day03_eesy_01account* @description:* @author: 14_赵芬* @create: 2020-11-13 20:55**/
package com.itheima.utils;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.sql.SQLException;/*** 和事务相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接*/
@Component("txManager")
@Aspect
public class TransactionManager {@Autowiredprivate ConnectionUtils connectionUtils;@Pointcut("execution(* com.itheima.service.impl.*.*(..))")private void pt1(){}/*** 开启事务*/public void beginTransaction(){try {connectionUtils.getThreadConnection().setAutoCommit(false);} catch (SQLException e) {e.printStackTrace();}}/*** 提交事务*/public void CommitTransaction(){try {connectionUtils.getThreadConnection().commit();} catch (SQLException e) {e.printStackTrace();}}/*** 回滚事务*/public void rollbackTransaction(){try {connectionUtils.getThreadConnection().rollback();} catch (SQLException e) {e.printStackTrace();}}/*** 释放连接*/public void releaseTransaction(){try {connectionUtils.getThreadConnection().close();  //还回连接池中connectionUtils.removeConnection();} catch (SQLException e) {e.printStackTrace();}}/*** 环绕通知* @param pjp* @return*/@Around("pt1()")public Object aroundAdvice(ProceedingJoinPoint pjp){Object rtValue=null;try{//1.获取参数Object[] args=pjp.getArgs();//2.开始事务this.beginTransaction();//3.执行方法rtValue=pjp.proceed(args);//4.提交事务this.CommitTransaction();// 返回结果}catch (Throwable e){//5.回滚事务this.rollbackTransaction();}finally {//6.释放资源this.releaseTransaction();}return rtValue;}
}

【Spring】基于注解实现事务控制(银行转账)相关推荐

  1. 从源码分析 Spring 基于注解的事务

    从源码分析 Spring 基于注解的事务 在spring引入基于注解的事务(@Transactional)之前,我们一般都是如下这样进行拦截事务的配置: <!-- 拦截器方式配置事务 --> ...

  2. Spring的三种事务控制

    Spring的三种方式的事务控制 1:基于编程式的事务控制 1.1:编程式事务控制相关对象 PlatformTransactionManager接口是spring的事务管理器,他里面提供了我们常用的操 ...

  3. Spring基于注解的方式一

    Spring基于注解的方式一 Spring注解简介 之前的时候我们学习的Spring都是基于Spring配置文件的形式来编写,现在很多的情况下使用SpringBoot的时候是基于注解的形式,这里我们首 ...

  4. (spring-第4回【IoC基础篇】)spring基于注解的配置

    (spring-第4回[IoC基础篇])spring基于注解的配置 基于XML的bean属性配置:bean的定义信息与bean的实现类是分离的. 基于注解的配置:bean的定义信息是通过在bean实现 ...

  5. Spring基于注解的自动装配

    Spring基于注解的自动装配 基于XML的自动装配是在配置文件的bean里设置autowire属性,有byType,byName的方式.而基于注解的自动装配同样是这样只不过我们直接在成员变量上直接标 ...

  6. Spring基于注解TestContext 测试框架使用详解

    原创整理不易,转载请注明出处:Spring基于注解TestContext 测试框架使用详解 代码下载地址:http://www.zuidaima.com/share/1775574182939648. ...

  7. 【spring】编程式事务控制

    结构: AccountServiceImpl package com.itheima.service.impl;import com.itheima.dao.IAccountDao; import c ...

  8. spring 基于注解的控制器配置

    http://ttaale.iteye.com/blog/787586 spring 基于注解的控制器配置 博客分类: spring SpringBeanServletMVCWeb 13.12. 基于 ...

  9. Spring 基于注解的配置

    转载自  Spring 基于注解的配置 基于注解的配置 从 Spring 2.5 开始就可以使用注解来配置依赖注入.而不是采用 XML 来描述一个 bean 连线,你可以使用相关类,方法或字段声明的注 ...

最新文章

  1. 自然语言处理「迷惑行为大赏」,自然语言处理太难难难了!
  2. java中文getbytes为3,java 中文乱码问题
  3. swapCursor vs changeCursor, what’s the difference?
  4. 【知识便利贴】ImageNet得的比Best Paper还牛的Longuet-Higgins奖是什么?
  5. java什么是网络接口_java 网络编程 -- IP地址的表示与网络接口信息的获取(InetAddress和NetworkInterface)...
  6. Top 10 Project Management Software
  7. 7宗命案,潜逃23年,大数据还是认出了她
  8. 微软在位Azure自动机器学习服务释无程序代码网页UI
  9. GDAL根据Shape文件切图(java)
  10. python自动化办公手册之python操作PPT
  11. 安全基础--21--安全运维
  12. StarRocks 极客营 | 王天宜:把 Usability 当产品来开发,愿运维把 StarRocks 用得飘逸
  13. 增量式编码器c语言,关于增量式编码器编程指导及使用方法的介绍
  14. 世界卫生组织0-10岁儿童体格心智发育评价标准(女)
  15. 基于B/S模式的设备管理系统开发
  16. eap wifi 证书_如何手动连接802.1x EAP证书加密WIFI
  17. 白盒测试方法的简单理解(通俗易懂)
  18. Windows驱动_WDDM之一
  19. 网红、大V、明星的隐私信息大量被泄露!走过路过不要错过,买不买没关系,到屋里瞧一瞧!
  20. 如何看待抖音被降权限流,该怎么挽回账号权重丨国仁网络资讯

热门文章

  1. python rgba_使用PIL将RGBA PNG转换为RGB
  2. java 操作 User32 的一次小尝试
  3. 随意php开源多功能留言板,随意多功能留言板 SyGuestBook v1.2
  4. 数据增强(图像)初探
  5. ssh卡在debug1: SSH2_MSG_KEXINIT sent解决方法
  6. SSL漏洞 TLS/SSL Sweet32 attack || TLS/SSL Wrak Cipher Suites[解决]
  7. 无尽对决一直显示正在连接服务器,无尽对决总是显示无法连接网络
  8. leecode++理解
  9. LCD显示器缺陷自动化检测方案
  10. 计算机科学与技术第3次上机实验,第十次上机实验 实验报告