MVC和三层架构总结【动力节点老杜】

文章目录

  • MVC【横向】
    • 不使用mvc模式实现银行转账功能
    • MVC理论基础
    • Model
      • dao
      • Pojo、bean、domain
      • Service
        • 事务的解决方法
        • 简单的ThreadLoacl
        • 使用ThreadLocal的DButil
  • 三层架构【纵向】
  • mvc和三层架构之间的关系
  • SSM与三层架构的关系
  • 最终实现
  • 待解决

MVC【横向】

参考动力节点老杜讲的MVC写的笔记,如有错误,还请指正!

不使用mvc模式实现银行转账功能

分析以下AccountTransferServlet他都负责了什么?

  1. 负责了数据接收
  2. 负责了核心的业务处理
  3. 负责了数据库表中数据的CRUD操作(Create【增】 Retrieve【查】 Update【改】 Delete【删】)
  4. 负责了页面的数据展示

缺点

  • 代码的复用性太差。(代码的重用性太差)
    • 因为没有进行“职能分工”,没有独立组件的概念,所以没有办法进行代码复用。代码和代码之间的耦合度太高,扩展力太差。
  • 耦合度高,导致了代码很难扩展。
  • 操作数据库的代码和业务逻辑混杂在一起,很容易出错。编写代码的时候很容易出错,无法专注业务逻辑的编写。

MVC理论基础

  • MVC架构模式【横向】

    • M:Model,数据/业务
      • pojo、bean、domain
      • service
      • dao
    • V:View,视图/展示
      • JSP
      • Freemarker
      • Velocity
      • Thymeleaf
      • html
    • C:Controller,控制器【核心】

    MVC和三层架构总结【动力节点老杜】_第1张图片

  • 对mvc的理解?【面试题】

    1. mvc是一种软件架构模式

      • M指Model,模型

        完成具体的业务操作,包括处理业务以及处理数据

      • V指View,视图

        使用JSP、html完成页面展示

      • C指Controller,是核心控制器

        负责获取View的请求,并调用模型将数据交给View展示

    2. 使用mvc架构处理用户请求的具体过程

      • 当用户发送请求时,控制器接受到用户的请求后,调用Model处理业务
      • Model与数据库相连接,处理完业务后将处理结果和处理完的数据返回给控制器
      • 控制器调用View组件进行结果的展示
    3. 使用mvc架构的优点

      • 降低代码的耦合度
      • 扩展能力增强
      • 组件的可复用性增强

Model

dao

  • Data Access Object,数据访问对象

  • DAO实际上是一种设计模式,属于JavaEE的设计模式之一(不是23种设计模式)

  • DAO只负责数据库表的CRUD,没有任何业务逻辑在里面

  • 一般情况下,一张表对应一个DAO对象

    • 比如t_act对应AccoutDao对象
    package com.st.bank.dao.impl;
    
    import com.st.bank.dao.AccountDao;
    import com.st.bank.pojo.Account;
    import com.st.bank.utils.DBUtil;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.List;
    
    /**
     * @author: TIKI
     * @Project: mvc -AccountDaoImpl
     * @Pcakage: com.st.bank.dao.Impl.AccountDaoImpl
     * @Date: 2022年10月23日 19:27
     * @Description:负责account数据的增删改查
     */
    public class AccountDaoImpl implements AccountDao {
    
        /** 插入账户信息
         * @param act 账户信息
         * @return 1表示插入成功
         */
        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) {
                throw new RuntimeException(e);
            }finally {
                DBUtil.close(null,ps,null);
            }
            return count;
        }
    
        /** 根据主键删除账户
         * @param id 主键
         * @return 删除成功返回1
         */
        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) {
                throw new RuntimeException(e);
            }finally {
                DBUtil.close(null,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 balance = ?, actno = ? where id=?";
                ps = conn.prepareStatement(sql);
                ps.setDouble(1,act.getBalance());
                ps.setString(2,act.getActno());
                ps.setLong(3,act.getId());
                count = ps.executeUpdate();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }finally {
                DBUtil.close(null,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");
                    act = new Account(id,actno,balance);
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }finally {
                DBUtil.close(null,ps,rs);
            }
            return act;
        }
    
        /** 获取所有的账户
         * @return
         */
        public List<Account> selectAll(){
            Connection conn = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
            int count = 0;
            List<Account> list= null;
            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");
                    list.add(new Account(id,actno,balance));
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }finally {
                DBUtil.close(null,ps,rs);
            }
            return list;
        }
    }
    

Pojo、bean、domain

  • DAO对象查到的数据如何返回给控制器进而显示到页面?

    • 利用java语言面向对象的特点->将数据封装成一个对象返回给控制器

      • 比如针对t_act设计Account类【pojo对象】

        有的人也会把这种专门封装数据的对象,称为【bean对象】,或者称为领域模型对象【domain对象】

        package com.st.bank.pojo;
        
        /**
         * @author: TIKI
         * @Project: mvc -Accout
         * @Pcakage: com.st.bank.mvc.Accout
         * @Date: 2022年10月22日 19:21
         * @Description:账号实体类
         */
        public class Account {
            // 一般属性不设计为基本数据类型,建议使用包装类(引用数据类型),防止查询数据为null带来的问题
            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;
            }
        }
        
    • 一般类的属性不设计为基本数据类型,建议使用包装类(引用数据类型),防止查询数据为null带来的问题

Service

  • service中编写纯业务代码,service中的方法名需要体现出处理的是什么业务

    • 比如针对Account业务,编写AccountService业务

      package com.st.bank.service.impl;
      
      import com.st.bank.dao.AccountDao;
      import com.st.bank.dao.impl.AccountDaoImpl;
      import com.st.bank.exceptions.AppException;
      import com.st.bank.exceptions.MoneyNotEnoughException;
      import com.st.bank.pojo.Account;
      import com.st.bank.service.AccountService;
      import com.st.bank.utils.DBUtil;
      
      import java.sql.Connection;
      import java.sql.SQLException;
      /**
       * @author: TIKI
       * @Project: mvc -AccountService
       * @Pcakage: com.st.bank.service.AccountService
       * @Date: 2022年10月23日 19:29
       * @Description: 专门处理Account业务的一个类
       *               在该类中应编写纯业务代码
       */
      
      
      
      public class AccountServiceImpl implements AccountService {
      
          // 为什么定义到这里?因为在每一个业务方法中都可以需要连接数据库。
          private AccountDao accountDao = new AccountDaoImpl();
      
          // 这里的方法起名,一定要体现出,你要处理的是什么业务。
          // 我们要提供一个能够实现转账的业务方法(一个业务对应一个方法。)
      
          /**
           * 完成转账的业务逻辑
           * @param fromActno 转出账号
           * @param toActno 转入账号
           * @param money 转账金额
           */
          public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, AppException {
              // service层控制事务
              try (Connection connection = DBUtil.getConnection()){
                  System.out.println(connection);
                  // 开启事务(需要使用Connection对象)
                  connection.setAutoCommit(false);
      
                  // 查询余额是否充足
                  Account fromAct = accountDao.selectByActno(fromActno);
                  if (fromAct.getBalance() < money) {
                      throw new MoneyNotEnoughException("对不起,余额不足");
                  }
                  // 程序到这里说明余额充足
                  Account toAct = accountDao.selectByActno(toActno);
                  // 修改余额(只是修改了内存中java对象的余额)
                  fromAct.setBalance(fromAct.getBalance() - money);
                  toAct.setBalance(toAct.getBalance() + money);
                  // 更新数据库中的余额
                  int count = accountDao.update(fromAct);
      
                  // 模拟异常
                  /*String s = null;
                  s.toString();*/
      
                  count += accountDao.update(toAct);
                  if (count != 2) {
                      throw new AppException("账户转账异常!!!");
                  }
                  // 提交事务
                  connection.commit();
              } catch (SQLException e) {
                  throw new AppException("账户转账异常!!!");
              }
          }
      
      }
      
  • 在service中调用dao和view

  • 事务一定是在service中实现的

    • 一般一个业务方法对应一个完整的事务【不绝对】

事务的解决方法

  1. 在DAO中方法均传入Connection对象,保证Service中和DAO中使用同一个Connection
  2. service调用DAO中的方法时,属于同一个线程,使用Map集合通过线程对象绑定Connection对象
    • ThreadLocal:实际上是一下Map集合

简单的ThreadLoacl

package com.powernode.threadlocal;

import java.util.HashMap;
import java.util.Map;

/**
 * 自定义一个ThreadLocal类
 */
public class MyThreadLocal<T> {

    /**
     * 所有需要和当前线程绑定的数据要放到这个容器当中
     */
    private Map<Thread, T> map = new HashMap<>();

    /**
     * 向ThreadLocal中绑定数据
     */
    public void set(T obj){
        map.put(Thread.currentThread(), obj);
    }

    /**
     * 从ThreadLocal中获取数据
     * @return
     */
    public T get(){
        return map.get(Thread.currentThread());
    }

    /**
     * 移除ThreadLocal当中的数据
     */
    public void remove(){
        map.remove(Thread.currentThread());
    }
}

使用ThreadLocal的DButil

package com.st.bank.utils;

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

/**
 * @author: TIKI
 * @Project: mvc -Dbutil
 * @Pcakage: com.st.bank.utils.DBUtil
 * @Date: 2022年10月22日 19:04
 * @Description:数据库工具类
 */
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(){}

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

    // 这个对象实际上在服务器中只有一个。
    private static ThreadLocal<Connection> local = new ThreadLocal<>();

    /**
     * 这里没有使用数据库连接池,直接创建连接对象。
     * @return 连接对象
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        Connection conn = local.get();
        if (conn == null) {
            conn = DriverManager.getConnection(url, user, password);
            local.set(conn);
        }
        return conn;
    }

    /**
     * 关闭资源
     * @param conn 连接对象
     * @param stmt 数据库操作对象
     * @param rs 结果集对象
     */
    public static void close(Connection conn, Statement stmt, ResultSet rs){
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (conn != null) {
            try {
                conn.close();
                // 思考一下:为什么conn关闭之后,这里要从大Map中移除呢?
                // 根本原因是:Tomcat服务器是支持线程池的。也就是说一个人用过了t1线程,t1线程还有可能被其他用户使用。
                local.remove();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

}
  • 思考一下:为什么conn关闭之后,这里要从大Map中移除呢?

    根本原因是:Tomcat服务器是支持线程池的。也就是说一个人用过了t1线程,t1线程还有可能被其他用户使用。

三层架构【纵向】

三层架构是指表示层、业务逻辑层和持久化层

  • 表示层/表现层/web层(UI)

    直接与前端交互

    • 接受前端ajax请求
    • 返回json数据给前端
  • 业务逻辑层Service(BLL)

    • 处理表现层转发过来的前端请求(具体业务)

    • 将从持久层获取的数据返回到表现层

  • 持久化层Dao(数据访问层)

    直接操作数据库完成CRUD,并将获得的数据返回到上一层,即业务逻辑层

    • 相关技术

      • JDBC

      • MyBatis

      • Hibernate(实现了JPA规范)

        配置较复杂、效率较低

      • JPA

      • SpringData(实现了JPA规范)

      • ActiveJDBC

MVC和三层架构总结【动力节点老杜】_第2张图片

mvc和三层架构之间的关系

MVC和三层架构总结【动力节点老杜】_第3张图片

  • 三层架构中的表示层即为mvc架构中的View和Controller
  • 三层架构中的业务逻辑层即为mvc架构中Model中的Service
  • 三层架构中的持久化层即为mvc架构中Model中的DAO

SSM与三层架构的关系

  • SSM框架包括Spring、 SpringMVC、MyBatis

    • Spring

      Spring负责管理整个项目,负责整个项目所有对象的创建、初始化、销毁以及维护对象和对象之间的关系。

      • Spring并不属于三层架构中的任何一层
    • SpringMVC(搭建了MVC架构模式)

      完成了三层架构中的表示层

      • 作为 View 层的实现者,完成用户的请求接收功能
      • SpringMVC 的 Controller作为整个应用的控制器,完成用户请求的转发及对用户的响应
      • 并提供了业务逻辑层的接口
    • MyBatis

      作为持久化层的实现者,完成对数据库的增、删、改、查功能

最终实现

不同功能的类放在不同的包下

层和层之间通过接口联系

  • pojo:实体类[M]
  • dao:数据库表(持久化层)[M]
    • AccountDao【接口】
    • impl
      • AccountDaoImpl【实现类】
  • service:纯业务代码(业务逻辑层)[M]
    • AccountService【接口】
    • impl
      • AccountServiceImpl【实现类】
  • web:Controller+view(表现层)[V][C]
  • utils:工具类
  • exceptions:异常

MVC和三层架构总结【动力节点老杜】_第4张图片

待解决

  1. service方法中的事务控制代码->通过动态代理机制解决
  2. 没有完全解决对象和对象之间的依赖关系->使用Spring的IoC容器解决

你可能感兴趣的:(Java,java,mvc,1024程序员节)