MVC 三层架构案例详细讲解

文章详细阐述了MVC设计模式,包括模型、视图和控制器的角色和职责,以及它们如何协同工作以实现业务逻辑和用户界面的分离。同时,文章对比了MVC与三层架构的异同,并通过用户账户转账的案例,演示了如何在实际开发中运用这些概念。此外,还提到了事务处理的重要性,尤其是在多层架构中的应用。
摘要由CSDN通过智能技术生成

每博一文案

 

tex

复制代码

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

1. MVC 概述

MVC开始是存在于桌面程序中的,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。比如一批统计数据可以分别用柱状图饼图来表示。C存在的目的则是确保M和V的同步,一旦M改变,V应该同步更新。 [1-2]

模型-视图-控制器(MVC)是[Xerox PARC](baike.baidu.com/item/Xerox PARC/10693263?fromModule=lemma_inlink)在二十世纪八十年代为编程语言Smalltalk-80发明的一种软件设计模式,已被广泛使用。后来被推荐为Oracle旗下Sun公司[Java EE](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(一个司令官,调度两个秘书,去做这件事),仅仅只做事务上的调度,而不做其他的操作

优点:

  1. 耦合性低,方便维护,可以利于分工协作
  2. 重用性高

缺点:

  1. 使得项目架构变得复杂,对开发人员要求高

3. 三层架构

三层架构(3-tier architecture) 通常意义上的三层架构就是将整个业务应用划分为:界面层[表示层](User Interface layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data access layer)。

区分层次的目的即为了“高内聚低耦合” 的思想。在软件体系架构设计中,分层式结构是最常见,也是最重要的一种结构。

 

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

三层架构的优点

  1. 开发人员可以只关注整个结构中的其中某一层;
  2. 可维护性高,可扩展性高
  3. 可以降低层与层之间的依赖;
  4. 有利于标准化;
  5. 利于各层逻辑的复用

三层架构的缺点:

  1. 降低了系统的性能。如果不采用分层式结构,很多业务可以直接造访数据库,以此获取相应的数据,如今却必须通过中间层来完成
  2. 有时会导致级联的修改,这种修改尤其体现在自上而下的方向。如果在表示层中需要增加一个功能,为保证其设计符合分层式结构,可能需要在相应的业务逻辑层和数据访问层中都增加相应的代码
  3. 增加了开发成本

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

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

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

5. 案例举例:用户账户转账

如下我们,实现一个用户账户转账操作的一个案例:

准备工作:创建表,创建数据

 

mysql

复制代码

CREATE DATABASE mvc; USE mvc; SHOW TABLES; CREATE TABLE t_act ( id BIGINT PRIMARY KEY AUTO_INCREMENT, actno VARCHAR(255) NOT NULL, balance DECIMAL(10,2) ); INSERT INTO t_act(actno,balance) VALUES('act001',50000.00),('act002',0.00); SELECT * FROM t_act;

5.1 M(Model :数据/业务处理层)

javaBean :Account 封装数据

账户实体类,封装账户信息的

  • 一般是一张表一个。
  • pojo 对象
  • 有的人也会把这种专门封装数据的对象,称为:"bean对象" (javabean对象,咖啡豆)
  • 有的人也会把这种专门封装数据的对象,称为领域模型对象,domain对象
  • 不同的程序员不同的习惯
 

java

复制代码

package com.RainbowSea.bank.mvc; import java.io.Serializable; import java.util.Objects; /** * 账户实体类,封装账户信息的 * 一般是一张表一个。 * pojo 对象 * 有的人也会把这种专门封装数据的对象,称为:"bean对象" (javabean对象,咖啡豆) * 有的人也会把这种专门封装数据的对象,称为领域模型对象,domain对象 * 不同的程序员不同的习惯。 */ public class Account implements Serializable { // 这种普通的简单的对象被成为pojo对象 // 注意我们这里定义的数据类型,使用引用数据类型 // 因为我们数据库中可能存在 null 值,而基本数据类型是不可以存储 null值的 private Long id = null; // id private String actno; // 账号 private Double balance; // 余额 // 反序列化 private static final long serialVersionUID = 1L; public Account() { } public Account(Long id, String actno, Double balance) { this.id = id; this.actno = actno; this.balance = balance; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getActno() { return actno; } public void setActno(String actno) { this.actno = actno; } public Double getBalance() { return balance; } public void setBalance(Double balance) { this.balance = balance; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Account)) return false; Account account = (Account) o; return Objects.equals(getId(), account.getId()) && Objects.equals(getActno(), account.getActno()) && Objects.equals(getBalance(), account.getBalance()); } @Override public int hashCode() { return Objects.hash(getId(), getActno(), getBalance()); } @Override public String toString() { return "Account{" + "id=" + id + ", actno='" + actno + '\'' + ", balance=" + balance + '}'; } }

DB连接数据库的工具:

 

properties

复制代码

driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mvc user=root password=MySQL

 

java

复制代码

package com.RainbowSea.bank.utils; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ResourceBundle; public class DBUtil { // resourceBundle 只能读取到 properties 后缀的文件,注意不要加文件后缀名 private static ResourceBundle resourceBundle = ResourceBundle.getBundle("resources/jdbc"); private static String driver = resourceBundle.getString("driver"); private static String url = resourceBundle.getString("url"); private static String user = resourceBundle.getString("user"); private static String password = resourceBundle.getString("password"); // DBUtil 类加载注册驱动 static { try { Class.forName(driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } // 将构造器私有化,不让创建对象,因为工具类中的方法都是静态的,不需要创建对象 // 为了防止创建对象,故将构造方法私有化 private DBUtil() { } /** * 这里没有使用数据库连接池,直接创建连接对象 */ public static Connection getConnection() { Connection connection = null; try { connection = DriverManager.getConnection(url, user, password); } catch (SQLException e) { throw new RuntimeException(e); } return connection; } /** * 资源的关闭 * 最后使用的最先关闭,逐个关闭,防止存在没有关闭的 */ public static void close(Connection connection , PreparedStatement preparedStatement, ResultSet resultSet) { if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { throw new RuntimeException(e); } } if (preparedStatement!=null) { try { preparedStatement.close(); } catch (SQLException e) { throw new RuntimeException(e); } } if (connection != null) { try { connection.close(); } catch (SQLException e) { throw new RuntimeException(e); } } } }

对应Account数据表的DAO操作工具类

AccountDao 是负责Account 数据的增上改查

什么是DAO ?

  • Data Access Object (数据访问对象)
  • DAO实际上是一种设计模式,属于 JavaEE的设计模式之一,不是 23种设计模式
  • DAO只负责数据库表的CRUD ,没有任何业务逻辑在里面
  • 没有任何业务逻辑,只负责表中数据增上改查的对象,有一个特俗的称谓:DAO对象

为什么叫做 AccountDao 呢?

  • 这是因为DAO是专门处理t_act 这张表的
  • 如果处理t_act 表的话,可以叫做:UserDao
  • 如果处理t-student表的话,可以叫做 StudentDao

主要定义如下:增删改查方法()

 

java

复制代码

int insert() ; int deleteByActno(); int update() ; Account selectByActno(); List<Account> selectAll();

 

java

复制代码

package com.RainbowSea.bank.mvc; import com.RainbowSea.bank.utils.DBUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; import java.util.List; /** * AccountDao 是负责Account 数据的增上改查 * <p> * 1. 什么是DAO ? * Data Access Object (数据访问对象) * 2. DAO实际上是一种设计模式,属于 JavaEE的设计模式之一,不是 23种设计模式 * 3.DAO只负责数据库表的CRUD ,没有任何业务逻辑在里面 * 4.没有任何业务逻辑,只负责表中数据增上改查的对象,有一个特俗的称谓:DAO对象 * 5. 为什么叫做 AccountDao 呢? * 这是因为DAO是专门处理t_act 这张表的 * 如果处理t_act 表的话,可以叫做:UserDao * 如果处理t-student表的话,可以叫做 StudentDao * <p> * int insert() ; * int deleteByActno(); * int update() ; * Account selectByActno(); * List<Account> selectAll(); */ public class AccountDao { /** * 插入数据 * * @param account * @return */ public int insert(Account account) { Connection connection = DBUtil.getConnection(); PreparedStatement preparedStatement = null; int count = 0; try { String sql = "insert into t_act(actno,balance) values(?,?)"; preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1, account.getActno()); preparedStatement.setDouble(2, account.getBalance()); count = preparedStatement.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e); } finally { DBUtil.close(connection, preparedStatement, null); } return count; } /** * 通过Id删除数据 * * @param id * @return */ public int deleteById(String id) { Connection connection = DBUtil.getConnection(); int count = 0; PreparedStatement preparedStatement = null; try { String sql = "delete from t_act where id = ?"; preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1, id); count = preparedStatement.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e); } finally { DBUtil.close(connection, preparedStatement, null); } return count; } /** * 更新数据 * * @param account * @return */ public int update(Account account) { Connection connection = DBUtil.getConnection(); PreparedStatement preparedStatement = null; int count = 0; try { String sql = "update t_act set balance = ?, actno = ? where id = ?"; preparedStatement = connection.prepareStatement(sql); //注意设置的 set类型要保持一致。 preparedStatement.setDouble(1, account.getBalance()); preparedStatement.setString(2, account.getActno()); preparedStatement.setLong(3, account.getId()); count = preparedStatement.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e); } finally { DBUtil.close(connection, preparedStatement, null); } return count; } /** * 通过 actno 查找账户信息 * * @param actno * @return */ public Account selectByActno(String actno) { Connection connection = DBUtil.getConnection(); PreparedStatement preparedStatement = null; ResultSet resultSet = null; Account account = new Account(); try { String sql = "select id,actno,balance from t_act where actno = ?"; preparedStatement = connection.prepareStatement(sql); //注意设置的 set类型要保持一致。 preparedStatement.setString(1, actno); resultSet = preparedStatement.executeQuery(); if (resultSet.next()) { Long id = resultSet.getLong("id"); Double balance = resultSet.getDouble("balance"); // 将结果集封装到java 对象中 account.setActno(actno); account.setId(id); account.setBalance(balance); } } catch (SQLException e) { throw new RuntimeException(e); } finally { DBUtil.close(connection, preparedStatement, resultSet); } return account; } /** * 查询所有的账户信息 * * @return */ public List<Account> selectAll() { Connection connection = DBUtil.getConnection(); PreparedStatement preparedStatement = null; ResultSet resultSet = null; List<Account> list = null; try { String sql = "select id,actno,balance from t_act"; preparedStatement = connection.prepareStatement(sql); resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { String actno = resultSet.getString("actno"); Long id = resultSet.getLong("id"); Double balance = resultSet.getDouble("balance"); // 将结果集封装到java 对象中 Account account = new Account(id,actno,balance); // 添加到List集合当中 list.add(account); } } catch (SQLException e) { throw new RuntimeException(e); } finally { DBUtil.close(connection, preparedStatement, resultSet); } return list; } }

对指定的数据表的数据进行service 业务逻辑处理操作:

service 翻译为:业务。

  • AccountService 专门处理Account业务的一个类
  • 在该类中应该编写纯业务代码。(只专注域业务处理,不写别的,不和其他代码混合在一块)
  • 只希望专注业务,能够将业务完美实现,少量bug.
  • 业务类一般起名:XXXService,XXXBiz...
 

java

复制代码

package com.RainbowSea.bank.mvc; /** * service 翻译为:业务。 * AccountService 专门处理Account业务的一个类 * 在该类中应该编写纯业务代码。(只专注域业务处理,不写别的,不和其他代码混合在一块) * 只希望专注业务,能够将业务完美实现,少量bug. * <p> * 业务类一般起名:XXXService,XXXBiz... */ public class AccountService { // 这里的方法起名,一定要体现出,你要处理的是什么业务: // 我们要提供一个能够实现转账的业务的方法(一个业务对应一个方法) // 比如:UserService StudentService OrderService // 处理Account 转账业务的增删改查的Dao 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); // 修改金额,先从内存上修改,再从硬盘上修改 fromAct.setBalance(fromAct.getBalance() - money); toAct.setBalance(toAct.getBalance() + money); // 从硬盘数据库上修改 int count = accountDao.update(fromAct); count += accountDao.update(toAct); if(count != 2) { throw new AppException("账户转账异常,请联系管理员"); } } }

异常处理类:

 

java

复制代码

package com.RainbowSea.bank.mvc; /** * 余额不足异常 */ public class AppException extends Exception{ public AppException() { } public AppException(String msg) { super(msg); } }

 

java

复制代码

package com.RainbowSea.bank.mvc; /** * 余额不足异常 */ public class MoneyNotEnoughException extends Exception{ public MoneyNotEnoughException() { } public MoneyNotEnoughException(String msg) { super(msg); } }

5.2 C (Controller : 控制层)

仅仅负责调度 M业务处理层,V视图显示层,而不做其他操作。

 

java

复制代码

package com.RainbowSea.bank.mvc; 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 是一个司令官,他负责调度其他组件来完成任务。 * */ @WebServlet("/transfer") public class AccountServlet extends HttpServlet { // AccountServlet 作为一个 Controller 司令官 @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取数据 String fromActno = request.getParameter("fromActno"); String toActno = request.getParameter("toActno"); double money = Double.parseDouble(request.getParameter("money")); // 调用业务方法处理业务(调度Model处理业务,其中是对应数据表的 CRUD操作) AccountService accountService = new AccountService(); try { accountService.transfer(fromActno,toActno,money); // 执行到这里说明,成功了, // 展示处理结束(调度 View 做页面展示) response.sendRedirect(request.getContextPath()+"/success.jsp"); } catch (MoneyNotEnoughException e) { // 执行到种类,说明失败了,(余额不足 // 展示处理结束(调度 View 做页面展示) response.sendRedirect(request.getContextPath()+"/error.jsp"); } catch (AppException e) { // 执行到种类,说明失败了,转账异常 // 展示处理结束(调度 View 做页面展示) response.sendRedirect(request.getContextPath()+"/error.jsp"); } // 页面的展示 (调度View做页面展示) } }

5.3 V (View :视图/展示)

index.jsp 转账页面:

 

jsp

复制代码

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>银行账号转账</title> </head> <body> <form action="<%=request.getContextPath()%>/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>

success转账成功的页面显示:

 

jsp

复制代码

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

error 转账失败的页面显示:

 

jsp

复制代码

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

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

如下是运用 TreadLocal 进行事务的处理:🔜🔜🔜 blog.csdn.net/weixin_6163…

6. 总结:

  1. MVC 从字面意思我们就可以看到:是分为了三层的,M(Mode 模型),V(View 视图),C(Controller 控制器)

  2. 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还是三层架构,都是一种规范,都是奔着"高内聚,低耦合"的思想

在刚刚步入“多层结构”Web应用程序开发的时候,我阅读过几篇关于“asp.net三层结构开发”的文章。但其多半都是对PetShop3.0和Duwamish7的局部剖析或者是学习笔记。对“三层结构”通体分析的学术文章几乎没有。 2005年2月11日,Bincess BBS彬月论坛开始试运行。不久之后,我写了一篇题目为《浅谈“三层结构”原理与用意》的文章。旧版文章以彬月论坛程序中的部分代码举例,通过全局视角阐述了什么是“三层结构”的开发模式?为什么要这样做?怎样做?……而在这篇文章的新作中,配合这篇文章我写了7个程序实例(TraceLWord1~TraceLWord7留言板)以帮助读者理解“三层结构”应用程序。这些程序示例可以在随带的CodePackage目录中找到——   对于那些有丰富经验的Web应用程序开发人员,他们认为文章写的通俗易懂,很值得一读。可是对于asp.net初学者,特别是没有任何开发经验的人,文章阅读起来就感到非常困难,不知文章所云。甚至有些读者对“三层结构”的认识更模糊了……   关于“多层结构”开发模式,存在这样一种争议:一部分学者认为“多层结构”与“面向对象的程序设计思想”有着非常紧密的联系。而另外一部分学者却认为二者之间并无直接联系。写作这篇文章并不是要终结这种争议,其行文目的是希望读者能够明白:在使用asp.net进行Web应用程序开发时,实现“多层结构”开发模式的方法、原理及用意。要顺利的阅读这篇文章,希望读者能对“面向对象的程序设计思想”有一定深度的认识,最好能懂一些“设计模式”的知识。如果你并不了解前面这些,那么这篇文章可能并不适合你现在阅读。不过,无论这篇文章面对的读者是谁,我都会尽量将文章写好。我希望这篇文章能成为学习“三层结构”设计思想的经典文章!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值