【架构】MVC架构模式 三层架构

1 不使用MVC架构模式完成银行账户转账

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <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>

异常类:

package com.powernode.bank.exceptions;

/**
 * App异常
 * @author 老杜
 * @version 1.0
 * @since 1.0
 */
public class AppException extends Exception{
    public AppException(){}
    public AppException(String msg){
        super(msg);
    }
}
package com.powernode.bank.exceptions;

/**
 * 余额不足异常
 * @author 老杜
 * @version 1.0
 * @since 1.0
 */
public class MoneyNotEnoughException extends Exception{
    public MoneyNotEnoughException(){}
    public MoneyNotEnoughException(String msg){
        super(msg);
    }
}

servlet:

package com.powernode.bank.web.servlet;

import com.powernode.bank.exceptions.AppException;
import com.powernode.bank.exceptions.MoneyNotEnoughException;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.*;

/**
 * 在不使用MVC架构模式的前提下,完成银行账户转账。
 * 分析这个程序存在哪些问题?
 *  缺点1> 代码的复用性太差。(代码的重用性太差)
 *  导致缺点1的原因?
 *      因为没有进行“职能分工”,没有独立组件的概念,所以没有办法进行代码复用。代码和代码之间的耦合度太高,扩展力太差。
 *  缺点2> 耦合度高,导致了代码很难扩展。
 *  缺点3> 操作数据库的代码和业务逻辑混杂在一起,很容易出错。编写代码的时候很容易出错,无法专注业务逻辑的编写。
 *
 * 分析以下AccountTransferServlet他都负责了什么?
 * 1> 负责了数据接收
 * 2> 负责了核心的业务处理
 * 3> 负责了数据库表中数据的CRUD操作(Create【增】 Retrieve【查】 Update【改】 Delete【删】)
 * 4> 负责了页面的数据展示
 * ....
 *
 * @author 老杜
 * @version 1.0
 * @since 1.0
 */
@WebServlet("/transfer")
public class AccountTransferServlet 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.cj.jdbc.Driver");
            // 获取连接
            String url = "jdbc:mysql://localhost:3306/mvc";
            String user = "root";
            String password = "root";
            conn = DriverManager.getConnection(url, user, password);
            // 开启事务(不再自动提交了,改为手动提交,业务完成之后再提交。)
            conn.setAutoCommit(false);
            // 获取预编译的数据库操作对象
            String sql1 = "select balance from t_act where actno = ?";
            ps = conn.prepareStatement(sql1);
            ps.setString(1, fromActno);
            // 执行SQL语句,返回结果集
            rs = ps.executeQuery();
            // 处理结果集
            if (rs.next()) {
                double balance = rs.getDouble("balance");
                if(balance < money) {
                    // 余额不足(使用异常处理机制。)
                    throw new MoneyNotEnoughException("对不起,余额不足");
                }
                // 程序能够执行到这里,说明余额一定是充足的
                // 开始转账
                // act001账户减去10000
                // act002账户加上10000
                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 s = null;
                s.toString();*/

                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 AppException("App异常,请联系管理员");
                }

                // 手动提交事务
                conn.commit();
                // 转账成功
                out.print("转账成功!");
            }
        } catch (Exception e) {
            // 保险起见:回滚事务。
            try {
                if (conn != null) {
                    conn.rollback();
                }
            } catch (SQLException ex) {
                throw new RuntimeException(ex);
            }
            // 异常处理,发生异常之后,你准备怎么做
            //e.printStackTrace();
            out.print(e.getMessage());

        } finally {
            // 释放资源
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
            if (ps2 != null) {
                try {
                    ps2.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
            if (ps3 != null) {
                try {
                    ps3.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
        }

    }
    
}

2 MVC架构模式

2.1 MVC架构模式的理论基础

 MVC架构模式的理解

2.2 JDBC工具类的封装

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mvc
user=root
password=root
package com.powernode.bank.utils;

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

/**
 * JDBC工具类
 * @author 老杜
 * @version 1.0
 * @since 1.0
 */
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(){}

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

    /**
     * 这里没有使用数据库连接池,直接创建连接对象。
     * @return 连接对象
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        Connection connection = DriverManager.getConnection(url, user, password);
        return connection;
    }

    /**
     * 关闭资源
     * @param conn 连接对象
     * @param stmt 数据库操作对象
     * @param rs 结果集对象
     */
    public static void close(Connection conn, Statement stmt, ResultSet rs){
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

}

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

DAO:Data Access Object(数据访问对象)

package com.powernode.bank.mvc;

/**
 * 账户实体类:封装账户信息的。
 * 一般是一张表一个。
 * pojo对象。Plain Ordinary Java Object,简单普通的Java对象
 * 有的人也会把这种专门封装数据的对象,称为bean对象。(javabean:咖啡豆)
 * 有的人也会把这种专门封装数据的对象,称为领域模型对象。domain对象。
 * 不同的程序员有不同的习惯。
 *
 * pojo、bean、domain.....
 *
 * @author 老杜
 * @version 1.0
 * @since 1.0
 */
public class Account { // 这种普通简单的对象被成为pojo对象。
    /**
     * 主键
     */
    // 一般这种属性不建议设计为基本数据类型,建议使用包装类。防止null带来的问题。
    //private long id;
    private Long id;

    /**
     * 账号
     */
    private String actno;

    /**
     * 余额
     */
    //private double balance;
    private Double balance;

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", actno='" + actno + '\'' +
                ", 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;
    }

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

    public Account() {
    }
}
package com.powernode.bank.mvc;

import com.powernode.bank.utils.DBUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * AccountDao是负责Account数据的增删改查的。
 * 1. 什么是DAO?
 *      Data Access Object(数据访问对象)
 * 2. DAO实际上是一种设计模式,属于JavaEE的设计模式之一。(不是23种设计模式。)
 * 3. DAO只负责数据库表的CRUD,没有任何业务逻辑在里面。
 * 4. 没有任何业务逻辑,只负责表中数据增删改查的对象,有一个特殊的称谓:DAO对象。
 * 5. 为什么叫做AccountDao呢?
 *      这是因为这个DAO是专门处理t_act这张表的。
 *      如果处理t_user表的话,可以叫做:UserDao
 *      如果处理t_student表的话,可以叫做:StudentDao
 * 6. 一般情况下:一张表会对应一个DAO对象。
 * 7. DAO中的方法名很固定了,一般都是:
 *      insert
 *      deleteByXxx
 *      update
 *      selectByXxx
 *      selectAll
 *
 *
 * @author 老杜
 * @since 1.0
 * @version 1.0
 */
public class AccountDao {

    /**
     * 插入账户信息
     * @param act  账户信息
     * @return 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) values(?,?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1, act.getActno());
            ps.setDouble(2, act.getBalance());
            count = ps.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(conn, ps, null);
        }
        return count;
    }

    /**
     * 根据主键删除账户
     * @param id 主键
     * @return
     */
    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) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(conn, ps, null);
        }
        return count;
    }

    /**
     * 更新账户
     * @param act
     * @return
     */
    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) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(conn, ps, null);
        }
        return count;
    }

    /**
     * 根据账号查询账户
     * @param actno
     * @return
     */
    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) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(conn, ps, rs);
        }
        return act;
    }

    /**
     * 获取所有的账户
     * @return
     */
    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");
                // 封装对象
                /*Account account = new Account();
                account.setId(id);
                account.setActno(actno);
                account.setBalance(balance);*/
                Account account = new Account(id, actno, balance);
                // 加到List集合
                list.add(account);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(conn, ps, rs);
        }
        return list;
    }

}

2.4 pojo bean domain的概念

POJO:Plain Ordinary Java Object(简单普通的Java对象)
有的人也会把这种专门封装数据的对象,称为bean对象。(javabean:咖啡豆)
有的人也会把这种专门封装数据的对象,称为领域模型对象。domain对象。
不同的程序员有不同的习惯。

DAO:Data Access Object(数据访问对象)

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

异常类:

package com.powernode.bank.exceptions;

/**
 * 余额不足异常
 * @author 老杜
 * @version 2.0
 * @since 2.0
 */
public class MoneyNotEnoughException extends Exception{

    public MoneyNotEnoughException(){

    }

    public MoneyNotEnoughException(String msg){
        super(msg);
    }
}
package com.powernode.bank.exceptions;

/**
 * App异常
 * @author 老杜
 * @version 2.0
 * @since 2.0
 */
public class AppException extends Exception{

    public AppException(){

    }

    public AppException(String msg){
        super(msg);
    }
}

service类: 

package com.powernode.bank.mvc;

import com.powernode.bank.exceptions.AppException;
import com.powernode.bank.exceptions.MoneyNotEnoughException;

/**
 * service翻译为:业务。
 * AccountService:专门处理Account业务的一个类。
 * 在该类中应该编写纯业务代码。(只专注业务。不写别的。不和其他代码混合在一块。)
 * 只希望专注业务,能够将业务完美实现,少量bug。
 *
 * 业务类一般起名:XxxService、XxxBiz.....
 *
 * @author 老杜
 * @version 1.0
 * @since 1.0
 */
public class AccountService {

    // 为什么定义到这里?因为在每一个业务方法中都可以需要连接数据库。
    private AccountDao accountDao = new AccountDao();

    // 这里的方法起名,一定要体现出,你要处理的是什么业务。
    // 我们要提供一个能够实现转账的业务方法(一个业务对应一个方法。)

    /**
     * 完成转账的业务逻辑
     * @param fromActno 转出账号
     * @param toActno 转入账号
     * @param money 转账金额
     */
    public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException {
        // 查询余额是否充足
        Account fromAct = accountDao.selectByActno(fromActno);
        if (fromAct.getBalance() < money) {
            throw new MoneyNotEnoughException("对不起,余额不足");
        }
        // 程序到这里说明余额充足
        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("账户转账异常!!!");
        }
    }

}

2.6 Controller调度其他组件完成任务

package com.powernode.bank.mvc;

import com.powernode.bank.exceptions.AppException;
import com.powernode.bank.exceptions.MoneyNotEnoughException;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

/**
 * 账户小程序
 * AccountServlet是一个司令官。他负责调度其他组件来完成任务。
 * @author 老杜
 * @version 2.0
 * @since 2.0
 */
@WebServlet("/transfer")
public class AccountServlet extends HttpServlet { // AccountServlet作为Controller

    private AccountService accountService = new AccountService();

    @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"));
        try {
            // 调用业务方法处理业务(调度Model处理业务)
            accountService.transfer(fromActno, toActno, money);
            // 执行到这里了,说明成功了。
            // 展示处理结果(调度View做页面展示)
            response.sendRedirect(request.getContextPath() + "/success.jsp");
        } catch(MoneyNotEnoughException e) {
            // 执行到这里了,说明失败了。(余额不足)
            // 展示处理结果(调度View做页面展示)
            response.sendRedirect(request.getContextPath() + "/moneynotenough.jsp");
        } catch(Exception e){
            // 执行到这里了,说明失败了。
            response.sendRedirect(request.getContextPath() + "/error.jsp");
        }
    }
}

success.jsp 

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>转账成功</title>
</head>
<body>

<h1>转账成功</h1>

</body>
</html>

 moneynotenough.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>转账失败</title>
</head>
<body>
<h1>余额不足</h1>
</body>
</html>

error.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>转账失败</title>
</head>
<body>

<h1>转账失败</h1>

</body>
</html>

2.7 MVC架构模式与三层架构的关系

三层架构

三层架构2

Spring:

项目大管家,负责整个项目所有对象的创建以及维护对象和对象之间的关系

SpringMVC:

将MVC架构模式体现的非常完美。在这个框架的基础上开发,一定是用了MVC架构模式的。SpringMVC框架已经把MVC架构给你搭建出来了。

MyBatis:

持久层框架

2.8 解决事务问题

package com.powernode.bank.mvc;

import com.powernode.bank.exceptions.AppException;
import com.powernode.bank.exceptions.MoneyNotEnoughException;
import com.powernode.bank.utils.DBUtil;

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

/**
 * service翻译为:业务。
 * AccountService:专门处理Account业务的一个类。
 * 在该类中应该编写纯业务代码。(只专注业务。不写别的。不和其他代码混合在一块。)
 * 只希望专注业务,能够将业务完美实现,少量bug。
 *
 * 业务类一般起名:XxxService、XxxBiz.....
 *
 * @author 老杜
 * @version 1.0
 * @since 1.0
 */
public class AccountService {

    // 为什么定义到这里?因为在每一个业务方法中都可以需要连接数据库。
    private AccountDao accountDao = new AccountDao();

    // 这里的方法起名,一定要体现出,你要处理的是什么业务。
    // 我们要提供一个能够实现转账的业务方法(一个业务对应一个方法。)

    /**
     * 完成转账的业务逻辑
     * @param fromActno 转出账号
     * @param toActno 转入账号
     * @param money 转账金额
     */
    public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException {
        // service层控制事务
        try (Connection connection = DBUtil.getConnection()){
            System.out.println(connection);
            // 开启事务(需要使用Connection对象)
            connection.setAutoCommit(false);

            // 查询余额是否充足
            Account fromAct = accountDao.selectByActno(fromActno, connection);
            if (fromAct.getBalance() < money) {
                throw new MoneyNotEnoughException("对不起,余额不足");
            }
            // 程序到这里说明余额充足
            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("账户转账异常!!!");
        }
    }

}
package com.powernode.bank.mvc;

import com.powernode.bank.utils.DBUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * AccountDao是负责Account数据的增删改查的。
 * 1. 什么是DAO?
 *      Data Access Object(数据访问对象)
 * 2. DAO实际上是一种设计模式,属于JavaEE的设计模式之一。(不是23种设计模式。)
 * 3. DAO只负责数据库表的CRUD,没有任何业务逻辑在里面。
 * 4. 没有任何业务逻辑,只负责表中数据增删改查的对象,有一个特殊的称谓:DAO对象。
 * 5. 为什么叫做AccountDao呢?
 *      这是因为这个DAO是专门处理t_act这张表的。
 *      如果处理t_user表的话,可以叫做:UserDao
 *      如果处理t_student表的话,可以叫做:StudentDao
 * 6. 一般情况下:一张表会对应一个DAO对象。
 * 7. DAO中的方法名很固定了,一般都是:
 *      insert
 *      deleteByXxx
 *      update
 *      selectByXxx
 *      selectAll
 *
 *
 * @author 老杜
 * @since 1.0
 * @version 1.0
 */
public class AccountDao {

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

    /**
     * 根据主键删除账户
     * @param id 主键
     * @return
     */
    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) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(null, ps, null);
        }
        return count;
    }

    /**
     * 更新账户
     * @param act
     * @return
     */
    public int update(Account act, Connection conn) {
        PreparedStatement ps = null;
        int count = 0;
        try {
            System.out.println(conn);
            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) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(null, ps, null);
        }
        return count;
    }

    /**
     * 根据账号查询账户
     * @param actno
     * @return
     */
    public Account selectByActno(String actno, Connection conn){
        PreparedStatement ps = null;
        ResultSet rs = null;
        Account act = null;
        try {
            System.out.println(conn);
            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) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(null, ps, rs);
        }
        return act;
    }

    /**
     * 获取所有的账户
     * @return
     */
    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");
                // 封装对象
                /*Account account = new Account();
                account.setId(id);
                account.setActno(actno);
                account.setBalance(balance);*/
                Account account = new Account(id, actno, balance);
                // 加到List集合
                list.add(account);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(null, ps, rs);
        }
        return list;
    }

}

3 ThreadLocal

ThreadLocal

3.1 手撕ThreadLocal源码

package com.powernode.threadlocal;

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

/**
 * 自定义一个ThreadLocal类
 */
public class MyThreadLocal<T> {

    /**
     * 所有需要和当前线程绑定的数据要放到这个容器当中
     */
    private Map<Thread, T> map = new HashMap<>();

    /**
     * 向ThreadLocal中绑定数据
     */
    public void set(T obj){
        map.put(Thread.currentThread(), obj);
    }

    /**
     * 从ThreadLocal中获取数据
     * @return
     */
    public T get(){
        return map.get(Thread.currentThread());
    }

    /**
     * 移除ThreadLocal当中的数据
     */
    public void remove(){
        map.remove(Thread.currentThread());
    }
}
package com.powernode.threadlocal;

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

public class DBUtil {

    // 静态变量特点:类加载时执行,并且只执行一次。
    // 全局的大Map集合
    private static MyThreadLocal<Connection> local = new MyThreadLocal<>();

    /**
     * 每一次都调用这个方法来获取Connection对象
     * @return
     */
    public static Connection getConnection(){
        Connection connection = local.get();
        if (connection == null) {
            // 第一次调用:getConnection()方法的时候,connection一定是空的。
            // 空的就new一次。
            connection = new Connection();
            // 将new的Connection对象绑定到大Map集合中。
            local.set(connection);
        }
        return connection;
    }

}

业务调用

package com.powernode.threadlocal;
// 张三发送请求,对应一个线程t1
// 李四发送请求,对应一个线程t2
public class Test {
    public static void main(String[] args) {

        Thread thread = Thread.currentThread();
        System.out.println(thread);

        // 调用service
        UserService userService = new UserService();
        userService.save();
    }
}
package com.powernode.threadlocal;

public class UserService {

    private UserDao userDao = new UserDao();

    public void save(){
        Thread thread = Thread.currentThread();
        System.out.println(thread);

        Connection connection = DBUtil.getConnection();
        System.out.println(connection);

        userDao.insert();
    }

}
package com.powernode.threadlocal;

public class UserDao {

    public void insert(){
        Thread thread = Thread.currentThread();
        System.out.println(thread);

        Connection connection = DBUtil.getConnection();
        System.out.println(connection);

        System.out.println("User DAO insert");
    }
}

3.2 项目中引入ThreadLocal

package com.powernode.bank.utils;

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

/**
 * JDBC工具类
 * @author 老杜
 * @version 1.0
 * @since 1.0
 */
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(){}

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

    // 这个对象实际上在服务器中只有一个。
    private static ThreadLocal<Connection> local = new ThreadLocal<>();

    /**
     * 这里没有使用数据库连接池,直接创建连接对象。
     * @return 连接对象
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        Connection conn = local.get();
        if (conn == null) {
            conn = DriverManager.getConnection(url, user, password);
            local.set(conn);
        }
        return conn;
    }

    /**
     * 关闭资源
     * @param conn 连接对象
     * @param stmt 数据库操作对象
     * @param rs 结果集对象
     */
    public static void close(Connection conn, Statement stmt, ResultSet rs){
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (conn != null) {
            try {
                conn.close();
                // 思考一下:为什么conn关闭之后,这里要从大Map中移除呢?
                // 根本原因是:Tomcat服务器是支持线程池的。也就是说一个人用过了t1线程,t1线程还有可能被其他用户使用。
                local.remove();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

}
package com.powernode.bank.mvc;

import com.powernode.bank.exceptions.AppException;
import com.powernode.bank.exceptions.MoneyNotEnoughException;
import com.powernode.bank.utils.DBUtil;

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

/**
 * service翻译为:业务。
 * AccountService:专门处理Account业务的一个类。
 * 在该类中应该编写纯业务代码。(只专注业务。不写别的。不和其他代码混合在一块。)
 * 只希望专注业务,能够将业务完美实现,少量bug。
 *
 * 业务类一般起名:XxxService、XxxBiz.....
 *
 * @author 老杜
 * @version 1.0
 * @since 1.0
 */
public class AccountService {

    // 为什么定义到这里?因为在每一个业务方法中都可以需要连接数据库。
    private AccountDao accountDao = new AccountDao();

    // 这里的方法起名,一定要体现出,你要处理的是什么业务。
    // 我们要提供一个能够实现转账的业务方法(一个业务对应一个方法。)

    /**
     * 完成转账的业务逻辑
     * @param fromActno 转出账号
     * @param toActno 转入账号
     * @param money 转账金额
     */
    public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException {
        // service层控制事务
        try (Connection connection = DBUtil.getConnection()){
            System.out.println(connection);
            // 开启事务(需要使用Connection对象)
            connection.setAutoCommit(false);

            // 查询余额是否充足
            Account fromAct = accountDao.selectByActno(fromActno);
            if (fromAct.getBalance() < money) {
                throw new MoneyNotEnoughException("对不起,余额不足");
            }
            // 程序到这里说明余额充足
            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("账户转账异常!!!");
        }
    }

}
package com.powernode.bank.mvc;

import com.powernode.bank.utils.DBUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * AccountDao是负责Account数据的增删改查的。
 * 1. 什么是DAO?
 *      Data Access Object(数据访问对象)
 * 2. DAO实际上是一种设计模式,属于JavaEE的设计模式之一。(不是23种设计模式。)
 * 3. DAO只负责数据库表的CRUD,没有任何业务逻辑在里面。
 * 4. 没有任何业务逻辑,只负责表中数据增删改查的对象,有一个特殊的称谓:DAO对象。
 * 5. 为什么叫做AccountDao呢?
 *      这是因为这个DAO是专门处理t_act这张表的。
 *      如果处理t_user表的话,可以叫做:UserDao
 *      如果处理t_student表的话,可以叫做:StudentDao
 * 6. 一般情况下:一张表会对应一个DAO对象。
 * 7. DAO中的方法名很固定了,一般都是:
 *      insert
 *      deleteByXxx
 *      update
 *      selectByXxx
 *      selectAll
 *
 *
 * @author 老杜
 * @since 1.0
 * @version 1.0
 */
public class AccountDao {

    /**
     * 插入账户信息
     * @param act  账户信息
     * @return 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) values(?,?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1, act.getActno());
            ps.setDouble(2, act.getBalance());
            count = ps.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(null, ps, null);
        }
        return count;
    }

    /**
     * 根据主键删除账户
     * @param id 主键
     * @return
     */
    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) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(null, ps, null);
        }
        return count;
    }

    /**
     * 更新账户
     * @param act
     * @return
     */
    public int update(Account act) {
        PreparedStatement ps = null;
        int count = 0;
        try {
            Connection conn = DBUtil.getConnection();
            System.out.println(conn);
            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) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(null, ps, null);
        }
        return count;
    }

    /**
     * 根据账号查询账户
     * @param actno
     * @return
     */
    public Account selectByActno(String actno){
        PreparedStatement ps = null;
        ResultSet rs = null;
        Account act = null;
        try {
            Connection conn = DBUtil.getConnection();
            System.out.println(conn);
            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) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(null, ps, rs);
        }
        return act;
    }

    /**
     * 获取所有的账户
     * @return
     */
    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");
                // 封装对象
                /*Account account = new Account();
                account.setId(id);
                account.setActno(actno);
                account.setBalance(balance);*/
                Account account = new Account(id, actno, balance);
                // 加到List集合
                list.add(account);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DBUtil.close(null, ps, rs);
        }
        return list;
    }

}

4 项目分层

4.1 不同功能的类放在不同的包下

4.2 层与层之间应该使用接口进行衔接

目前项目仍然存在缺陷:
1> 在service层控制了事务,service方法中的事务控制代码看着有点别扭,以后能不能不写????
可以使用动态代理机制解决这个问题。

2> 目前虽然面向接口编程了,但是并没有完全解决对象和对象之间的依赖关系。怎么办?可以使用spring的IoC容器来解决这个问题。
对象的创建我不用管了,对象和对象之间关系的管理我也不想管了,都交给spring容器来负责这件事。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis加入系统服务,开机自启

vi /etc/systemd/system/redis.service i :wq #加载服务配置文件 systemctl daemon-reload #启动redis systemctl start redis #设置开机自启 systemctl enable redis #查看启动状态 systemctl status redis

每日一练 2024.5.10

题目&#xff1a; 给定一个非负整数数组 nums&#xff0c; nums 中一半整数是 奇数 &#xff0c;一半整数是 偶数 。 对数组进行排序&#xff0c;以便当 nums[i] 为奇数时&#xff0c;i 也是 奇数 &#xff1b;当 nums[i] 为偶数时&#xff0c; i 也是 偶数 。 示例 1&#…

Day 29 MySQL的主从复制集群

一&#xff1a;主从复制 1.主从复制概念 什么是主从复制&#xff1a; ​ 主从复制&#xff0c;是用来建立一个和主数据库完全一样的数据库环境&#xff0c;称为从数据库&#xff1b;主数据库一般是准实时的业务数据库 主从复制的作用&#xff1a; ​ 做数据的热备&#xf…

【Python 下载大量品牌网站的图片(一)】关于图片的处理和下载,吃满带宽,可多开窗口下载多个网站,DOS窗口类型

写作日期&#xff1a;2024.05.11 使用工具&#xff1a;Python 可修改功能&#xff1a;线程量、UA、Cookie、代理、存储目录、间隔时间、超时时间、图片压缩、图片缩放 默认功能&#xff1a;图片转换、断续下载、图片检测、路径处理、存储文件 GUI&#xff1a;DOS窗口 类型&…

K8s源码分析(二)-K8s调度队列介绍

本文首发在个人博客上&#xff0c;欢迎来踩&#xff01; 本次分析参考的K8s版本是 文章目录 调度队列简介调度队列源代码分析队列初始化QueuedPodInfo元素介绍ActiveQ源代码介绍UnschedulableQ源代码介绍**BackoffQ**源代码介绍队列弹出待调度的Pod队列增加新的待调度的Podpod调…

C++:week3:数据结构与算法

文章目录 (十一) 常用数据结构1.动态数组(1)模型(2).h与.c(3)实现 2.链表(1)模型(2)分类(3)基本操作(API)(4)实现(5)链表常见面试题(6)空间与时间 3.栈(1)模型(2)基本操作(3)实现(4)栈的应用 4.队列(1)模型(2)基本操作(API)(3)实现(4)队列的应用 5.哈希表(1)哈希表的提出原因(2…

支付宝小程序如何去除页面下拉回弹

描述&#xff1a;支付宝小程序页面下拉时会产生回弹&#xff0c;如果页面上有拖拽功能&#xff0c;会有影响 解决方法&#xff1a; 页面xx.config.js中设置&#xff1a;allowsBounceVertical: “NO” 官方文档&#xff1a;https://opensupport.alipay.com/support/FAQ/7110b5d…

什么是Jetpack

Jetpack Jetpack 是一套组件库、工具&#xff0c;可帮助开发人员遵循最佳做法&#xff0c;减少样板代码并编写可在 Android 版本和设备上一致工作的代码&#xff0c;以便开发人员可以专注于他们关心的代码 组成 主要包含四部分&#xff1a;架构&#xff08;Architecture&…

手写一个SPI FLASH 读写擦除控制器(未完)

文章目录 flash读写数据的特点1. 扇擦除SE&#xff08;Sector Erase&#xff09;1.1 flash_se 模块设计1.1.1 信号连接示意图&#xff1a;1.1.2 SE状态机1.1.3 波形图设计&#xff1a;1.1.4 代码 2. 页写PP(Page Program)2.1 flash_pp模块设计2.1.1 信号连接示意图&#xff1a;…

社交媒体数据恢复:快手、快手极速版

一、备份快手数据 登录快手账号&#xff1a;首先&#xff0c;打开快手APP&#xff0c;登录您的快手账号。 进入设置&#xff1a;在快手首页点击右上角的三条横线图标&#xff0c;进入设置页面。 数据备份&#xff1a;在设置页面找到“备份与恢复”选项&#xff0c;点击进入。…

【机器学习】 人工智能和机器学习辅助决策在空战中的未来选择

&#x1f680;传送门 &#x1f680;文章引言&#x1f512;技术层面&#x1f4d5;作战结构&#x1f308;替代决策选项&#x1f3ac;选项 1&#xff1a;超级战争&#xff08;Hyperwar&#xff09;&#x1f320;选项 2&#xff1a;超越OODA&#x1f302;选项 3&#xff1a;阻止其他…

【漏洞复现】用友U8-Cloud XChangeServlet XXE漏洞

0x01 产品简介 用友U8Cloud是用友推出的新一代云ERP,主要聚焦成长型、创新型企业,提供企业级云ERP整体解决方案。 0x02 漏洞概述 用友U8 cloud /service/XChangeServlet接口存在XXE漏洞,未授权的攻击者可通过此漏洞获取数据库敏感信息,从而盗取服务器数据,造成服务器信…

API接口开发实现一键智能化自动抓取电商平台热销商品数据支持高并发免费接入示例

为了实现一键智能化自动抓取电商平台热销商品数据支持高并发免费接入&#xff0c;你可以使用Python编程语言和相关库&#xff08;如requests、BeautifulSoup等&#xff09;来开发一个API接口&#xff0c;也可以使用封装好的api接口获取&#xff0c;注册一个api账号获取key和sec…

代码审计平台sonarqube的安装及使用

docker搭建代码审计平台sonarqube 一、代码审计关注的质量指标二、静态分析技术分类三、使用sonarqube的目的四、sonarqube流程五、docker快速搭建sonarqube六、sonarqube scanner的安装和使用七、sonarqube对maven项目进行分析八、sonarqube分析报告解析九、代码扫描规则定制十…

使用 AI Assistant for Observability 和组织的运行手册增强 SRE 故障排除

作者&#xff1a;Almudena Sanz Oliv, Katrin Freihofner, Tom Grabowski 通过本指南&#xff0c;你的 SRE 团队可以实现增强的警报修复和事件管理。 可观测性 AI 助手可帮助用户使用自然语言界面探索和分析可观测性数据&#xff0c;利用自动函数调用来请求、分析和可视化数据…

【二叉树算法题记录】二叉树的所有路径,路径总和——回溯

目录 257. 二叉树的所有路径题目描述题目分析cpp代码 112. 路径总和题目描述题目分析cpp代码 257. 二叉树的所有路径 题目描述 给你一个二叉树的根节点root &#xff0c;按任意顺序&#xff0c;返回所有从根节点到叶子节点的路径。 题目分析 其实从根节点往下走&#xff0c…

VM虚拟机安装调试(步骤如下图)

VM虚拟机安装调试 随着一顿安装操作&#xff0c;还有enter键敲下&#xff0c;出现如下界面。

从木匠到编程匠的传承

我的父亲在1906年出生于宁波北仑西岙村。年轻时他在老家从木匠学徒做起&#xff0c;学到了一手好技艺。 宁波乡下的老式雕花木床分为三弯、五弯、七弯等三种档次&#xff0c;其中七弯的做工最复杂。父亲说&#xff0c;他是会做七弯木床的。 上世纪三十年代&#xff0c;父亲举…

Spring Cloud Gateway 11种断言工厂

系列文章目录 文章目录 系列文章目录前言前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 Spring Cloud Gateway路由匹配是Spring WebFlux基础功能的一部分,在Spri…

商场综合体能源监管平台,实现能源高效管理

商场作为大型综合体建筑&#xff0c;其能源消耗一直是备受关注的问题。为了有效管理商场能耗&#xff0c;提高商场能源效率&#xff0c;商场综合体能源监管平台应运而生。 商场综合体能源监管平台可通过软硬件一起进行节能监管&#xff0c;硬件设备包括各种传感器、监测仪表和…