MVC架构模式 | 使用银行转账的功能实现引出MVC架构模式

news2024/11/18 1:24:41

一、银行转账的功能实现

数据库表的准备

创建数据库表,主要包括三个字段:自增的id、账户名、账户余额
    • 不使用MVC架构模式完成账户转账

首先写一个页面,写入转账的账户和金额;并发送post请求
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <%--<base href="http://localhost:8080/bank/">--%>
    <base href="${pageContext.request.scheme}://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/">
    <title>转账</title>
  </head>
  <body>
    <form action="transfer" method="post">
      转出账户:<input type="text" name="fromActno"><br>
      装入账户:<input type="text" name="toActno"><br>
      转账金额:<input type="text" name="money"><br>
      <input type="submit" value="转账">
    </form>
  </body>
</html>

根据发送的数据请求,编写Servlet,连接数据库进行转账操作
package com.bjpowernode.zl.javaweb;

import com.bjpowernode.zl.exception.MoneyNotException;
import com.bjpowernode.zl.exception.ResultException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.*;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.zl.javaweb
 * @Project:mvc-test
 * @name:TransferServlet
 * @Date:2022/12/23 18:53
 */
@WebServlet("/transfer")
public class TransferServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取前端提交的数据
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        String fromActno = request.getParameter("fromActno");
        String toActno = request.getParameter("toActno");
        double money = Double.parseDouble(request.getParameter("money"));

        // 连接数据库,进行转账操作
        // 1. 转账之前先要进行查询操作,有没有钱可以转
        Connection conn = null;
        PreparedStatement ps = null;
        PreparedStatement ps2 = null;
        PreparedStatement ps3 = null;
        ResultSet rs = null;

        try {
            // 注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 获取连接
            String url = "jdbc:mysql://localhost:3306/mvc";
            String username = "root";
            String password = "123";
            conn = DriverManager.getConnection(url, username, password);
            // 获取预编译的数据库操作对象
            String sql = "select balance from t_act where actno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,fromActno);
            // 执行sql
            rs = ps.executeQuery();
            // 处理查询结果集
            if (rs.next()) {
                double balance = rs.getDouble("balance");
                if(balance < money){
                    // 余额不足,抛出异常
                    throw new MoneyNotException("对不起,余额不足!");
                }
                // 2. 余额充足,进行转账
                // 开启事务(手动提交)
                conn.setAutoCommit(false);

                String sql2 = "update t_act set balance = balance - ? where actno = ?";
                ps2 = conn.prepareStatement(sql2);
                ps2.setDouble(1,money);
                ps2.setString(2,fromActno);
                int count = ps2.executeUpdate();

                String sql3 = "update t_act set balance = balance + ? where actno = ?";
                ps3 = conn.prepareStatement(sql3);
                ps3.setDouble(1,money);
                ps3.setString(2,toActno);
                // 累计
                count += ps3.executeUpdate();

                if (count != 2){
                    throw  new ResultException("转账过程出现问题,请联系管理员");
                }
                // 转账成功
                // 手动提交事务
                conn.commit();

                out.print("转账成功!");
            }
        } catch (Exception e) {
            // 只要出现异常,进行回滚
            try {
                if (conn != null){
                    conn.rollback();
                }
            } catch (SQLException e1) {
                e1.printStackTrace();
            }

            // e.printStackTrace();
            // 打印异常的信息
            out.print(e.getMessage());
        } finally {
            // 释放资源
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (ps2 != null) {
                try {
                    ps2.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (ps3 != null) {
                try {
                    ps3.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }


    }
}
两个异常处理
package com.bjpowernode.zl.exception;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.zl.exception
 * @Project:mvc-test
 * @name:MoneyNotException
 * @Date:2022/12/23 19:56
 */
public class MoneyNotException  extends Exception{
    public MoneyNotException() {
    }
    public MoneyNotException(String msg) {
        super(msg);
    }
}
package com.bjpowernode.zl.exception;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.zl.exception
 * @Project:mvc-test
 * @name:ResultException
 * @Date:2022/12/23 20:14
 */
public class ResultException extends Exception {
    public ResultException() {
    }
    public ResultException(String message) {
        super(message);
    }
}
缺点:一个Servlet实现了所有的功能
①负责数据的接收
②负责核心的业务处理
③负责数据库中数据的CRUD
④负责了页面的展示,打印输出信息到浏览器上
原因:没有进行“职能分工”
①没有独立组件的概念,所以没有办法进行代码复用,代码和代码之间的耦合度太高,扩展力太差!
②操作数据库的代码和业务逻辑混杂在一起,很容易出错!

    • MVC架构模式的理论基础

(1)系统为什么分层?
希望各司其职,专人干专事。职能分工要明确,这样可以让代码耦合度降低,扩展力增强,组件的可复用性增强。
(2)软件的架构中,一个著名的架构模式:MVC架构模式
①M(Model:数据/业务)、V(View:视图/展示)、C(Controller:控制器)
②C(是核心,是控制,是司令官)
③M(处理业务/处理数据的一个秘书)
④V(负责页面展示的一个秘书)
⑤MVC(实际上就是一个司令官,调度两个秘书)
  1. JDBC工具类的封装

为了下面我们方便写代码,还是先封装一个JDBC工具类,进行数据库连接的操作
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mvc
user=root
password=123
package com.bjpowernode.bank.utils;

import java.sql.*;
import java.util.ResourceBundle;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.utils
 * @Project:mvc-test
 * @name:DBUtil
 * @Date:2022/12/24 11:10
 */
public class DBUtil {

    private static ResourceBundle bundle = ResourceBundle.getBundle("resources.jdbc");
    private static String driver = bundle.getString("driver");
    private static String url = bundle.getString("url");
    private static String user = bundle.getString("user");
    private static String password = bundle.getString("password");

    // 先提供给一个私有的构造方法:防止实例化对象
    private DBUtil(){}

    // 类加载时,注册驱动
    static {
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    // 获取连接
    public static Connection getConnection() throws SQLException {
        Connection connection = DriverManager.getConnection(url, user, password);
        return connection;
    }

    // 关闭资源
    public static void close(Connection conn, Statement stmt, ResultSet rs){
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}

  1. JavaEE设计模式之DAO模式以及DAO的编写

创建一个类AccountDao负责Account数据的增删改查
①什么是DAO?Data Access Object(数据访问对象)
②DAO实际上是一种设计模式,属于JavaEE的设计模式(不是23种设计模式)
③DAO只负责数据表的CRUD,没有任何的业务逻辑在里面。
④没有任何逻辑业务,只负责表中数据的增删改查的对象,就叫做:DAO对象
⑤一般情况下:一张表就对应一个DAO对象

Account类:把查询的结果封装成一个Java对象,封装账户信息的;

注意:一般属性不建议设置为基本数据类型,建议使用包装类,防止null带来的问题

注:这种普通的对象也就做pojo对象(或者javabean对象、或者领域模型对象:domain对象)

package com.bjpowernode.bank.mvc;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.mvc
 * @Project:mvc-test
 * @name:Account
 * @Date:2022/12/24 13:50
 */
public class Account {
    // 一般属性不建议设置为基本数据类型,建议使用包装类,防止null带来的问题
    // 使用Long和Double就算取出来的数据是null也能直接赋值过去,完全没有问题
    private Long id;
    private String actno;
    private Double balance;

    public Account() {
    }

    public Account(Long id, String actno, Double balance) {
        this.id = id;
        this.actno = actno;
        this.balance = balance;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public Double getBalance() {
        return balance;
    }

    public void setBalance(Double balance) {
        this.balance = balance;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", actno='" + actno + '\'' +
                ", balance=" + balance +
                '}';
    }
}

AccountDao类:只负责数据库的访问,处理数据(增删改查)

DAO中的方法名很固定,一般都是:insert、deleteByXxx、update、selectByXxx、selectAll
package com.bjpowernode.bank.mvc;

import com.bjpowernode.bank.utils.DBUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.mvc
 * @Project:mvc-test
 * @name:AccountDao
 * @Date:2022/12/24 13:39
 */
public class AccountDao { // 负责数据的增删改查

    // 1、插入账户信息,1表示插入成功
    public int insert(Account act){
        Connection conn = null;
        PreparedStatement ps = null;
        int count = 0;
        try {
            conn = DBUtil.getConnection();
            String sql = "insert into t_act(actno,balance) value(?,?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1,act.getActno());
            ps.setDouble(2,act.getBalance());
            count = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(conn,ps,null);
        }
        return count;
    }

    ///2、根据主键id删除账户
    public int deleteById(Long id){
        Connection conn = null;
        PreparedStatement ps = null;
        int count = 0;
        try {
            conn = DBUtil.getConnection();
            String sql = "delete from t_act where id = ?";
            ps = conn.prepareStatement(sql);
            ps.setLong(1,id);
            count = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(conn,ps,null);
        }
        return count;
    }

    //  3、更新账户
    public int update(Account act){
        Connection conn = null;
        PreparedStatement ps = null;
        int count = 0;
        try {
            conn = DBUtil.getConnection();
            String sql = "update t_act set balance = ?,actno = ? where id = ?";
            ps = conn.prepareStatement(sql);
            ps.setDouble(1,act.getBalance());
            ps.setString(2,act.getActno());
            ps.setLong(3,act.getId());
            count = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(conn,ps,null);
        }
        return count;
    }

    // 4、根据账号查询账户
    public Account selectByActno(String actno){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        Account act = null;
        try {
            conn = DBUtil.getConnection();
            String sql = "select id,balance from t_act where actno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,actno);
            rs = ps.executeQuery();
            if (rs.next()) {
                Long id = rs.getLong("id");
                Double balance = rs.getDouble("balance");
                // 将结果集封装成java对象
                act = new Account();
                act.setId(id);
                act.setActno(actno);
                act.setBalance(balance);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(conn,ps,rs);
        }
        return act;
    }

    // 5、获取所有的账户
    public List<Account> selectAll(){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        List<Account> list = new ArrayList<>();
        try {
            conn = DBUtil.getConnection();
            String sql = "select id,actno,balance from t_act";
            ps = conn.prepareStatement(sql);
            rs = ps.executeQuery();
            while (rs.next()) {
                // 取数据
                Long id = rs.getLong("id");
                String actno = rs.getString("actno");
                Double balance = rs.getDouble("balance");
                // 将结果集封装成java对象
                Account act = new Account();
                act.setId(id);
                act.setActno(actno);
                act.setBalance(balance);
                // 放到集合当中
                list.add(act);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(conn,ps,rs);
        }
        return list;
    }
}

    • 业务层抽取以及业务方法的实现

前面我们已经写了两个类:Account类主要负责封装数据的;AccountDao类主要处理数据的;这里就要再抽取出来一个类AccountService类用来处理逻辑业务的!
①service翻译为:业务,在AccountService类中编写纯业务代码
②业务类一般起名:XxxService、XxxBiz...
package com.bjpowernode.bank.mvc;

import com.bjpowernode.bank.exception.AppException;
import com.bjpowernode.bank.exception.MoneyNotException;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.mvc
 * @Project:mvc-test
 * @name:AccountService
 * @Date:2022/12/24 15:43
 * 专门编写业务的
 */
public class AccountService { // 处理业务
    // 每一个业务都有可能连接数据库,所以定义在方法的外面
    private AccountDao accountDao = new AccountDao();

    // 提供一个能够实现转账的业务方法(一个业务对应一个方法)
    /**
     * 完成转账的业务逻辑
     * @param fromActno 转出账号
     * @param toActno 转入账号
     * @param money 转账金额
     */
    public void transfer(String fromActno,String toActno,double money) throws MoneyNotException, AppException {
        // 先查询余额是否充足,返回的是一个Account对象
        Account fromAct = accountDao.selectByActno(fromActno);
        if (fromAct.getBalance() < money){
            throw  new MoneyNotException("对不起,余额不足");
        }
        // 程序走到这里,说明余额充足
        Account toAct = accountDao.selectByActno(toActno);
        // 修改金额(只是修改内存中java对象的余额)
        fromAct.setBalance(fromAct.getBalance()-money);
        toAct.setBalance(toAct.getBalance()+money);
        // 更新数据库中的余额
        int count = accountDao.update(fromAct);
        count += accountDao.update(toAct);
        // 判断
        if (count != 2) {
            throw new AppException("转账异常!");
        }
    }
}

AccountServlet类:相当于司令官,进行整个业务的调度

package com.bjpowernode.bank.mvc;

import com.bjpowernode.bank.exception.AppException;
import com.bjpowernode.bank.exception.MoneyNotException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.mvc
 * @Project:mvc-test
 * @name:AccountServlet
 * @Date:2022/12/24 13:36
 */
@WebServlet("/transfer")
public class AccountServlet extends HttpServlet { // AccountServlet作为Controller
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 接收数据
        String fromActno = request.getParameter("fromActno");
        String toActno = request.getParameter("toActno");
        double money = Double.parseDouble(request.getParameter("money"));
        // 调用业务方法处理业务(调度Model处理业务)
        AccountService accountService = new AccountService();
        try {
            accountService.transfer(fromActno,toActno,money); // 进行转账
            // 转账成功
            // 展示处理结果(调度View做页面展示)
            response.sendRedirect(request.getContextPath()+"/success.jsp");
        } catch (MoneyNotException e) {
            // 余额不足
            // 展示处理结果(调度View做页面展示)
            response.sendRedirect(request.getContextPath()+"/money-not-enough.jsp");
        } catch (AppException e) {
            // 失败
            // 展示处理结果(调度View做页面展示)
            response.sendRedirect(request.getContextPath()+"/error.jsp");
        }


    }
}
  1. MVC架构模式与三层架构的关系

通过前面的一步步分层,实际就达到了以下三层架构的模型
(横向)

(纵向)

所以,对于原来的图,就可以进一步细化,展示出:MVC架构模式与三层架构的关系

补充:SSM框架的理解
①Spring:项目大管家,负责整个项目所有对象的创建以及维护对象和对象之间的关系。
②SpringMVC:将MVC结构模式体现的非常完美;在这个框架的基础上开发一定是用了MVC架构模式;实际上SringMVC架构已经把MVC架构的环境已经搭建出来了。
③MyBatis:属于持久化层的框架。

  1. 解决事务问题

上述我们已经可以实现所有的功能,但是还没有添加事务,一旦出现异常现象会出现问题。
注: 事务一定是在业务逻辑层(service层)进行控制。

第一种方式:不可以,不是同一个Connection对象,无法控制事务;但是是在同一个线程里面的

package com.bjpowernode.bank.mvc;

import com.bjpowernode.bank.exception.AppException;
import com.bjpowernode.bank.exception.MoneyNotException;
import com.bjpowernode.bank.utils.DBUtil;

import java.sql.Connection;
import java.sql.SQLException;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.mvc
 * @Project:mvc-test
 * @name:AccountService
 * @Date:2022/12/24 15:43
 * 专门编写业务的
 */
public class AccountService { // 处理业务
    // 每一个业务都有可能连接数据库,所以定义在方法的外面
    private AccountDao accountDao = new AccountDao();
    // 提供一个能够实现转账的业务方法(一个业务对应一个方法)
    /**
     * 完成转账的业务逻辑
     * @param fromActno 转出账号
     * @param toActno 转入账号
     * @param money 转账金额
     */
    public void transfer(String fromActno,String toActno,double money) throws MoneyNotException, AppException {

        try (Connection connection = DBUtil.getConnection()){//资源自动管理,写在这里面,就不用写finally关闭资源
            // 开启事务(需要使用Connection对象)
            connection.setAutoCommit(false);

            Account fromAct = accountDao.selectByActno(fromActno);
            if (fromAct.getBalance() < money){
                throw  new MoneyNotException("对不起,余额不足");
            }
            // 程序走到这里,说明余额充足
            Account toAct = accountDao.selectByActno(toActno);
            // 修改金额(只是修改内存中java对象的余额)
            fromAct.setBalance(fromAct.getBalance()-money);
            toAct.setBalance(toAct.getBalance()+money);
            // 更新数据库中的余额
            int count = accountDao.update(fromAct);


            // 模拟异常
            String s = null;
            s.toString();

            count += accountDao.update(toAct);
            // 判断
            if (count != 2) {
                throw new AppException("转账异常!");
            }

            // 提交事务
            connection.commit();
        } catch (SQLException e) {
            // 这里先不会滚也可以,遇到异常就不会提交了
            throw new AppException("转账异常!");
        }

    }

}

第二种方式:把这个Connection对象传参传过去,这样就能共享同一个Connection对象;可以,但是代码比较丑陋!

package com.bjpowernode.bank.mvc;

import com.bjpowernode.bank.exception.AppException;
import com.bjpowernode.bank.exception.MoneyNotException;
import com.bjpowernode.bank.utils.DBUtil;

import java.sql.Connection;
import java.sql.SQLException;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.mvc
 * @Project:mvc-test
 * @name:AccountService
 * @Date:2022/12/24 15:43
 * 专门编写业务的
 */
public class AccountService { // 处理业务
    // 每一个业务都有可能连接数据库,所以定义在方法的外面
    private AccountDao accountDao = new AccountDao();
    // 提供一个能够实现转账的业务方法(一个业务对应一个方法)
    /**
     * 完成转账的业务逻辑
     * @param fromActno 转出账号
     * @param toActno 转入账号
     * @param money 转账金额
     */
    public void transfer(String fromActno,String toActno,double money) throws MoneyNotException, AppException {

        try (Connection connection = DBUtil.getConnection()){//资源自动管理,写在这里面,就不用写finally关闭资源
            // 开启事务(需要使用Connection对象)
            connection.setAutoCommit(false);

            Account fromAct = accountDao.selectByActno(fromActno,connection);
            if (fromAct.getBalance() < money){
                throw  new MoneyNotException("对不起,余额不足");
            }
            // 程序走到这里,说明余额充足
            Account toAct = accountDao.selectByActno(toActno,connection);
            // 修改金额(只是修改内存中java对象的余额)
            fromAct.setBalance(fromAct.getBalance()-money);
            toAct.setBalance(toAct.getBalance()+money);
            // 更新数据库中的余额
            int count = accountDao.update(fromAct,connection);


            // 模拟异常
            String s = null;
            s.toString();

            count += accountDao.update(toAct,connection);
            // 判断
            if (count != 2) {
                throw new AppException("转账异常!");
            }

            // 提交事务
            connection.commit();
        } catch (SQLException e) {
            // 这里先不会滚也可以,遇到异常就不会提交了
            throw new AppException("转账异常!");
        }

    }

}
AccountDao代码也要进行修改。

package com.bjpowernode.bank.mvc;
import com.bjpowernode.bank.utils.DBUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.mvc
 * @Project:mvc-test
 * @name:AccountDao
 * @Date:2022/12/24 13:39
 * 专门处理数据的
 */
public class AccountDao { // 负责数据的增删改查

    // 1、插入账户信息,1表示插入成功
    public int insert(Account act,Connection conn){
        PreparedStatement ps = null;
        int count = 0;
        try {
            String sql = "insert into t_act(actno,balance) value(?,?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1,act.getActno());
            ps.setDouble(2,act.getBalance());
            count = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(null,ps,null);
        }
        return count;
    }

    ///2、根据主键id删除账户
    public int deleteById(Long id, Connection conn){
        PreparedStatement ps = null;
        int count = 0;
        try {
            String sql = "delete from t_act where id = ?";
            ps = conn.prepareStatement(sql);
            ps.setLong(1,id);
            count = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(null,ps,null);
        }
        return count;
    }

    //  3、更新账户
    public int update(Account act, Connection conn){
        /*Connection conn = null;*/
        PreparedStatement ps = null;
        int count = 0;
        try {
            /*conn = DBUtil.getConnection();*/
            String sql = "update t_act set balance = ?,actno = ? where id = ?";
            ps = conn.prepareStatement(sql);
            ps.setDouble(1,act.getBalance());
            ps.setString(2,act.getActno());
            ps.setLong(3,act.getId());
            count = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            /*DBUtil.close(conn,ps,null);*/
            DBUtil.close(null,ps,null);
        }
        return count;
    }

    // 4、根据账号查询账户
    public Account selectByActno(String actno, Connection conn){
        PreparedStatement ps = null;
        ResultSet rs = null;
        Account act = null;
        try {
            String sql = "select id,balance from t_act where actno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,actno);
            rs = ps.executeQuery();
            if (rs.next()) {
                Long id = rs.getLong("id");
                Double balance = rs.getDouble("balance");
                // 将结果集封装成java对象
                act = new Account();
                act.setId(id);
                act.setActno(actno);
                act.setBalance(balance);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(null,ps,rs);
        }
        return act;
    }

    // 5、获取所有的账户
    public List<Account> selectAll(Connection conn){
        PreparedStatement ps = null;
        ResultSet rs = null;
        List<Account> list = new ArrayList<>();
        try {
            String sql = "select id,actno,balance from t_act";
            ps = conn.prepareStatement(sql);
            rs = ps.executeQuery();
            while (rs.next()) {
                // 取数据
                Long id = rs.getLong("id");
                String actno = rs.getString("actno");
                Double balance = rs.getDouble("balance");
                // 将结果集封装成java对象
                Account act = new Account();
                act.setId(id);
                act.setActno(actno);
                act.setBalance(balance);
                // 放到集合当中
                list.add(act);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(null,ps,rs);
        }
        return list;
    }
}
在AccountService的transfer方法中调用AccountDao中的任意一个方法;实际上访问的还是同一个线程对象:Thread.currentThread;所以就可以搞一个Map集合<Thread,Object>:key存线程对象,value存Connection对象!

    • 手撕ThreadLocal源码

根据我们上面的理解,我们需要一个大Map集合;怎么设置这个大Map?不妨从一个简单的例子,一步步进行改进优化!

定义一个类Connection,作为参数传参

package com.bjpowernode.threadlocal;
public class Connection {
}

定义UserService类,再定义个save方法;在save方法中调用UserDao类的insert方法

package com.bjpowernode.threadlocal;
public class UserService {
    private UserDao userDao = new UserDao();
    public void save(Connection conn){
        // 获取当前线程
        Thread thread = Thread.currentThread();
        System.out.println(thread);
        // 在UserService的save方法当中去调用UserDao类的insert方法
        userDao.insert(conn);

    }
}

定义UserDao类

package com.bjpowernode.threadlocal;

public class UserDao {
    public void insert(Connection conn){
        // 获取当前线程
        Thread thread = Thread.currentThread();
        System.out.println(thread);
        System.out.println("UserDao insert");
    }
}

测试类Test

package com.bjpowernode.threadlocal;

public class Test {
    public static void main(String[] args) {
        // 获取Connection对象
        Connection conn = new Connection();

        // 获取当前线程
        Thread thread = Thread.currentThread();
        System.out.println(thread);

        // 调用service
        UserService userService = new UserService();
        userService.save(conn);
    }
}

通过测试发现,所有获得的是同一个线程对象,并且通过传参Connection,也可以正常调用

既然所有的线程对象相同,我们不妨通过一个Map集合的方式Map<线程对象,Connection对象>,做到不传参Connection,也做到所有类的Connection对象是同一个对象!

编写一个MyThreadLocal类:里面是一个Map集合,所有需要和当前线程绑定的数据要放到这个容器里

package com.bjpowernode.threadlocal;

import java.util.HashMap;
import java.util.Map;

public class MyThreadLocal<T> {
    // 所有需要和当前线程绑定的数据要放到这个容器里
    private Map<Thread,T> map = new HashMap<>();
    // 存
    public void set(T t){
        map.put(Thread.currentThread(),t);
    }
    // 取
    public T get(){
        return map.get(Thread.currentThread());
    }
    // 删除
    public void remove(){
        map.remove(Thread.currentThread());
    }
}

DBUtil工具类:用来获取Connection

package com.bjpowernode.utils;

import com.bjpowernode.threadlocal.Connection;
import com.bjpowernode.threadlocal.MyThreadLocal;

public class DBUtil { // 封装一个工具类
    // 静态变量:类加载时执行,并且只执行一次
    // 全局的大Map集合
    private static MyThreadLocal<Connection> local = new MyThreadLocal<>();
    // 每一次调用这个方法获取Connection对象
    public static Connection getConnection(){
        Connection connection = local.get();
        if (connection == null) {
            // 空的就new一次
            connection = new Connection();
           // 将new的Connection对象绑定到大Map集合
            local.set(connection);
        }
        return connection;
    }
}
原来的Test类中new Connection对象,就不许需要了;方法中为了保持所有的Connection一样传参Connection也不需要了,直接使用工具类就可以获取全部相同的Connection对象!

Test类

package com.bjpowernode.threadlocal;

public class Test {
    public static void main(String[] args) {
        // 获取当前线程
        Thread thread = Thread.currentThread();
        System.out.println(thread);

        // 调用service
        UserService userService = new UserService();
        userService.save();
    }
}

UserService类

package com.bjpowernode.threadlocal;

import com.bjpowernode.utils.DBUtil;

public class UserService {
    private UserDao userDao = new UserDao();
    public void save(){
        // 获取当前线程
        Thread thread = Thread.currentThread();
        System.out.println(thread);

        // 直接获取Connextion对象
        Connection connection = DBUtil.getConnection();
        System.out.println(connection);

        // 在UserService的save方法当中去调用UserDao类的insert方法
        userDao.insert();

    }
}

UserDao类

package com.bjpowernode.threadlocal;

import com.bjpowernode.utils.DBUtil;

public class UserDao {
    public void insert(){
        // 获取当前线程
        Thread thread = Thread.currentThread();
        System.out.println(thread);

        // 获取Connection对象
        Connection connection = DBUtil.getConnection();
        System.out.println(connection);

        System.out.println("UserDao insert");
    }
}

通过测试发现,这两个直接获取到的Connection对象,确实是同一个Connection对象

第三种方法:项目中直接引入ThreadLocal(java.lang.ThreadLocal已经封装好了,不需要手写!在连接数据的DBUtil工具类中进行代码的修改

补充:Tomcat服务器内置了一个线程池,线程池中有很多线程对象:这些线程对象t1,t2,t3都是提前创建好的,也就是说t1,t2,t3存在重复使用的现象!
package com.bjpowernode.bank.utils;

import java.sql.*;
import java.util.ResourceBundle;

public class DBUtil {

    private static ResourceBundle bundle = ResourceBundle.getBundle("resources.jdbc");
    private static String driver = bundle.getString("driver");
    private static String url = bundle.getString("url");
    private static String user = bundle.getString("user");
    private static String password = bundle.getString("password");

    // 先提供给一个私有的构造方法:防止实例化对象
    private DBUtil(){}

    // 类加载时,注册驱动
    static {
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    // 创建ThreadLocal(本质上是一个大Map)
    private static ThreadLocal<Connection> local = new ThreadLocal<>();


    // 获取连接
    public static Connection getConnection() throws SQLException {
        // 获取connection对象
        Connection connection = local.get();
        if (connection == null) {
            // 如果为空,就创建出来
            connection = DriverManager.getConnection(url, user, password);
            // 放入集合当中
            local.set(connection);
        }
        return connection;
    }

    // 关闭资源
    public static void close(Connection conn, Statement stmt, ResultSet rs){
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (conn != null) {
            try {
                conn.close();
                // 思考:conn关闭之后,线程要从大Map移除?
                // 根本原因:Tomcat服务器时支持多线程的,也就是说一个人用过了t1线程,t1线程还有可能被其他用户使用
                local.remove();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}
修改以后,其他类的方法传参时就不需要传Connection对象了,因为它们获得的都是同一个Connection对象,可以正常关闭!

AccountDao类:关于数据的增删改查操作,不需要传参Connection,还是使用工具类直接获取

package com.bjpowernode.bank.mvc;

import com.bjpowernode.bank.utils.DBUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.mvc
 * @Project:mvc-test
 * @name:AccountDao
 * @Date:2022/12/24 13:39
 * 专门处理数据的
 */
public class AccountDao { // 负责数据的增删改查

    // 1、插入账户信息,1表示插入成功
    public int insert(Account act){
        PreparedStatement ps = null;
        int count = 0;
        try {
            Connection conn = DBUtil.getConnection();
            String sql = "insert into t_act(actno,balance) value(?,?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1,act.getActno());
            ps.setDouble(2,act.getBalance());
            count = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(null,ps,null);
        }
        return count;
    }

    ///2、根据主键id删除账户
    public int deleteById(Long id){
        PreparedStatement ps = null;
        int count = 0;
        try {
            Connection conn = DBUtil.getConnection();
            String sql = "delete from t_act where id = ?";
            ps = conn.prepareStatement(sql);
            ps.setLong(1,id);
            count = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(null,ps,null);
        }
        return count;
    }

    //  3、更新账户
    public int update(Account act){
        PreparedStatement ps = null;
        int count = 0;
        try {
            Connection conn = DBUtil.getConnection();
            String sql = "update t_act set balance = ?,actno = ? where id = ?";
            ps = conn.prepareStatement(sql);
            ps.setDouble(1,act.getBalance());
            ps.setString(2,act.getActno());
            ps.setLong(3,act.getId());
            count = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(null,ps,null);
        }
        return count;
    }

    // 4、根据账号查询账户
    public Account selectByActno(String actno){
        PreparedStatement ps = null;
        ResultSet rs = null;
        Account act = null;
        try {
            Connection conn = DBUtil.getConnection();
            String sql = "select id,balance from t_act where actno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,actno);
            rs = ps.executeQuery();
            if (rs.next()) {
                Long id = rs.getLong("id");
                Double balance = rs.getDouble("balance");
                // 将结果集封装成java对象
                act = new Account();
                act.setId(id);
                act.setActno(actno);
                act.setBalance(balance);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(null,ps,rs);
        }
        return act;
    }

    // 5、获取所有的账户
    public List<Account> selectAll(){
        PreparedStatement ps = null;
        ResultSet rs = null;
        List<Account> list = new ArrayList<>();
        try {
            Connection conn = DBUtil.getConnection();
            String sql = "select id,actno,balance from t_act";
            ps = conn.prepareStatement(sql);
            rs = ps.executeQuery();
            while (rs.next()) {
                // 取数据
                Long id = rs.getLong("id");
                String actno = rs.getString("actno");
                Double balance = rs.getDouble("balance");
                // 将结果集封装成java对象
                Account act = new Account();
                act.setId(id);
                act.setActno(actno);
                act.setBalance(balance);
                // 放到集合当中
                list.add(act);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(null,ps,rs);
        }
        return list;
    }
}

AccountService类:逻辑业务代码调用增删改查的方法也不需要传参数

package com.bjpowernode.bank.mvc;

import com.bjpowernode.bank.exception.AppException;
import com.bjpowernode.bank.exception.MoneyNotException;
import com.bjpowernode.bank.utils.DBUtil;

import java.sql.Connection;
import java.sql.SQLException;

/**
 * @Author:朗朗乾坤
 * @Package:com.bjpowernode.bank.mvc
 * @Project:mvc-test
 * @name:AccountService
 * @Date:2022/12/24 15:43
 * 专门编写业务的
 */
public class AccountService { // 处理业务
    // 每一个业务都有可能连接数据库,所以定义在方法的外面
    private AccountDao accountDao = new AccountDao();
    // 提供一个能够实现转账的业务方法(一个业务对应一个方法)
   
    public void transfer(String fromActno,String toActno,double money) throws MoneyNotException, AppException {

        try (Connection connection = DBUtil.getConnection()){//资源自动管理,写在这里面,就不用写finally关闭资源
            // 开启事务(需要使用Connection对象)
            connection.setAutoCommit(false);

            Account fromAct = accountDao.selectByActno(fromActno);
            if (fromAct.getBalance() < money){
                throw  new MoneyNotException("对不起,余额不足");
            }
            // 程序走到这里,说明余额充足
            Account toAct = accountDao.selectByActno(toActno);
            // 修改金额(只是修改内存中java对象的余额)
            fromAct.setBalance(fromAct.getBalance()-money);
            toAct.setBalance(toAct.getBalance()+money);
            // 更新数据库中的余额
            int count = accountDao.update(fromAct);


            // 模拟异常
           /* String s = null;
            s.toString();*/

            count += accountDao.update(toAct);
            // 判断
            if (count != 2) {
                throw new AppException("转账异常!");
            }

            // 提交事务
            connection.commit();
        } catch (SQLException e) {
            // 这里先不会滚也可以,遇到异常就不会提交了
            throw new AppException("转账异常!");
        }

    }

}
这样就能做到事务的控制!

不能功能放到不同的包下

按照三层架构分析:web下的UserServlet是表示层、service包下的UserService是业务层、dao包下的UserDao是持久化层
按照MVC架构模式分析:pojo下的Account类、dao包下的UserDao、service包下的UserService属于M、所有的.jsp属于V、web下的UserServlet属于C
并把AccountService和AccountDao类写成接口的形式(面向接口编程),然后写对应的实现类去实现这个接口即可!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/148455.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JavaEE】进入Web开发的世界-HTML

目录 一、HTML 1.1概念篇 1.2工具篇 1.2.1文本类型的标签 1.2.2多媒体标签 1.2.3超链接 1.2.4target 1.2.5表格相关的标签 1.2.6 列表标签 1.2.7表单标签 进入Web开发的世界&#xff0c;了解html、css、JavaScript的使用。 Web开发的核心&#xff1a;准备各式各样的资…

元壤:国内首家免费的数字藏品、DAO数字化营销SaaS平台

元壤&#xff1a;国内首家免费的数字藏品、DAO数字化营销SaaS平台 元壤是 Web3.0 时代 NFT 数字藏品与用户服务的数字化工具。元壤是中国企业数字资产化及数字藏品营销解决方案提供商。元壤致力于通过产品和服务,助力企业资产数字化,数字营销化,通过科技驱动数字商业变革,让数…

如何仿真MOS电容的电压-电容曲线?

一、原理 电容的阻抗为&#xff1a; 假设在电容两端施加频率为 f 的小信号电压 v &#xff0c;电容上流过的小信号电流为 i &#xff0c;那么三者有如下关系&#xff1a; 二、仿真 设置频率为1/2pi&#xff0c;这样算出来斜率即为1/C。设置f为0.159155 斜率就是1/C&#xff0c…

你问我答|DDR5对企业用户意味着什么?

自从2014年DDR4内存在市场推出以来,时间已经过去了八年,这对日新月异的计算机行业来说,无疑是相当长的一段时间了。这期间,更快的CPU和存储介质等产品的技术进步,促进了对更大容量的内存、更高内存带宽和更快速率的需求,服务器市场尤其如此。      而在2023年,我们终于迎来…

Sentinel 初始化监控以及流控规则简介

Sentinel 初始化监控 第一步&#xff1a;创建8401微服务 cloudalibaba-sentinel-service 并引入依赖 .yml 配置文件 server:port: 8401spring:application:name: cloudalibaba-sentinel-servicecloud:nacos:server-addr: localhost:8848 #Nacos服务注册中心地址sentinel:trans…

抖音搜索里的百科词条如何创建?

头条系的两大拳头产品&#xff0c;一个是抖音&#xff0c;一个是今日头条。现在头条系的运营重心向抖音倾斜&#xff0c;而抖音搜索里也存在一个固定的位置给快懂百科&#xff0c;这个位置不是像短视频一样滚动更新&#xff0c;做抖音搜索优化的话&#xff0c;小马识途营销顾问…

gateway网关的使用

今天与大家分享gateway网关的使用 1. gateway简介 1.1 是什么 SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关&#xff0c;目标是替代 Zuul&#xff0c;在Spring Cloud 2.0以上版本中&#xff0c;没有对新版本的Zuul 2.0以上最新高性能版本进行集成&#xff0c;仍…

Java设计模式中外观模式是什么/外观模式有什么用,如何实现

继续整理记录这段时间来的收获&#xff0c;详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用&#xff01; 5.6 外观模式 5.6.1 概述 又称门面模式&#xff0c;通过为多个子系统提供一个一致接口&#xff0c;而使这些子系统更加容易被访问的模式对外有一个统一接口&…

SAP工作流规则

代理人规则获取部分&#xff0c;灵活工作流和传统工作流一致 1. 事务代码&#xff1a;PFAC&#xff0c;用来创建规则 2. 规则用来确定代理&#xff0c;可通过如下下拉框中多种方式确定代理人 责任可在事务代码OOCU_RESP中配置代理人&#xff0c;可用来代替配置表确定代理人的…

Python蓝桥杯训练:数组和字符串 Ⅴ

Python蓝桥杯训练&#xff1a;数组和字符串 Ⅴ 文章目录Python蓝桥杯训练&#xff1a;数组和字符串 Ⅴ一、找到数组的中间位置二、使用最小花费爬楼梯一、找到数组的中间位置 给你一个下标从 0 开始的整数数组 nums &#xff0c;请你找到 最左边 的中间位置 middleIndex &#…

推荐系统实战5——EasyRec 在DSSM召回模型中添加负采样构建CTR点击平台

推荐系统实战5——EasyRec 在DSSM召回模型中添加负采样构建CTR点击平台学习前言EasyRec仓库地址DSSM实现思路一、DSSM整体结构解析二、网络结构解析1、Embedding层的构建2、网络层的构建3、相似度计算三、训练部分解析训练自己的DSSM模型一、数据集的准备二、Config配置文件的设…

一键生成分享链接的贺卡制作工具

不用自己动手设计&#xff0c;在线模板帮你轻松搞定新春贺卡设计&#xff0c;免下载的设计工具。跟着小编的设计教程&#xff0c;教你如何使用乔拓云工具&#xff0c;在线搞定你的新春祝福贺卡设计&#xff0c;不用任何设计经验&#xff0c;只需要跟着教程就能搞定的专属贺卡设…

论文笔记:RCLane: Relay Chain Prediction for Lane Detection

RCLane: Relay Chain Prediction for Lane Detection笔记摘要动机模型结构方法其他模型试验结果笔记摘要 该篇论文的核心创新点在于head。论文根据车道线既需要局部信息&#xff0c;也需要全局信息才能很好拟合的特性&#xff0c;设计了相应的算法head。并且论文实验证明该方法…

机器视觉(十一):条码识别

目录&#xff1a; 机器视觉&#xff08;一&#xff09;&#xff1a;概述 机器视觉&#xff08;二&#xff09;&#xff1a;机器视觉硬件技术 机器视觉&#xff08;三&#xff09;&#xff1a;摄像机标定技术 机器视觉&#xff08;四&#xff09;&#xff1a;空域图像增强 …

记一次虚拟机编译c程序错误

file included from /usr/include/stdio.h:74:0, from opendir.c:2: /usr/include/libio.h:302:3: error: unknown type name ‘size_t’ size_t __pad5; ^ /usr/include/libio.h:305:67: error: ‘size_t’ undeclared here (not in a function) ch…

黑马程序员 Maven 教程

Maven 简介 传统项目管理的缺点&#xff1a; (1) jar 包不统一&#xff0c;jar 包不兼容; (2) 工程升级维护过程操作繁琐; Maven 是什么 Maven 的本质是一个项目管理工具&#xff0c;将项目开发和管理过程抽象成一个项目对象模型 (POM) POM (Project Object Model) : 项目对…

二分搜索算法

目录1.概述2.代码实现2.1.最基本的二分搜索2.2.搜索最左侧边界2.3.搜索最右侧边界3.应用本文参考&#xff1a; LABULADONG 的算法网站 《大话数据结构》 1.概述 &#xff08;1&#xff09;二分搜索 (Binary Search)&#xff0c;又称为折半搜索 (Half-interval Search)。它的前…

云收藏系统|基于Springboot实现云收藏系统

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 收藏点赞不迷路 关注作者有好处 文末获取源…

Java实现队列

目录 一、队列概述 二、队列的模拟实现 1、入队 2、出队 3、取队头元素 4、获取队列长度 三、循环队列 1、入队 2、出队 3、取队头元素 4、取队尾元素 四、面试题 1、用队列实现栈 2、用栈实现队列 一、队列概述 队列也是常见的数据结构&#xff0c;是一…

Mybatis源码解析二:DataSource数据源负责创建连接以及Transaction的事物管理

简介 对于一个成熟的ORM框架来说&#xff0c;数据源的管理以及事务的管理一定是不可或缺的组成&#xff0c;对于Mybatis来说&#xff0c;为了使用方便以及扩展简单也是做了一系列的封装&#xff0c;这一篇主要介绍mybatis是如何管理数据源以及事务的。 数据源DataSource Dat…