【架构】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 {@Overrideprotected 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账户加上10000String 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;@Overridepublic 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作为Controllerprivate AccountService accountService = new AccountService();@Overrideprotected 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);// 调用serviceUserService 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容器来负责这件事。

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

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

相关文章

多个.C文件被编译为一个可执行文件的详细过程

多个.C文件被编译为一个可执行文件的详细过程 文章目录 多个.C文件被编译为一个可执行文件的详细过程前言一、一个.C文件的编译过程二、多个.C文件的链接过程1.文件信息2.链接过程3.makefile 总结 前言 C语言经典的 “hello world ” 程序从编写、编译到运行&#xff0c;看到屏…

大型外企都在用的邮件大附件系统,究竟哪里好?

外企的业务往来复杂&#xff0c;内部沟通频繁&#xff0c;且普遍采用邮件作为沟通方式&#xff0c;外企一般使用的邮件系统多种多样&#xff0c;但其中一些较为常见和广泛使用的包括Zoho Mail和Outlook等。 Outlook作为微软旗下的全球主流电子邮件服务提供商之一&#xff0c;也…

GAME101-Lecture06学习

前言 上节课主要讲的是三角形的光栅化。重要的思想是要利用像素的中心对三角形可见性的函数进行采样。 这节课主要就是反走样。 课程链接&#xff1a;Lecture 06 Rasterization 2 (Antialiasing and Z-Buffering)_哔哩哔哩_bilibili 反走样引入 ​ 通过采样&#xff0c;得到…

Spring Boot集成activiti快速入门Demo

1.什么事activiti&#xff1f; Activiti是一个工作流引擎,可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言BPMN2.0进行定义,业务流程按照预先定义的流程进行执行,实现了系统的流程流activiti进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系…

2024全新小狐狸AI免授权源码

源码安装说明&#xff1a; 下 载 地 址 &#xff1a; runruncode.com/php/19757.html 1. 在宝塔新建一个站点&#xff0c;选择 PHP 版本为 7.2、7.3 或 7.4。将压缩包上传到站点的根目录&#xff0c;并设置运行目录为 /public。 2. 导入数据库文件&#xff0c;该文件位于 …

如何通过汽车制造供应商协同平台,提高供应链的效率与稳定性?

汽车制造供应商协同是指在汽车制造过程中&#xff0c;整车制造商与其零部件供应商之间建立的一种紧密合作的关系。这种协同关系旨在优化整个供应链的效率&#xff0c;降低成本&#xff0c;提高产品质量&#xff0c;加快创新速度&#xff0c;并最终提升整个汽车产业的竞争力。以…

Vue3专栏项目 -- 三、使用vue-router 和 vuex(上)

前面我们开发了两个页面的组件&#xff0c;现在我们需要把它们分成几个页面了&#xff0c;那么一个网页多个页面我们都熟悉&#xff0c;针对不同的url渲染不同的html静态页面&#xff0c;这是web世界的基本工作方式。 有时候我们点击一个东西&#xff0c;地址栏的路由跳转&…

二级等保与三级等保的区别有哪些

二级等保和三级等保的区别主要体现在保护能力、安全要求、监管严格程度等方面。以下是根据提供的搜索结果中关于二级和三级等保的具体差异&#xff1a; 1. 保护能力&#xff1a; 二级等保要求信息系统能够防护来自外部小型组织的威胁&#xff0c;发现重要的安全漏洞和事件&…

机器学习实战宝典:用scikit-learn打造智能应用

书接上文——《数据探险家的终极指南&#xff1a;用Python挖掘机器学习的奥秘》 前文我们在这段精彩的机器学习探险之旅中&#xff0c;从基础概念出发&#xff0c;深入探索了使用Python和scikit-learn库进行数据分析和模型构建的全过程。 我们首先了解了机器学习的基本原理&am…

Nextcloud私有云盘-重新定义云存储体验

Nextcloud私有云盘-重新定义云存储体验 1. 什么是Nextcloud ​ Nextcloud是一个开源的云存储和协作平台&#xff0c;旨在为个人用户、企业和团队提供安全、隐私保护的数据存储和共享解决方案。它允许您在不同设备之间同步、共享文件&#xff0c;提供了强大的协作工具和应用生…

上网卡免费领取,无成本,免费领,超暴力蓝海项目

随着短视频和直播的流行&#xff0c;对大流量电话卡的需求日益增加。我们提供100G-300G大流量电话卡&#xff0c;包含100多分钟语音通话&#xff0c;月费仅19-29元。这些都是三大运营商的靠谱已有号卡&#xff0c;全国免费领取并包邮到家。客户收到卡片后&#xff0c;可在线充值…

工作中使用IDEA查看Stream变化

工作中使用IDEA查看stream变化 代码debug调试 代码 package com.demo;import java.util.stream.LongStream;public class LamdaDemo {public static void main(String[] args) {long[] dataResult LongStream.of(1,5,7,9).filter(data -> data > 10).map(data -> da…

C++学习第二十九课:C++ 输入输出流详解:从基础到高级应用

在 C 中&#xff0c;流&#xff08;stream&#xff09;是一种用于实现输入输出操作的抽象概念。流可以看作是字节的流动&#xff0c;这些字节可以从一个地方流向另一个地方&#xff0c;例如从键盘输入到程序中&#xff0c;或者从程序输出到屏幕。C 提供了一套完整的流库来处理各…

C语言什么是散列法?

一、问题 什么是散列法&#xff1f; 二、解答 散列法是⼀种将字符组成字符串&#xff0c;转换为固定长度&#xff08;⼀般是更短长度&#xff09;的数值或索引值的⽅法&#xff0c;也叫哈希法&#xff0c;⼜可以称为杂凑法或关键码⼀地址转换法。 那么&#xff0c;通过散列函数…

嵌入式开发十一:GPIO端口的八种工作模式

功夫不负有心人&#xff0c;相信学习至此你已经掌握了入门STM32基础知识。希望通过前面的学习&#xff0c;你已经掌握了 STM32 开发的工具和方法。本篇博客我们将和大家一起来学习 STM32 的一个最基础设&#xff0c;这些外设实际项目中经常会用到&#xff0c;希望大家认真学习和…

Html生成自定义函数的图形(2024/5/10)

大概效果如下&#xff1a; 可以自定义函数和x的定义域。 我们可以使用数学表达式解析库来解析用户输入的函数方程&#xff0c;并根据给定的 x 区间计算函数的值&#xff0c;然后使用图表库绘制图形。 在这里&#xff0c;我将使用 math.js 库来解析数学表达式&#xff0c;并使…

杰发科技AC7801——ADC之Bandgap和内部温度计算

0. 参考 电流模架构Bandgap设计与仿真 bandgap的理解&#xff08;内部带隙电压基准&#xff09; ​ ​ 虽然看不懂这些公式&#xff0c;但是比较重要的一句应该是这个&#xff1a;因为传统带隙基准的输出值为1.2V ​ 1. 使用 参考示例代码。 40002000是falsh控制器寄…

2024数维杯数学建模竞赛A题完整代码和思路论文解析

2024数维杯数学建模完整代码和成品论文已更新&#xff0c;获取↓↓↓↓↓ https://www.yuque.com/u42168770/qv6z0d/bgic2nbxs2h41pvt?singleDoc# 2024数维杯数学建模A题34页论文已完成&#xff0c;论文包括摘要、问题重述、问题分析、模型假设、符号说明、模型的建立和求解&…

Python-VBA函数之旅-slice函数

目录 一、slice函数的常见应用场景 二、slice函数使用注意事项 三、如何用好slice函数&#xff1f; 1、slice函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff1a; https://blog.csdn.net/ygb_1024?spm1010.…

【Python特征工程系列】排列重要性法分析特征重要性-随机森林模型为例(案例+源码)

这是我的第277篇原创文章。 一、引言 排列重要性&#xff08;Permutation Importance&#xff09;是一种基于模型的方法&#xff0c;用于评估每个特征对模型性能的影响程度。该方法通过随机打乱单个特征的值并观察模型性能的变化&#xff0c;从而确定特征的重要性。如果某个特征…