1 银行转账案例界面

2 表结构

3 代码调用关系

4 关键代码

(1)TransferServlet

@WebServlet(name = "transferServlet", urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {// 1. 实例化service层对象(需要被改造)private TransferService transferService = new TransferServiceImpl();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponseresp) throws ServletException, IOException {// 设置请求体的字符编码req.setCharacterEncoding("UTF-8");String fromCardNo = req.getParameter("fromCardNo");String toCardNo = req.getParameter("toCardNo");String moneyStr = req.getParameter("money");int money = Integer.parseInt(moneyStr);Result result = new Result();try {// 2. 调⽤service层⽅法transferService.transfer(fromCardNo, toCardNo, money);result.setStatus("200");} catch (Exception e) {e.printStackTrace();result.setStatus("201");result.setMessage(e.toString());}// 响应resp.setContentType("application/json;charset=utf-8");resp.getWriter().print(JsonUtils.object2Json(result));}
}
(2)TransferService 的实现类
public class TransferServiceImpl implements TransferService {// 需要被改造,当 AccountDao 的实现类发生了切换,这里的代码也要跟着改动了 private AccountDao accountDao = new JdbcAccountDaoImpl();/*** 没有加事务控制*/@Overridepublic void transfer(String fromCardNo, String toCardNo, int money)throws Exception {Account from = accountDao.queryAccountByCardNo(fromCardNo);Account to = accountDao.queryAccountByCardNo(toCardNo);from.setMoney(from.getMoney()-money);to.setMoney(to.getMoney()+money);accountDao.updateAccountByCardNo(from);accountDao.updateAccountByCardNo(to);}
}

(3)AccountDao 基于 JDBC 的实现类

public class JdbcAccountDaoImpl implements AccountDao {@Overridepublic Account queryAccountByCardNo(String cardNo) throws Exception {//从连接池获取连接Connection con = DruidUtils.getInstance().getConnection();String sql = "select * from account where cardNo=?";PreparedStatement preparedStatement = con.prepareStatement(sql);preparedStatement.setString(1,cardNo);ResultSet resultSet = preparedStatement.executeQuery();Account account = new Account();while(resultSet.next()) {account.setCardNo(resultSet.getString("cardNo"));account.setName(resultSet.getString("name"));account.setMoney(resultSet.getInt("money"));}resultSet.close();preparedStatement.close();con.close();return account;}@Overridepublic int updateAccountByCardNo(Account account) throws Exception {//从连接池获取连接Connection con = DruidUtils.getInstance().getConnection();String sql = "update account set money=? where cardNo=?";PreparedStatement preparedStatement = con.prepareStatement(sql);preparedStatement.setInt(1,account.getMoney());preparedStatement.setString(2,account.getCardNo());int i = preparedStatement.executeUpdate();preparedStatement.close();con.close();return i;}
}

5 问题分析

问题⼀:

在上述案例实现中,service 层实现类在使⽤ dao 层对象时,直接在 TransferServiceImpl 中通过 AccountDao accountDao = new JdbcAccountDaoImpl() 获得了 dao层对象,然⽽⼀个 new 关键字却将 TransferServiceImpl 和 dao 层具体的⼀个实现类 JdbcAccountDaoImpl 耦合在了⼀起,如果技术架构发⽣变动,dao 层的实现要使⽤其它技术, ⽐如 Mybatis,思考切换起来的成本?每⼀个 new 的地⽅都需要修改源代码,重新编译,⾯向接⼝开发的意义将⼤打折扣。

问题⼆:
service 层代码没有进⾏事务控制 !如果转账过程中出现异常,将可能导致数据库数据错乱,后果可能会很严重,尤其在⾦融业务。

6 问题解决思路

6.1 问题一解决思路

(1)实例化对象的⽅式除了 new 之外,还有什么技术?反射 (需要把类的全限定类名配置在 xml)
(2)考虑使⽤设计模式中的⼯⼚模式解耦合,在⼯⼚中使⽤反射技术实例化对象。以后需要什么对象,就直接去工厂取。
 

(3)进一步优化:代码中能否只声明所需实例的接⼝类型,不出现 new 也不出现⼯⼚类的字眼?能的!声明⼀个变量并提供 set ⽅法,在反射的时候通过 set 方法注入所需要的对象。

6.2 问题二解决思路

service 层没有添加事务控制,出现异常会导致数据错乱。

分析:数据库事务归根结底是 Connection 的事务,Connection 默认开启了事务自动提交。

connection.commit()

connection.rollback()

(1)两次 update 使用了两个数据库连接 Connection,导致不属于同一个事务控制。

(2)事务控制在 Dao 层,没有控制在 Service 层

解决思路:

(1)让两次 update 使用同一个数据库连接 Connection。因为两次 update 属于同一个线程内的执行调用,可以给当前线程绑定一个 Connection,和当前线程有关的数据库操作都使用该 Connection。

(2)把事务控制添加在 service 层

7 代码改造

7.1 针对问题一的代码改造

(1)beans.xml

<beans><bean id="transferService" class="com.service.impl.TransferServiceImpl"><property name="AccountDao" ref="accountDao"></property></bean><bean id="accountDao" class="com.dao.impl.JdbcAccountDaoImpl"></bean>
</beans>
(2)BeanFactory 生产对象
加载 BeanFactory 时,先执行 static 代码块,加载解析xml中bean的信息,通过反射技术实例化bean对象,然后放入map中待用。
提供方法供外部根据id获取bean。维护bean的依赖关系,调用set方法传值。
/*** 工厂类,生产对象(使用反射技术)*/
public class BeanFactory {/*** 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)* 任务二:对外提供获取实例对象的接口(根据id获取)*/private static Map<String,Object> map = new HashMap<>();  // 存储对象static {// 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)// 加载xml,以字节流的形式加载到内存InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");// 解析xmlSAXReader saxReader = new SAXReader();try {Document document = saxReader.read(resourceAsStream);Element rootElement = document.getRootElement();List<Element> beanList = rootElement.selectNodes("//bean");for (int i = 0; i < beanList.size(); i++) {Element element =  beanList.get(i);// 处理每个bean元素,获取到该元素的 id 和 class 属性String id = element.attributeValue("id");        String clazz = element.attributeValue("class");  // 通过反射技术实例化对象Class<?> aClass = Class.forName(clazz);Object o = aClass.newInstance();  // 实例化之后的对象// 存储到map中待用map.put(id,o);}// 实例化完成之后维护对象的依赖关系,检查哪些对象需要传值进入,根据它的配置,我们传入相应的值// 有 property 子元素的 bean 就有传值需求List<Element> propertyList = rootElement.selectNodes("//property");// 解析 property,获取父元素for (int i = 0; i < propertyList.size(); i++) {//<property name="AccountDao" ref="accountDao"></property>Element element =  propertyList.get(i);   String name = element.attributeValue("name");String ref = element.attributeValue("ref");// 找到当前需要被处理依赖关系的beanElement parent = element.getParent();// 调用父元素对象的反射功能String parentId = parent.attributeValue("id");Object parentObject = map.get(parentId);// 遍历父对象中的所有方法,找到 "set" + nameMethod[] methods = parentObject.getClass().getMethods();for (int j = 0; j < methods.length; j++) {Method method = methods[j];// 该方法就是 setAccountDao(AccountDao accountDao)if(method.getName().equalsIgnoreCase("set" + name)) {  method.invoke(parentObject,map.get(ref));}}// 把处理之后的parentObject重新放到map中map.put(parentId,parentObject);}} catch (Exception e) {e.printStackTrace();} }// 任务二:对外提供获取实例对象的接口(根据id获取)public static  Object getBean(String id) {return map.get(id);}}

(3)改造 TransferServlet

//private TransferService transferService = new TransferServiceImpl();
private TransferService transferService = (TransferService) BeanFactory.getBean("transferService");

(4)改造 TransferServiceImpl

// private AccountDao accountDao = new JdbcAccountDaoImpl();
// private AccountDao accountDao = (AccountDao) BeanFactory.getBean("accountDao");private AccountDao accountDao;
// 构造函数传值/set方法传值
public void setAccountDao(AccountDao accountDao) {this.accountDao = accountDao;
}

7.2 针对问题二的代码改造

7.2.1 改造一

(1)Dao 层,从当前线程获取绑定的 connection 连接

public int updateAccountByCardNo(Account account) throws Exception {// 从连接池获取连接 Connection con = DruidUtils.getInstance().getConnection();// 改造为:从当前线程当中获取绑定的 connection 连接Connection con = ConnectionUtils.getInstance().getCurrentThreadConn();String sql = "update account set money=? where cardNo=?";PreparedStatement preparedStatement = con.prepareStatement(sql);preparedStatement.setInt(1,account.getMoney());preparedStatement.setString(2,account.getCardNo());int i = preparedStatement.executeUpdate();preparedStatement.close();//不用关闭了,不然别的方法用不了了 //con.close();return i;
}

(2)ConnectionUtils 保证一个线程只有一个连接

public class ConnectionUtils {private ConnectionUtils() {}// 单例模式,构造函数私有化private static ConnectionUtils connectionUtils = new ConnectionUtils();public static ConnectionUtils getInstance() {return connectionUtils;}// 存储当前线程的连接private ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); /*** 从当前线程获取连接*/public Connection getCurrentThreadConn() throws SQLException {/*** 判断当前线程中是否已经绑定连接,如果没有绑定,需要从连接池获取一个连接绑定到当前线程*/Connection connection = threadLocal.get();if(connection == null) {// 从连接池拿连接并绑定到线程connection = DruidUtils.getInstance().getConnection();// 绑定到当前线程threadLocal.set(connection);}return connection;}
}

(3)service 层添加事务

public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {//Connection connection = ConnectionUtils.getInstance().getCurrentThreadConn();try{// 开启事务(关闭自动提交事务)//connection.setAutoCommit(false);TransactionManager.getInstance().beginTransaction();Account from = accountDao.queryAccountByCardNo(fromCardNo);Account to = accountDao.queryAccountByCardNo(toCardNo);from.setMoney(from.getMoney()-money);to.setMoney(to.getMoney()+money);accountDao.updateAccountByCardNo(to);int c = 1/0;accountDao.updateAccountByCardNo(from);// 提交事务(正常执行)//connection.commit();TransactionManager.getInstance().commit();}catch (Exception e) {e.printStackTrace();// 回滚事务(发生异常)//connection.rollback();TransactionManager.getInstance().rollback();// 抛出异常便于上层servlet捕获throw e;}
}

(4)增加 TransactionManager 事务管理器类把事务的管理放在一个类里

/**** 事务管理器类:负责手动事务的开启、提交、回滚*/
public class TransactionManager {private ConnectionUtils connectionUtils;public void setConnectionUtils(ConnectionUtils connectionUtils) {this.connectionUtils = connectionUtils;}// 单例模式private TransactionManager(){}private static TransactionManager transactionManager = new TransactionManager();public static TransactionManager getInstance() {return  transactionManager;}// 开启手动事务控制public void beginTransaction() throws SQLException {connectionUtils.getCurrentThreadConn().setAutoCommit(false);}// 提交事务public void commit() throws SQLException {connectionUtils.getCurrentThreadConn().commit();}// 回滚事务public void rollback() throws SQLException {connectionUtils.getCurrentThreadConn().rollback();}
}

思考:如果有 service层的上千百万个类的上千百万个方法都要加事务,咋办?用动态代理,把事务逻辑代码和业务逻辑代码分离。

7.2.2 改造二

采用动态代理改造 Service 层事务控制

磨人的动态代理

(1)代理对象工厂 ProxyFactory

/*** 代理对象工厂:生成代理对象的*/public class ProxyFactory {private ProxyFactory(){}private static ProxyFactory proxyFactory = new ProxyFactory();public static ProxyFactory getInstance() {return proxyFactory;}/*** Jdk动态代理* @param obj  委托对象* @return   代理对象*/public Object getJdkProxy(Object obj) {// 获取代理对象return  Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object result = null;try{// 开启事务(关闭事务的自动提交)transactionManager.beginTransaction();result = method.invoke(obj,args);// 提交事务transactionManager.commit();}catch (Exception e) {e.printStackTrace();// 回滚事务transactionManager.rollback();// 抛出异常便于上层servlet捕获throw e;}return result;}});}/*** 使用cglib动态代理生成代理对象* @param obj 委托对象* @return*/public Object getCglibProxy(Object obj) {return  Enhancer.create(obj.getClass(), new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {Object result = null;try{// 开启事务(关闭事务的自动提交)transactionManager.beginTransaction();result = method.invoke(obj,objects);// 提交事务transactionManager.commit();}catch (Exception e) {e.printStackTrace();// 回滚事务transactionManager.rollback();// 抛出异常便于上层servlet捕获throw e;}return result;}});}
}

(2)TransferServlet 改为依赖代理类

// 1. 实例化service层对象
//private TransferService transferService = new TransferServiceImpl();
//private TransferService transferService = (TransferService) BeanFactory.getBean("transferService");// 从工厂中获取代理service的对象
private TransferService transferService = (TransferService) ProxyFactory.getInstance().getJdkProxy(BeanFactory.getBean("transferService")) ;

7.2.3 改造三

把依赖都配置到xml,把实例化交给 BeanFactory

<!--配置新增的三个Bean-->
<bean id="connectionUtils" class="com.utils.ConnectionUtils"></bean><!--事务管理器-->
<bean id="transactionManager" class="com.utils.TransactionManager"><property name="ConnectionUtils" ref="connectionUtils"/>
</bean><!--代理对象工厂-->
<bean id="proxyFactory" class="com.factory.ProxyFactory"><property name="TransactionManager" ref="transactionManager"/>
</bean>

通过AOP和IOC改造银行转账案例相关推荐

  1. Spring之银行转账案例(七)

    文章目录 (一)案例中添加转账方法并演示事务问题 (二)分析事务的问题并编写ConnectionUtils (三)编写事务管理工具类并分析连接和线程解绑 (四)编写业务层和持久层事务控制代码并配置sp ...

  2. Spring-学习笔记07【银行转账案例】

    Java后端 学习路线 笔记汇总表[黑马程序员] Spring-学习笔记01[Spring框架简介][day01] Spring-学习笔记02[程序间耦合] Spring-学习笔记03[Spring的 ...

  3. AOP和IOC的作用

    IOC:控制反转,是一种设计模式.一层含义是控制权的转移:由传统的在程序中控制依赖转移到由容器来控制:第二层是依赖注入:将相互依赖的对象分离,在spring配置文件中描述他们的依赖关系.他们的依赖关系 ...

  4. 某大型数据中心离心式冷水机组 控制板UPS电源改造项目案例分享

    摘  要  离心式冷水机组作为大部分数据中心的核心设备,其是否稳定.安全运行决定着数据中心制冷系统的安全运行.本文分析了某大型数据中心离心式冷水机组控制板UPS电源改造创新案例,分析了改造项目过程中可 ...

  5. Spring的AOP和IOC是什么?使用场景有哪些?Spring事务与数据库事务,传播行为,数据库隔离级别

    Spring的AOP和IOC是什么?使用场景有哪些?Spring事务与数据库事务,传播行为,数据库隔离级别 AOP:面向切面编程. 即在一个功能模块中新增其他功能,比方说你要下楼取个快递,你同事对你说 ...

  6. Cookie和Session-学习笔记02【Cookie案例、JSP改造Cookie案例】

    Java后端 学习路线 笔记汇总表[黑马程序员] Cookie和Session-学习笔记01[Cookie_快速入门.Cookie_细节] Cookie和Session-学习笔记02[Cookie案例 ...

  7. AOP和IOC的区别

    AOP和IOC的区别 二者没有联系.ioc是控制反转,aop是面向事务. IOC就是一个生产和管理bean的容器就行了,原来需要在调用类中new的东西,现在都是通过容器生成,同时,要是产生的是单例的b ...

  8. spring的AOP和IOC的原理

    目录 一.spring的ioc与aop原理 二.代理模式: 三.静态代理 四.动态代理 五.实际的操作 六.动态代理的实现: 七.什么是AOP 八.主流的AOP框架: 九.术语: 十.通知的五种类型: ...

  9. 低成本 汉朔 墨水屏 msp430g2553主控 改造完整案例

    低成本 汉朔 2.13寸墨水屏 msp430g2553主控 改造完整案例 无需额外开发版 首先是原料购买 我们需要用到串口烧录器,CH340G或者CP2102: 注意:如果购买的话尽量买有DTR,RT ...

最新文章

  1. 这 4 款 MySQL 调优工具 yyds
  2. outlook地址薄导入到FOXMAIL
  3. 检查字符串首字母的性能
  4. 查询数据库占用磁盘大小
  5. python symbol函数展开_QGIS表达式中的函数
  6. Nginx服务学习(6)-日志模块
  7. bean交个spring和new比较区别
  8. Sharepoint页面里添加.net托管代码
  9. Linux查看系统cpu个数、核心书、线程数
  10. php html 目录列表,PHP获取文件目录列表
  11. asp.net GridView控件的列属性
  12. 网络安全 Python 编程指南
  13. 计算机分析桁架受力,结构力学教学中桁架的概念分析与实践
  14. 九针串口定义(公口、母口图解)
  15. SpringCloud 第十二章 Gateway新一代网关
  16. photo技能大全,想学ps的来看看
  17. project下build.gradle文件和module下buil.gradle
  18. RoboCupRescue心得
  19. 通过两个小例子,更快了解-Xms -Xmx
  20. JavaScript-----JSON和数组对象

热门文章

  1. 第十六篇 Computed 计算属性的应用
  2. ThinkPHP3.2 -1146:Table 'lianxi_1.think_from' doesn't exist [ SQL语句 ] : SHOW COLUMNS FROM `think_fro
  3. ppt矩形里面的图片怎么放大缩小_图形的放大与缩小(公开课 ppt课件).ppt
  4. 串口硬件流量控制说明
  5. 小黄鸡 java_Java版本小黄鸡
  6. 最佳 WordPress 静态缓存插件 WP Super Cache 安装和使用(转)
  7. [iOS] 文字描边方法
  8. java实现qq抢红包_Java实现抢红包功能
  9. 天相投顾这么看新钢钒
  10. 如何优雅的使用GMS,为此我做了个食用Google服务的magisk microG懒人模块。