利用“纯”Servlet做一个单表的CRUD操纵
使用“纯”Servlet做一个单表的CRUD操作
1. 项目说明
介绍 这里我们使用 纯粹 的 Servlet 完成单表【对部门的】的增删改查操作。(B/S结构的。)
结构图
初始的欢迎页面
部门列表页面
部门详情
修改部门
删除部门
新增部门
2. 具体对应的功能的代码实现
2.1 准备工作
- 我们使用数据库,存储数据,这里我们使用的数据库是 MYSQL 。
- 我们需要准备一个名为 dept的数据表,并插入一些数据。
create table dept (
depton int primary key,
dname varchar(255),
loc varchar(255)
);
insert into dept(depton,dname,loc) values(10,'xiaoShouBu','BEIJING');
insert into dept(depton,dname,loc) values(20,'YanFaBu','SHANGHAI');
insert into dept(depton,dname,loc) values(30,'JisShuBu','GUANGZHOU');
insert into dept(depton,dname,loc) values(40,'MeiTiBu','SHENZHEN');
select from dept;
小技巧 MySQL 在 cmd 命令中,批量执行 sql语句的方法如下,将编写好的 .sql 文件存储起来。如下图所示,
再打开cmd 进入命令窗口,再进入到Mysql当中,输入如下命令
source 后接文件路径(要执行的批量的.sql文件)
当前数据表 dept 的信息内容如下
- 为该模块导入 MYSQL的 JDBC 的 jar 包。
注意 因为我们是在 Tomcat 服务器当中部署项目的,所以我们需要在 WEB-INF 的目录下,创建一个名为 lib 的目录文件夹,用来存放相关的 依赖jar 包,注意路径位置不可以修改,目录文件必须为 lib 不然,当你启动的Tocmat 服务器的时候,是无法找到该对应的 依赖jar 包的。具体如下,我们将 Mysql对应的 jdbc jar 包导入其中。
- 创建一个ebapp(给这个ebapp添加servlet-api.jar和jsp-api.jar到classpath当中。)
- 向ebapp中添加连接数据库的jar包(mysql驱动)必须在WEB-INF目录下新建lib目录,然后将mysql的驱动jar包拷贝到这个lib目录下。这个目录名必须叫做lib,全部小写的。
2.2 模块目录结构
2.3 工具类 DBUtil
这里因为我们要连接数据库,所以我们编写一个连接Mysql 数据库的 工具类,这里我们名为一个 DBUtil 的工具类。
这里我们通过读取配置jdbc.properties的配置文件的方式,注册相对应的Mysql驱动 。
如下是相关: jdbc.properties 的配置信息
driver=.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
user=root
passord=123
再编写好相关的DBUtil 类 ,具体代码的编写内容如下
package .RainboSea.DBUtil;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ResourceBundle;
/
JDBC工具类
/
public class DBUtil {
// 静态变量,在类加载时执行
// 都是一些从 jdbc.properties 读取到的配置文件的信息
// 该方法仅仅只会读取 “.properties" 的后缀的文件,注意默认是从src目录开始的,有子目录需要写明子目录
private static ResourceBundle bundle = ResourceBundle.getBundle("/RainboSea/resources/jdbc");
private static String driver = bundle.getString("driver"); // 根据properties中的name读取对应的value值
private static String url = bundle.getString("url");
private static String user = bundle.getString("user");
private static String passord = bundle.getString("passord");
static {
// 注册驱动(注册驱动只需要注册一次,放在静态代码当中,DBUtil类加载的时候执行)
// ".mysql.jdbc.Driver"是连接数据库的驱动,不能写死,因为以后可能还会连接Oracle数据库。
// OCP开闭原则: 对扩展开放,对修改关闭(什么是符合 OCP呢?在进行功能扩展的时候,不需要修改java源代码)
// Class.forName(".mysql.jdbc.Driver")
try {
Class.forName(driver); // 加载驱动
} catch (ClassNotFoundException e) {
thro ne RuntimeException(e);
}
}
/
获取数据库连接
@return Connection
/
public static Connection getConnection() thros SQLException {
return DriverManager.getConnection(url,user,passord);
}
/
关闭连接
@param connection
@param statement
@param resultSet
/
public static void close(Connection connection, Statement statement, ResultSet resultSet) {
// 注意分开try,使用的资源,优先关闭
if(resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
thro ne RuntimeException(e);
}
}
if(statement != null) {
try {
statement.close();
} catch (SQLException e) {
thro ne RuntimeException(e);
}
}
if(connection != null) {
try {
connection.close();
} catch (SQLException e) {
thro ne RuntimeException(e);
}
}
}
}
2.4 功能一 index.html 该项目的欢迎页面如下
默认在 eb 当中,一个全局配置信息当中,会将名为 index.html 的文件,设置为该项目的欢迎页面。相应的具体内容大家可以移步至 关于Web的欢迎页面的开发设置_ChinaRainboSea的博客-CSDN博客
欢迎使用OA系统
查看部门列表
2.5 功能二部门列表 DeptListServlet
注意因为我们这里使用的是 纯 Servlet 编写的一个项目,所以在后端想要将相关的 HTML 标签相应到前端浏览器,被浏览器渲染的话,则需要特殊的方法如下
// 设置将后端的字符串的 html 标签相应到浏览器端执行处理,并设置相应的字符集编码
response.setContentType("text/html;charSet=UTF-8");
PrintWriter riter = response.getWriter();
思路:
- 在DeptListServlet类的doGet方法中连接数据库,查询所有的部门,动态的展示部门列表页面.
- 分析 html 页面中哪部分是固定死的,哪部分是需要动态展示的。
- html页面中的内容所有的双引号要替换成单引号,因为out.print("")这里有一个双引号,容易冲突。
- 现在写完这个功能之后,你会有一种感觉,感觉开发很繁琐,只使用servlet写代码太繁琐了
- 我们需要连接数据库,从数据库中获取到数据,显示到前端浏览器当中。
- 注意我们这里上面的 index.html 中是通过超链接的方式,跳转到该 部门列表页面的。超链接是 doGet 请求。
package .RainboSea.servlet;
import .RainboSea.DBUtil.DBUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/
部门列表
/
public class DeptListServlet extends HttpServlet {
/
说明这里使用了doGet,和 goPost 的原因是,我们前端的 DeptSaveServlet 的新增部门,
的请求是doPost,从 doPost 请求 "转发"出来的同样是 doPost请求的,而 重定向就是doGet请求了,无论是doPost,doGet请求都是
所以这里为了,处理接受到 DeptSaveServlet 的新增部门的 "转发"请求,写了一个doPost 请求处理
/
// 优化,将转发,替换成了 重定向的机制,(重定向的机制)是自发到浏览器前端的地址栏上的,后自发的执行
// 地址栏上是 doGet 请求的,就不需要 doPost 请求了。
/ @Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) thros ServletException,
IOException {
doGet(request, response); // 调用本身这里的doGet()请求
}/
/
因为我们前端使用的是 超链接,是goGet请求所以,
前后端的请求保持一致。
/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) thros ServletException,
IOException {
// 设置前端浏览器显示的格式类型,以及编码
response.setContentType("text/html;charSet=UTF-8");
PrintWriter riter = response.getWriter();
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
// 获取到该ebapp的项目根路径也就是在Tomcat 当中设置的访问的项目路径
// 注意的是: getContextPath()获取返回的路径是带有 "/项目名"的,所以不要多写了 /
String contextPath = request.getContextPath();
int i = 0;
riter.println(" ");
riter.println("");
riter.println("");
riter.println(" ");
riter.println(" 部门列表页面 ");
riter.println("");
riter.println("
以上的前端程序要写到后端的java代码当中DeptListServlet类的doGet方法当中,使用out.print()方法,将以上的前端代码输出到浏览器上。
删除成功或者失败的时候的一个处理(这里我们一开始使用的选择的是转发,后面优化使用的是重定向机制。)
删除成功我们跳转到部门列表当中。DeptListServlet
删除失败我们跳转到一个失败的页面当中。这里我们将该失败的页面名为: error.html 页面如下
error
操作失败:
返回
具体的 Servlet 编写如下
package .RainboSea.servlet;
import .RainboSea.DBUtil.DBUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/
删除部门
/
public class DeptDelServlet extends HttpServlet {
/
注意前端是超链接的方式是get请求
/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) thros ServletException,
IOException {
response.setContentType("text/html;charSet=UTF-8");
PrintWriter riter = response.getWriter();
request.setCharacterEncoding("UTF-8");
// 思路:
/
根据部门编号删除信息,
删除成功,跳转回原来的部门列表页面
删除失败,跳转删除失败的页面
/
Connection connection = null;
PreparedStatement preparedStatement = null;
// 记录删除数据库的行数
int count = 0;
// 获取到前端提交的数据
String deptno = request.getParameter("deptno");
// 连接数据库进行删除操作
try {
// 1.注册驱动,连接数据库
connection = DBUtil.getConnection();
// 开启事务(取消自动提交机制),实现可回滚
connection.setAutoCommit(false);
// 2. 预编译sql语句,sql测试
String sql = "delete from dept here depton = ?"; // ? 占位符
preparedStatement = connection.prepareStatement(sql);
// 3. 填充占位符,真正的执行sql语句
preparedStatement.setString(1, deptno);
// 返回影响数据库的行数
count = preparedStatement.executeUpdate();
connection.mit(); // 手动提交数据
} catch (SQLException e) {
// 遇到异常回滚
if (connection != null) {
try {
// 事务的回滚
connection.rollback();
} catch (SQLException ex) {
thro ne RuntimeException(ex);
}
}
thro ne RuntimeException(e);
} finally {
// 4. 释放资源
// 因为这里是删除数据,没有查询操作,所以 没有 ResultSet 可以传null
DBUtil.close(connection, preparedStatement, null);
}
if (count == 1) {
// 删除成功
// 仍然跳转到部门列表页面
// 部门列表页面的显示需要执行一个Servlet,怎么办,可以使用跳转,不过这里是使用重定向
// 注意转发是在服务器间的,所以不要加“项目名” 而是 / + eb.xml 映射的路径即可
//request.getRequestDispatcher("/dept/list/").forard(request,response);
// 优化使用重定向机制 注意: 重定向是自发到前端的地址栏上的,前端所以需要指明项目名
// 注意: request.getContextPath() 返回的根路径是,包含了 "/" 的
response.sendRedirect(request.getContextPath() + "/dept/list/");
} else {
// 删除失败
// eb当中的 html资源,这里的 "/" 表示 eb 目录
//request.getRequestDispatcher("/error.html/").forard(request, response);
// 优化,使用重定向
response.sendRedirect(request.getContextPath() + "/error.html/");
}
}
}
2.8 功能五新增部门 DeptSaveServlet
思路
获取到前端 form 表单提交的数据,这里我们 form 表单 中的 metod = post 设置为了 doPost 请求。
再连接数据库,添加一条记录。
添加成功我们跳转到部门列表当中。DeptListServlet
添加失败我们跳转到一个失败的页面当中。这里我们将该失败的页面名为: error.html
注意点
保存成功之后,跳转到 /dept/list 的时候,如果你使用的是 转发 机制的话,这里因为你是从 doPost 请求 转发过去的(转发是一次请求,之前是post,之后还是post,因为它是一次请求。),所以对应接收该 doPost 请求的也要是 doPost 方法处理该请求,不然会报 405 错误。
而这时候接收该 转发 的 /dept/list Servlet当中只有一个doGet方法。就会报 405 错误。
怎么解决?两种方案
第一种在/dept/list Servlet中添加doPost方法,然后在doPost方法中调用doGet。第二种使用重定向的方式,进行跳转,重定向的机制是改变浏览器的请求路径URL,让浏览器重新发送跳转之后的 URL 地址,该方式是从浏览器地址栏上跳转的,所以是 doGet 请求,就不要编写 doPost 请求了。
具体代码编写如下
package .RainboSea.servlet;
import .RainboSea.DBUtil.DBUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/
增加部门数据
/
public class DeptSaveServlet extends HttpServlet {
// 前端是注册信息,是post 请求
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) thros ServletException,
IOException {
/
思路:
获取到前端的提交的数据,注意 编码设置post 请求
连接数据库: 进行添加数据
添加成功: 返回部门列表页面
添加失败: 返回失败的页面
/
request.setCharacterEncoding("UTF-8");
// 获取到前端的数据,建议 name 使用复制
String deptno = request.getParameter("deptno");
String dname = request.getParameter("dname");
String loc = request.getParameter("loc");
// 连接数据库,添加数据
Connection connection = null;
PreparedStatement preparedStatement = null;
// 影响数据库的行数
int count = 0;
try {
// 1. 注册驱动,连接数据库
connection = DBUtil.getConnection();
// 2. 获取操作数据库对象,预编译sql语句,Sql测试
String sql = "insert into dept(depton,dname,loc) values(?,?,?)";
preparedStatement = connection.prepareStatement(sql);
// 3. 填充占位符, 真正执行sql语句,
// 注意 占位符的填充是从 1 开始的,基本上数据库相关的起始下标索引都是从 1下标开始的
preparedStatement.setString(1, deptno);
preparedStatement.setString(2, dname);
preparedStatement.setString(3, loc);
// 返回影响数据库的行数
count = preparedStatement.executeUpdate();
// 5.释放资源
} catch (SQLException e) {
thro ne RuntimeException(e);
} finally {
DBUtil.close(connection, preparedStatement, null);
}
// 保存成功,返回部门列表页面
if (count == 1) {
// 这里应该使用,重定向
// 这里用的转发,是服务器内部的,不要加项目名
//request.getRequestDispatcher("/dept/list/").forard(request, response);
// 重定向
response.sendRedirect(request.getContextPath() + "/dept/list/");
} else {
// 保存失败
// eb当中的 html资源,这里的 "/" 表示 eb 目录
//request.getRequestDispatcher("/error.html").forard(request, response);
response.sendRedirect(request.getContextPath() + "/error.html");
}
}
}
2.9 功能六跳转到修改部门的页面 DepEditServlet
思路:
获取到提交的过来的 部门编号
根据部门编号修改信息,注意部门编号是唯一的不要被修改了
连接数据库,查询到相关信息显示到浏览器页面当中,方便用户修改
具体的代码编写如下
package .RainboSea.servlet;
import .RainboSea.DBUtil.DBUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/
跳转到修改部门的页面
/
public class DepEditServlet extends HttpServlet {
// 超链接是 doGet()请求
// http://127.0.0.1:8080/servlet09/dept/edit?deptno=10
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) thros ServletException,
IOException {
response.setContentType("text/html;charSet=UTF-8");
PrintWriter riter = response.getWriter();
riter.println(" ");
riter.println("");
riter.println("");
riter.println(" ");
riter.println(" 部门列表页面 ");
riter.println("");
riter.println("");
riter.println(" 修改部门
");
riter.println(" ");
riter.println("");
riter.println("");
}
}
2.10 功能七修改部门 DeptSaveServlet
思路:
获取到前端的提交的数据,注意 编码设置post 请求
连接数据库: 进行添加数据
添加成功: 返回部门列表页面
添加失败: 返回失败的页面
具体的代码编写如下
package .RainboSea.servlet;
import .RainboSea.DBUtil.DBUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/
增加部门数据
/
public class DeptSaveServlet extends HttpServlet {
// 前端是注册信息,是post 请求
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) thros ServletException,
IOException {
/
思路:
获取到前端的提交的数据,注意 编码设置post 请求
连接数据库: 进行添加数据
添加成功: 返回部门列表页面
添加失败: 返回失败的页面
/
request.setCharacterEncoding("UTF-8");
// 获取到前端的数据,建议 name 使用复制
String deptno = request.getParameter("deptno");
String dname = request.getParameter("dname");
String loc = request.getParameter("loc");
// 连接数据库,添加数据
Connection connection = null;
PreparedStatement preparedStatement = null;
// 影响数据库的行数
int count = 0;
try {
// 1. 注册驱动,连接数据库
connection = DBUtil.getConnection();
// 2. 获取操作数据库对象,预编译sql语句,Sql测试
String sql = "insert into dept(depton,dname,loc) values(?,?,?)";
preparedStatement = connection.prepareStatement(sql);
// 3. 填充占位符, 真正执行sql语句,
// 注意 占位符的填充是从 1 开始的,基本上数据库相关的起始下标索引都是从 1下标开始的
preparedStatement.setString(1, deptno);
preparedStatement.setString(2, dname);
preparedStatement.setString(3, loc);
// 返回影响数据库的行数
count = preparedStatement.executeUpdate();
// 5.释放资源
} catch (SQLException e) {
thro ne RuntimeException(e);
} finally {
DBUtil.close(connection, preparedStatement, null);
}
// 保存成功,返回部门列表页面
if (count == 1) {
// 这里应该使用,重定向
// 这里用的转发,是服务器内部的,不要加项目名
//request.getRequestDispatcher("/dept/list/").forard(request, response);
// 重定向
response.sendRedirect(request.getContextPath() + "/dept/list/");
} else {
// 保存失败
// eb当中的 html资源,这里的 "/" 表示 eb 目录
//request.getRequestDispatcher("/error.html").forard(request, response);
response.sendRedirect(request.getContextPath() + "/error.html");
}
}
}
3. 的 eb.xml 配置信息
<?xml version="1.0" encoding="UTF-8"?>
list
.RainboSea.servlet.DeptListServlet
list
/dept/list/
detail
.RainboSea.servlet.DeptDetailServlet
detail
/dept/detail
delete
.RainboSea.servlet.DeptDelServlet
delete
/dept/delete
save
.RainboSea.servlet.DeptSaveServlet
save
/dept/save
edit
.RainboSea.servlet.DepEditServlet
edit
/dept/edit
modify
.RainboSea.servlet.DeptModifyServlet
modify
/dept/modify
4. 优化方案 @WebServlet 注解 + 模板方法
由于设计到文章的篇幅过多,大家想要了解的可以移步至 Servlet注解的使用,简化配置 以及,使用模板方法设计模式优化oa项目_ChinaRainboSea的博客-CSDN博客
5.
- 每次前端提交的数据都通过浏览器 F12 检查的方式,查看我们提交的数据是否,是我们需要的,是否满足条件。
- 每次后端从前端浏览器获取到的数据,同样都是需要打印或者调试看看,我们获取的数据是否存在错误,或者乱码的情况。
- 如果对SQL语句不太熟练的话,建议无论是否是简单的 SQL语句都,可以先在对应的数据库中运行测试看看,是否存在错误。
- 尽可能的做到,每实现一点功能就测试一下,是否存在错误,而不是一顿操作下来,虽然所以代码都编写完了,到测试的时候,一堆 BUG 。
- 我们应该怎么去实现一个功能呢?
- 建议你可以从后端往前端一步一步写。也可以从前端一步一步往后端写。都可以。千万要记住不要想起来什么写什么。你写代码的过程最好是程序的执行过程。也就是说程序执行到哪里,你就写哪里。这样一个顺序流下来之后,基本上不会出现什么错误、意外。
- 从哪里开始?假设从前端开始,那么一定是从用户点击按钮那里开始的
- 分析清楚哪里使用的是 doGet 请求 ,哪里使用的是 doPost 请求。
- 分析清楚哪里使用的是 服务器端的转发 ,哪里使用的是 重定向机制。
- 注意 在服务器当需要使用到的 jar 包,必须在 WEB-INF 的目录下,创建一个名为 lib 的目录文件夹,用来存放相关的 依赖jar 包,注意路径位置不可以修改,目录文件必须为 lib 不然,当你启动的Tocmat 服务器的时候,是无法找到该对应的 依赖jar 包的。
6.
限于自身水平,其中存在的错误,希望大家,给予指教,韩信点兵——多多益善,谢谢大家,江湖再见,后会有期!!!
奇闻怪事
- 黎姿老公马廷强前妻 黎姿老公马廷强前妻是谁
- 紫禁城闹鬼是真的吗 紫禁城闹鬼是怎么回事
- 80年黄河透明棺材事件 80年代黄河透明棺材始末
- 51区外星人录像是真的吗 51区外星人真的存在吗
- 姜潮麦迪娜恋爱过程 姜潮麦迪娜怎么认识的
- 根达亚文明大概离现在多久 根达亚文明距今多少
- 赤塔事件真的还是假的 赤塔事件到底怎么回事
- 百慕大三角在哪个国家 百慕大三角在哪个国家的
- 邓超出轨安以轩:邓超出轨安以轩是不是真的
- 中国十大闹鬼最凶的地方 中国十大闹鬼最凶的地
- 湘西鬼结婚事件:湘西鬼结婚事件真假
- 中国昆仑山10大灵异绝密档案 中国昆仑山10大灵异
- 李维嘉的父亲是谁 李维嘉父母是哪里人
- 朱秀华事件是不是真的 朱秀华事件的真相是什么
- 太湖冤魂:太湖冤魂事件真假
- 爪哇虎和东北虎谁厉害 爪哇虎和东北虎谁厉害一