1.前言
Spring JDBC可以帮助开发者节省大量开发工作 自动去处理一些低级细节 比如:异常处理、打开和关闭资源(Connection、PreparedStatement、Statement、ResultSet)
- 需要下载的jar包(普通jar包、源码jar包):
- 由于依赖其他的jar包 所以需要另行下载其他的jar包
- 总之 需要下载的jar包有:spring-jdbc、spring-beans(spring-jdbc依赖)、spring-core(spring-jdbc依赖)、spring-tx(spring-jdbc依赖)、spring-jcl(spring-core依赖)
2.拷贝模块
- 我们可以通过ctrl+c、ctrl+v拷贝模块
- 每一个项目中都存在一个iml文件 作为项目的配置文件 里面储存了项目所依赖的库 一旦该文件失效 那么项目中使用到依赖库的任何地方也会失效 在模块拷贝的过程中 虽然项目名称被我们手动修改了 但是iml文件的名称并没有随之修改 所以我们要去项目所在文件夹中进行手动修改 才能正常使用项目
- 我们在导入拷贝以后的模块时 如果选择iml描述文件来代替整个项目的话 那么将不需要若干个下一步的操作 提高效率
- 模块拷贝之后 由于没有进行编译打包的操作 所以Tomcat服务器无法加载该模块 因此我们需要编译打包将其置于aritfacts目录中 之后在将其部署到Tomcat服务器上即可
3.Sprint JDBC的核心类:JdbcTemplate
- 构造方法
- public JdbcTemplate(DataSource dataSource)
- 执行DDL、DML语句
- int update(String sql, Object… args)
- 执行DQL语句
- <T> List<T> query(String sql, RowMapper<T> rowMapper, Object… args)
- 我们利用JdbcTemplate来简化Dbs、Dao两个类
- 具体就是Dbs中用getTpl来代替update、query、RowMapper三个接口 原因在于JdbcTemplat内置了这三个接口 Dao中的save、list分别调用tpl的update、query方法来执行相关操作
- 我们可以通过BeanPropertyRowMapper(Class beanClass)来代替代码形参 如此也简化了代码的编写
- 他的底层实现大致为先通过getField获取bean的Class对象中的属性 然后带入rs的get方法获取相应字段值 接着通过拼串的方式获取相应的setter方法 比如:set+name 最后在根据getMethod调用相应的setter方法
- 底层已经做了Java规范和数据库规范的映射 在Java中 我们的属性标识符通常采用小驼峰形式 而在数据库中 我们的字段标识符通常采用小写、_拼接的方式(比如:my_name)
- 如果Java的属性名称和数据库的字段名称对应不上的话/映射关系不符的话 那么BeanPropertyRowMapper将无法作用于相关的字段/属性
- 代码实现
- Dbs.java
package com.axihh.util; import com.alibaba.druid.pool.DruidDataSourceFactory; import org.springframework.jdbc.core.JdbcTemplate; import javax.sql.DataSource; import java.io.InputStream; import java.util.Properties; public class Dbs { // 常量 private static JdbcTemplate tpl; static { try(// 通过当前类获取字节码文件 然后通过字节码文件获取类加载器 然后通过类加载器获取资源文件 并且通过字节输入流读取该资源文件 InputStream is = Dbs.class.getClassLoader().getResourceAsStream("druid.properties")) { // 创建properties对象 然后将字节输入流读取的内容加载进该对象中 Properties properties = new Properties(); properties.load(is); // 然后创建连接池 DataSource ds = DruidDataSourceFactory.createDataSource(properties); // 通过连接池创建tpl tpl = new JdbcTemplate(ds); } catch (Exception e) { e.printStackTrace(); } } // 提供一个获取tpl的方法 来代替update、list以及RowMapper 以后可以通过调用tpl的api来访问被代替的方法 public static JdbcTemplate getTpl() { return tpl; } }
- CustomerDao.java
package com.axihh.dao; import com.axihh.bean.Customer; import com.axihh.util.Dbs; import org.springframework.jdbc.core.BeanPropertyRowMapper; import java.util.List; public class CustomerDao { public boolean save(Customer customer){ String sql = "INSERT INTO customer(name, age, height) VALUES (?, ?, ?)"; return Dbs.getTpl().update(sql, customer.getName(), customer.getAge(), customer.getHeight()) > 0; } public List<Customer> list() { String sql = "SELECT id, name, age, height FROM customer"; // return Dbs.getTpl().query(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")); // return customer; // }); return Dbs.getTpl().query(sql, new BeanPropertyRowMapper<>(Customer.class)); } }
4.删除功能的实现
前面我们已经实现了增删改查中的增、查两个功能 现在我们就来实现一下删除功能吧 具体的操作就是在展示页面中的每一条记录旁边多出可点击的删除按钮 一点击删除按钮 就会执行servlet中的remove方法 而该方法则会执行dao中的remove方法 而该方法又会调用dbs中tpl的update方法 该方法专门用于执行dml语句(包括增加、删除、修改记录)
- 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[] cmps = request.getRequestURI().split("/");
String methodName = cmps[cmps.length - 1];
// 第四步 利用Java中的反射技术 根据methodName调用该类中的对应方法
Method method = getClass().getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
// 第五步 调用反射得到的结果
method.invoke(this, request, response);
} catch (Exception e) {
e.printStackTrace();
// 当你随意传递一个路径的时候 需要给予一个友善的界面进行提醒
request.setAttribute("error", "你的路径有问题");
request.getRequestDispatcher("/page/error.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("/crm6/customer/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);
}
// 提供一个方法 用于删除客户表的某一条记录
public void remove(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取当前记录的id
int id = Integer.parseInt(request.getParameter("id"));
// 如果成功删除 那么就跳转到展示页面
if(cd.remove(id)) {
response.sendRedirect("/crm6/customer/list");
}else {
// 如果删除失败的话 那么就跳转到失败页面
request.setAttribute("error", "删除客户信息失败");
request.getRequestDispatcher("/page/error.jsp").forward(request, response);
}
}
}
- CustomerDao.java
package com.axihh.dao;
import com.axihh.bean.Customer;
import com.axihh.util.Dbs;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import java.util.List;
public class CustomerDao {
public boolean save(Customer customer){
String sql = "INSERT INTO customer(name, age, height) VALUES (?, ?, ?)";
return Dbs.getTpl().update(sql, customer.getName(), customer.getAge(), customer.getHeight()) > 0;
}
public List<Customer> list() {
String sql = "SELECT id, name, age, height FROM customer";
// return Dbs.getTpl().query(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"));
// return customer;
// });
return Dbs.getTpl().query(sql, new BeanPropertyRowMapper<>(Customer.class));
}
public boolean remove(int id) {
String sql = "DELETE FROM customer WHERE id = ?";
return Dbs.getTpl().update(sql, id) > 0;
}
}
- Dbs.java
package com.axihh.util;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.io.InputStream;
import java.util.Properties;
public class Dbs {
// 常量
private static JdbcTemplate tpl;
static {
try(// 通过当前类获取字节码文件 然后通过字节码文件获取类加载器 然后通过类加载器获取资源文件 并且通过字节输入流读取该资源文件
InputStream is = Dbs.class.getClassLoader().getResourceAsStream("druid.properties")) {
// 创建properties对象 然后将字节输入流读取的内容加载进该对象中
Properties properties = new Properties();
properties.load(is);
// 然后创建连接池
DataSource ds = DruidDataSourceFactory.createDataSource(properties);
// 通过连接池创建tpl
tpl = new JdbcTemplate(ds);
} catch (Exception e) {
e.printStackTrace();
}
}
// 提供一个获取tpl的方法 来代替update、list以及RowMapper 以后可以通过调用tpl的api来访问被代替的方法
public static JdbcTemplate getTpl() {
return tpl;
}
}
- list.jsp
<%--
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="/crm6/page/add.html">添加</a>
<table>
<thead>
<tr>
<th>id</th>
<th>姓名</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>
<td>
<a href="#">编辑</a>
<a href="/crm6/customer/remove?id=${customer.id}">删除</a>
</td>
</tr>
</c:forEach>
</tbody>
</table>
5.修改功能的实现
前面我们已经实现了增加、删除、查找三个功能 三缺一 还差一个修改功能 具体的实现方式为:执行servlet 将完整信息展示到要跳转的jsp页面 在jsp中修改当前记录 提交给servlet 再由servlet对数据库进行修改 修改成功便转发到展示页面 否则就重定向到失败页面
- CustomerServlet
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[] cmps = request.getRequestURI().split("/");
String methodName = cmps[cmps.length - 1];
// 第四步 利用Java中的反射技术 根据methodName调用该类中的对应方法
Method method = getClass().getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
// 第五步 调用反射得到的结果
method.invoke(this, request, response);
} catch (Exception e) {
e.printStackTrace();
// 当你随意传递一个路径的时候 需要给予一个友善的界面进行提醒
request.setAttribute("error", "路径错误");
request.getRequestDispatcher("/page/error.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("/crm6/customer/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);
}
// 提供一个方法 用于删除客户表的某一条记录
public void remove(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取当前记录的id
Integer id = Integer.valueOf(request.getParameter("id"));
// 如果成功删除 那么就跳转到展示页面
if(cd.remove(id)) {
response.sendRedirect("/crm6/customer/list");
}else {
// 如果删除失败的话 那么就跳转到失败页面
request.setAttribute("error", "删除客户信息失败");
request.getRequestDispatcher("/page/error.jsp").forward(request, response);
}
}
// 提供一个方法 用于获取客户表某一条记录修改前的完整信息
public void edit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取当前记录的id
Integer id = Integer.valueOf(request.getParameter("id"));
// 获取id对应的这一行记录的完整信息
Customer customer = cd.find(id);
// 将customer储存到request中 然后转发到edit.jsp页面
request.setAttribute("customer", customer);
request.getRequestDispatcher("/page/edit.jsp").forward(request, response);
}
// 提供一个方法 用于修改客户某一条记录的信息 并且转发到展示页面
public void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 将表单数据映射为bean
Customer customer = new Customer();
customer.setId(Integer.valueOf(request.getParameter("id")));
customer.setName(request.getParameter("name"));
customer.setAge(Integer.valueOf(request.getParameter("age")));
customer.setHeight(Double.valueOf(request.getParameter("height")));
// 调用Dao中的Update方法 用于将更新以后的记录添加到数据库中 如果修改成功的话 那么就跳转到展示页面 否则的话 就跳转到失败页面
if(cd.update(customer)) {
response.sendRedirect("/crm6/customer/list");
}else {
request.setAttribute("error", "修改客户信息失败");
request.getRequestDispatcher("/page/error.jsp");
}
}
}
- CustomerDao.java
package com.axihh.dao;
import com.axihh.bean.Customer;
import com.axihh.util.Dbs;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import java.util.List;
public class CustomerDao {
public boolean save(Customer customer){
String sql = "INSERT INTO customer(name, age, height) VALUES (?, ?, ?)";
return Dbs.getTpl().update(sql, customer.getName(), customer.getAge(), customer.getHeight()) > 0;
}
public List<Customer> list() {
String sql = "SELECT id, name, age, height FROM customer";
// return Dbs.getTpl().query(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"));
// return customer;
// });
return Dbs.getTpl().query(sql, new BeanPropertyRowMapper<>(Customer.class));
}
public boolean remove(int id) {
String sql = "DELETE FROM customer WHERE id = ?";
return Dbs.getTpl().update(sql, id) > 0;
}
public Customer find(Integer id) {
String sql = "SELECT id, name, age, height FROM customer WHERE id = ?";
return Dbs.getTpl().queryForObject(sql, new BeanPropertyRowMapper<>(Customer.class), id);
}
public boolean update(Customer customer) {
String sql = "UPDATE customer SET name = ?, age = ?, height = ? WHERE id = ?";
return Dbs.getTpl().update(sql, customer.getName(), customer.getAge(), customer.getHeight(), customer.getId()) > 0;
}
}
- edit.jsp
<%--
Created by IntelliJ IDEA.
User: 19938
Date: 2024/5/29
Time: 11: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>
<form action="/crm6/customer/update" method="post">
<input type="hidden" name="id" value="${customer.id}">
<div>姓名 <input type="text" name="name" value="${customer.name}"></div>
<div>年龄 <input type="text" name="age" value="${customer.age}"></div>
<div>身高 <input type="text" name="height" value="${customer.height}"></div>
<div><button type="submit">更新</button></div>
</form>
</body>
</html>
6.重复代码抽取
我们发现 增删改查功能齐全的servlet中 仍然存在大量重复的代码 比如:失败页面的跳转、表单数据映射为bean
- 抽取失败页面的跳转代码(封装到forwardError方法中)
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[] cmps = request.getRequestURI().split("/");
String methodName = cmps[cmps.length - 1];
// 第四步 利用Java中的反射技术 根据methodName调用该类中的对应方法
Method method = getClass().getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
// 第五步 调用反射得到的结果
method.invoke(this, request, response);
} catch (Exception e) {
e.printStackTrace();
String error = "路径错误";
forwardError(request, response, error);
}
}
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("/crm6/customer/list");
} else {
String error = "保存客户信息失败";
forwardError(request, response, error);
}
}
// 提供一个方法 用于模拟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);
}
// 提供一个方法 用于删除客户表的某一条记录
public void remove(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取当前记录的id
Integer id = Integer.valueOf(request.getParameter("id"));
// 如果成功删除 那么就跳转到展示页面
if(cd.remove(id)) {
response.sendRedirect("/crm6/customer/list");
}else {
String error = "删除客户信息失败";
forwardError(request, response, error);
}
}
// 提供一个方法 用于获取客户表某一条记录修改前的完整信息
public void edit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取当前记录的id
Integer id = Integer.valueOf(request.getParameter("id"));
// 获取id对应的这一行记录的完整信息
Customer customer = cd.find(id);
// 将customer储存到request中 然后转发到edit.jsp页面
request.setAttribute("customer", customer);
request.getRequestDispatcher("/page/edit.jsp").forward(request, response);
}
// 提供一个方法 用于修改客户某一条记录的信息 并且转发到展示页面
public void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 将表单数据映射为bean
Customer customer = new Customer();
customer.setId(Integer.valueOf(request.getParameter("id")));
customer.setName(request.getParameter("name"));
customer.setAge(Integer.valueOf(request.getParameter("age")));
customer.setHeight(Double.valueOf(request.getParameter("height")));
// 调用Dao中的Update方法 用于将更新以后的记录添加到数据库中 如果修改成功的话 那么就跳转到展示页面 否则的话 就跳转到失败页面
if(cd.update(customer)) {
response.sendRedirect("/crm6/customer/list");
}else {
String error = "更新客户信息失败";
forwardError(request, response, error);
}
}
// 提供一个方法 用于跳转到失败页面
private void forwardError(HttpServletRequest request, HttpServletResponse response, String error) throws ServletException, IOException {
// 设置失败信息
request.setAttribute("error", error);
// 转发到失败页面
request.getRequestDispatcher("/page/error.jsp").forward(request, response);
}
}
- 抽取表单数据映射为bean模型的重复代码(封装为newCustomer)
- 方法一
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.InvocationTargetException; 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[] cmps = request.getRequestURI().split("/"); String methodName = cmps[cmps.length - 1]; // 第四步 利用Java中的反射技术 根据methodName调用该类中的对应方法 Method method = getClass().getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); // 第五步 调用反射得到的结果 method.invoke(this, request, response); } catch (Exception e) { e.printStackTrace(); String error = "路径错误"; forwardError(request, response, error); } } 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 = newCustomer(request); // 如果保存成功的话 就跳转到展示页面 如果保存失败的话 那么就跳转到失败页面 if(cd.save(customer)) { // 执行的是重定向操作 response.sendRedirect("/crm6/customer/list"); } else { String error = "保存客户信息失败"; forwardError(request, response, error); } } // 提供一个方法 用于模拟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); } // 提供一个方法 用于删除客户表的某一条记录 public void remove(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取当前记录的id Integer id = Integer.valueOf(request.getParameter("id")); // 如果成功删除 那么就跳转到展示页面 if(cd.remove(id)) { response.sendRedirect("/crm6/customer/list"); }else { String error = "删除客户信息失败"; forwardError(request, response, error); } } // 提供一个方法 用于获取客户表某一条记录修改前的完整信息 public void edit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取当前记录的id Integer id = Integer.valueOf(request.getParameter("id")); // 获取id对应的这一行记录的完整信息 Customer customer = cd.find(id); // 将customer储存到request中 然后转发到edit.jsp页面 request.setAttribute("customer", customer); request.getRequestDispatcher("/page/edit.jsp").forward(request, response); } // 提供一个方法 用于修改客户某一条记录的信息 并且转发到展示页面 public void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 将表单数据映射为bean Customer customer = newCustomer(request); customer.setId(Integer.valueOf(request.getParameter("id"))); // 调用Dao中的Update方法 用于将更新以后的记录添加到数据库中 如果修改成功的话 那么就跳转到展示页面 否则的话 就跳转到失败页面 if(cd.update(customer)) { response.sendRedirect("/crm6/customer/list"); }else { String error = "更新客户信息失败"; forwardError(request, response, error); } } // 提供一个方法 用于跳转到失败页面 private void forwardError(HttpServletRequest request, HttpServletResponse response, String error) throws ServletException, IOException { // 设置失败信息 request.setAttribute("error", error); // 转发到失败页面 request.getRequestDispatcher("/page/error.jsp").forward(request, response); } // 提供一个方法 用于保存表单数据映射为bean模型的代码 private Customer newCustomer(HttpServletRequest request) { Customer customer = new Customer(); // 将表单数据保存到bean模型中 customer.setName(request.getParameter("name")); customer.setAge(Integer.valueOf(request.getParameter("age"))); customer.setHeight(Double.valueOf(request.getParameter("height"))); return customer; } }
- 方法二
- 方法二的优越性在于它利用了Java的反射技术 简化了手动设置参数的过程
- 需要依赖函数库commons-beanutils.jar、commons-collections.jar、commons-loggin.jar 调用他们的api 即BeanUtils.populate方法
- 同时我们优化了异常抛出 将增删改查方法所抛出的异常全部修改为Exception 但是forwardError、doGet、doPost这三个方法所抛出的异常不能修改 因为他们都受到父类或者主调方法的限制 不能超出他们所抛异常的范围
package com.axihh.servlet; import com.axihh.bean.Customer; import com.axihh.dao.CustomerDao; import org.apache.commons.beanutils.BeanUtils; 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.InvocationTargetException; 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[] cmps = request.getRequestURI().split("/"); String methodName = cmps[cmps.length - 1]; // 第四步 利用Java中的反射技术 根据methodName调用该类中的对应方法 Method method = getClass().getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); // 第五步 调用反射得到的结果 method.invoke(this, request, response); } catch (Exception e) { e.printStackTrace(); String error = "路径错误"; forwardError(request, response, error); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } // 提供一个方法 用于模拟SaveServlet 由于请求路径中需要访问 所以直接公有化 public void save(HttpServletRequest request, HttpServletResponse response) throws Exception { // 获取客户端发送的用户信息 并且封装为bean Customer customer = new Customer(); BeanUtils.populate(customer, request.getParameterMap()); // 如果保存成功的话 就跳转到展示页面 如果保存失败的话 那么就跳转到失败页面 if(cd.save(customer)) { // 执行的是重定向操作 response.sendRedirect("/crm6/customer/list"); } else { String error = "保存客户信息失败"; forwardError(request, response, error); } } // 提供一个方法 用于模拟ListServlet 由于请求路径中同样需要访问 所以直接公有化 public void list(HttpServletRequest request, HttpServletResponse response) throws Exception { // 将返回值存入attribute中 request.setAttribute("customers", cd.list()); // 转发到展示页面 request.getRequestDispatcher("/page/list.jsp").forward(request, response); } // 提供一个方法 用于删除客户表的某一条记录 public void remove(HttpServletRequest request, HttpServletResponse response) throws Exception { // 获取当前记录的id Integer id = Integer.valueOf(request.getParameter("id")); // 如果成功删除 那么就跳转到展示页面 if(cd.remove(id)) { response.sendRedirect("/crm6/customer/list"); }else { String error = "删除客户信息失败"; forwardError(request, response, error); } } // 提供一个方法 用于获取客户表某一条记录修改前的完整信息 public void edit(HttpServletRequest request, HttpServletResponse response) throws Exception { // 获取当前记录的id Integer id = Integer.valueOf(request.getParameter("id")); // 获取id对应的这一行记录的完整信息 Customer customer = cd.find(id); // 将customer储存到request中 然后转发到edit.jsp页面 request.setAttribute("customer", customer); request.getRequestDispatcher("/page/edit.jsp").forward(request, response); } // 提供一个方法 用于修改客户某一条记录的信息 并且转发到展示页面 public void update(HttpServletRequest request, HttpServletResponse response) throws Exception { // 将表单数据映射为bean Customer customer = new Customer(); BeanUtils.populate(customer, request.getParameterMap()); // 调用Dao中的Update方法 用于将更新以后的记录添加到数据库中 如果修改成功的话 那么就跳转到展示页面 否则的话 就跳转到失败页面 if(cd.update(customer)) { response.sendRedirect("/crm6/customer/list"); }else { String error = "更新客户信息失败"; forwardError(request, response, error); } } // 提供一个方法 用于跳转到失败页面 private void forwardError(HttpServletRequest request, HttpServletResponse response, String error) throws ServletException, IOException { // 设置失败信息 request.setAttribute("error", error); // 转发到失败页面 request.getRequestDispatcher("/page/error.jsp").forward(request, response); } }
- 以上是Servlet中所有可抽取的点 接下来我们将抽取的目光放在Dao中 可以发现 其中 sql语句参数的传递可能会因为数量过多导致设置麻烦 又因为他们存在公共部分 所以说 我们将为sql语句设置参数的代码部分抽取为一个方法 即buildArgs
package com.axihh.dao;
import com.axihh.bean.Customer;
import com.axihh.util.Dbs;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import java.util.ArrayList;
import java.util.List;
public class CustomerDao {
public boolean save(Customer customer){
String sql = "INSERT INTO customer(name, age, height) VALUES (?, ?, ?)";
List<Object> args = buildArgs(customer);
return Dbs.getTpl().update(sql, args.toArray()) > 0;
}
public List<Customer> list() {
String sql = "SELECT id, name, age, height FROM customer";
// return Dbs.getTpl().query(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"));
// return customer;
// });
return Dbs.getTpl().query(sql, new BeanPropertyRowMapper<>(Customer.class));
}
public boolean remove(int id) {
String sql = "DELETE FROM customer WHERE id = ?";
return Dbs.getTpl().update(sql, id) > 0;
}
public Customer find(Integer id) {
String sql = "SELECT id, name, age, height FROM customer WHERE id = ?";
return Dbs.getTpl().queryForObject(sql, new BeanPropertyRowMapper<>(Customer.class), id);
}
public boolean update(Customer customer) {
String sql = "UPDATE customer SET name = ?, age = ?, height = ? WHERE id = ?";
List<Object> args = buildArgs(customer);
args.add(customer.getId());
return Dbs.getTpl().update(sql, args.toArray()) > 0;
}
private List<Object> buildArgs(Customer customer) {
List<Object> args = new ArrayList<>();
args.add(customer.getName());
args.add(customer.getAge());
args.add(customer.getHeight());
return args;
}
}
7.Junit
Junit是Java中最常用的单元测试框架
-
所需jar包
- junit.jar
- hamcrest.jar
- hamcrest-core.jar
-
建议单元测试类的命名规范为:XxTest
-
常用注解
- @Test:正常的测试方法 建议格式为public void testXx()
- @Before:在每个@Test方法执行之前执行 建议格式为public void before()
- @After:在每个@Tes方法执行之后执行 建议格式为public void after()
- @BeforeClass:在第一个@Test方法执行之前执行 建议格式为public static void beforeClass()
- @AfterClass:在最后一个@Test方法执行之后执行 建议格式为public static void afterClass()
-
断言类Assert的常用方法
- assertTrue
- assertFalse
- assertEquals
- assertNotEquals
- assertNotNull
- assertNull
-
测试类建议存放在Test(和src同级)目录下(你可以右击文件夹中mark directory as 选择test resources root 就可以达到绿色文件夹的效果了)
- 这个文件夹出现的目的在于测试类不用部署到服务器上运行 所以说仅仅需要通过编译 而不需要打包 所以需要独立于src文件夹在设置一个新文件夹 而且是专门为test设置的文件夹
- 这个文件夹出现的目的在于测试类不用部署到服务器上运行 所以说仅仅需要通过编译 而不需要打包 所以需要独立于src文件夹在设置一个新文件夹 而且是专门为test设置的文件夹
-
测试用例
package com.axihh.test;
import com.axihh.bean.Customer;
import com.axihh.dao.CustomerDao;
import org.junit.*;
public class CustomerDaoTest {
@BeforeClass
public static void beforeClass() {
System.out.println("beforeClass");
}
@AfterClass
public static void afterClass() {
System.out.println("afterClass");
}
@Before
public void before() {
System.out.println("before");
}
@After
public void after() {
System.out.println("after");
}
@Test
public void testSave() {
Customer customer = new Customer();
customer.setName("张三");
customer.setAge(11);
customer.setHeight(1.1);
CustomerDao cd = new CustomerDao();
Assert.assertTrue(cd.save(customer));
}
@Test
public void testSum() {
int a = 1;
int b = 2;
Assert.assertEquals(a + b, 3);
}
}
8.合并页面
对于add.html和edit.jsp两个页面来说 其实存在着大量重复的代码 我们可以将其合并为一个页面 合并之后 servlet中的save和update两个方法以及dao中的save和update两个方法也要合并 而且由于dao中的两方法合并 所以说 之前抽取出来的buildArgs方法可以推出使命了 而且 由于servlet中的save和update方法已然合并 所以说edit中的表单数据提交地址也得改成合并之后的页面
- 注意1:在dao中合并以后的save方法中判断添加页面还是编辑页面的时候 判断的依据主要是bean中的id是否为空 但是数据库中的id至少都为1 所以在判断添加页面的时候 除了判空以外 还有一种情况也需要判断 就是id值小于1的时候
- 注意2:在合并之后的save.jsp页面中 我们判断添加页面还是编辑页面的时候 主要是通过if-else/switch语句进行的 但是呢 在jstl标签库中 并没有模拟Java中if-else的语法(仅有多个if的语法 即<c:if test=“”>) 而只有swtich的语法(即<c:choose> > <c:when test=“”>、<c:otherwise>)
- 注意3:jstl标签库和el表达式是两种不一样的东西 jstl提供的是标签 而el表达式的格式为${}
- 注意4:在修改/删除页面中可以获取id的原因在于:为编辑设置超链接时 在地址后面拼接了id参数
- 代码实现
- save.jsp
<%-- Created by IntelliJ IDEA. User: 19938 Date: 2024/5/31/031 Time: 10:20 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> <%-- 两者的网页标题不一致 所以得通过c:if语句进行判断 --%> <c:choose> <c:when test="${empty customer}"> <title>添加页面</title> </c:when> <c:otherwise> <title>编辑页面</title> </c:otherwise> </c:choose> </head> <body> <form action="/crm6/customer/save"> <%-- 在el表达式中 如果对象为空的话 那么不会出现空指针异常这种现象 而是直接结果为空 --%> <%-- 如果是添加页面 那么id的文本框不用出现 防止利用框架进行表单数据->bean的自动转换时 将null值转换为0 而在数据库中 id这个主键的值至少都是1 --%> <%-- 而且由于呢 一个页面需要显示 另外一个页面不需要显示 所以说 我们不需要choose>when、otherwise这个jstl标签 直接使用if标签即可 --%> <c:if test="${not empty customer}"> <div><input type="hidden" name="id" value="${customer.id}"></div> </c:if> <div>姓名 <input type="text" name="name" value="${customer.name}"></div> <div>年龄 <input type="text" name="age" value="${customer.age}"></div> <div>身高 <input type="text" name="height" value="${customer.height}"></div> <div> <button type="submit"> <c:choose> <c:when test="${empty customer}">添加</c:when> <c:otherwise>更新</c:otherwise> </c:choose> </button> </div> </form> </body> </html>
- CustomerServlet.java
package com.axihh.servlet; import com.axihh.bean.Customer; import com.axihh.dao.CustomerDao; import org.apache.commons.beanutils.BeanUtils; 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.InvocationTargetException; 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[] cmps = request.getRequestURI().split("/"); String methodName = cmps[cmps.length - 1]; // 第四步 利用Java中的反射技术 根据methodName调用该类中的对应方法 Method method = getClass().getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); // 第五步 调用反射得到的结果 method.invoke(this, request, response); } catch (Exception e) { e.printStackTrace(); String error = "路径错误"; forwardError(request, response, error); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } // 提供一个方法 用于模拟SaveServlet 由于请求路径中需要访问 所以直接公有化 public void save(HttpServletRequest request, HttpServletResponse response) throws Exception { // 获取客户端发送的用户信息 并且封装为bean Customer customer = new Customer(); BeanUtils.populate(customer, request.getParameterMap()); // 如果保存成功的话 就跳转到展示页面 如果保存失败的话 那么就跳转到失败页面 if(cd.save(customer)) { // 执行的是重定向操作 response.sendRedirect("/crm6/customer/list"); } else { String error = "保存客户信息失败"; forwardError(request, response, error); } } // 提供一个方法 用于模拟ListServlet 由于请求路径中同样需要访问 所以直接公有化 public void list(HttpServletRequest request, HttpServletResponse response) throws Exception { // 将返回值存入attribute中 request.setAttribute("customers", cd.list()); // 转发到展示页面 request.getRequestDispatcher("/page/list.jsp").forward(request, response); } // 提供一个方法 用于删除客户表的某一条记录 public void remove(HttpServletRequest request, HttpServletResponse response) throws Exception { // 获取当前记录的id Integer id = Integer.valueOf(request.getParameter("id")); // 如果成功删除 那么就跳转到展示页面 if(cd.remove(id)) { response.sendRedirect("/crm6/customer/list"); }else { String error = "删除客户信息失败"; forwardError(request, response, error); } } // 提供一个方法 用于获取客户表某一条记录修改前的完整信息 public void edit(HttpServletRequest request, HttpServletResponse response) throws Exception { // 获取当前记录的id Integer id = Integer.valueOf(request.getParameter("id")); // 获取id对应的这一行记录的完整信息 Customer customer = cd.find(id); // 将customer储存到request中 然后转发到edit.jsp页面 request.setAttribute("customer", customer); request.getRequestDispatcher("/page/edit.jsp").forward(request, response); } // 提供一个方法 用于跳转到失败页面 private void forwardError(HttpServletRequest request, HttpServletResponse response, String error) throws ServletException, IOException { // 设置失败信息 request.setAttribute("error", error); // 转发到失败页面 request.getRequestDispatcher("/page/error.jsp").forward(request, response); } }
- CustomerDao.java
package com.axihh.dao; import com.axihh.bean.Customer; import com.axihh.util.Dbs; import org.springframework.jdbc.core.BeanPropertyRowMapper; import java.util.ArrayList; import java.util.List; public class CustomerDao { public boolean save(Customer customer){ List<Object> args = new ArrayList<>(); args.add(customer.getName()); args.add(customer.getAge()); args.add(customer.getHeight()); Integer id = customer.getId(); String sql; // 根据不同的页面做出不同的操作 if(id == null || id < 1) { sql = "INSERT INTO customer(name, age, height) VALUES (?, ?, ?)"; }else { // 别忘了更新页面需要多设置一个参数 args.add(id); sql = "UPDATE customer SET name = ?, age = ?, height = ? WHERE id = ?"; } return Dbs.getTpl().update(sql, args.toArray()) > 0; } public List<Customer> list() { String sql = "SELECT id, name, age, height FROM customer"; // return Dbs.getTpl().query(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")); // return customer; // }); return Dbs.getTpl().query(sql, new BeanPropertyRowMapper<>(Customer.class)); } public boolean remove(int id) { String sql = "DELETE FROM customer WHERE id = ?"; return Dbs.getTpl().update(sql, id) > 0; } public Customer find(Integer id) { String sql = "SELECT id, name, age, height FROM customer WHERE id = ?"; return Dbs.getTpl().queryForObject(sql, new BeanPropertyRowMapper<>(Customer.class), id); } }
- edit.jsp
<%-- Created by IntelliJ IDEA. User: 19938 Date: 2024/5/29 Time: 11: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> <form action="/crm6/customer/save" method="post"> <input type="hidden" name="id" value="${customer.id}"> <div>姓名 <input type="text" name="name" value="${customer.name}"></div> <div>年龄 <input type="text" name="age" value="${customer.age}"></div> <div>身高 <input type="text" name="height" value="${customer.height}"></div> <div><button type="submit">更新</button></div> </form> </body> </html>