ToB企服应用市场:ToB评测及商务社交产业平台

标题: MVC 三层架构案例详细讲解 [打印本页]

作者: 钜形不锈钢水箱    时间: 2023-5-17 16:14
标题: MVC 三层架构案例详细讲解
MVC 三层架构案例详细讲解


@
目录

每博一文案
  1. 多读书,书中有,你对生活,困难所解不开的答案
  2. 比如:《杀死一只是更鸟》中提到的
  3. 对应我们:我们努力中考,高考,升本,考研,每天都在努力学习,但是某天突然想到万一没有考上的话,那现在的努力又有什么意义呢?
  4. 答案:在《杀死一只是更鸟》里有这样一段话:
  5. > 勇敢是,当你还未开始,你就知道自己会输,可你依然要去做,而且无论如何都要把它坚持到底,你很少能赢,但有时也会。努力的这个过程本身就是有意义,能够获得理想的结果当然很好,但如果失败了也没关系。因为你的勇敢,从未辜负你的青春,而黎明的光亮,总有一刻,会照亮穿梭于黑暗之中的自己。况且,你还不一定会输呢。
复制代码
1. MVC 概述

MVC开始是存在于桌面程序中的,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。比如一批统计数据可以分别用柱状图饼图来表示。C存在的目的则是确保M和V的同步,一旦M改变,V应该同步更新。 [1-2]
模型-视图-控制器(MVC)是[Xerox PARC](https://baike.baidu.com/item/Xerox PARC/10693263?fromModule=lemma_inlink)在二十世纪八十年代为编程语言Smalltalk-80发明的一种软件设计模式,已被广泛使用。后来被推荐为Oracle旗下Sun公司[Java EE](https://baike.baidu.com/item/Java EE/2180381?fromModule=lemma_inlink)平台的设计模式,并且受到越来越多的使用ColdFusionPHP的开发者的欢迎。模型-视图-控制器模式是一个有用的工具箱,它有很多好处,但也有一些缺点。
2. MVC设计思想

MVC(Model View Controller)是软件工程中的一种软件架构模式,它把软件系统分为模型视图控制器三个基本部分。用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

MVC 主要的核心就是:分层:希望专人干专事,各司其职,职能分工要明确,这样可以让代码耦合度降低,扩展力增强,组件的可复用性增强
MVC 从字面意思我们就可以看到:是分为了三层的,M(Mode 模型),V(View 视图),C(Controller 控制器)

M即model模型:是指模型表示业务规则。在MVC的三个部件中,模型拥有最多的处理任务。被模型返回的数据是中立的,模型与数据格式无关,这样一个模型能为多个视图提供数据,由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性
V即View视图:是指用户看到并与之交互的界面。比如由html元素组成的网页界面,或者软件的客户端界面。MVC的好处之一在于它能为应用程序处理很多不同的视图。在视图中其实没有真正的处理发生,它只是作为一种输出数据并允许用户操作的方式。
C即controller控制器:是指控制器接受用户的输入并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。
M(Model :数据/业务) V (View :视图/展示) C (Controller : 控制层)
C(是核心,是控制器,是司令官)
M(处理业务/处理数据的一个秘书)
V(负责页面展示的一个秘书)
MVC(一个司令官,调度两个秘书,去做这件事),仅仅只做事务上的调度,而不做其他的操作


优点:
缺点:
3. 三层架构

三层架构(3-tier architecture) 通常意义上的三层架构就是将整个业务应用划分为:界面层[表示层](User Interface layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data access layer)。
区分层次的目的即为了“高内聚低耦合” 的思想。在软件体系架构设计中,分层式结构是最常见,也是最重要的一种结构。



三层架构每层之间的逻辑关系:

三层架构的优点
三层架构的缺点:
4. MVC 与 三层架构的关系:

MVC的也可以被说成是 MVC三层架构,说白了,它们其实都是一个东西,只是在一些细节上有稍微的不同,大致设计思想都是一样的:“高内聚,低耦合”。

其实,无论是MVC还是三层架构,都是一种规范,都是奔着"高内聚,低耦合"的思想来设计的。三层中的UI和Servlet来分别对应MVC中的View和Controller,业务逻辑层是来组合数据访问层的原子性功能的。
5. 案例举例:用户账户转账

如下我们,实现一个用户账户转账操作的一个案例:
准备工作:创建表,创建数据
  1. CREATE DATABASE mvc;
  2. USE mvc;
  3. SHOW TABLES;
  4. CREATE TABLE t_act (
  5.    id BIGINT PRIMARY KEY AUTO_INCREMENT,
  6.    actno VARCHAR(255) NOT NULL,
  7.    balance DECIMAL(10,2)
  8. );
  9. INSERT INTO t_act(actno,balance)
  10. VALUES('act001',50000.00),('act002',0.00);
  11. SELECT *
  12. FROM t_act;
复制代码
5.1 M(Model :数据/业务处理层)

javaBean :Account 封装数据
账户实体类,封装账户信息的
  1. package com.RainbowSea.bank.mvc;
  2. import java.io.Serializable;
  3. import java.util.Objects;
  4. /**
  5. * 账户实体类,封装账户信息的
  6. * 一般是一张表一个。
  7. * pojo 对象
  8. * 有的人也会把这种专门封装数据的对象,称为:"bean对象" (javabean对象,咖啡豆)
  9. * 有的人也会把这种专门封装数据的对象,称为领域模型对象,domain对象
  10. * 不同的程序员不同的习惯。
  11. */
  12. public class Account implements Serializable {  // 这种普通的简单的对象被成为pojo对象
  13.     // 注意我们这里定义的数据类型,使用引用数据类型
  14.     // 因为我们数据库中可能存在 null 值,而基本数据类型是不可以存储 null值的
  15.     private Long id = null;  // id
  16.     private String actno;  // 账号
  17.     private Double balance; // 余额
  18.     // 反序列化
  19.     private static final long serialVersionUID = 1L;
  20.     public Account() {
  21.     }
  22.     public Account(Long id, String actno, Double balance) {
  23.         this.id = id;
  24.         this.actno = actno;
  25.         this.balance = balance;
  26.     }
  27.     public Long getId() {
  28.         return id;
  29.     }
  30.     public void setId(Long id) {
  31.         this.id = id;
  32.     }
  33.     public String getActno() {
  34.         return actno;
  35.     }
  36.     public void setActno(String actno) {
  37.         this.actno = actno;
  38.     }
  39.     public Double getBalance() {
  40.         return balance;
  41.     }
  42.     public void setBalance(Double balance) {
  43.         this.balance = balance;
  44.     }
  45.     @Override
  46.     public boolean equals(Object o) {
  47.         if (this == o) return true;
  48.         if (!(o instanceof Account)) return false;
  49.         Account account = (Account) o;
  50.         return Objects.equals(getId(), account.getId()) && Objects.equals(getActno(), account.getActno()) && Objects.equals(getBalance(), account.getBalance());
  51.     }
  52.     @Override
  53.     public int hashCode() {
  54.         return Objects.hash(getId(), getActno(), getBalance());
  55.     }
  56.     @Override
  57.     public String toString() {
  58.         return "Account{" +
  59.                 "id=" + id +
  60.                 ", actno='" + actno + '\'' +
  61.                 ", balance=" + balance +
  62.                 '}';
  63.     }
  64. }
复制代码
DB连接数据库的工具:
  1. driver=com.mysql.jdbc.Driver
  2. url=jdbc:mysql://localhost:3306/mvc
  3. user=root
  4. password=MySQL
复制代码
  1. package com.RainbowSea.bank.utils;
  2. import java.sql.Connection;
  3. import java.sql.DriverManager;
  4. import java.sql.PreparedStatement;
  5. import java.sql.ResultSet;
  6. import java.sql.SQLException;
  7. import java.util.ResourceBundle;
  8. public class DBUtil {
  9.     // resourceBundle 只能读取到 properties 后缀的文件,注意不要加文件后缀名
  10.     private static ResourceBundle resourceBundle = ResourceBundle.getBundle("resources/jdbc");
  11.     private static String driver = resourceBundle.getString("driver");
  12.     private static String url = resourceBundle.getString("url");
  13.     private static String user = resourceBundle.getString("user");
  14.     private static String password = resourceBundle.getString("password");
  15.     // DBUtil 类加载注册驱动
  16.     static {
  17.         try {
  18.             Class.forName(driver);
  19.         } catch (ClassNotFoundException e) {
  20.            e.printStackTrace();
  21.         }
  22.     }
  23.     // 将构造器私有化,不让创建对象,因为工具类中的方法都是静态的,不需要创建对象
  24.     // 为了防止创建对象,故将构造方法私有化
  25.     private DBUtil() {
  26.     }
  27.     /**
  28.      * 这里没有使用数据库连接池,直接创建连接对象
  29.      */
  30.     public static Connection getConnection()  {
  31.         Connection connection = null;
  32.         try {
  33.             connection = DriverManager.getConnection(url, user, password);
  34.         } catch (SQLException e) {
  35.             throw new RuntimeException(e);
  36.         }
  37.         return  connection;
  38.     }
  39.     /**
  40.      * 资源的关闭
  41.      * 最后使用的最先关闭,逐个关闭,防止存在没有关闭的
  42.      */
  43.     public static void close(Connection connection , PreparedStatement preparedStatement, ResultSet resultSet) {
  44.         if (resultSet != null) {
  45.             try {
  46.                 resultSet.close();
  47.             } catch (SQLException e) {
  48.                 throw new RuntimeException(e);
  49.             }
  50.         }
  51.         if (preparedStatement!=null) {
  52.             try {
  53.                 preparedStatement.close();
  54.             } catch (SQLException e) {
  55.                 throw new RuntimeException(e);
  56.             }
  57.         }
  58.         if (connection != null) {
  59.             try {
  60.                 connection.close();
  61.             } catch (SQLException e) {
  62.                 throw new RuntimeException(e);
  63.             }
  64.         }
  65.     }
  66. }
复制代码
对应Account数据表的DAO操作工具类
AccountDao 是负责Account 数据的增上改查
什么是DAO ?
为什么叫做 AccountDao 呢?
主要定义如下:增删改查方法()
  1. int insert() ;
  2. int deleteByActno();
  3. int update() ;
  4. Account selectByActno();
  5. List<Account> selectAll();
复制代码
  1. package com.RainbowSea.bank.mvc;
  2. import com.RainbowSea.bank.utils.DBUtil;
  3. import java.sql.Connection;
  4. import java.sql.PreparedStatement;
  5. import java.sql.ResultSet;
  6. import java.sql.SQLException;
  7. import java.util.Collection;
  8. import java.util.List;
  9. /**
  10. * AccountDao 是负责Account 数据的增上改查
  11. * <p>
  12. * 1. 什么是DAO ?
  13. * Data Access Object (数据访问对象)
  14. * 2. DAO实际上是一种设计模式,属于 JavaEE的设计模式之一,不是 23种设计模式
  15. * 3.DAO只负责数据库表的CRUD ,没有任何业务逻辑在里面
  16. * 4.没有任何业务逻辑,只负责表中数据增上改查的对象,有一个特俗的称谓:DAO对象
  17. * 5. 为什么叫做 AccountDao 呢?
  18. * 这是因为DAO是专门处理t_act 这张表的
  19. * 如果处理t_act 表的话,可以叫做:UserDao
  20. * 如果处理t-student表的话,可以叫做 StudentDao
  21. * <p>
  22. * int insert() ;
  23. * int deleteByActno();
  24. * int update() ;
  25. * Account selectByActno();
  26. * List<Account> selectAll();
  27. */
  28. public class AccountDao {
  29.     /**
  30.      * 插入数据
  31.      *
  32.      * @param account
  33.      * @return
  34.      */
  35.     public int insert(Account account) {
  36.         Connection connection = DBUtil.getConnection();
  37.         PreparedStatement preparedStatement = null;
  38.         int count = 0;
  39.         try {
  40.             String sql = "insert into t_act(actno,balance) values(?,?)";
  41.             preparedStatement = connection.prepareStatement(sql);
  42.             preparedStatement.setString(1, account.getActno());
  43.             preparedStatement.setDouble(2, account.getBalance());
  44.             count = preparedStatement.executeUpdate();
  45.         } catch (SQLException e) {
  46.             throw new RuntimeException(e);
  47.         } finally {
  48.             DBUtil.close(connection, preparedStatement, null);
  49.         }
  50.         return count;
  51.     }
  52.     /**
  53.      * 通过Id删除数据
  54.      *
  55.      * @param id
  56.      * @return
  57.      */
  58.     public int deleteById(String id) {
  59.         Connection connection = DBUtil.getConnection();
  60.         int count = 0;
  61.         PreparedStatement preparedStatement = null;
  62.         try {
  63.             String sql = "delete from t_act where id = ?";
  64.             preparedStatement = connection.prepareStatement(sql);
  65.             preparedStatement.setString(1, id);
  66.             count = preparedStatement.executeUpdate();
  67.         } catch (SQLException e) {
  68.             throw new RuntimeException(e);
  69.         } finally {
  70.             DBUtil.close(connection, preparedStatement, null);
  71.         }
  72.         return count;
  73.     }
  74.     /**
  75.      * 更新数据
  76.      *
  77.      * @param account
  78.      * @return
  79.      */
  80.     public int update(Account account) {
  81.         Connection connection = DBUtil.getConnection();
  82.         PreparedStatement preparedStatement = null;
  83.         int count = 0;
  84.         try {
  85.             String sql = "update t_act set balance = ?, actno = ? where id = ?";
  86.             preparedStatement = connection.prepareStatement(sql);
  87.             //注意设置的 set类型要保持一致。
  88.             preparedStatement.setDouble(1, account.getBalance());
  89.             preparedStatement.setString(2, account.getActno());
  90.             preparedStatement.setLong(3, account.getId());
  91.             count = preparedStatement.executeUpdate();
  92.         } catch (SQLException e) {
  93.             throw new RuntimeException(e);
  94.         } finally {
  95.             DBUtil.close(connection, preparedStatement, null);
  96.         }
  97.         return count;
  98.     }
  99.     /**
  100.      * 通过 actno 查找账户信息
  101.      *
  102.      * @param actno
  103.      * @return
  104.      */
  105.     public Account selectByActno(String actno) {
  106.         Connection connection = DBUtil.getConnection();
  107.         PreparedStatement preparedStatement = null;
  108.         ResultSet resultSet = null;
  109.         Account account = new Account();
  110.         try {
  111.             String sql = "select id,actno,balance from t_act where actno = ?";
  112.             preparedStatement = connection.prepareStatement(sql);
  113.             //注意设置的 set类型要保持一致。
  114.             preparedStatement.setString(1, actno);
  115.            resultSet = preparedStatement.executeQuery();
  116.             if (resultSet.next()) {
  117.                 Long id = resultSet.getLong("id");
  118.                 Double balance = resultSet.getDouble("balance");
  119.                 // 将结果集封装到java 对象中
  120.                 account.setActno(actno);
  121.                 account.setId(id);
  122.                 account.setBalance(balance);
  123.             }
  124.         } catch (SQLException e) {
  125.             throw new RuntimeException(e);
  126.         } finally {
  127.             DBUtil.close(connection, preparedStatement, resultSet);
  128.         }
  129.         return account;
  130.     }
  131.     /**
  132.      * 查询所有的账户信息
  133.      *
  134.      * @return
  135.      */
  136.     public List<Account> selectAll() {
  137.         Connection connection = DBUtil.getConnection();
  138.         PreparedStatement preparedStatement = null;
  139.         ResultSet resultSet = null;
  140.         List<Account> list = null;
  141.         try {
  142.             String sql = "select id,actno,balance from t_act";
  143.             preparedStatement = connection.prepareStatement(sql);
  144.             resultSet = preparedStatement.executeQuery();
  145.             while (resultSet.next()) {
  146.                 String actno = resultSet.getString("actno");
  147.                 Long id = resultSet.getLong("id");
  148.                 Double balance = resultSet.getDouble("balance");
  149.                 // 将结果集封装到java 对象中
  150.                 Account account = new Account(id,actno,balance);
  151.                 // 添加到List集合当中
  152.                 list.add(account);
  153.             }
  154.         } catch (SQLException e) {
  155.             throw new RuntimeException(e);
  156.         } finally {
  157.             DBUtil.close(connection, preparedStatement, resultSet);
  158.         }
  159.         return list;
  160.     }
  161. }
复制代码
对指定的数据表的数据进行service 业务逻辑处理操作:
service 翻译为:业务。
  1. package com.RainbowSea.bank.mvc;
  2. /**
  3. * service 翻译为:业务。
  4. * AccountService 专门处理Account业务的一个类
  5. * 在该类中应该编写纯业务代码。(只专注域业务处理,不写别的,不和其他代码混合在一块)
  6. * 只希望专注业务,能够将业务完美实现,少量bug.
  7. * <p>
  8. * 业务类一般起名:XXXService,XXXBiz...
  9. */
  10. public class AccountService {
  11.     // 这里的方法起名,一定要体现出,你要处理的是什么业务:
  12.     // 我们要提供一个能够实现转账的业务的方法(一个业务对应一个方法)
  13.     // 比如:UserService StudentService OrderService
  14.     // 处理Account 转账业务的增删改查的Dao
  15.     private AccountDao accountDao = new AccountDao();
  16.     /**
  17.      * 完成转账的业务逻辑
  18.      *
  19.      * @param fromActno 转出账号
  20.      * @param toActno   转入账号
  21.      * @param money     转账金额
  22.      */
  23.     public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException {
  24.         // 查询余额是否充足
  25.         Account fromAct = accountDao.selectByActno(fromActno);
  26.         if (fromAct.getBalance() < money) {
  27.             throw new MoneyNotEnoughException("对不起,余额不足");
  28.         }
  29.         // 程序到这里说明余额充足
  30.         Account toAct = accountDao.selectByActno(toActno);
  31.         // 修改金额,先从内存上修改,再从硬盘上修改
  32.         fromAct.setBalance(fromAct.getBalance() - money);
  33.         toAct.setBalance(toAct.getBalance() + money);
  34.         // 从硬盘数据库上修改
  35.         int count = accountDao.update(fromAct);
  36.         count += accountDao.update(toAct);
  37.         if(count != 2) {
  38.             throw new AppException("账户转账异常,请联系管理员");
  39.         }
  40.     }
  41. }
复制代码
异常处理类:
  1. package com.RainbowSea.bank.mvc;
  2. /**
  3. * 余额不足异常
  4. */
  5. public class AppException extends Exception{
  6.         public AppException() {
  7.         }
  8.         public AppException(String msg) {
  9.             super(msg);
  10.         }
  11. }
复制代码
  1. package com.RainbowSea.bank.mvc;
  2. /**
  3. * 余额不足异常
  4. */
  5. public class MoneyNotEnoughException extends Exception{
  6.     public MoneyNotEnoughException() {
  7.     }
  8.     public MoneyNotEnoughException(String msg) {
  9.         super(msg);
  10.     }
  11. }
复制代码
5.2 C (Controller : 控制层)

仅仅负责调度 M业务处理层,V视图显示层,而不做其他操作。
  1. package com.RainbowSea.bank.mvc;
  2. import jakarta.servlet.ServletException;
  3. import jakarta.servlet.annotation.WebServlet;
  4. import jakarta.servlet.http.HttpServlet;
  5. import jakarta.servlet.http.HttpServletRequest;
  6. import jakarta.servlet.http.HttpServletResponse;
  7. import java.io.IOException;
  8. /**
  9. * 账户小程序
  10. * AccountServlet 是一个司令官,他负责调度其他组件来完成任务。
  11. *
  12. */
  13. @WebServlet("/transfer")
  14. public class AccountServlet extends HttpServlet { // AccountServlet 作为一个 Controller 司令官
  15.     @Override
  16.     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
  17.             IOException {
  18.         // 获取数据
  19.         String fromActno = request.getParameter("fromActno");
  20.         String toActno = request.getParameter("toActno");
  21.         double money = Double.parseDouble(request.getParameter("money"));
  22.         // 调用业务方法处理业务(调度Model处理业务,其中是对应数据表的 CRUD操作)
  23.         AccountService accountService = new AccountService();
  24.         try {
  25.             accountService.transfer(fromActno,toActno,money);
  26.             // 执行到这里说明,成功了,
  27.             // 展示处理结束(调度 View 做页面展示)
  28.             response.sendRedirect(request.getContextPath()+"/success.jsp");
  29.         } catch (MoneyNotEnoughException e) {
  30.             // 执行到种类,说明失败了,(余额不足
  31.             // 展示处理结束(调度 View 做页面展示)
  32.             response.sendRedirect(request.getContextPath()+"/error.jsp");
  33.         } catch (AppException e) {
  34.             // 执行到种类,说明失败了,转账异常
  35.             // 展示处理结束(调度 View 做页面展示)
  36.             response.sendRedirect(request.getContextPath()+"/error.jsp");
  37.         }
  38.         // 页面的展示 (调度View做页面展示)
  39.     }
  40. }
复制代码
5.3 V (View :视图/展示)

index.jsp 转账页面:
  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  2. <html>
  3. <head>
  4.   <title>银行账号转账</title>
  5. </head>
  6. <body>
  7. <form action="<%=request.getContextPath()%>/transfer" method="post">
  8.   转出账户: <input type="text" name="fromActno" />
  9.   转入账户: <input type="text" name="toActno" />
  10.   转账金额: <input type="text" name="money" />
  11.   <input type="submit" value="转账" />
  12. </form>
  13. </body>
  14. </html>
复制代码
success转账成功的页面显示:
  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  2. <html>
  3. <head>
  4.     <title>转账成功</title>
  5. </head>
  6. <body>
  7. <h3>转账成功</h3>
  8. </body>
  9. </html>
复制代码
error 转账失败的页面显示:
  1. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  2. <html>
  3. <head>
  4.     <title>转账失败</title>
  5. </head>
  6. <body>
  7. <h3>转账失败</h3>
  8. </body>
  9. </html>
复制代码

虽然上述:代码成功实现的了用户转账的操作,但是并没有进行事务的处理。

如下是运用 TreadLocal 进行事务的处理:
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4