实验内容: 使用servlet+DAO实现基本数据库交互
具体要求
编写一个静态网页,网页命名:student.html
编写一个Servlet,命名:StudentServlet
创建hit数据库(PostgreSQL或MySQL均可),其中有两个表:student和major。student表中gender字段存储性别用m和f表示男和女,存储专业编号的mid字段对应于专业表major中的专业编号mid。
运行student.html,网页打开后以表格的方式读取到数据库中所有学生数据,其中性别要显示男或女,专业显示专业名称(mname)。
在student.html的某一个位置有一个用于新增学生信息的区域:其中性别是单选按钮,显示“男”和“女”,默认选中“男”;专业以下拉列表方式,显示major表中的专业名称(mname)信息,其中默认选中第一项,第一项内容是:-请选择专业-。录入过程中当输入学号时,数据库中如果有已存在的学号要在学号的输入框后提示:该学号已存在。
生日使用H5的input(type=date)元素,年龄使用input(type=number)即可。
提交数据前在前端要使用JS进行数据验证,提交后在后端(Servlet)也要进行数据验证,后端数据验证不合法不能将数据保存到数据库,并要返回给前端错误信息并在适当的位置显示错误信息。
数据录入成功后在提示区域显示:新增学生成功,表格中需要把新录入的数据显示出来(重新加载学生数据)。
前端所有请求均使用AJAX(建议使用jQuery AJAX),后端使用JDBC + DAO。
程序运行过程中浏览器地址始终显示student.html,页面不能跳转或刷新。
项目中的文件命名要求:
student.html
StudentServlet.java
Student.java
StudentDao.java
StudentDaoImpl.java
Major.java
MajorDao.java
MajorDaoImpl.java
DBUtil.java
吐槽:并没有听课,踩着ddl边学边做边写blog(
前置:java(不咋会,但是可以硬干,然后就会了(((
0.整体结构 环境配置
前端是html,后端是servlet,后面那一串文件是在分层开发。比如Student.java里面定义跟student表对应的student实体类,StudentDao.java定义DAO接口,Impl给出DAO接口函数的具体实现,然后servlet脚本就可以调用DAO类。DBUtil是数据库连接。
什么是JDBC:一个用来访问数据库的java类库,拿来写DBUtil
什么是DAO:一种模式,大概意思就是封装数据库操作,方便调用。
运行servlet项目需要用到tomcat作服务器,配置方法可以看这两篇:Tomcat安装及配置 、Eclipse开发Servlet项目详细教程
新版eclipse创建的动态web项目结构跟老版的不太一样,注意原来的webcontent对应到src/main/webapp就行。
先从tomcat安装路径的lib文件夹里复制servlet-api.jar,放到src/main/webapp/WEB-INF/lib下面。
下载mysql驱动jar包,放到WEB-INF下的lib文件夹中。
src/main/java下新建四个包:servlet,util,dao,entity,分别存放servlet类,数据库连接类,DAO类,实体类,然后把要求的文件先建好(在对应包下面新建Class)。
新建一个student.html放在webapp下面。
接着在web.xml里面修改一下配置。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0"><display-name>web_2</display-name><welcome-file-list><welcome-file>student.html</welcome-file> <!-- 这里是默认页面 --></welcome-file-list>
<!-- servlet配置 -->
<servlet><servlet-name>StudentServlet</servlet-name><servlet-class>servlet.StudentServlet</servlet-class> <!-- 包名.类名 -->
</servlet>
<servlet-mapping><servlet-name>StudentServlet</servlet-name><url-pattern>/StudentServlet</url-pattern> <!-- 访问servlet的url -->
</servlet-mapping>
</web-app>
现在项目的目录结构长这样,可以先启动一下服务器试试(项目上右键run as->run on server),应该显示的就是student.html。
1.数据库建立
这里笔者用的是mysql,在数据库里面建两个表,按要求来就行。
然后手动往里面加点数据用来测试。
2.html文件
页面元素:
- 一个展示信息的表格
- 一个提交信息的表单
前端js代码模块:
- 查询
- 提交
- 校验
要提交多种请求,所以发送的时候手动加个参数action,方便后端判断。拼接字符串的时候尤其要小心。
student.html
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>学生信息</title><script src="https://lib.sinaapp.com/js/jquery/2.0.2/jquery-2.0.2.min.js"></script><style>form {margin-left: 10px;margin-top: 10px;}table,td,th {table-layout: fixed;width: 200;}#new {margin-left: 10px;}#err {color: red;}#suc {color: green;}#nam {color: red;}</style><script>function stuquery(){$("#info").empty();$("#info").append("<tr><th>学号</th><th>姓名</th><th>性别</th><th>年龄</th><th>生日</th><th>专业</th></tr>");$.ajax({url: "http://localhost:8080/web_2/StudentServlet",type: "post",data: "action=qs",dataType: "json",success: (studata) => {var str="";for (var i=0; i<studata.length; i++) {str = "<tr><td>" + studata[i].sid + "</td><td>" + studata[i].sname + "</td><td>" + studata[i].gender + "</td><td>" + studata[i].age + "</td><td>" + studata[i].birthday + "</td><td>" + studata[i].mname + "</td>";$("#info").append(str);}},error: (msg) => {alert("Ajax Error: "+msg);}})}function majorquery(){$.ajax({url: "http://localhost:8080/web_2/StudentServlet",type: "post",data: "action=qm",dataType: "json",success: (mjdata) => {var str='';for (var i=0; i<mjdata.length; i++) {str = '<option value="'+ mjdata[i].mid + '">' + mjdata[i].mname + '</option>';$("#mj").append(str);}},error: (msg) => {alert("Ajax Error: "+msg);}})}$(document).ready(() => {stuquery();majorquery();});function datacheck(){let sid = $("#ssid").val();let sname = $("#ssname").val();$("#err").empty();if (sid.length > 10) {$("#err").append("学号不得超过10位");return(false);}if (sname.length > 20) {$("#err").append("姓名不得超过20位");return(false);}return(true);}function send_data() {var formdata = $('#new form').serialize();formdata = formdata + "&action=send";var check = datacheck();if (check == true) {$.ajax({url: "http://localhost:8080/web_2/StudentServlet",type: "post",data: formdata,success: (ret)=>{if (ret == '0') {$("#err").empty();$("#nam").empty();$("#suc").append("提交成功");stuquery();}else if (ret == '1'){$("#suc").empty();$("#err").append("校验失败:姓名不合法");}else if (ret == '2'){$("err").empty();$("#suc").empty();$("#nam").append("学号已存在");}},error: (msg) => {alert("Ajax Error: "+msg);}});}}</script>
</head>
<body><table border="1" id="info"></table><br><br><div id="new">新增学生信息<form action=""><table><tr><td>学号:</td><td><input type="text" name="sid" id="ssid"></td><td><div id="nam"></div></td></tr><tr><td>姓名:</td><td><input type="text" name="sname" id="ssname"></td></tr><tr><td>性别:</td><td><input type="radio" name="gender" value="m" checked>男 <input type="radio" name="gender" value="f">女</td></tr><tr><td>年龄:</td><td><input type="number" name="age"></td></tr><tr><td>生日:</td><td><input type="date" name="birthday"></td></tr><tr><td>专业:</td><td><select name="major" id="mj"><option value="" disabled selected hidden>-请选择专业-</option></select></td></tr></table></form><button id="bb" type="button" onclick="send_data()">提交信息</button></div><div id="err"></div><div id="suc"></div>
</body>
</html>
3.JDBC连接数据库 (DBUtil.java)
这个类里面提供四个基本函数作为接口:连接,增删改,查询,关闭连接。其中增删改和查询均接收一个sql字符串作为参数,查询函数需返回查询结果(ResultSet类型)。
下面的代码基本是直接copy网上的:
DBUtil.java
package util;import java.sql.*;public class DBUtil {static String url = "jdbc:mysql://localhost:3306/hit"; static String username = "root"; static String password = "password"; static Connection conn = null;static ResultSet rs = null;static PreparedStatement ps =null;public static void init(){try {Class.forName("com.mysql.jdbc.Driver");conn = DriverManager.getConnection(url,username,password);} catch (Exception e) {e.printStackTrace();}}public static int addUpdDel(String sql){int i = 0;try {PreparedStatement ps = conn.prepareStatement(sql);i = ps.executeUpdate();} catch (SQLException e) {e.printStackTrace();}return i;}public static ResultSet selectSql(String sql){try {ps = conn.prepareStatement(sql);rs = ps.executeQuery(sql);} catch (SQLException e) {e.printStackTrace();}return rs;}public static void closeConn(){try {conn.close();} catch (SQLException e) {e.printStackTrace();}}
}
4.建立实体类(Student.java, Major.java)
实体类里面定义跟数据表各字段对应的成员变量,成员函数只需要实现简单的get和set即可。(我直接丢给chatgpt写,活用生产力工具)
Student.java
package entity;public class Major {private String mid;private String mname;public String getMid() {return mid;}public void setMid(String mid) {this.mid = mid;}public String getMname() {return mname;}public void setMname(String mname) {this.mname = mname;}
}
Major.java
package entity;public class Student {private String sid;private String sname;private String gender;private String age;private String birthday;private String mid;public String getSid() {return sid;}public void setSid(String sid) {this.sid = sid;}public String getSname() {return sname;}public void setSname(String sname) {this.sname = sname;}public String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;}public String getAge() {return age;}public void setAge(String age) {this.age = age;}public String getBirthday() {return birthday;}public void setBirthday(String birthday) {this.birthday = birthday;}public String getMid() {return mid;}public void setMid(String mid) {this.mid = mid;}
}
5.建立DAO接口类(StudentDao.java, StudentDaoImpl.java, MajorDao.java, MajorDaoImpl.java)
根据要实现的功能写函数接口。
需要实现的功能:查询学生信息列表,查找学号是否存在,新增学生信息,查询专业信息列表,查询专业id对应的专业名。
StudentDao.java
package dao;import java.util.List;
import entity.Student;public interface StudentDao {public List<Student> getStudentInfo();public boolean add(String sid,String sname,String gender,String age,String birthday,String mid);public boolean exist(String sid);
}
StudentDaoImpl.java
package dao;import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;import entity.Student;
import util.DBUtil;public class StudentDaoImpl implements StudentDao {public List<Student> getStudentInfo(){List<Student> list = new ArrayList<Student>();try {DBUtil.init();ResultSet rs = DBUtil.selectSql("select * from student");while (rs.next()){Student stu = new Student();stu.setSid(rs.getString("sid"));stu.setSname(rs.getString("sname"));stu.setGender(rs.getString("gender"));stu.setAge(rs.getString("age"));stu.setBirthday(rs.getString("birthday"));stu.setMid(rs.getString("mid"));list.add(stu);}DBUtil.closeConn();return list;} catch (SQLException e) {e.printStackTrace();}return null;}public boolean add(String sid,String sname,String gender,String age,String birthday,String mid){boolean flag = false;DBUtil.init();String sql = "INSERT INTO student (`sid`, `sname`, `gender`, `age`, `birthday`, `mid`) VALUES ('"+ sid + "','" + sname + "','" + gender + "','"+ age + "','" + birthday + "','" + mid + "')";System.out.println(sql);int i =DBUtil.addUpdDel(sql);if(i>0){flag = true;}DBUtil.closeConn();return flag;}public boolean exist(String sid){DBUtil.init();String sql = "select * from student where sid='"+sid+"'";ResultSet rs = DBUtil.selectSql(sql);try {if (rs.next()) return true;else return false;} catch (SQLException e) {e.printStackTrace();}return false;}
}
MajorDao.java
package dao;import java.util.List;
import entity.Major;public interface MajorDao {public List<Major> getMajorInfo();public String getMname(String mid);
}
MajorDaoImpl.java
package dao;import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;import entity.Major;
import util.DBUtil;public class MajorDaoImpl implements MajorDao{public List<Major> getMajorInfo(){List<Major> list = new ArrayList<Major>();try {DBUtil.init();ResultSet rs = DBUtil.selectSql("select * from major");while (rs.next()){Major mj = new Major();mj.setMid(rs.getString("mid"));mj.setMname(rs.getString("mname"));list.add(mj);}DBUtil.closeConn();return list;} catch (SQLException e) {e.printStackTrace();}return null;}public String getMname(String mid){try {DBUtil.init();String sql="select * from major where mid='"+mid+"'";ResultSet rs = DBUtil.selectSql(sql);while (rs.next())return rs.getString("mname");} catch (SQLException e) {e.printStackTrace();}return null;}
}
# 6.编写Servlet
前面DAO类准备完成之后就可以写Servlet脚本了。
这里实验要求是只有一个Servlet,要处理多种请求,所以在html里面请求的时候带一个action参数用来辨别。到后端要先提取判断。
servlet的一般写法是扩展HttpServlet类,重写doGet和doPost方法。
涉及到字符串和json之间的倒腾来倒腾去,引入GSON包处理json数据。(外部jar包全放在WEB-INF/lib下面)
异常数据处理:直接返回去一个数字,0代表成功,1代表姓名不合法(其实就是只判断开头是不是问号,偷懒了),2代表学号有重复,html文件有对应处理。
package servlet;import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import dao.StudentDao;
import dao.StudentDaoImpl;
import dao.MajorDao;
import dao.MajorDaoImpl;
import entity.Student;
import entity.Major;public class StudentServlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doPost(request, response);}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {String act = request.getParameter("action");if (act.equals("qs")){StudentDao stu = new StudentDaoImpl();MajorDao mj = new MajorDaoImpl();List<Student> info = stu.getStudentInfo();Gson gson = new Gson();JsonArray stulist = gson.fromJson(gson.toJson(info), JsonArray.class);JsonArray stu_ = new JsonArray();for (int i=0; i<stulist.size();i++){JsonObject stuu = (JsonObject) stulist.get(i);String midd = stuu.get("mid").getAsString();stuu.addProperty("mname", mj.getMname(midd));System.out.println(mj.getMname(midd));stu_.add(stuu);}response.setContentType("application/json");response.setCharacterEncoding ("UTF-8");response.getWriter().print(stu_.toString());}if (act.equals("qm")){MajorDao mj = new MajorDaoImpl();List<Major> minfo = mj.getMajorInfo();Gson gson = new Gson();response.setContentType("application/json");response.setCharacterEncoding ("UTF-8");response.getWriter().print(gson.toJson(minfo));}if (act.equals("send")){response.setContentType("text/plain");response.setCharacterEncoding ("UTF-8");String sid = request.getParameter("sid");String sname = request.getParameter("sname");String gender = request.getParameter("gender");String age = request.getParameter("age");String birthday = request.getParameter("birthday");String major = request.getParameter("major");StudentDao stu = new StudentDaoImpl();if (sname.charAt(0)=='?') {response.getWriter().print("1");}else if (stu.exist(sid)){response.getWriter().print("2");}else {stu.add(sid, sname, gender, age, birthday, major);response.getWriter().print("0");}}}
}
7.一些踩过的坑
- sql插入语句字段名要用反单引号括起来,普通单引号报错,在mysql里直接输入才发现这个。
- 谷歌的GSON和阿里的Fastjson不太一样,在网上查资料注意辨别(比如说一个json类型名叫JsonObject,一个叫JSONObject)
- java判断字符串相等得用equals方法,不是==(一开始不知道,chatgpt告诉我的 2333333)
- 项目跑起来好像比较慢?刚启动服务器的时候浏览器访问html页面不一定能及时返回数据库查询结果,多刷新几下才正常,不要一上来看到报错就自己先陷入暴躁了。
- CSS的样式冲突真的很讨厌,md,善用浏览器控制台查看问题出处。
8.结语
超长的一篇……整个项目做完其实已经超ddl了,不过考察课扣几分就扣吧。做了好几个晚上来着。
从啥玩意都不懂开始,自力更生,还是挺不错的。
一条经验:做不太明白的东西先从最基本的能跑起来的helloworld开始,然后往这个微不足道但是五脏俱全的小东西里面一点一点加功能,每走一步都先完善到能顺利跑起来的程度,这样错误就比较好排查了。一开始就想着把一部分功能尽善尽美地做完其实吃力不讨好,后面报错多的话人都麻了。
最上面的html看似是一开始写好的,其实跟servlet相纠缠改了好多遍的。