2

MVC 三层架构案例详细讲解 - Rainbow-Sea

 1 year ago
source link: https://www.cnblogs.com/TheMagicalRainbowSea/p/17409206.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

MVC 三层架构案例详细讲解

在这里插入图片描述

每博一文案

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

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

img

MVC 主要的核心就是:分层:希望专人干专事,各司其职,职能分工要明确,这样可以让代码耦合度降低,扩展力增强,组件的可复用性增强

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

img

M即model模型:是指模型表示业务规则。在MVC的三个部件中,模型拥有最多的处理任务。被模型返回的数据是中立的,模型与数据格式无关,这样一个模型能为多个视图提供数据,由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性

V即View视图:是指用户看到并与之交互的界面。比如由html元素组成的网页界面,或者软件的客户端界面。MVC的好处之一在于它能为应用程序处理很多不同的视图。在视图中其实没有真正的处理发生,它只是作为一种输出数据并允许用户操作的方式。

C即controller控制器:是指控制器接受用户的输入并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。

M(Model :数据/业务) V (View :视图/展示) C (Controller : 控制层)

C(是核心,是控制器,是司令官)

M(处理业务/处理数据的一个秘书)

V(负责页面展示的一个秘书)

MVC(一个司令官,调度两个秘书,去做这件事),仅仅只做事务上的调度,而不做其他的操作

在这里插入图片描述
JavaBean

优点:

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

缺点:

  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. 案例举例:用户账户转账

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

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



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对象
  • 不同的程序员不同的习惯
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连接数据库的工具:

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mvc
user=root
password=MySQL
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

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

int insert() ;
int deleteByActno();
int update() ;
Account selectByActno();
List<Account> selectAll();
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...
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("账户转账异常,请联系管理员");
        }

    }
}

异常处理类:

package com.RainbowSea.bank.mvc;


/**
 * 余额不足异常
 */
public class AppException extends Exception{

        public AppException() {

        }

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

}

package com.RainbowSea.bank.mvc;


/**
 * 余额不足异常
 */
public class MoneyNotEnoughException extends Exception{
    public MoneyNotEnoughException() {

    }

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

5.2 C (Controller : 控制层)

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

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 转账页面:


<%@ 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转账成功的页面显示:


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

<h3>转账成功</h3>
</body>
</html>

error 转账失败的页面显示:


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

在这里插入图片描述

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

如下是运用 TreadLocal 进行事务的处理:🔜🔜🔜 https://blog.csdn.net/weixin_61635597/article/details/130728394?csdn_share_tail

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

7. 最后:

⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐ 感谢如下博主的分享: ⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

【1】https://blog.csdn.net/weixin_43232955/article/details/104963392?ops_request_misc

【2】https://blog.csdn.net/weixin_42694511/article/details/120690083?ops_request_misc

限于自身水平,其中存在的错误,希望大家,给予指教,韩信点兵——多多益善,谢谢大家,江湖再见,后会有期!!!

在这里插入图片描述

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK