08 MVC设计模式案例(上)

本文介绍了一种基于JavaWeb的MVC设计模式的实际应用案例,包括需求分析、技术选型、架构设计及实现细节,特别关注了DAO层的设计与实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MVC设计模式案例(上)

标签(空格分隔): JavaWeb


需求

因为很简单,所以就不写需求分析了。主要有一下几点:

  1. 创建一个数据表,包含id,name,address,phone
  2. 可以在页面上对整张表进行查询
  3. 可以创建一个新的条目
  4. 可以对原有的数据进行修改和删除
  5. 在创建或修改的情况下,验证用户名是否已经被使用,并给出提示
  6. 支持模糊查询

分析

使用到的技术

  • MVC 设计模式:JSP、Servlet,POJO
  • 数据库使用 MySQL
  • 连接数据库需要使用 C3P0 数据库连接池
  • JDBC 工具采用 DBUtils
  • 页面上的提示操作使用 jQuery

技术难点

  • 多个请求如何使用一个 Servlet ?
  • 如何模糊查询 ?
  • 如何在创建 或 修改 的情况下,验证用户名是否已经被使用,并给出提示

架构

此处输入图片的描述

MySQL

Model:用于存储数据

Model:DAO
  1. 获取数据库连接
  2. 执行CRUD操作
  3. 返回结果

注意:
1. 不能跨层访问
2. 只能自上向下以来,而不能自下向上依赖

Controller:Servlet
  1. 获取请求信息:获取请求参数(以供与JSP进行交互)
  2. 验证请求参数的合法性:验证失败需要返回页面并给出提示信息
  3. 把请求参数封装为一个JavaBean
  4. 调用DAO的方法获取返回的结果
  5. 把返回结果放入把request中
  6. 响应页面:转发、重定向
View:JSP
  1. 呈现数据:从request中获取Servlet中放入的属性
  2. 接收用户的输入
  3. 编写JS代码给出对应的提示

DAO层设计与实现

设计思路

  1. 创建数据表
  2. 创建一个实体类Customer
  3. 创建一个JDBCUtils,使用c3p0数据库连接池
  4. 创建一个DAO类,用来封装基本的CRUD操作,具体通过DBUtils工具来实现
  5. 创建一个CustomerDAO接口,声明了具体业务相关的方法
  6. 创建CustomerDAO接口的实现类CustomerDAOJDBCImpl,继承了DAO类。通过DAO类中的方法来实现CustomerDAO的方法

创建数据表

Create table customers(
    id int primary key auto_increment,
    name varchar(30) not null unique,
    address varchar(30),
    phone varchar(30)
);

为 name 字段添加唯一约束:

alter table customers add constraint name_uk unique(name);

Customer实体类

在domain包下封装一个实体类

package mvcapp.domain;

/**
 * Created by japson on 12/24/2017.
 */
public class Cusromer {
    private Integer id;
    private String name;
    private String address;
    private String phone;

    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }
    public String getName() { return name; 
    public void setName(String name) { this.name = name; }
    public String getAddress() { return address; }
    public void setAddress(String address) { this.address = address; }
    public String getPhone() { return phone; }
    public void setPhone(String phone) { this.phone = phone; }
    @Override
    public String toString() {
        return "Cusromer{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", phone='" + phone + '\'' +
                '}';}
}

JDBCUtils

JDBC操作的工具类,使用c3p0数据库连接池。有创建连接和释放连接的方法。我们需要导入两个jar包:c3p0-0.9.5.2.jarmchange-commons-java-0.2.11.jar。并且在src文件夹下建立c3p0-config.xml配置文件(参见数据库连接池)。

我们通过c3p0数据源来实现JDBCUtils工具类

package mvcapp.db;

/**
 * Created by japson on 12/24/2017.
 */

import com.mchange.v2.c3p0.ComboPooledDataSource;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * JDBC操作的工具类
 */
public class JDBCUtils {

    /**
     * 指定数据源为c3p0的配置文件
     */
    private static DataSource dataSource = null;

    static {
        dataSource = new ComboPooledDataSource("mvcapp");
    }

    /**
     * 返回数据源的一个Connection对象
     * @return
     */
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    /**
     * 关闭Connection连接
     * @param connection
     */
    public static void releaseConnection(Connection connection) {
        try {
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

    }
}

DAO类

DAO是一个数据访问对象。创建了一个DAO类,该类封装了基本的对数据库进行增删改查的操作方法,以供子类继承用。当前的DAO没有事务,直接在方法中获取数据库连接。

该类的参数是T,当前DAO处理的实体类的类型是什么。使用反射技术进行解决。详见DBUtils编写DAO

整个的DAO类采取DBUtils解决方案。导入DBUtils的jar包

package mvcapp.dao;

/**
 * Created by japson on 12/24/2017.
 */

import mvcapp.db.JDBCUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;


import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

/**
 * 封装了基本的CRUD的方法,以供子类继承使用
 * 当前DAO没有事务,直接在方法中获取数据库连接。
 * 整个DAO采取DBUtils解决方案
 * @param <T> 当前DAO处理的实体类的类型是什么
 */
public class DAO<T> {

    //DBUtils的Queryrunner对象
    private QueryRunner queryRunner = new QueryRunner();

    //用来表示泛型的类型
    public Class<T> clazz;

    //在构造函数中以反射的方式
    public DAO() {
        //获取此Class所表示的实体的父类的泛型参数类型
        Type superClass = getClass().getGenericSuperclass();
        if (superClass instanceof ParameterizedType) {
            //将父类的泛型参数类型,强转为参数化类型
            ParameterizedType parameterizedType = (ParameterizedType) superClass;
            //获取参数化类型的数组
            Type[] typeArgs = parameterizedType.getActualTypeArguments();
            if (typeArgs[0] instanceof Class) {
                //取数组的第一个值为需要传入泛型的类型
                clazz = (Class<T>) typeArgs[0];
            }
        }
    }

    /**
     * 该方法封装了INSERT,DELETE,UPDATE操作
     * @param sql SQL语句
     * @param args 填充SQL语句的占位符
     */
    public void update(String sql,Object... args) {
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            queryRunner.update(connection,sql,args);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.releaseConnection(connection);
        }
    }

    /**
     * 返回对应的T的一个实体类的对象
     */
    public T get(String sql,Object... args) {
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            return queryRunner.query(connection,sql, new BeanHandler<T>(clazz),args);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.releaseConnection(connection);
        }
        return null;
    }

    /**
     * 返回T所对应的List
     */
    public List<T> getForList(String sql,Object... args) {
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            return queryRunner.query(connection,sql, new BeanListHandler<T>(clazz),args);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.releaseConnection(connection);
        }
        return null;
    }

    /**
     * 返回一个字段的值,例如返回某一条记录的customerName,或返回数据表中有多少条记录等
     */
    public <E> E getForValue(String sql,Object... args) {
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            return (E) queryRunner.query(connection,sql, new ScalarHandler(),args);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.releaseConnection(connection);
        }
        return null;
    }
}

CustomerDAO接口

CustomerDAO接口中的方法取决于业务的具体方法:
1. 显式数据库,获取数据库所有的信息,即返回一个Customer的List
2. 添加一个新的Customer并保存
3. 更新一条数据的时候需要先根据id将该数据查询并显示出来
4. 删除数据,根据id
5. 在添加的时候,要看跟要添加的数据的name一样的数据有几个,如果不是0个,则不能添加

package mvcapp.dao;

import mvcapp.domain.Customer;

import java.util.List;

/**
 * Created by japson on 12/24/2017.
 */
public interface CustomerDAO {
    List<Customer> getAll();

    void save(Customer cusromer);

    Customer get(Integer id);

    void delete(Integer id);

    /**
     * 返回和name相等的记录
     * @param name
     * @return
     */
    long getCountWithName(String name);

    void update(Customer customer);
}

接口的实现类CustomerDAOJDBCImpl

继承DAO<Cusromer>类实现CustomerDAO接口

package mvcapp.dao.impl;

import mvcapp.dao.CustomerDAO;
import mvcapp.dao.DAO;
import mvcapp.domain.Customer;
import java.util.List;

public class CustomerDAOJDBCImpl extends DAO<Customer> implements CustomerDAO {
    @Override
    public List<Customer> getAll() {
        String sql = "SELECT id,NAME,address,phone FROM webcustomers";
        return getForList(sql);
    }

    @Override
    public void save(Customer cusromer) {
        String sql = "INSERT INTO webcustomers(NAME,address,phone) VALUES(?,?,?)";
        update(sql,cusromer.getName(),cusromer.getAddress(),cusromer.getPhone());
    }

    @Override
    public Customer get(Integer id) {
        String sql = "SELECT id,name,address,phone FROM webcustomers WHERE id = ?";
        return get(sql,id);
    }

    @Override
    public void delete(Integer id) {
        String sql = "DELETE FROM webcustomers WHERE id = ?";
        update(sql,id);
    }

    @Override
    public long getCountWithName(String name) {
        String sql = "SELECT COUNT(id) FROM webcustomers WHERE name = ?";
        return getForValue(sql,name);
    }

    @Override
    public void update(Customer customer) {
        String sql = "UPDATE webcustomers SET name = ?,address = ?,phone  = ? WHERE id = ?";
        update(sql,customer.getName(),customer.getAddress(),customer.getPhone(),customer.getId());
    }
}

技术问题:多个请求使用同一个Servlet

我们要提出一个技术问题:如何多个请求使用同一个Servlet。

假设我们现在有三种请求操作:添加请求,查询请求,删除请求。如果每一个请求操作对应一个Servlet,那么太繁琐了。而如果我们要使用同一个Servlet,那么我们还需要区分三种操作的不同。那么我们首先提出一种方法:

在路径中添加request参数

  1. 获取method请求参数的值
  2. 根据method的值调用对应的方法

首先我们对应三种请求方式,给出一个连接下的三个不同的参数:

<a href="customerServlet?method=add">add</a>

<a href="customerServlet?method=query">query</a>

<a href="customerServlet?method=delete">delete</a>

然后在CustomerServlet中:

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String method = request.getParameter("method");

        switch (method) {
            case "add" : add(request,response); break;
            case "query" : query(request,response); break;
            case "delete" : delete(request,response); break;
        }
    }

这样我们就可以实现了,但是这个方法还有一些不足:
1. 当添加一个请求时,需要在Servlet中修改两处代码
2. url中使用method=xxx的方法暴露了要调用的方法,不私密

更好的解决方案

我们要给出一个更通用的方案,对于url中暴露的参数和方法,我们使用一种加扩展名的servletPath。跳转到一个通用的Servlet中然后我们对servletPath进行处理,得到方法名。然后利用反射,通过方法名来获得对应的方法。具体方法如下:

  1. 获取servletPath:/addCustomer.do(自定义一个扩展名)
  2. 更改servlet的urlPatterns为*.do,该映射可以接收一切以.do结尾的请求
  3. 去除/和.do,得到方法名
  4. 利用反射调用servletPath对应的方法
  5. 创建对应的方法

具体代码如下:

<a href="addCustomer.do">add</a>

<a href="query.do">query</a>

<a href="deleteCustomer.do">delete</a>

可见:在点击之后会转入到addCustomer.do这样的连接。我们就要在servlet的注解中更改servlet的urlPatterns:

@WebServlet(name = "CustomerServlet",urlPatterns = "*.do")

这样,只要我们点击带有.do拓展名的路径,就会转到这用一个servlet中。
然后我们进行如下的操作:
1. 通过getServletPath()方法获取ServletPath: /xxxx.do 形式
2. 通过substring取出/.do,得到xxxx字符串
3. 利用反射,通过指定方法名,即字符串xxxx,获取叫这个名的方法,为Method类型的
4. 利用反射调用对应的方法

 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取ServletPath: /xxxx.do 形式
        String servletPath = request.getServletPath();

        //取出/和.do,得到xxxxx字符串
        String methodName = servletPath.substring(1);
        methodName = methodName.substring(0,methodName.length()-3);
        System.out.println(methodName);

        //利用反射,通过字符串xxxx,获取叫这个名的方法
        try {
            //getMethod()方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象。
            Method method = getClass().getDeclaredMethod(methodName,HttpServletRequest.class,HttpServletResponse.class);
            //利用反射调用对应的方法
            //可以在不知道具体类的情况下,根据配置字符串去调用一个类的方法。
            method.invoke(this,request,response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Servlet之查询操作

查询整张表

查询操作涉及了MVC的整个的流程query.do --> doPost --> query -->JSP

通过上面的方法,我们将各种操作请求对应成了一个Servlet,因此我们可以在Servlet中写具体的方法。其中所有的操作都是基于DAO实现的:

private CustomerDAO customerDAO = new CustomerDAOJDBCImpl();

Servlet中的query()方法的操作步骤如下:

  1. 调用CustomerDAO的getAll()方法得到Customer的集合
  2. 把Customer的集合作为request的属性
  3. 转发页面到index.jsp(不能使用重定向)
private void query(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. 调用CustomerDAO的getAll()方法,得到Customer的集合
        List<Customer> customers = customerDAO.getAll();
        //2. 把Customer的集合放入request中
        request.setAttribute("customers",customers);
        //3. 转发页面到index.jsp
        request.getRequestDispatcher("/index.jsp").forward(request,response);
    }

在JSP中的操作为:
1. 获取request中的customer属性
2. 遍历显示

模糊查询

我们下面要把查询操作变为模糊查询,即:仅输入部分条件即可进行,而之前的查询整张表的操作可以看作是模糊查询中对的无条件查询。

根据传入的name,address,phone进行模糊查询。其关键在于SQL语句使用条件查询。举一个例子: name: a、address: b、phone: 3 则 SQL 语句的样子为: SELECT id, name, address, phone FROM customers WHERE name LIKE ‘%a%’ AND address LIKE ‘%b%’ AND phone LIKE ‘%3%’

那么我们具体写代码的时候,我们从底层DAO开始考虑。目前有三个DAO,一个是DAO类,封装了基本操作;一个是CustomerDAO接口,是提供了业务相关的操作的声明;CustomerDAOJDBCImpl是一个实现类,继承了DAO类实现了CustomerDAO方法。因此我们应该在CustomerDAO接口中声明模糊查询的方法,然后在CustomerDAOJDBCImpl类中实现它。

但是在此之前我们要创建一个CriteriaCustomer类,用来封装查询条件,也就是name,address等属性。那么我们为什么要创建一个新的类,而不用之前的domain类呢?因为CriteriaCustomer类是专门为查询条件而服务的:我们在模糊查询的时候,有时不仅是对实体对象的属性进行查询,还要对属性的某一个区间进行查询。例如我们在购买手机时,除了品牌等属性,还有会价格区间,这是和domain中的属性不同的,因此需要一个单独的类。

我们在CustomerDAO中封装了查询条件:

//返回满足查询条件的List
 List<Customer> getForListWithCriteriaCustomer(CriteriaCustomer criteriaCustomer);

通过上面的一个例子我们知道查询语句是这样的:SELECT id, name, address, phone FROM customers WHERE name LIKE ‘%a%’ AND address LIKE ‘%b%’ AND phone LIKE ‘%3%’,那么我们从页面获取的信息是a,b,3,这样的,我们还需要将其拼接%a%。我们可以把这一步放到CriteriaCustomer类的Getter方法中操作:

    public String getName() {
        if (name == null)
            name = "%%";
        else
            name = "%" + name + "%";
        return name;
    }

这样我们就可以直接在实现方法中使用getName()方法而不需要进行拼接%%了:

public List<Customer> getForListWithCriteriaCustomer(CriteriaCustomer criteriaCustomer) {
        String sql = "SELECT id,name,address,phone FROM webcustomers WHERE " +
                "name LIKE ? AND address LIKE ?";
        //在CriteriaCustomer中的getter方法中返回带有"%%"的字符串
        return getForList(sql,criteriaCustomer.getName(),criteriaCustomer.getAddress(),criteriaCustomer.getPhone());
    }

之后我们需要修改Servlet:

private void query(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取模糊查询的请求参数
        String name = request.getParameter("name");
        String address = request.getParameter("address");
        String phone = request.getParameter("phone");
        //把请求参数封装为一个CriteriaCustomer对象
        CriteriaCustomer criteriaCustomer = new CriteriaCustomer(name,address,phone);

        //1. 调用CustomerDAO的getForListWithCriteriaCustomer()方法,参数为封装了请求参数的对象,得到Customer的集合
        List<Customer> customers = customerDAO.getForListWithCriteriaCustomer(criteriaCustomer);
        //2. 把Customer的集合放入request中
        request.setAttribute("customers",customers);
        //3. 转发页面到index.jsp
        request.getRequestDispatcher("/index.jsp").forward(request,response);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值