2. J2EE
来源:互联网 发布:威锋网 mac office 编辑:程序博客网 时间:2024/06/05 23:02
一、需求
之前的一篇博文讲了最简单的 MVC 案例,其中有很多不成熟的地方。
首先数据库方面,我们可以使用连接池,也可以写一个 DAO 的基类供具体的实现类去继承。
在之前的 Servlet 中,一个 Servlet 只可以处理一种请求。这次我们将使用反射使一个 Servlet 可以同时处理多个请求。
这次的案例完成后如下图所示,可以在三个输入栏中添加信息,点击“search”进行模糊查询,也可以添加新用户,也可以在表格中删除信息。
二、代码解析
通常来说,当我们写一个项目或者案例时,都是从底层开始写起,这样可以逐层测试以便排查错误。下面我们就从底层数据库开始写起。
这次的数据库中,用户 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 的角色。这样一来,整个项目的结构就清晰了很多。
- 2. J2EE
- 2.怎样学好J2EE
- J2EE
- J2EE
- J2EE
- J2EE
- J2EE
- J2ee
- j2ee
- J2EE
- J2EE
- j2ee
- J2EE
- J2EE
- j2ee
- J2EE
- J2EE
- J2EE
- 关于UIImage方向的坑--imageOrientation
- Android控件--RecyclerView
- Hanoi问题递归求解
- JDK源码(FutureTask)——java.util.concurrent(十)
- 定时器与休眠
- 2. J2EE
- Python
- vs打包
- 区分正向代理和反向代理(forward proxy, reverse proxy)
- 【设计模式】观察者模式
- 2017计蒜之道 第三场 腾讯狼人杀(简单)
- k.计软联谊 「游族杯」上海市高校程序设计邀请赛(数论)
- vue事件绑定详解
- android fragment懒加载细致解析