mysql数据库事务分析:实现银行转账功能的优化,附代码+实现过程
上一篇文章文章使用的是三层架构模式,因为事务开启需要在service层,所以只能在service层开启事务,为了保证是同一事务,所以要在service层获取到connection对象,然后将这个对象传递到dao层,但是这个有一个问题,就是connection是操作数据库的东西,他不应该写在service层
那么是否可以将开启事务和关闭事务以及回滚事务和获取连接对象封装起来,保证在任何方法中操作的都是同一个connection,这样在dao层就可以直接获取connection了,就没有必要service层去传输connection了,这样service代码中就直接调用封装好的开启事务,提交事务的方法,而dao层为了查询,或者修改数据库所用到的connection对象,直接调用封装好的获取connection方法,他们获取的这些封装好的方法中操作的connection是一个
思想是有了,那么如何才能保证在任何方法中操作的都是同一个connection对象呢?这里首先要介绍一个类Threadlocal,这个类的本质是一个map,但是它很特殊,因为它的键我们不用操作,键固定为当前线程的名字,我们只用操作值,这个类可以帮助我们获取connection对象,然后存入Threadlocal,这就可以保证只要我们始终在一个线程中,那么取出的connction就始终是一个,这样我们在银行转账这个单线程程序中,在任何地方都是操作同一个connction,service不再需要传递connection给dao了,dao只要直接在Threadlocal中获取,就可以获取到和service一样的connection。
这个ThreadLocal虽然是map但是因为键固定,所以我们只需要操作值,所以我们只需要泛型写值就ok了,还有就是它的基本方法,map中增加删除,都需要指定键这个不需要,这个获取直接t1.get(),删除直接t1.remove()因为键默认为当前线程的名字。
自定义MyDataSource工具类
package com.huanfeng.utils;import java.sql.Connection;import java.sql.SQLException;import com.mchange.v2.c3p0.ComboPooledDataSource;public class MyDataSource {//获得Connection ----- 从连接池中获取private static ComboPooledDataSource dataSource = new ComboPooledDataSource();//创建ThreadLocalprivate static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();//开启事务public static void startTransaction() throws SQLException{Connection conn = getCurrentConnection();conn.setAutoCommit(false);}//获得当前线程上绑定的connpublic static Connection getCurrentConnection() throws SQLException{//从ThreadLocal寻找 当前线程是否有对应ConnectionConnection conn = tl.get();if(conn==null){//获得新的connectionconn = getConnection();//将conn资源绑定到ThreadLocal(map)上tl.set(conn);}return conn;}public static Connection getConnection() throws SQLException{return dataSource.getConnection();}//回滚事务public static void rollback() throws SQLException {getCurrentConnection().rollback();}//提交事务public static void commit() throws SQLException {Connection conn = getCurrentConnection();conn.commit();//将Connection从ThreadLocal中移除tl.remove();conn.close();}}transfer.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Insert title here</title></head><body><form action="${pageContext.request.contextPath }/transfer" method="post">转出账户:<input type="text" name="out"><br>转入账户:<input type="text" name="in"><br>转账金额:<input type="text" name="money"><br><input type="submit" value="确认转账"><br></form></body></html>TransferServlet
package com.huanfeng.cn;import java.io.IOException;import java.net.URLEncoder;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;public class TransferServlet extends HttpServlet {protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {//接受转账的参数request.setCharacterEncoding("UTF-8");String out = request.getParameter("out");String in = request.getParameter("in");String moneyStr = request.getParameter("money");double money = Double.parseDouble(moneyStr);//调用业务层的转账方法TransferService service = new TransferService();boolean isTransferSuccess = service.transfer(out,in,money);response.setContentType("text/html;charset=UTF-8");if(isTransferSuccess){response.getWriter().write("转账成功!!!");}else{response.getWriter().write("转账失败!!!");}}protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}}TransferService
package com.huanfeng.cn;import java.sql.Connection;import java.sql.SQLException;import com.huanfeng.utils.MyDataSource;public class TransferService {public boolean transfer(String out, String in, double money) {TransferDao dao = new TransferDao();boolean isTranferSuccess = true;try {MyDataSource.startTransaction();dao.out(out,money);dao.in(in,money);} catch (Exception e) {isTranferSuccess = false;//回滚事务try {MyDataSource.rollback();} catch (SQLException e1) {e1.printStackTrace();}e.printStackTrace();} finally{try {MyDataSource.commit();} catch (SQLException e) {e.printStackTrace();}}return isTranferSuccess;}}TransferDao
package com.huanfeng.cn;import java.sql.Connection;import java.sql.SQLException;import org.apache.commons.dbutils.QueryRunner;import org.junit.Test;import com.huanfeng.utils.MyDataSource;public class TransferDao {public void out(String out, double money) throws SQLException {QueryRunner runner = new QueryRunner();Connection conn = MyDataSource.getCurrentConnection();String sql = "update account set money=money-? where name=?";int b=runner.update(conn, sql, money,out);System.out.println(b+"aa");}public void in(String in, double money) throws SQLException {QueryRunner runner = new QueryRunner();Connection conn = MyDataSource.getCurrentConnection();String sql = "update account set money=money+? where name=?";runner.update(conn, sql, money,in);}}数据库
至此程序就ok了但是这个程序还有一个问题,就是我们判断是否转账成功是根据是都有异常产生的,如果有异常产生就表示转账不成功的,
但是这个程序转账不成功也就是update执行失败不会产生异常,只会返回0,也就是说如果我们表单中输入的name数据库中没有,他也不会产生异常,不会产生异常那么service就认为数据库操作执行成功,所以这是不对的,应该判断返回值,如果的返回值为0,就表示数据库操作失败