2. J2EE

来源:互联网 发布:威锋网 mac office 编辑:程序博客网 时间:2024/06/05 23:02

一、需求

之前的一篇博文讲了最简单的 MVC 案例,其中有很多不成熟的地方。
首先数据库方面,我们可以使用连接池,也可以写一个 DAO 的基类供具体的实现类去继承。
在之前的 Servlet 中,一个 Servlet 只可以处理一种请求。这次我们将使用反射使一个 Servlet 可以同时处理多个请求。
这次的案例完成后如下图所示,可以在三个输入栏中添加信息,点击“search”进行模糊查询,也可以添加新用户,也可以在表格中删除信息。
完成效果.png

二、代码解析

通常来说,当我们写一个项目或者案例时,都是从底层开始写起,这样可以逐层测试以便排查错误。下面我们就从底层数据库开始写起。
这次的数据库中,用户 ID 是主键,它具有自增的属性,每条记录还有姓名,地址,电话三个子段,其中姓名有唯一的属性。

1. 数据库交互层

第一步:建立数据库连接池 & 获取连接

数据库连接池是指在程序启动时建立足够的数据库连接,并将这些连接组成一个连接池,由程序动态地对池中的连接进行申请,使用,释放。
简单地说,程序启动时就建立了多个数据库连接放在缓存区,用户需要时可以马上取出,使用完之后放回连接池即可,不用每次需要操作时就去做建立数据库连接这种耗时的操作了。

导入 c3p0 数据库连接池以及 mysql-connector 的 jar 包。

在 src 目录下建立一个名为 c3p0-config.xml (必须是这个名字)的配置文件,然后在其中配置用户名,密码,驱动类,数据库 url 等信息,具体如下所示。

<?xml version="1.0" encoding="UTF-8"?><c3p0-config>  <named-config name="mvc_example">    <property name="user">root</property>    <property name="password">root</property>    <property name="driverClass">com.mysql.jdbc.Driver</property>    <property name="jdbcUrl">jdbc:mysql://localhost/lister</property>    <property name="acquireIncrement">5</property>    <property name="initialPoolSize">10</property>    <property name="minPoolSize">10</property>    <property name="maxPoolSize">50</property>    <property name="maxStatements">20</property>     <property name="maxStatementsPerConnection">5</property>  </named-config></c3p0-config>

配置完 c3p0 连接池以后,我们就可以通过它的方法来获取连接了。
新建 JDBCUtils 类,在 static 代码块中新建数据库连接池,这样连接池只会被初始化一次。

public class JDBCUtils {    // dataSource 只有一份且只被初始化一次    private static DataSource dataSource;    static {        dataSource = new ComboPooledDataSource("mvc_example");    }    // 获取数据库连接    public static Connection getConnection() throws SQLException {        return dataSource.getConnection();    }    // 关闭数据库连接    public static void releaseConnection(Connection connection) {        if (connection != null) {            try {                connection.close();            } catch (SQLException e) {                e.printStackTrace();            }        }    }}

至此,我们已经可以通过 JDBCUtils 的静态方法获取和释放连接。

第二步:DAO 基类

定义一个带泛型的通用的数据库基类 DAO< T >,这个 T 就是泛型类,它随着我们操作的实体类的不同而变化。
在它的构造方法中我们可以通过反射将具体的类 T 取出。它也有一系列基础的数据库操作的方法,这个类的派生类都可以直接使用这些操作。

导入 common-dbutils jar 包

dbutils 大大简化了数据库操作,以前我们需要将数据库信息一条一条取出,再把属性一个一个赋值给实体类。
而在 dbutils 中,BeanHandler 可以操作一个实体类对象,直接把一条数据库记录赋值给实体类,它甚至还有 BeanListHandler 用于操作多条记录,程序员只需要一行代码,就可以将数据库的记录取到内存,具体如下所示。

public class DAO<T> {    private QueryRunner queryRunner = new QueryRunner();    private Class<T> clazz;    public DAO() {        // 获得带有泛型的父类        Type superClass = getClass().getGenericSuperclass();        // ParameterizedType 是参数化类型,即泛型        if (superClass instanceof ParameterizedType) {            ParameterizedType parameterizedType = (ParameterizedType) superClass;            // 因为泛型可能有多个,所以使用参数类型数组保存            Type[] typeArgs = parameterizedType.getActualTypeArguments();            if (typeArgs != null && typeArgs.length > 0) {                if (typeArgs[0] instanceof Class) {                    clazz = (Class<T>) typeArgs[0];                }            }        }    }    // 获取到数据库中的某个字段的值    public <E> E getValue(String sql, Object ... args) {        Connection connection = null;        try {            connection = JDBCUtils.getConnection();            return (E) queryRunner.query(connection, sql, new ScalarHandler(), args);        } catch (Exception e) {            e.printStackTrace();        } finally {            JDBCUtils.releaseConnection(connection);        }        return null;    }    // 获取到数据库中的 N 条记录的列表    public List<T> getList(String sql, Object ... args) {        Connection connection = null;        try {            connection = JDBCUtils.getConnection();            return queryRunner.query(connection, sql, new BeanListHandler<>(clazz), args);        } catch (Exception e) {            e.printStackTrace();        } finally {            JDBCUtils.releaseConnection(connection);        }        return null;    }    // 获取到数据库中的一条记录    public T get(String sql, Object ... args) {        Connection connection = null;        try {            connection = JDBCUtils.getConnection();            return queryRunner.query(connection, sql, new BeanHandler<>(clazz), args);        } catch (Exception e) {            e.printStackTrace();        } finally {            JDBCUtils.releaseConnection(connection);        }        return null;    }    // 进行 insert, delete, update 操作    public void update(String sql, Object ... args) {        Connection connection = null;        try {            connection = JDBCUtils.getConnection();            queryRunner.update(connection, sql, args);        } catch (Exception e) {            e.printStackTrace();        } finally {            JDBCUtils.releaseConnection(connection);        }    }}

DAO 具体实现

在建立具体的数据库操作类之前,我们先把对应数据库信息的实体类写出来。
实体类没什么好说的,但要注意第二个构造方法中,我们不传入 ID,因为数据库会自动生成。

public class Customer {    private Integer id;    private String name;    private String address;    private String phone;    public Customer() {        super();    }    public Customer(String name, String address, String phone) {        super();        this.name = name;        this.address = address;        this.phone = phone;    }    public Integer getId() { return 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 "Customer [id=" + id + ", name=" + name + ", address=" + address + ", phone=" + phone + "]";    }}

对于具体的数据库操作类,我们可以先定义一个接口。

public interface CustomerDAO {    public List<Customer> fuzzySearch(SearchInfo searchInfo);    public List<Customer> getAll();    public void add(Customer customer);    public Customer get(Integer id);    public void delete(Integer id);    public long getNameCount(String name);}

接下来就是具体的 DAO 实现类了。
第一个模糊查询方法可以先不看,其中的 SearchInfo 实体类在下面给出。
最后一个方法用于检测数据库中某个名字是否已经存在,因为数据库中的名字是唯一的,在我们新建用户时,不能再使用已经存在的名字。

public class CustomerDAOImpl extends DAO<Customer> implements CustomerDAO {    // 通过表单提交的信息进行模糊查询    @Override    public List<Customer> fuzzySearch(SearchInfo searchInfo) {        String sql = "select id, name, address, phone from customer "                + "where name like ? and address like ? and phone like ?";        return getList(sql, searchInfo.getName(), searchInfo.getAddress(), searchInfo.getPhone());    }    // 获取所有的信息    @Override    public List<Customer> getAll() {        String sql = "select id, name, address, phone from customer";        return super.getList(sql);    }    @Override    public void add(Customer customer) {        String sql = "insert into customer(name, address, phone) values (?, ?, ?)";        update(sql, customer.getName(), customer.getAddress(), customer.getPhone());    }    @Override    public Customer get(Integer id) {        String sql = "select id, name, address, phone from customer where id = ?";        return get(sql, id);    }    @Override    public void delete(Integer id) {        String sql = "delete from customer where id = ?";        update(sql, id);    }    @Override    public long getNameCount(String name) {        String sql = "select count(id) from customer where name = ?";        return getValue(sql, name);    }}

模糊查询

模糊查询使用的 SearchInfo 实体类,如果你了解数据库语句,结合上面的方法就能看懂。

public class SearchInfo {    private String name;    private String address;    private String phone;    public SearchInfo(String name, String address, String phone) {        super();        this.name = name;        this.address = address;        this.phone = phone;    }    public String getName() {        if (name == null) {            return "%%";        } else {            return "%" + name + "%";        }    }    public String getAddress() {        if (address == null) {            return "%%";        } else {            return "%" + address + "%";        }    }    public String getPhone() {        if (phone == null) {            return "%%";        } else {            return "%" + phone + "%";        }    }}

到此,数据库部分全部完成。

2. Servlet & jsp

开头我们说过,我们可以通过反射让一个 Servlet 处理多个请求,那么如何做到呢?
Servlet 提供了一种 <url-pattern>,让我们可以在 jsp 中使用 *.xxx 的请求来匹配同一个 Servlet。
我们新建一个 Servlet,并将 <url-pattern> 配置为 *.customer,如下所示。

@WebServlet("*.customer")public class CustomerServlet extends HttpServlet {    private static final long serialVersionUID = 1L;    private CustomerDAO dao = new CustomerDAOImpl();    public CustomerServlet() {        super();    }    protected void doGet(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        doPost(request, response);    }    protected void doPost(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        // ......    }}

新建 index.jsp,body 标签下的代码如下所示。
我们使用 queryInfo.customer 的请求来进行查询,使用 deleteInfo.customer 的请求来进行删除。这些请求最终都会被匹配到上面的 CustomerServlet 中。

<body>    <form action="queryInfo.customer" method="post">        <table>            <tr>                <td>姓名:</td>                <td><input type = "text" name = "name"/></td>            </tr>            <tr>                <td>地址:</td>                <td><input type = "text" name = "address"/></td>            </tr>            <tr>                <td>电话:</td>                <td><input type = "text" name = "phone"/></td>            </tr>            <tr>                <td><input type = "submit" value = "search"/></td>                <td><a href = "addcustomer.jsp">添加新客户</a></td>            </tr>        </table>    </form>    <br><br>    <% List<Customer> list = (List<Customer>)request.getAttribute("customers"); %>    <table border = "1" cellpadding = "10" cellspacing = "0">        <tr>            <td>用户ID</td>            <td>姓名</td>            <td>地址</td>            <td>电话</td>            <td>删除操作</td>        </tr>        <%             if (list != null && list.size() > 0) {                for (int i = 0; i < list.size(); i++) {         %>        <tr>            <td><%=list.get(i).getId() %></td>            <td><%=list.get(i).getName() %></td>            <td><%=list.get(i).getAddress() %></td>            <td><%=list.get(i).getPhone() %></td>            <td><a href="deleteInfo.customer?id=<%=list.get(i).getId() %>" class="delete">删除</a></td>        </tr>        <% } }%>    </table></body>

新增用户的 addcustomer.jsp 如下所示,很显然,它的请求 addInfo.customer 也会由 CustomerServlet 处理。

<body>    <%        Object msg = request.getAttribute("message");        if (msg != null) {    %>        <font color="red"><%=msg %></font>    <% } %>    <form action="addInfo.customer" method="post">        <table>            <tr>                <td>姓名:</td>                <td><input type = "text" name = "name"                     value = "<%=request.getParameter("name") == null ? "" : request.getParameter("name")%>"/></td>            </tr>            <tr>                <td>地址:</td>                <td><input type = "text" name = "address"                    value = "<%=request.getParameter("address") == null ? "" : request.getParameter("address")%>"/></td>            </tr>            <tr>                <td>电话:</td>                <td><input type = "text" name = "phone"                    value = "<%=request.getParameter("phone") == null ? "" : request.getParameter("phone")%>"/></td>            </tr>            <tr>                <td><input type = "submit" value = "添加用户"/></td>            </tr>        </table>    </form></body>

在 CustomerServlet 中,我们将通过请求的名字来辨别不同的请求。
比如接收到 addInfo.customer 请求时 ,我们抽取其中的 addInfo 字符串,再定义一个与该字符串同名的 addInfo 方法。
为什么需要同名的方法呢?
我们抽取出 addInfo 字符串时,可以通过反射的 invoke(…) 寻找 addInfo 方法并执行,只有方法名与该字符串相同,方法才会被正确找到并执行。

CustomerServlet 完整代码如下:

@WebServlet("*.customer")public class CustomerServlet extends HttpServlet {    private static final long serialVersionUID = 1L;    private CustomerDAO dao = new CustomerDAOImpl();    public CustomerServlet() {        super();    }    protected void doGet(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        doPost(request, response);    }    protected void doPost(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        request.setCharacterEncoding("utf-8");        response.setCharacterEncoding("utf-8");        response.setContentType("text/html;charset=utf-8");        // 获得请求的 ServletPath 并抽取其中的方法名字段        String methodName = request.getServletPath();        methodName = methodName.substring(1, methodName.length() - 9);        // 通过方法名找到方法并调用        try {            Method method = getClass().getDeclaredMethod(methodName,                     HttpServletRequest.class, HttpServletResponse.class);            method.invoke(this, request, response);        } catch (Exception e) {            e.printStackTrace();        }    }    /**     * 查询指定的客户信息     * @param request     * @param response     */    public void queryInfo(HttpServletRequest request, HttpServletResponse response)             throws ServletException, IOException {        // 获取表单提交的参数        // 通过该参数进行模糊查询        String name = request.getParameter("name");        String address = request.getParameter("address");        String phone = request.getParameter("phone");        SearchInfo searchInfo = new SearchInfo(name, address, phone);        List<Customer> list = new ArrayList<>();        list = dao.fuzzySearch(searchInfo);        // 将查询到的信息放入请求中        // 转发到 jsp 页面进行显示        request.setAttribute("customers", list);        request.getRequestDispatcher("/index.jsp").forward(request, response);    }    /**     * 添加客户信息     * @param request     * @param response     */    public void addInfo(HttpServletRequest request, HttpServletResponse response)             throws ServletException, IOException {        String name = request.getParameter("name");        String address = request.getParameter("address");        String phone = request.getParameter("phone");        // 如果用户名被占用        if (dao.getNameCount(name) > 0) {            request.setAttribute("message", "用户名" + name + "已被占用,请重新选择!");            request.getRequestDispatcher("/addcustomer.jsp").forward(request, response);            return;        }        // 如果用户名没有被占用        Customer customer = new Customer(name, address, phone);        dao.add(customer);        response.sendRedirect("success.jsp");    }    /**     * 删除客户信息     * @param request     * @param response     */    public void deleteInfo(HttpServletRequest request, HttpServletResponse response)             throws ServletException, IOException {        try {            int id = Integer.parseInt(request.getParameter("id"));            dao.delete(id);        } catch (Exception e) {            e.printStackTrace();        }        // 请求重定向        response.sendRedirect("queryInfo.customer");    }}

三、总结

这个案例比上个案例更能凸显 MVC 的层次化设计。
这里的数据库交互层的设计很好的使用了面向对象的思想,其中的 DAO 基类在任何的项目中都可以复用,只需要重写一个继承于它的 DAO 类即可。
而这个案例中的控制器 Servlet 可以执行所有的请求,更好地充当了 Controller 的角色。这样一来,整个项目的结构就清晰了很多。

原创粉丝点击