天天看點

MVC 三層架構案例詳細講解

作者:小心程式猿QAQ

MVC 三層架構案例詳細講解

MVC 三層架構案例詳細講解

每博一文案

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)平台的設計模式,并且受到越來越多的使用ColdFusion和PHP的開發者的歡迎。模型-視圖-控制器模式是一個有用的工具箱,它有很多好處,但也有一些缺點。

2. MVC設計思想

MVC(Model View Controller)是軟體工程中的一種軟體架構模式,它把軟體系統分為模型、視圖和控制器三個基本部分。用一種業務邏輯、資料、界面顯示分離的方法組織代碼,将業務邏輯聚集到一個部件裡面,在改進和個性化定制界面及使用者互動的同時,不需要重新編寫業務邏輯。

MVC 三層架構案例詳細講解

MVC 主要的核心就是:分層:希望專人幹專事,各司其職,職能分工要明确,這樣可以讓代碼耦合度降低,擴充力增強,元件的可複用性增強。

MVC 從字面意思我們就可以看到:是分為了三層的,M(Mode 模型),V(View 視圖),C(Controller 控制器)

MVC 三層架構案例詳細講解

M即model模型:是指模型表示業務規則。在MVC的三個部件中,模型擁有最多的處理任務。被模型傳回的資料是中立的,模型與資料格式無關,這樣一個模型能為多個視圖提供資料,由于應用于模型的代碼隻需寫一次就可以被多個視圖重用,是以減少了代碼的重複性。

V即View視圖:是指使用者看到并與之互動的界面。比如由html元素組成的網頁界面,或者軟體的用戶端界面。MVC的好處之一在于它能為應用程式處理很多不同的視圖。在視圖中其實沒有真正的處理發生,它隻是作為一種輸出資料并允許使用者操作的方式。

C即controller控制器:是指控制器接受使用者的輸入并調用模型和視圖去完成使用者的需求,控制器本身不輸出任何東西和做任何處理。它隻是接收請求并決定調用哪個模型構件去處理請求,然後再确定用哪個視圖來顯示傳回的資料。

M(Model :資料/業務) V (View :視圖/展示) C (Controller : 控制層)

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

M(處理業務/處理資料的一個秘書)

V(負責頁面展示的一個秘書)

MVC(一個司令官,排程兩個秘書,去做這件事),僅僅隻做事務上的排程,而不做其他的操作

MVC 三層架構案例詳細講解
MVC 三層架構案例詳細講解

優點:

  1. 耦合性低,友善維護,可以利于分工協作
  2. 重用性高

缺點:

  1. 使得項目架構變得複雜,對開發人員要求高

3. 三層架構

三層架構(3-tier architecture) 通常意義上的三層架構就是将整個業務應用劃分為:界面層[表示層](User Interface layer)、業務邏輯層(Business Logic Layer)、資料通路層(Data access layer)。

區分層次的目的即為了“高内聚低耦合” 的思想。在軟體體系架構設計中,分層式結構是最常見,也是最重要的一種結構。

MVC 三層架構案例詳細講解
MVC 三層架構案例詳細講解
MVC 三層架構案例詳細講解

三層架構每層之間的邏輯關系:

MVC 三層架構案例詳細講解

三層架構的優點

  1. 開發人員可以隻關注整個結構中的其中某一層;
  2. 可維護性高,可擴充性高
  3. 可以降低層與層之間的依賴;
  4. 有利于标準化;
  5. 利于各層邏輯的複用

三層架構的缺點:

  1. 降低了系統的性能。如果不采用分層式結構,很多業務可以直接造訪資料庫,以此擷取相應的資料,如今卻必須通過中間層來完成
  2. 有時會導緻級聯的修改,這種修改尤其展現在自上而下的方向。如果在表示層中需要增加一個功能,為保證其設計符合分層式結構,可能需要在相應的業務邏輯層和資料通路層中都增加相應的代碼
  3. 增加了開發成本

4. MVC 與 三層架構的關系:

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>

           
MVC 三層架構案例詳細講解

雖然上述:代碼成功實作的了使用者轉賬的操作,但是并沒有進行事務的處理。

如下是運用 TreadLocal 進行事務的處理: blog.csdn.net/weixin_6163…

6. 總結:

  1. MVC 從字面意思我們就可以看到:是分為了三層的,M(Mode 模型),V(View 視圖),C(Controller 控制器)
  2. M(Model :資料/業務) V (View :視圖/展示) C (Controller : 控制層)
  3. C(是核心,是控制器,是司令官)
  4. M(處理業務/處理資料的一個秘書)
  5. V(負責頁面展示的一個秘書)
  6. MVC(一個司令官,排程兩個秘書,去做這件事),僅僅隻做事務上的排程,而不做其他的操作
  7. 三層架構(3-tier architecture) 通常意義上的三層架構就是将整個業務應用劃分為:界面層[表示層](User Interface layer)、業務邏輯層(Business Logic Layer)、資料通路層(Data access layer)。
  8. 無論是MVC還是三層架構,都是一種規範,都是奔着"高内聚,低耦合"的思想

作者:RainbowSea

連結:https://juejin.cn/post/7234060427412668471

繼續閱讀