Java学习-MVC架构模式

目录

1 传统方式处理业务的缺点

2 MVC架构模式理论基础

4 MVC架构模式如何设计

4.1 设计JDBC工具类的封装

4.2 创建封装数据的对象

4.3 设计Dao模式

4.4 业务逻辑编写

4.5 调度中心的编写

4.6 MVC框架和三层架构之间的关系

4.7 解决事务问题


1 传统方式处理业务的缺点

首先创建一个数据库表:

新增两个数据:

代码如下:

package com.itzw.bank.web.servlet;

import com.itzw.bank.exceptions.AppException;
import com.itzw.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.*;

@WebServlet("/tansfer")
public class AccountTransferServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();

        //获取前端提交的信息
        String formAct = request.getParameter("formAct");
        String toAct = request.getParameter("toAct");
        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://127.0.0.1:3306/mvc";
            String user = "root";
            String password = "123456";
            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,formAct);
            //执行sql
            rs = ps.executeQuery();
            //处理结果集
            if (rs.next()){
                double balance = rs.getDouble("balance");
                if (balance < money){
                    //余额不足(报余额不足异常)
                    throw new MoneyNotEnoughException("您的余额不足,赶紧充钱!");
                }
                //到这余额是够的
                //给001减10000,
                //给002加10000
                String sql2 = "update t_act set balance = balance - ? where actno = ?";
                ps2 = conn.prepareStatement(sql2);
                ps2.setDouble(1,money);
                ps2.setString(2,formAct);
                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,toAct);
                //累计
                count += ps3.executeUpdate();
                if (count != 2){
                    throw new AppException("App异常,请联系管理员");
                }
                //手动提交事务
                conn.commit();
                //转账成功
                out.print("转账成功!");
            }
        } catch (Exception e) {
            //事务回滚
            if (conn != null) {
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            //报错提示
            //e.printStackTrace();
            out.print(e.getMessage());
        }finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (ps2 != null) {
                try {
                    ps2.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (ps3 != null) {
                try {
                    ps3.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }
}

<%@ 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="tansfer" method="post">
    转出账户:<input type="text" name="formAct"><br>
    转入账户:<input type="text" name="toAct"><br>
    转账金额:<input type="text" name="money"><br>
    <input type="submit" value="转账"><br>
  </form>
  </body>
</html>

这种银行转账的代码我们之前就写过,首先写一个前端页面,在数据库中创建一个表,然后使用Servlet连接数据库并进行核心业务处理,注意还要手动提交事务,最终完成转账,但我们也发现一个问题,本次转账代码中的Servlet基本上完成了所有任务,比如数据接收、核心业务处理、数据库表中数据的曾删改查、页面数据展示。这就导致这个程序的复用性很低,当我还有别的业务需求的时候,需要再写一遍相同的代码;耦合度高,程序很难扩展;操作数据库的代码和业务逻辑混杂在一起,很容易出错。无法专注业务逻辑的编写。

2 MVC架构模式理论基础

系统为什么要分层?

希望专人干专事,各司其职,职能分工明确,这样可以让代码耦合度降低,扩展能力增强。

软件架构中,有一个非常著名的架构模式:MVC架构模式

  • M(Model:数据/业务) V(View:视图/展示) C(Controller:控制器)
  • C是核心,是控制器,是司令官
  • M处理业务,是处理数据的一个秘书
  • V负责页面的展示的另一个秘书
  • MVC:一个司令官,调度两个秘书去做这件事

实现MVC大致流程:用户发送请求,Controller接收请求,它会把请求发给Model处理,Model连接数据库处理完数据在返回给Controller,拿到数据再发给View展示数据,然后返回给Controller,最终响应到用户。

4 MVC架构模式如何设计

4.1 设计JDBC工具类的封装

package com.itzw.bank.utils;

import java.sql.*;
import java.util.ResourceBundle;

/**
 * JDBC工具类的封装
 * @author zhouzhouzhou
 * @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(){}

    //在类加载时注册驱动
    static {
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取连接,没有使用数据库连接池
     * @return connection
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        Connection connection =  DriverManager.getConnection(url, user, password);
        return connection;
    }

    public static void close(Connection conn, Statement ps, ResultSet rs){
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

4.2 创建封装数据的对象

创建名为Account的类,用它来封装对象,有的人会把这种专门封装数据的对象称之为bean对象,也有人称之为pojo对象,还有人称为domain对象,都一样,称呼不同而已。还需要注意的是属性的类型选择,一般不建议设计为基本数据类型,建议使用包装类,防止null带来问题,就比如我这次的数据类型,它是对象数据库的,数据库中的bigint对应java的long,但是我用Long代替long,还有数据库中的decimal表示java中的double,我用Double代替double:

package com.itzw.bank.mvc;

public class Account {
    //id
    private Long id;
    //账号
    private String actno;
    //余额
    private Double balance;

    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 String toString() {
        return "Account{" +
                "id=" + id +
                ", actno='" + actno + '\'' +
                ", balance=" + balance +
                '}';
    }
}

4.3 设计Dao模式

  • 它是JavaEE设计模式之一
  • 创建AccountDao类,它用来负责对Account数据的增删改查
  • 什么是Dao:Data Access Object(数据访问对象)
  • Dao只负责对数据库表的CRUD,没有任何业务逻辑在里面。
  • 一般情况下一张表对应一个Dao
package com.itzw.bank.mvc;

import com.itzw.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;

public class AccountDao {
    /**
     * 插入数据
     * @param act
     * @return
     */
    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) {
            e.printStackTrace();
        }finally {
            DBUtil.close(conn,ps,null);
        }
        return count;
    }

    /**
     * 根据id删除数据
     * @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) {
            e.printStackTrace();
        }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 actno = ?,balance = ? where id = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,act.getActno());
            ps.setDouble(2,act.getBalance());
            ps.setLong(3,act.getId());
            count = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }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(id,actno,balance);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }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 act = new Account(id,actno,balance);
                list.add(act);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DBUtil.close(conn,ps,rs);
        }
        return list;
    }
}

4.4 业务逻辑编写

package com.itzw.bank.mvc;

import com.itzw.bank.exceptions.AppException;
import com.itzw.bank.exceptions.MoneyNotEnoughException;

public class AccountService {
    private AccountDao accountDao = new AccountDao();


    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("数据库异常,请联系管理员!");
        }
    }

}

4.5 调度中心的编写

package com.itzw.bank.mvc;

import com.itzw.bank.exceptions.AppException;
import com.itzw.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;

/**
 * 账户小程序,它是一个司令官或者说是Controller
 * @author zhouzhou
 * @version 2.0
 * @since 2.0
 */

@WebServlet("/tansfer")
public class AccountServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //接收数据
        String formAct = request.getParameter("formAct");
        String toAct = request.getParameter("toAct");
        double money = Double.parseDouble(request.getParameter("money"));
        //调用业务方法处理
        AccountService accountService = new AccountService();
        try {
            accountService.transfer(formAct,toAct,money);
            //成功了,展示处理结果
            response.sendRedirect(request.getContextPath()+"/success.jsp");
        } catch (MoneyNotEnoughException e) {
            //失败了(余额不足)
            response.sendRedirect(request.getContextPath()+"/moneynotenough.jsp");
        } catch (Exception e) {
            //失败了
            response.sendRedirect(request.getContextPath()+"/error.jsp");
        }
    }
}

4.6 MVC框架和三层架构之间的关系

三层架构是什么样的呢?

  • 三层架构的三层分别为:表现层(web层/表示层)、业务逻辑层、持久化层
  • 其中表现层中包含的有Servlet、JSP等等;业务逻辑层是service;持久化层是Dao
  • 当用户发送请求的时候,会先发送给表现层,然后表现层发给业务逻辑层,然后业务逻辑层发给持久化层,持久化层再连接数据库,得到数据再原路返回,最终返回到用户。
  • 就像前面我们写的代码一样,Servlet中包含着service,service包含着Dao,而Dao又连接着DB数据库

MVC框架之前我们就讲过:

Controller像总司令一样负责控制Model和View,而其中Model也就是负责处理数据的环节,它包含了业务逻辑层和持久化层,这也很好理解,Model负责业务处理,而业务逻辑层也就是service就是负责业务处理的,而业务处理必然是包含着Dao也就是持久化层的。总司令controller和view就相当于表现层,因为他们是直接与用户接触的。这也MVC框架和三层架构的关系就了然了。

4.7 解决事务问题

我们发现将各种事情分开来做之后,事务就很难写进去,需要改很多东西,而改过之后代码复杂很多,而且失去了原有的专人做专事的特性,改造过程有一点麻烦,懒得改了。。。究其原因,为什么改造麻烦,因为Connection会创建多个,为了不创建多个不得不在多个文件中传入相同的Connection。然后我们发现线程Thread是不会变得,有没有可能存在一个Map集合,它里面的key装着当前的Thread,而value就是Connection,这样我们不需要传入Connection,只需要获取Thread即可。还真有,那就是ThreadLocal集合,可以将Connection对象装入其中,需要放在DBUtil文件中的getConnection中,在Dao文件使用这个工具类时或者其它文件使用这个工具类时拿到的Connection对象都是同一个Connection对象。主要修改getConnection方法即可,如下:

    private static ThreadLocal<Connection> local = new ThreadLocal<>();

    /**
     * 获取连接,没有使用数据库连接池
     * @return connection
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        Connection connection = local.get();
        if (connection == null){
            connection =  DriverManager.getConnection(url, user, password);
            local.set(connection);
        }
        return connection;
    }

注意最后要将conn从Map集合中移除

        if (conn != null) {
            try {
                conn.close();
                //思考,为什么conn关闭之后要从大Map中移除呢
                //根本原因是:Tomcat服务器是支持线程池的,也就是说一个人用过了t1线程,t1线程还有其它用户使用
                local.remove();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

至此,这个小项目已经相当完美了!!但还是可以改进,比如目录,给不同任务的文件都新建一个对应的包,如下:

还有一点,一般层与层直接是用接口连接,这样可以降低耦合度,提高扩展力。也就是业务逻辑层和持久化层用接口实现。也就是dao文件和service文件需要使用接口实现,如下:

package com.itzw.bank.dao;

import com.itzw.bank.pojo.Account;

import java.util.List;

public interface AccountDao {
    int insert(Account act);
    int deleteById(Long id);
    int update(Account act);
    Account selectByActno(String actno);
    List<Account> selectAll();
}
package com.itzw.bank.service;

import com.itzw.bank.exceptions.AppException;
import com.itzw.bank.exceptions.MoneyNotEnoughException;

public interface AccountService {
    public void transfer(String fromActno,String toActno,double money) throws MoneyNotEnoughException, AppException;
}

然后在新建impl包在里面新建类实现对应接口即可,注意实现接口要使用多态了,如下:

    private AccountDao accountDao = new AccountDaoImpl();//多态

到这就差不多结束了,依然有一些问题,不过大致格式就是这样,以后写项目就按这种目录结构编写,非常的规范。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值