使用Servlet做一个单表的CRUD操作
开发前的准备
导入sql脚本创建一张部门表
drop table if exists dept;
create table dept(
deptno int primary key,
dname varchar(255),
loc varchar(255)
);
insert into dept(deptno, dname, loc) values(10, 'XiaoShouBu', 'BeiJing');
insert into dept(deptno, dname, loc) values(20, 'YanFaBu', 'SHANGHAI');
insert into dept(deptno, dname, loc) values(30, 'JiShuBu', 'GUANGZHOU');
insert into dept(deptno, dname, loc) values(40, 'MeiTiBu', 'SHENZHEN');
commit;
select * from dept;
通过绑定属性资源配置文件的方式创建JDBC的工具类utils/DBUtil
#在src类路径下新建包resourcse,在包中新建一个属性配置文件jdbc.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/javaweb
user=root
password=123456
public class DBUtil {
// 绑定属性资源配置文件,配置文件的后缀名不用写,文件路径路径分隔符可以是"."也可以是"/"
private static ResourceBundle bundle = ResourceBundle.getBundle("resources.jdbc");
// 根据属性配置文件的key获取value,静态变量在类加载时按自上而下的顺序执行
private static String driver = bundle.getString("driver");
private static String url = bundle.getString("url");
private static String user = bundle.getString("user");
private static String password = bundle.getString("password");
static {
// 由于注册驱动只需要注册一次所以可以放在静态代码块当中,当DBUtil类加载的时候执行静态代码块中的代码
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取数据库连接对象
* @return conn 连接对象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
// 获取连接
Connection conn = DriverManager.getConnection(url, user, password);
return conn;
}
/**
* 释放资源
* @param conn 连接对象
* @param ps 数据库操作对象
* @param rs 结果集对象
*/
public static void close(Connection conn, Statement ps, ResultSet rs){
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
实现系统功能
分析系统的功能: 只要这个操作连接了数据库,就表示一个独立的功能
- 查看部门列表 , 新增部门 , 删除部门 , 查看部门详细信息 , 跳转到修改页面(动态的从数据库中获取信息并显示) , 修改部门
实现一个具体功能: 可以从后端往前端一步一步写, 以可以从前端一步一步往后端写 , 千万不要想起来什么写什么
- 假设从前端开始,那么一定是从用户点击按钮那里开始的—>用户点击的东西在哪里–>用户点击的按钮时具体发起什么请求
实现查看部门列表功能
设置欢迎页面index.html的超链接,用户通过点击"查看部门列表按钮"跳转到部门列表页面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>欢迎使用OA系统</title>
</head>
<body>
<!--前端超链接发送请求的时候,请求路径以“/”开始,并且要带着项目名-->
<a href="/oa/dept/list">查看部门列表</a>
</body>
</html>
编写DeptListServlet类继承HttpServlet类并重写doGet方法(配置到web.xml文件中)
- 在DeptListServlet类的doGet方法中连接数据查询所有的部门,分析部门列表页面中哪部分是固定死的,哪部分是需要动态展示数据的
- 部门列表页面中的内容所有的双引号要替换成单引号,因为out.print(“”)这里有一个双引号容易冲突 (可以利用文本工具查找替换)
<servlet>
<servlet-name>list</servlet-name>
<servlet-class>com.bjpowernode.oa.web.action.DeptListServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>list</servlet-name>
<!--web.xml文件中的这个路径也是以“/”开始的,但是不需要加项目名-->
<url-pattern>/dept/list</url-pattern>
</servlet-mapping>
public class DeptListServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取应用的根路径
String contextPath = request.getContextPath();
// 设置响应的内容类型以及字符集,防止中文乱码问题
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
// 使用多行编辑功能 , 按住Alt键 , 鼠标往下托就行
out.print("<!DOCTYPE html>");
out.print("<html>");
out.print(" <head>");
out.print(" <meta charset='utf-8'>");
out.print(" <title>部门列表页面</title>");
//删除按钮执行的事件函数
out.print("<script type='text/javascript'>");
out.print(" function del(dno){");
out.print(" if(window.confirm('亲,删了不可恢复哦!')){");
out.print(" document.location.href = '"+contextPath+"/dept/delete?deptno=' + dno");
out.print(" }");
out.print(" }");
out.print("</script>");
out.print(" </head>");
out.print(" <body>");
out.print(" <h1 align='center'>部门列表</h1>");
out.print(" <hr >");
out.print(" <table border='1px' align='center' width='50%'>");
out.print(" <tr>");
out.print(" <th>序号</th>");
out.print(" <th>部门编号</th>");
out.print(" <th>部门名称</th>");
out.print(" <th>操作</th>");
out.print(" </tr>");
/*上面一部分是死的*/
// 动态的连接数据库,查询所有的部门
//...................
/*下面一部分是死的*/
out.print(" </table>");
out.print(" <hr >");
out.print(" <a href='"+contextPath+"/add.html'>新增部门</a>");
out.print(" </body>");
out.print("</html>");
}
}
动态的连接数据库查询所有的部门
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 获取连接
conn = DBUtil.getConnection();
// 获取预编译的数据库操作对象
String sql = "select deptno as a,dname,loc from dept";
ps = conn.prepareStatement(sql);
// 执行SQL语句
rs = ps.executeQuery();
// 处理结果集
// i 表示序号
int i = 0;
while(rs.next()){
String deptno = rs.getString("a");
String dname = rs.getString("dname");
String loc = rs.getString("loc");
out.print(" <tr>");
out.print(" <td>"+(++i)+"</td>");
out.print(" <td>"+deptno+"</td>");
out.print(" <td>"+dname+"</td>");
out.print(" <td>");
//点击按钮不会进行页面的跳转,而是执行事件函数
out.print(" <a href='javascript:void(0)' οnclick='del("+deptno+")'>删除</a>");
out.print(" <a href='"+contextPath+"/dept/edit?deptno="+deptno+"'>修改</a>");
out.print(" <a href='"+contextPath+"/dept/detail?deptno="+deptno+"'>详情</a>");
out.print(" </td>");
out.print(" </tr>");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 释放资源
DBUtil.close(conn, ps, rs);
}
实现查看部门详情功能
用户点的“详情”的按钮是DeptListServlet 动态的响应的内容,后端连接数据库查询部门信息时,不同的部门对应不同的编号,同理请求参数deptno的值也不同
- 这个超链接路径是需要加项目名的, 并且请求参数中需要携带你想要查看的部门编号 , 这样才能连接数据库查询对应部门信息
while(rs.next()){
String deptno = rs.getString("a");
// 部分代码省略
// 向服务器提交数据的格式:uri?name=value&name=value&...
out.print(" <a href='"+contextPath+"/dept/detail?deptno="+deptno+"'>详情</a>");
}
编写DeptDetailServlet继承HttpServlet并重写doGet方法((配置到web.xml文件中)
- 在doGet方法当中根据获取的部门编号连接数据库查询对应部门的信息, 并动态的将部门详情页展示到浏览器上
<servlet>
<servlet-name>detail</servlet-name>
<servlet-class>com.bjpowernode.oa.web.action.DeptDetailServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>detail</servlet-name>
<url-pattern>/dept/detail</url-pattern>
</servlet-mapping>
public class DeptDetailServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print("<!DOCTYPE html>");
out.print("<html>");
out.print(" <head>");
out.print(" <meta charset='utf-8'>");
out.print(" <title>部门详情</title>");
out.print(" </head>");
out.print(" <body>");
out.print(" <h1>部门详情</h1>");
out.print(" <hr >");
/*上面一部分是死的*/
// 根据部门编号连接数据库查询对应部门的信息并动态展示
//...................
/*下面一部分是死的*/
out.print(" <input type='button' value='后退' οnclick='window.history.back()'/>");
out.print(" </body>");
out.print("</html>");
}
}
从请求地址[/oa/dept/detail?deptno=30][]中uri中的请求参数获取部门编号然后连接数据库查询该部门的信息,最后动态展示部门详情页的代码
// 虽然浏览器提交的是30这个数字,但是服务器获取的是"30"字符串
String deptno = request.getParameter("deptno");
// 连接数据库根据部门编号查询部门信息
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnection();
// mysql底层自动会把"30"转换成30
String sql = "select dname,loc from dept where deptno = ?";
ps = conn.prepareStatement(sql);
ps.setString(1, deptno);
rs = ps.executeQuery();
// 这个结果集一定只有一条记录
if(rs.next()){
String dname = rs.getString("dname");
String loc = rs.getString("loc");
out.print("部门编号:"+deptno+" <br>");
out.print("部门名称:"+dname+"<br>");
out.print("部门位置:"+loc+"<br>");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(conn, ps, rs);
}
实现删除部门功能
用户点的“删除”按钮是DeptListServlet 动态响应的内容 ,点击删除按钮要执行的事件函数也是DeptListServlet动态的响应的内容
- 删除这个动作是比较危险的,所以一般通过点击超链接后先执行事件函数不跳转页面, 在事件函数中发起请求实现删除部门的功能
- 后端连接数据库查询部门信息时, 不同的部门对应不同的编号,同理请求参数deptno的值也不同
- 删除部门对应两种结果成功或者失败,成功如何处理,失败又如何处理
while(rs.next()){
// 部分代码省略
String deptno = rs.getString("a");
// href设置为javascript:void(0)或javascript:;都会保留超链接的样子,但是点击后不进行页面的跳转
out.print(" <a href='javascript:void(0)' οnclick='del("+deptno+")'>删除</a>");
}
// 根据部门编号删除对应的部门
out.print("<script type='text/javascript'>");
out.print(" function del(dno){");
out.print(" if(window.confirm('亲,删了不可恢复哦!')){");
// 跳转页面的几种方式: document.location = "请求路径"(document都可以替换成window)
out.print(" document.location.href = '"+contextPath+"/dept/delete?deptno=' + dno");
out.print(" }");
out.print(" }");
out.print("</script>");
编写DeptDelServlet继承HttpServlet并重写doGet方法(配置到web.xml文件中)
- 在doGet方法当中根据部门编号删除对应的部门,删除成功后再重定向到部门列表页面(Servlet)
<servlet>
<servlet-name>delete</servlet-name>
<servlet-class>com.bjpowernode.oa.web.action.DeptDelServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>delete</servlet-name>
<url-pattern>/dept/delete</url-pattern>
</servlet-mapping>
public class DeptDelServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 根据请求参数获取的部门编号连接数据库删除对应部门记录
String deptno = request.getParameter("deptno");
Connection conn = null;
PreparedStatement ps = null;
// count判断判断删除成功了还是失败了
int count = 0;
try {
conn = DBUtil.getConnection();
// 开启事务(自动提交机制关闭)
conn.setAutoCommit(false);
String sql = "delete from dept where deptno = ?";
ps = conn.prepareStatement(sql);
ps.setString(1, deptno);
// 返回值是影响了数据库表当中多少条记录
count = ps.executeUpdate();
// 事务提交
conn.commit();
} catch (SQLException e) {
// 遇到异常要回滚
if (conn != null) {
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally {
DBUtil.close(conn, ps, null);
}
// 判断删除成功了还是失败了..........
}
}
删除成功或者失败的时候的一个处理 , 因为不需要共享数据所以我们使用重定向机制
- 删除成功重定向到部门列表页面即执行DeptListServlet
- 删除失败重定向到error.html页面
if (count == 1) { //删除成功
response.sendRedirect(request.getContextPath() + "/dept/list");
}else{ // 删除失败
response.sendRedirect(request.getContextPath() + "/error.html");
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>error</title>
</head>
<body>
<h1>操作失败,<a href="javascript:void(0)" onclick="window.history.back()">返回</a></h1>
</body>
</html>
实现新增部门功能
“新增部门”的按钮在DeptListServlet动态响应的内容中, 通过点击该链接直接跳转到新增部门的表单页面,利用表单发起post请求提交新增部门的信息
/*下面一部分是死的*/
out.print(" <a href='"+contextPath+"/add.html'>新增部门</a>");
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>新增部门</title>
</head>
<body>
<h1>新增部门</h1>
<hr >
<!--发起post请求,新增部门-->
<form action="/oa/dept/save" method="post">
部门编号<input type="text" name="deptno"/><br>
部门名称<input type="text" name="dname"/><br>
部门位置<input type="text" name="loc"/><br>
<input type="submit" value="保存"/><br>
</form>
</body>
</html>
编写DeptSaveServlet继承HttpServlet并重写doPost方法(配置到web.xml文件中)
- 在doPost方法当中连接数据库保存新增的部门信息,新增部门成功后重定向到部门列表页面(如果转发转发的是post请求需要执行doPost方法)
- 保存新增部门信息时可能保存成功也可能保存失败,保存成功如何处理,失败后又如何处理
<servlet>
<servlet-name>save</servlet-name>
<servlet-class>com.bjpowernode.oa.web.action.DeptSaveServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>save</servlet-name>
<url-pattern>/dept/save</url-pattern>
</servlet-mapping>
public class DeptSaveServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取部门的信息时注意乱码问题(Tomcat10不会出现这个问题)
request.setCharacterEncoding("UTF-8");
String deptno = request.getParameter("deptno");
String dname = request.getParameter("dname");
String loc = request.getParameter("loc");
// 连接数据库执行insert语句
Connection conn = null;
PreparedStatement ps = null;
// 判断保存成功还是失败
int count = 0;
try {
conn = DBUtil.getConnection();
String sql = "insert into dept(deptno, dname, loc) values(?,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1, deptno);
ps.setString(2, dname);
ps.setString(3, loc);
count = ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(conn, ps, null);
}
// 判断保存成功还是失败 , 保存成功跳转到列表页面.................
}
}
转发到/dept/list会出现405错误, 由于转发是一次请求, 保存新增部门信息表单发起的是post请求, 转发到DeptListServlet时需要调用doPost方法处理请求
public class DeptListServlet extends HttpServlet {
// 处理post请求然后在doPost方法中调用doGet方法
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 连接数据库,动态展示部门列表页面
}
重定向到/oa/dept/list, 浏览器此时会在地址栏上发起一次全新的get请求,调用DeptListServlet的doGet方法处理请求
if (count == 1) {//新增成功
// 显示部门列表页面
response.sendRedirect(request.getContextPath() + "/dept/list");
}else{// 新增失败
// 使用重定向跳转到错误页面
response.sendRedirect(request.getContextPath() + "/error.html");
}
实现查看部门详情可修改功能
用户点击的“修改”按钮是DeptListServlet动态响应的内容,后端连接数据库查询部门信息时,不同的部门对应不同的编号,同理请求参数deptno的值也不同
- 这个路径是需要加项目名的, 请求参数携带你想要查看的部门编号 , 这样才能连接数据库查询部门信息动态的输出要修改的部门信息
while(rs.next()){
String deptno = rs.getString("a");
out.print(" <a href='"+contextPath+"/dept/edit?deptno="+deptno+"'>修改</a>");
}
编写DeptEditServlet继承HttpServlet并重写doGet方法(配置到web.xml文件中)
- 在doGet方法中根据请求参数中获取的部门编号连接数据库查询对应部门的信息并以表单的形式展示数据
<servlet>
<servlet-name>edit</servlet-name>
<servlet-class>com.bjpowernode.oa.web.action.DeptEditServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>edit</servlet-name>
<url-pattern>/dept/edit</url-pattern>
</servlet-mapping>
public class DeptEditServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取应用的根路径
String contextPath = request.getContextPath();
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print("<!DOCTYPE html>");
out.print("<html>");
out.print(" <head>");
out.print(" <meta charset='utf-8'>");
out.print(" <title>修改部门</title>");
out.print(" </head>");
out.print(" <body>");
out.print(" <h1>修改部门</h1>");
out.print(" <hr >");
// 利用表单发起请求,保存部门修改后的信息
out.print(" <form action='"+contextPath+"/dept/modify' method='post'>");
//上面一部分是死的
// 连接数据库,动态输出部门的信息.....
。
//下面一部分是死的
out.print(" <input type='submit' value='修改'/><br>");
out.print(" </form>");
out.print(" </body>");
out.print("</html>");
}
}
根据请求参数中携带的部门编号连接数据库,动态的将查询到的部门信息保存到一个表单中并返回给浏览器(这里的部门编号是只读的,readonly属性)
// 获取请求参数中携带的部门编号
String deptno = request.getParameter("deptno");
// 根据部门编号连接数据库查询部门信息
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnection();
String sql = "select dname, loc as location from dept where deptno = ?";
ps = conn.prepareStatement(sql);
ps.setString(1, deptno);
rs = ps.executeQuery();
// 这个结果集中只有一条记录
if(rs.next()){
String dname = rs.getString("dname");
// 参数"location"是sql语句查询结果列的列名
String location = rs.getString("location");
// 部门编号是只读的
out.print(" 部门编号<input type='text' name='deptno' value='"+deptno+"' readonly /><br>");
out.print(" 部门名称<input type='text' name='dname' value='"+dname+"'/><br>");
out.print(" 部门位置<input type='text' name='loc' value='"+location+"'/><br>");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(conn, ps, rs);
}
实现修改部门功能
用户点击的“修改”按钮是DeptEditServlet动态响应的内容,这个路径是需要加项目名的, 表单的name作为请求参数,表单的值作为value
// 利用表单发起请求,保存部门修改后的信息
out.print(" <form action='"+contextPath+"/dept/modify' method='post'>");
//下面一部分是死的
out.print(" <input type='submit' value='修改'/><br>");
out.print(" </form>");
编写DeptModifyServlet类继承HttpServlet并重写doPost方法(配置到web.xml文件中)
- 在doPost方法当中连接数据库完成部门信息的更新 , 数据库更新成功后需要重定向到部门列表页面(Servlet)
- 更新部门信息时可能更新成功也可能更新失败,更新成功如何处理,更新失败后又如何处理
<servlet>
<servlet-name>modify</servlet-name>
<servlet-class>com.bjpowernode.oa.web.action.DeptModifyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>modify</servlet-name>
<url-pattern>/dept/modify</url-pattern>
</servlet-mapping>
public class DeptModifyServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 解决请求体的中文乱码问题
request.setCharacterEncoding("UTF-8");
// 获取表单中的数据
String deptno = request.getParameter("deptno");
String dname = request.getParameter("dname");
String loc = request.getParameter("loc");
// 连接数据库执行更新语句
Connection conn = null;
PreparedStatement ps = null;
// 判断更新成功或者失败
int count = 0;
try {
conn = DBUtil.getConnection();
String sql = "update dept set dname = ?, loc = ? where deptno = ?";
ps = conn.prepareStatement(sql);
ps.setString(1, dname);
ps.setString(2, loc);
ps.setString(3, deptno);
count = ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(conn, ps, null);
}
//更新部门信息成功或者失败的一个处理.....
}
}
更新部门信息成功或者失败的一个处理, 如果不需要共享数据,页面跳转都是以重定向
if (count == 1) { // 更新成功
// 重定向到部门列表页面(部门列表页面是通过Java程序动态生成的,所以还需要再次执行另一个Servlet)
response.sendRedirect(request.getContextPath() + "/dept/list");
}else{ // 更新失败
response.sendRedirect(request.getContextPath() + "/error.html");
}
使用模板方法设计模式优化oa项目
模板方法设计模式解决类爆炸
如果每一个请求都对应一个Servlet类就会导致类爆炸 , 一般一个请求对应一个方法 , 一个业务对应一个Servlet类
- 处理部门相关业务的对应一个DeptServlet ,处理用户相关业务的对应一个UserServlet
重写HttpServlet类提供的重载的参数含httpXxx的service模板方法,本类提供以后会调本类的service模板方法(但是就享受不到HTTP协议专属的提醒)
// 模糊匹配 ,只要请求路径是以"/dept"开始的,都走这个Servlet
@WebServlet("/dept/*")
// @WebServlet({"/dept/list", "/dept/save", "/dept/edit", "/dept/detail", "/dept/delete", "/dept/modify"})
public class DeptServlet extends HttpServlet {
// 重写service模板方法(并没有重写doGet或者doPost)
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取servlet path
String servletPath = request.getServletPath();
if("/dept/list".equals(servletPath)){
doList(request, response);
} else if("/dept/save".equals(servletPath)){
doSave(request, response);
} else if("/dept/edit".equals(servletPath)){
doEdit(request, response);
} else if("/dept/detail".equals(servletPath)){
doDetail(request, response);
} else if("/dept/delete".equals(servletPath)){
doDel(request, response);
} else if("/dept/modify".equals(servletPath)){
doModify(request, response);
}
}
private void doList(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 查看部门列表的业务逻辑
}
private void doSave(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 保存部门的业务逻辑
}
private void doEdit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 查看部门可修改的业务逻辑
}
private void doDetail(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 查看部门详情的业务逻辑
}
private void doDel(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 删除部门的业务逻辑
}
private void doModify(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 修改部门的业务逻辑
}
}