1.Dbs封装
每一个数据库都对应着一个dao 每个dao势必存在公共部分 我们需要将公共部分抽取出来 封装成一个工具类 保留个性化代码即可
- 我们的工具类一般命名为xxxs 比如Strings 就是字符串相关的工具类 而工具类 我们将其放置于util包中
- 我们以是否有<T>区分泛型方法和非泛型方法
- 建议先创建语句、传递参数之后 在执行语句 即不要着急将ResultSet放置在try-with-resources中
- 你可以通过live template快速生成一些代码模板 比如itar可以帮助我们快速生成遍历数组元素的模板 我们也可以自定义动态模板
- dao中需要传递一段个性化代码(即ResultSet->bean)给dbs 要求dbs存在一个接口(用于将数据库中每一行ResultSet映射为bean)用于接收代码 同时可以设置一个row 用于显示当前所在的行号(数据库中的行号一般从0开始计数 列号一般从1开始计数)
- 既然存在dbs 那么以前的三个常量URL、USERNAME、PASSWORD就可以作为公共部分存放在dbs中
- 代码实现
- Dbs.java
package com.axihh.util; import com.axihh.Constants; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; public class Dbs { // 常量 public static final String URL = "jdbc:mysql://localhost:3306/crm?serverTimezone=UTC"; public static final String USERNAME = "root"; public static final String PASSWORD = "root"; // 定义一个方法 用于存放数据库保存的公共代码 public static boolean update(String sql, Object ...args) { try { // 注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); try(Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD); PreparedStatement pstmt = conn.prepareStatement(sql)) { // 设置参数 for (int i = 0; i < args.length; i++) { pstmt.setObject(i + 1, args[i]); } // 执行sql语句 return pstmt.executeUpdate() > 0; } }catch(Exception e) { e.printStackTrace(); // 执行到此 说明没有任何行受到影响 return false; } } // 定义一个方法 用于存放用户信息集合获取的公共代码 public static <T> List<T> list(String sql, RowMapper<T> rm, Object ...args) { try { // 注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); try(Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD); PreparedStatement pstmt = conn.prepareStatement(sql)) { // 建议不要将resultset写入try-with-resources中 因为我们要保持顺序的先后性 先创建语句 在执行语句 保持这样的顺序 // 设置参数 for (int i = 0; i < args.length; i++) { pstmt.setObject(i + 1, args[i]); } // 执行sql语句 ResultSet rs = pstmt.executeQuery(); List<T> array = new ArrayList<>(); // resultset映射bean for(int row = 0; rs.next(); row++) { array.add(rm.map(rs, row)); } return array; } }catch(Exception e) { e.printStackTrace(); // 如果出现异常 证明返回值为空 return null; } } // 定义一个接口 用于接收resultset映射bean的代码 具体就是数据库中的每一行对应的resultset映射为bean public interface RowMapper<T> { public T map(ResultSet rs, int row) throws Exception; } }
- CustomerDao.java
package com.axihh.dao; import com.axihh.bean.Customer; import com.axihh.util.Dbs; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import com.axihh.util.Dbs.RowMapper; import java.util.List; public class CustomerDao { public boolean save(Customer customer){ String sql = "INSERT INTO customer(name, age, height) VALUES (?, ?, ?)"; return Dbs.update(sql, customer.getName(), customer.getAge(), customer.getHeight()); } public List<Customer> list() { String sql = "SELECT id, name, age, height FROM customer"; return Dbs.list(sql, (rs, row) -> { Customer customer = new Customer(); customer.setId(rs.getInt("id")); customer.setName(rs.getString("name")); customer.setAge(rs.getInt("age")); customer.setHeight(rs.getDouble("height")); System.out.println(row + "_" + customer); return customer; }); } }
2.配置文件
虽然 我们将dao的公共代码抽取到dbs中以期简化了dao 但是仍然存在着一些问题 常量(比如PASSWORD)动态修改时 需要打开源码修改 再重新编译打包 显然很麻烦
- 解决方案:利用配置文件帮助我们将一些需要动态修改的值放入配置文件中 以代替在写死的Java代码
1.常见的配置文件
- properties:比较单一 适合量小、简单的配置
- key、value的分隔符是=、:
- 建议分隔符左右不要留空格
- #、!开头是单行注释
- 可以用\来连接多行内容(内容太多一行中无法显示)
- xml:比较灵活 适合量大、复杂的配置
2.properties解决方案
- 首先新建一个properties文件 具体就是在src下右键选择resource bundle
- 编写properties内容 以键值对的形式定义三个动态修改的变量(不包含任何的修饰符和字符串双引号 仅仅只是键和值)
- 读取properties内容 根据键找值的方式获取相关值写入Dbs中对应的变量 由于变量支持动态修改 所以不在通过final修饰 并且变量不再是常量 所以无需大写
- 我们将读取properties 写入三个动态修改的变量代码内置于静态初始化块中 表明程序运行过程中仅需进行一次赋值
- 调用class获取当前类的字节码文件 然后通过getClassLoader获取加载该字节码文件的类加载器 然后通过getResourceAsStream获取资源文件 并且通过字节输入流读取该资源文件
- 定义Properties对象 将资源文件加载到该对象中
- 通过键找值的方式 完成对Dbs文件中三个动态修改变量的赋值操作
- 注意 其中InputStream属于资源 需要关闭以释放 通过try-with-resources将其自动关闭
package com.axihh.util;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
public class Dbs {
// 常量
public static String url;
public static String username;
public static String password;
static {
try(// 通过当前类获取字节码文件 然后通过字节码文件获取类加载器 然后通过类加载器获取资源文件 并且通过字节输入流读取该资源文件
InputStream is = Dbs.class.getClassLoader().getResourceAsStream("db.properties")) {
// 创建properties对象 然后将字节输入流读取的内容加载进该对象中
Properties properties = new Properties();
properties.load(is);
// 然后通过键找值的方式获取指定变量对应的值
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
} catch (IOException e) {
e.printStackTrace();
}
}
// 定义一个方法 用于存放数据库保存的公共代码
public static boolean update(String sql, Object ...args) {
try {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
try(Connection conn = DriverManager.getConnection(url, username, password);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
// 设置参数
for (int i = 0; i < args.length; i++) {
pstmt.setObject(i + 1, args[i]);
}
// 执行sql语句
return pstmt.executeUpdate() > 0;
}
}catch(Exception e) {
e.printStackTrace();
// 执行到此 说明没有任何行受到影响
return false;
}
}
// 定义一个方法 用于存放用户信息集合获取的公共代码
public static <T> List<T> list(String sql, RowMapper<T> rm, Object ...args) {
try {
// 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
try(Connection conn = DriverManager.getConnection(url, username, password);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
// 建议不要将resultset写入try-with-resources中 因为我们要保持顺序的先后性 先创建语句 在执行语句 保持这样的顺序
// 设置参数
for (int i = 0; i < args.length; i++) {
pstmt.setObject(i + 1, args[i]);
}
// 执行sql语句
ResultSet rs = pstmt.executeQuery();
List<T> array = new ArrayList<>();
// resultset映射bean
for(int row = 0; rs.next(); row++) {
array.add(rm.map(rs, row));
}
return array;
}
}catch(Exception e) {
e.printStackTrace();
// 如果出现异常 证明返回值为空
return null;
}
}
// 定义一个接口 用于接收resultset映射bean的代码 具体就是数据库中的每一行对应的resultset映射为bean
public interface RowMapper<T> {
public T map(ResultSet rs, int row) throws Exception;
}
}
- 细节
我们将properties文件创建在src中 目的在于经过编译打包这一系列操作之后 该资源文件被内置于classes目录中 经过查询 可知该目录中正好是存放src下字节码文件的位置 类加载器将字节码文件加载到JVM中 前提肯定是知道classes目录的位置 那么我们就可以利用这一点通过类加载器找到classes中的properties文件
3.Servlet调整
按照之前的逻辑 我们访问ListServlet时的请求路径为/crm/list 但是这并不能够真正透露我们操作的数据库对象 因为数据库中可能存在多个表格 /crm/list仅仅只能够表示展示表格信息 并不能说明展示的是哪一张表格的信息 更具体的行为例如/crm/customer/list 即展示用户表格的信息
这时候 可能会有人提出这样的解决方案 即某一张表格的某一个操作对应一个Servlet/WebServlet 比如:customer表格的展示用户信息操作对应CustomerListServlet 并且WebServlet为"/customer/list" 但是呢 数量上看 未免需要太多的Servlet 能不能有更优解以优化Servlet的数量
更优的做法是某一个张表格仅仅对应一个Servlet/WebServlet 比如:customer表格对应CustomerServlet 而当请求路径为/crm/customer/list时 访问customer对应servlet中的list方法 用于展示用户信息
而这种做法则需要使用Java中的反射技术 通过请求路径中的操作名称来反射调用servlet中的对应操作
请求路径的表现形式有两种:1.URI(展示请求请求路径的部分路径) 2.URL(展示请求路径的完整路径)
知识点回顾1:客户端发送请求给服务器之后 服务器首先会调用service方法 然后再由service方法决定调用doGet还是doPost方法
获取Class对象的两种方法:1.getClass() 实例方法 只能通过实例访问 2.xxx.class 静态方法 实例和类都可以访问
Servlet中定义main方法不能运行的原因在于Servlet的访问是通过Tomcat提供的一系列接口init、service、destroy完成的
getMethod/getMethods和getDeclaredMethod/getDeclaredMethods的区别在于前者用于获取当前类和父类中的(所有)public方法 而后者则用于获取当前类中声明的(所有)方法(包含private方法)
知识点回顾2
Servlet->Dao->Dbs的过程:ListServlet/SaveServlet中均存在数据库访问的代码 可以将其抽取到专门存放数据库访问的Dao类 其中 list方法存放ListServlet中数据库访问的代码 save方法存放SaveServlet中数据库访问的代码 一个数据库对应着一个Dao类 不同的数据库存在着公共代码和个性化代码 我们将公共代码抽取出来存放到Dbs中 其中 list方法用于存放Dao中list的公共代码 save方法用于存放Dao中save的公共代码
ListServlet的总体逻辑:注册驱动 连接数据库 创建语句 定义sql语句 执行sql语句 rs转换为bean 将bean组成的集合存放到attribute 转化到展示页面 其中 抽取到Dao的部分为注册驱动到返回集合的过程 抽取到Dbs的部分为sql语句定义以及rs转换为bean这种个性化代码以外的代码
SaveServlet的总体逻辑:请求参数转换为bean 注册驱动 连接数据库 创建语句 定义sql语句 执行sql语句 根据返回值执行不同的跳转操作 如果保存成功 则重定向到展示页面 保存失败 则转发到失败页面 其中 抽取到Dao的部分为注册驱动到返回保存结果的过程 而抽取到Dbs的部分为sql语句定义这种个性化代码以外的代码
- 接下来我们真正的开始优化我们的Servlet代码
- CustomerServlet.java
package com.axihh.servlet; import com.axihh.bean.Customer; import com.axihh.dao.CustomerDao; import com.axihh.util.Dbs; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @WebServlet("/customer/*") public class CustomerServlet extends HttpServlet { // 定义一个CustomerDao对象 用于访问其中的save、list方法 private final CustomerDao cd = new CustomerDao(); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { // 第一步 先获取指定路径 String uri = request.getRequestURI(); // 第二步 对指定路径进行分割操作 String[] cmps = uri.split("/"); // 第三步 获取cmps中的操作名称部分 String methodName = cmps[cmps.length - 1]; // 第四步 利用Java中的反射技术 根据methodName调用该类中的对应方法 Method method = CustomerServlet.class.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); // 第五步 调用反射得到的结果 method.invoke(this, request, response); } catch (Exception e) { e.printStackTrace(); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } // 提供一个方法 用于模拟SaveServlet 由于请求路径中需要访问 所以直接公有化 public void save(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取客户端发送的用户信息 并且封装为bean Customer customer = new Customer(); customer.setName(request.getParameter("name")); customer.setAge(Integer.parseInt(request.getParameter("age"))); customer.setHeight(Double.parseDouble(request.getParameter("height"))); // 如果保存成功的话 就跳转到展示页面 如果保存失败的话 那么就跳转到失败页面 if(cd.save(customer)) { // 执行的是重定向操作 response.sendRedirect("/crm5/customer/list"); } else { // 执行的是转发操作 request.getRequestDispatcher("/page/error.jsp"); } } // 提供一个方法 用于模拟ListServlet 由于请求路径中同样需要访问 所以直接公有化 public void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 将返回值存入attribute中 request.setAttribute("customers", cd.list()); // 转发到展示页面 request.getRequestDispatcher("/page/list.jsp").forward(request, response); } }
- CustomerDao.java
package com.axihh.dao; import com.axihh.bean.Customer; import com.axihh.util.Dbs; import java.util.List; public class CustomerDao { public boolean save(Customer customer){ String sql = "INSERT INTO customer(name, age, height) VALUES (?, ?, ?)"; return Dbs.update(sql, customer.getName(), customer.getAge(), customer.getHeight()); } public List<Customer> list() { String sql = "SELECT id, name, age, height FROM customer"; return Dbs.list(sql, (rs, row) -> { Customer customer = new Customer(); customer.setId(rs.getInt("id")); customer.setName(rs.getString("name")); customer.setAge(rs.getInt("age")); customer.setHeight(rs.getDouble("height")); System.out.println(row + "_" + customer); return customer; }); } }
- Dbs.java
package com.axihh.util; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; import java.util.Properties; public class Dbs { // 常量 public static String url; public static String username; public static String password; static { try(// 通过当前类获取字节码文件 然后通过字节码文件获取类加载器 然后通过类加载器获取资源文件 并且通过字节输入流读取该资源文件 InputStream is = Dbs.class.getClassLoader().getResourceAsStream("db.properties")) { // 创建properties对象 然后将字节输入流读取的内容加载进该对象中 Properties properties = new Properties(); properties.load(is); // 然后通过键找值的方式获取指定变量对应的值 url = properties.getProperty("url"); username = properties.getProperty("username"); password = properties.getProperty("password"); } catch (IOException e) { e.printStackTrace(); } } // 定义一个方法 用于存放数据库保存的公共代码 public static boolean update(String sql, Object ...args) { try { // 注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); try(Connection conn = DriverManager.getConnection(url, username, password); PreparedStatement pstmt = conn.prepareStatement(sql)) { // 设置参数 for (int i = 0; i < args.length; i++) { pstmt.setObject(i + 1, args[i]); } // 执行sql语句 return pstmt.executeUpdate() > 0; } }catch(Exception e) { e.printStackTrace(); // 执行到此 说明没有任何行受到影响 return false; } } // 定义一个方法 用于存放用户信息集合获取的公共代码 public static <T> List<T> list(String sql, RowMapper<T> rm, Object ...args) { try { // 注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); try(Connection conn = DriverManager.getConnection(url, username, password); PreparedStatement pstmt = conn.prepareStatement(sql)) { // 建议不要将resultset写入try-with-resources中 因为我们要保持顺序的先后性 先创建语句 在执行语句 保持这样的顺序 // 设置参数 for (int i = 0; i < args.length; i++) { pstmt.setObject(i + 1, args[i]); } // 执行sql语句 ResultSet rs = pstmt.executeQuery(); List<T> array = new ArrayList<>(); // resultset映射bean for(int row = 0; rs.next(); row++) { array.add(rm.map(rs, row)); } return array; } }catch(Exception e) { e.printStackTrace(); // 如果出现异常 证明返回值为空 return null; } } // 定义一个接口 用于接收resultset映射bean的代码 具体就是数据库中的每一行对应的resultset映射为bean public interface RowMapper<T> { public T map(ResultSet rs, int row) throws Exception; } }
- list.jsp(需要注意的是 添加超链接的url需要指定为/contextPath/xxx 否则请求路径和页面不符)
<%-- Created by IntelliJ IDEA. User: 19938 Date: 2024/5/25 Time: 9:51 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <title>Title</title> <style> th, td { border: 1px solid red; } </style> </head> <body> <a href="/crm5/page/add.html">添加</a> <table> <thead> <tr> <th>id</th> <th>姓名</th> <th>年龄</th> <th>身高</th> </tr> </thead> <tbody> <%-- 我们在获取存储在attribute中的customers时 我们应该通过el表达式去获取 具体的格式就是${} 而本质上就是在调用getAttribute --%> <c:forEach items="${customers}" var="customer"> <tr> <%-- 我们以下的el表达式的本质是调用getter获取属性 --%> <td>${customer.id}</td> <td>${customer.name}</td> <td>${customer.age}</td> <td>${customer.height}</td> </tr> </c:forEach> </tbody> </table> </body> </html>
- add.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/crm5/save" method="post"> <div>姓名 <input type="text" name="name"></div> <div>年龄 <input type="text" name="age"></div> <div>身高 <input type="text" name="height"></div> <div><button>保存</button></div> </form> </body> </html>
- error.jsp
<%-- Created by IntelliJ IDEA. User: 19938 Date: 2024/5/25 Time: 17:05 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>${error}</h1> </body> </html>
- 客户端发送的请求路径除了/crm/customer/list以外 还可以将操作作为参数拼接在请求路径之后 然后CustomerServlet中通过getParameter方式获取具体操作名称
- CustomerServlet.java
package com.axihh.servlet; import com.axihh.bean.Customer; import com.axihh.dao.CustomerDao; import com.axihh.util.Dbs; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @WebServlet("/customer") public class CustomerServlet extends HttpServlet { // 定义一个CustomerDao对象 用于访问其中的save、list方法 private final CustomerDao cd = new CustomerDao(); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { // 获取调用方法的名称 String methodName = request.getParameter("cmd"); // 第四步 利用Java中的反射技术 根据methodName调用该类中的对应方法 Method method = CustomerServlet.class.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); // 第五步 调用反射得到的结果 method.invoke(this, request, response); } catch (Exception e) { e.printStackTrace(); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } // 提供一个方法 用于模拟SaveServlet 由于请求路径中需要访问 所以直接公有化 public void save(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取客户端发送的用户信息 并且封装为bean Customer customer = new Customer(); customer.setName(request.getParameter("name")); customer.setAge(Integer.parseInt(request.getParameter("age"))); customer.setHeight(Double.parseDouble(request.getParameter("height"))); // 如果保存成功的话 就跳转到展示页面 如果保存失败的话 那么就跳转到失败页面 if(cd.save(customer)) { // 执行的是重定向操作 response.sendRedirect("/crm5/customer?cmd=list"); } else { // 执行的是转发操作 request.getRequestDispatcher("/page/error.jsp"); } } // 提供一个方法 用于模拟ListServlet 由于请求路径中同样需要访问 所以直接公有化 public void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 将返回值存入attribute中 request.setAttribute("customers", cd.list()); // 转发到展示页面 request.getRequestDispatcher("/page/list.jsp").forward(request, response); } }
- add.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!--<form action="/crm5/customer/save" method="get">--> <!-- <div>姓名 <input type="text" name="name"></div>--> <!-- <div>年龄 <input type="text" name="age"></div>--> <!-- <div>身高 <input type="text" name="height"></div>--> <!-- <div><button type="submit">保存</button></div>--> <!--</form>--> <form action="/crm5/customer?cmd=save" method="post"> <div>姓名 <input type="text" name="name"></div> <div>年龄 <input type="text" name="age"></div> <div>身高 <input type="text" name="height"></div> <div><button type="submit">保存</button></div> </form> </body> </html>
- 当客户端发送一个任意的访问路径时 我们可以给予一个友善的提醒 比如说一个提醒界面
- CustomerServlet.java
package com.axihh.servlet; import com.axihh.bean.Customer; import com.axihh.dao.CustomerDao; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Method; @WebServlet("/customer") public class CustomerServlet extends HttpServlet { // 定义一个CustomerDao对象 用于访问其中的save、list方法 private final CustomerDao cd = new CustomerDao(); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { // 获取调用方法的名称 String methodName = request.getParameter("cmd"); // 第四步 利用Java中的反射技术 根据methodName调用该类中的对应方法 Method method = getClass().getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); // 第五步 调用反射得到的结果 method.invoke(this, request, response); } catch (Exception e) { e.printStackTrace(); // 当你随意传递一个路径的时候 需要给予一个友善的界面进行提醒 request.setAttribute("error2", "你的路径有问题"); request.getRequestDispatcher("/page/error2.jsp").forward(request, response); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } // 提供一个方法 用于模拟SaveServlet 由于请求路径中需要访问 所以直接公有化 public void save(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取客户端发送的用户信息 并且封装为bean Customer customer = new Customer(); customer.setName(request.getParameter("name")); customer.setAge(Integer.parseInt(request.getParameter("age"))); customer.setHeight(Double.parseDouble(request.getParameter("height"))); // 如果保存成功的话 就跳转到展示页面 如果保存失败的话 那么就跳转到失败页面 if(cd.save(customer)) { // 执行的是重定向操作 response.sendRedirect("/crm5/customer?cmd=list"); } else { // 执行的是转发操作 request.setAttribute("error", "保存客户信息失败"); request.getRequestDispatcher("/page/error.jsp").forward(request, response); } } // 提供一个方法 用于模拟ListServlet 由于请求路径中同样需要访问 所以直接公有化 public void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 将返回值存入attribute中 request.setAttribute("customers", cd.list()); // 转发到展示页面 request.getRequestDispatcher("/page/list.jsp").forward(request, response); } }
- error2.jsp
<%-- Created by IntelliJ IDEA. User: 19938 Date: 2024/5/29 Time: 6:40 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>${error2}</h1> </body> </html>
4.数据库连接池
连接池的出现契机:我们每进行一次请求发送 都要创建一个全新的Connection对象 这未免很麻烦 因此我们需要更优解
数据库连接池:可以提高访问数据库的性能 负责创建、分配、管理、释放数据库连接
- 基本思想
- 在初始化时 创建一定数量的数据库连接对象储存到内存中
- 当需要访问数据库时 并非创建一个新的连接 而是从数据库池中取出一个已经创建的空闲的连接对象
- 使用完毕以后 也并非直接关闭 而是放回连接池中 以供下一次请求使用
- 当连接的空闲时间达到最大空闲时间或者连接的次数达到最大连接次数 将会被释放掉
- 可以通过参数来设置初始连接数、连接的上下限(连接池中的最小连接容量、最大连接容量)、每个连接的最大使用次数、最大空闲时间 这就是连接池管理的具体操作
- 可以通过其管理机制来监视数据库连接的数量、使用情况
- Java中常见的开源的数据库连接池
- C3P0、Proxool、DBCP、BoneCP、Druid等
- 我们这边使用Druid 是来自阿里巴巴的一款数据库连接池
- 我们可以去druid下载地址下载相关的jar包 具体下载普通jar包和源码jar包
- 代码实现
- 前置工作:将jar包导入项目 并且添加到类路径中 然后定义druid配置文件 里面设置相关的属性
- 由于我们的Connection对象是通过连接池创建的 所以不需要url、username、password三个参数 仅仅需要DataSource这个参数用于接收连接池对象 并且通过ds创建的数据库连接对象会自行关闭 所以无需我们加入try-with-resources进行自动关闭
- 具体实现(Dbs.java)
import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.InputStream; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; import java.util.Properties; public class Dbs { // 常量 public static DataSource ds; static { try(// 通过当前类获取字节码文件 然后通过字节码文件获取类加载器 然后通过类加载器获取资源文件 并且通过字节输入流读取该资源文件 InputStream is = Dbs.class.getClassLoader().getResourceAsStream("db.properties")) { // 创建properties对象 然后将字节输入流读取的内容加载进该对象中 Properties properties = new Properties(); properties.load(is); // 然后创建连接池 ds = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } // 定义一个方法 用于存放数据库保存的公共代码 public static boolean update(String sql, Object ...args) { try { // 注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); Connection conn = ds.getConnection(); try(PreparedStatement pstmt = conn.prepareStatement(sql)) { // 设置参数 for (int i = 0; i < args.length; i++) { pstmt.setObject(i + 1, args[i]); } // 执行sql语句 return pstmt.executeUpdate() > 0; } }catch(Exception e) { e.printStackTrace(); // 执行到此 说明没有任何行受到影响 return false; } } // 定义一个方法 用于存放用户信息集合获取的公共代码 public static <T> List<T> list(String sql, RowMapper<T> rm, Object ...args) { try { // 注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); Connection conn = ds.getConnection(); try(PreparedStatement pstmt = conn.prepareStatement(sql)) { // 建议不要将resultset写入try-with-resources中 因为我们要保持顺序的先后性 先创建语句 在执行语句 保持这样的顺序 // 设置参数 for (int i = 0; i < args.length; i++) { pstmt.setObject(i + 1, args[i]); } // 执行sql语句 ResultSet rs = pstmt.executeQuery(); List<T> array = new ArrayList<>(); // resultset映射bean for(int row = 0; rs.next(); row++) { array.add(rm.map(rs, row)); } return array; } }catch(Exception e) { e.printStackTrace(); // 如果出现异常 证明返回值为空 return null; } } // 定义一个接口 用于接收resultset映射bean的代码 具体就是数据库中的每一行对应的resultset映射为bean public interface RowMapper<T> { public T map(ResultSet rs, int row) throws Exception; } }