这个笔记用于我本人的复习,所以引用了很多东西。如果被侵权了请联系我,我会删掉,谢谢。:-)
写这个笔记,根本目的是希望我能养成做有效笔记的习惯,帮助我提高学习的效率。除此之外,我还希望能借由这个渠道来锻炼我书面表达的能力(因为发现上了大学之后越来越不会写文章了,偏偏又还有一堆文章要写 :-( )以及提取信息的能力。
说实话,不知道我能坚持多久,但是我还是希望我能一直坚持下去。
今天来看看之前做的一个Web实验,希望在我还有印象之前总结一下。
实验要求
新建项目并添加postgresql依赖
首先在IDEA新建项目,选择Jakarta EE项目,模板选择Web应用程序,构建系统选择Maven,服务器选择TomCat,如下图。
然后前往postgresql驱动下载官网,Copy Maven,复制到pom.xml的dependency之下:
再把目录设置成如下样式即可:
数据库操作类
首先我们先做一个数据库连接的基本操作类,有多基本呢,这个类中包括了连接的创建、获取和删除。我们将这个类放在util目录下,命名为Dbutil。
以下是一些基本的jdbc语言,域名和端口号这些都是固定的,当然后面那个hgd是你自己在postgresql中命名的。
//这是用于实现sql语句的工具类,与具体的表无关
private static Connection con;
//jdbc连接数据库
private static final String URL = "jdbc:postgresql://localhost:5432/hgd";
private static final String USERNAME = "postgres";
//下面可得写自己设置的密码
private static final String PASSWORD = "***";
回到代码,创建和获取连接是这么写的:
private static Connection createConnection(){
//使用反射将数据库驱动类加载到JVM虚拟机中
//导入库时,可在“项目结构-库-按加号进行导入-选择正确jar文件即可”
try {
Class.forName("org.postgresql.Driver");
con = DriverManager.getConnection(URL,USERNAME,PASSWORD);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
//以上是经典的错误处理环节,最好都写成e.printStackTrace,这样可以打印出异常信息在程序中的位置与出错原因
return con;
}
public static Connection GetConnection(){
//con为空或者已经关闭时
try {
if(con == null || con.isClosed()){
con = createConnection();
}
} catch (SQLException e) {
e.printStackTrace();
}
return con;
}
有创建自然就有关闭,注意这里我们把close方法给重载了一遍,这是因为后面可能有些地方需要用到ResultSet来参与数据表单的遍历。
public static void close(PreparedStatement ps, Connection con){
try {
if(!ps.isClosed()){
ps.close();
}
if(!con.isClosed()){
con.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
//方法的重载,因为之后需要用到ResultSet
public static void close(ResultSet rs,PreparedStatement ps, Connection con){
try {
if(!rs.isClosed())
{
rs.close();
}
if(!ps.isClosed()){
ps.close();
}
if(!con.isClosed()){
con.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
Dbutil类的编辑到此结束。
实体类
我们要操作的实体很明显,就是学生。所以我们建一个Student类来存储学生信息。
private String sid;
private String name;
private int age;
private String birthday;
private String gender;
然后右键-生成,把构造函数、setter和getter全部让IDE帮我们自动生成。
Postgresql新建表
右键注册一个服务器,设置名字和密码,端口域名那些默认即可。
在“架构”中找到表,右键-属性,按如下方式创建:
增删改查操作类
根据实验要求,增删改查操作类全部放在Dao目录下,而且我们分别定义了接口和实现类。
我们先谈关于学生信息的Dao操作类,这是接口:
public interface StudentDao {
void Add(Student student);
//添加
void Modify(Student student);
//修改
void Remove(String sid);
//删除
List<Student> GetAll(String sql);
//读取所有的学生信息,相当于遍历数据库
Student GetStu(String sid);
//通过学号来获取学生信息
List<Student> GetByName(String name);
//用名字来模糊查询
}
以下是接口的实现类,我们先看那Add方法的实现类:
@Override
public void Add(Student student) {
String sid = student.getSid();
String name = student.getName();
int age = student.getAge();
String birthday = student.getBirthday();
String gender = student.getGender();
//先读取所有的变量信息
String sql = "insert into student values(?,?,?,?,?)";
//这里的student是我们创建的表的名字
//调用工具类创建连接
Connection con = DbUtil.GetConnection();
PreparedStatement ps = null;
try {
//把数据写入
ps = con.prepareStatement(sql);
ps.setString(1,sid);
ps.setString(2,name);
ps.setInt(3,age);
//sql语句的Date类型允许由String类型转型,使用ValueOf方法
ps.setDate(4, Date.valueOf(birthday));
ps.setString(5,gender);
//ps更新
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
finally {
//结束了就关闭
DbUtil.close(ps,con);
}
}
con用于读取连接,ps用于读取sql语句。
再看一个需要遍历的方法是如何实现的,以GetAll方法为例:
@Override
public List<Student> GetAll(String sql) {
String sid;
String name;
int age;
String birthday;
String gender;
Connection con = DbUtil.GetConnection();
PreparedStatement ps = null;
ResultSet rs = null;
List<Student> List = new ArrayList<>();
try {
//ps读取sql语句
ps = con.prepareStatement(sql);
//以下是循环的写法
rs = ps.executeQuery();
//ResultSet对象可用于执行遍历操作
while(rs.next()){
sid = rs.getString("sid");
name = rs.getString("name");
age = rs.getInt("age");
birthday = rs.getString("birthday");
gender = rs.getString("gender");
List.add(new Student(sid,name,age,birthday,gender));
}
return List;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
finally {
DbUtil.close(rs,ps,con);
}
}
显然地,我们这里引入ResultSet是为了循环的进行,这时我们的重载的close方法就派上用场了。
还有更新信息的写法:
@Override
public void Modify(Student student) {
String sid = student.getSid();
String name = student.getName();
int age = student.getAge();
String birthday = student.getBirthday();
String gender = student.getGender();
String sql = "update student set name = ?,age = ?,birthday=? ,gender=? where sid = ?";
Connection con = DbUtil.GetConnection();
PreparedStatement ps = null;
try {
ps = con.prepareStatement(sql);
ps.setString(5,sid);
ps.setString(4,gender);
ps.setString(1,name);
ps.setInt(2,age);
ps.setDate(3, Date.valueOf(birthday));
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
finally {
DbUtil.close(ps,con);
}
}
删除的方法:
@Override
public void Remove(String sid) {
String sql = "delete from student where sid = ?";
//都是先建立连接然后读取sql语句
Connection con = DbUtil.GetConnection();
PreparedStatement ps = null;
try {
ps = con.prepareStatement(sql);
ps.setString(1,sid);
//如果内容有更新就加上以下语句
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
finally {
DbUtil.close(ps,con);
}
}
按照学号以及按照姓名来查找学生:
@Override
public Student GetStu(String sid) {
String name;
int age;
String birthday;
String gender;
String sql = "select * from student where sid = ?";
Connection con = DbUtil.GetConnection();
PreparedStatement ps = null;
ResultSet rs = null;
Student student = null;
try {
ps = con.prepareStatement(sql);
//寻找特定sid的学生
ps.setString(1,sid);
rs = ps.executeQuery();
if(rs.next()) {
sid = rs.getString("sid");
name = rs.getString("name");
age = rs.getInt("age");
birthday = rs.getString("birthday");
gender = rs.getString("gender");
student = new Student(sid, name, age, birthday,gender);
}
return student;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
finally {
DbUtil.close(rs,ps,con);
}
}
@Override
public List<Student> GetByName(String name) {
String sid;
int age;
String birthday;
String gender;
//模糊查询?
String sql = "select * from student where name like '%" + name + "%'";
Connection con = DbUtil.GetConnection();
PreparedStatement ps = null;
ResultSet rs = null;
List<Student> List = new ArrayList<>();
try {
ps = con.prepareStatement(sql);
rs = ps.executeQuery();
while(rs.next()){
sid = rs.getString("sid");
name = rs.getString("name");
age = rs.getInt("age");
birthday = rs.getString("birthday");
gender = rs.getString("gender");
List.add(new Student(sid,name,age,birthday,gender));
}
return List;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
finally {
DbUtil.close(rs,ps,con);
}
}
index.jsp
首先我们需要把所有用到的类全部引入
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="cn.edu.hit.dao.StudentDao,cn.edu.hit.dao.impl.StudentDaoImpl" %>
<%@ page import="cn.edu.hit.entity.Student, java.util.List, java.util.ArrayList" %>
index页面就是你在登陆成功后所展示给你的界面,就像下面一样:
首先我们确保index.jsp不能被直接访问(不然那个登录界面就没有意义了),我们用如下的代码来实现。注意java的代码需要用<% %>来括起来。
<%
String usename = (String)session.getAttribute("usename");
String password = (String)session.getAttribute("password");
if(usename == null){
out.println("非法用户,请<a href='login.html'>返回登录界面</a>");
return;
}
StudentDao dao = new StudentDaoImpl();
String sid,name,birthday,gender;
int age;
List<Student> list = dao.GetAll("select * from student order by sid ");
%>
下面那个表格的写法如下:
<a href="add.html">增加学生</a>
<table border="1">
<tr>
<th>学号</th><th>姓名</th><th>性别</th><th>年龄</th><th>生日</th><th>修改</th><th>删除</th>
</tr>
<%
for(Student student : list){
sid = student.getSid();
name = student.getName();
age = student.getAge();
birthday = student.getBirthday();
gender = student.getGender();
%>
<tr>
<td><%=sid%></td><td><%=name%><td><%=gender.equals("m")?"男":"女"%></td></td><td><%=age%></td><td><%=birthday%></td>
<td><a href="modify.jsp?sid=<%=sid%>">修改</a></td>
<td><a onclick="return confirm('确定要删除<%=name%>的数据吗?')" href="student-servlet?action=remove&sid=<%=sid%>">删除</a></td>
</tr>
<%
}
%>
Add.html(表单提交与ajax提交)
在建立Add.html的时候,我们需要知道:Add.html和Modify.jsp中的的数据先要传到Servlet,然后在Servlet中调用Dao层的方法对数据库进行操作。
又因为它们的数据都是传到同一个Servlet下(student-servlet)下,所以需要放置一个隐形的判断变量,也就是名为action的变量,来告诉student-servlet该执行添加学生操作还是修改学生操作。
<form action="student-servlet" method="post">
<input type="hidden" name="action" value="add"/>
<table border="1">
<tr>
<td>学号</td>
<td><input type="text" name="sid"/></td>
</tr>
<tr>
<td>姓名</td>
<td><input type="text" name="name"/></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><button type="submit">提交</button></td>
</tr>
</table>
</form>
可以看到的是,这里我们使用的是表单的提交形式。每次提交都会进行页面的刷新。而在之前的实验一中我们用到了ajax的形式,当登录信息错误时不刷新界面,直接给出错误信息。
先来看看实验一的提交页面和servlet,这会对我们下面构建servlet提供灵感。
登录界面代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width ,initial-scale=1.0">
<title>登录界面</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<!--引入Jquery-->
<script src="js/jquery-3.7.1.min.js"></script>
<style>
.img{
float: left;
display: inline;
}
.TakeIn{
float: left;
margin-left: 10px;
margin-top: 50px;
}
</style>
<script>
function login(){
//读取文本框的值
var usename = $("#usename").val();
var password = $("#password").val();
if(usename=="" || password==""){
alert("必须输入,不能为空!");
return false;
}
$.ajax(
{
url:"http://localhost:8080/demo_war_exploded/login-servlet",
type:'post',
dataType:'json',
data:{
usename:usename,
password:password
},
success:function(res){
if(res.code=="200"){
sessionStorage.setItem("usename",usename)
window.location = "index.html"
}{
$("#msg").text(res.msg)
}
},
error:function(){
console.log("Error!");
}
}
)
}
</script>
</head>
<body>
<div class="img">
<img src="/img/OIP-C.jpg" alt="logo" class="img-fluid visible-lg-block" width="300" height="150">
</div>
<div class="TakeIn">
<input type="text" id= "usename" placeholder="请输入用户名" ><br>
<input type="password" id="password" placeholder="请输入密码"> <br>
<button type="button" onclick="login()"> 登录 </button><br>
<span id="msg" style="color: red;"></span>
</div>
</body>
</html>
这个页面的内容相当丰富。
首先是引入特定的库,Bootstrap的作用是让页面大小发生变化时呈现不同的结果,利于Web项目适配移动端。他的作用代码其实只有img类div中的"img-fluid visible-lg-block",用于页面缩小时隐藏图片。
我们这里的引入的Jquery是从本地引入的,引入它是为了更方便地使用ajax,Jquery的标志就是那个很显眼的美元符号($)。
ajax可以不刷新页面返回结果。在输入错误的时候不传给servlet,只有正确的时候才传过去。
<style>里面的是css,是为了修改下面div的样式。
code的设定,我们可以在servlet里面看到。
你大概已经看出来了程序的逻辑:
首先,当我点下“登录”界面的时候,login函数立刻开始执行。函数首先读取usename和password判断是不是空的,然后进入ajax执行。如果执行成功再进行进一步判断。
if中的第二个大括号中的语句(msg)不受if的约束,也就是说不管登录成不成功都执行。
来看看servlet吧。
@WebServlet(name = "loginServlet", value = "/login-servlet")
public class loginServlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
//添加响应头,允许跨域
response.addHeader("Access-Control-Allow-Origin","*");
response.addHeader("Access-Control-Allow-Method","POST,GET");
response.setContentType("application/json");
String usename = request.getParameter("usename");
String password = request.getParameter("password");
PrintWriter out = response.getWriter();
if(usename.equals("admin") && password.equals("123")){
out.println("{\"code\":200,\"msg\":\"ok\"}");
}else {
out.println("{\"code\":600,\"msg\":\"用户名或密码错误\"}");
}
}
public void destroy() {
}
}
首先需要确保方法是post的(get就全看到了),然后你可以看到,这里有对code的定义,那个\“表示转义字符。
Servlet
收拾行囊,我们回到实验二。首先不要忘了引入相应的元素和把servlet的value设定好,这样前端才能对应到它。
import cn.edu.hit.dao.StudentDao;
import cn.edu.hit.dao.impl.StudentDaoImpl;
import cn.edu.hit.entity.Student;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet(name = "StudentServlet", value = "/student-servlet")
接下来看看正文怎么写:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException{
doPost(request,response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html");
//为了判断是谁提交的操作(add.html还是modify.jsp都提交到student-servlet)
//所以我们需要设置一个隐藏的变量,用于判断
String action = request.getParameter("action");
StudentDao dao = new StudentDaoImpl();
if(action.equals("add")) {
//以下的sid指的就是我表单里的name
String sid = request.getParameter("sid");
String name = request.getParameter("name");
//这里要做数据类型的强制转换,因为getParameter方法默认读取的是String类型
int age = Integer.parseInt(request.getParameter("age"));
String birthday = request.getParameter("birthday");
String gender = request.getParameter("gender");
Student student = new Student(sid, name, age, birthday,gender);
dao.Add(student);
}
else if (action.equals("modify")){
String sid = request.getParameter("sid");
String name = request.getParameter("name");
//这里要做数据类型的强制转换,因为getParameter方法默认读取的是String类型
int age = Integer.parseInt(request.getParameter("age"));
String birthday = request.getParameter("birthday");
String gender = request.getParameter("gender");
Student student = new Student(sid,name,age,birthday,gender);
dao.Modify(student);
} else if (action.equals("remove")) {
String sid = request.getParameter("sid");
dao.Remove(sid);
}
//进行重定向操作,表明无论进行什么操作,最后都能回到最开始的页面
response.sendRedirect("index.jsp");
}
你可以发现两者是有共同点的(request.getParameter方法),而且student-servlet看来还要更简单了。
注意我们之前所说的,student-servlet需要判断到底应该执行什么操作,所以这里写了一个if条件判断语句。
Modify.jsp
<%--
Created by IntelliJ IDEA.
User: Lenovo
Date: 2024/4/4
Time: 11:24
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="cn.edu.hit.dao.StudentDao,cn.edu.hit.dao.impl.StudentDaoImpl" %>
<%@ page import="cn.edu.hit.entity.Student" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
//“sid”是href中所提及的sid,并不是java语言中的sid
//顺带一提,在jsp文件中,request与response被称为内置对象,是不用再额外声明的
String sid = request.getParameter("sid");
StudentDao dao = new StudentDaoImpl();
Student student = dao.GetStu(sid);
String name = student.getName();
int age = student.getAge();
String gender = student.getGender();
String birthday = student.getBirthday();
%>
<form action="student-servlet" method="post">
<input type="hidden" name="action" value="modify"/>
<table border="1">
<tr>
<td>学号</td>
<td><input type="text" name="sid" value="<%=sid%>" readonly="true"/></td>
</tr>
<tr>
<td>姓名</td>
<td><input type="text" name="name" value="<%=name%>"/></td>
</tr>
<tr>
<td>性别</td>
<td>
<!--是男的就默认先选男,是女的就默认先选女,然后两个radio的name要取得一样,这可以让二者的选择存在一种互斥关系-->
<input type="radio" name="gender" value="m" <%if(gender.equals("m")) out.print("checked");%>/>男
<input type="radio" name="gender" value="f" <%if(gender.equals("f")) out.print("checked");%>/>女
</td>
</tr>
<tr>
<td>年龄</td>
<td><input type="number" name="age" value="<%=age%>"/></td>
</tr>
<tr>
<td>生日</td>
<td><input type="date" name="birthday" value="<%=birthday%>"/></td>
</tr>
<tr>
<td></td>
<td><button type="submit">提交</button></td>
</tr>
</table>
</form>
</body>
</html>
不要忘记jsp中引入操作要怎么写 —— <%@ page import a,b...%>
我们这里使用到了循环来将已知的消息填入空格作为缺省值,而且学号是不能改的readonly=="true"
login.html和login-servlet
在login.html中,表单登录的编写你应该已经很熟悉了:
<form action="login-servlet" method="post">
<table border="1">
<!--value=<%=>的方法是java方法,js的写法见下文-->
<tr>
<td>学号</td>
<td><input type="text" id="usename" name="usename" placeholder="请输入用户名" /></td>
</tr>
<tr>
<td>学号</td>
<td><input type="text" id="password" name="password" placeholder="请输入密码"/></td>
</tr>
<td></td>
<td><button type="submit">登录</button>
<!--这里需要选择checkbox-->
<input type="checkbox" name="rememberMe" value="1"/>记住我</td>
</table>
</form>
login-servlet的代码如下 :
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html");
String usename = request.getParameter("usename");
String password = request.getParameter("password");
UserDao userDao = new UserDaoImpl();
if(userDao.login(usename,password)){
String remenberMe = request.getParameter("rememberMe");
if(remenberMe!=null){
Cookie cookie1 = new Cookie("usename",usename);
Cookie cookie2 = new Cookie("password",password);
//给cookie设置有效期,这下面的单位是秒
cookie1.setMaxAge(60*60*24*7);
cookie2.setMaxAge(60*60*24*7);
response.addCookie(cookie1);
response.addCookie(cookie2);
}
//判断正确的话,我可以获取session对象
HttpSession session = request.getSession();
session.setAttribute("usename",usename);
//这里的session是传给index.jsp的
response.sendRedirect("index.jsp");
}
else{
response.sendRedirect("login.html");
}
我们先忽略其中的Cookie操作,我们发现这些语句我们是相当熟悉的。
制作User数据表单,记录所有合法的登录
这是表单的样子:
根据前面的已有的操作,我们还可以尝试实现一个“注册”功能。
但是我懒得尝试了。
使用Cookie来“记住我”
重新回来看看login-servlet中的那个cookie操作:
String remenberMe = request.getParameter("rememberMe");
if(remenberMe!=null){
Cookie cookie1 = new Cookie("usename",usename);
Cookie cookie2 = new Cookie("password",password);
//给cookie设置有效期,这下面的单位是秒
cookie1.setMaxAge(60*60*24*7);
cookie2.setMaxAge(60*60*24*7);
response.addCookie(cookie1);
response.addCookie(cookie2);
}
你可以认为Cookie是存在本地的数据记录(这并不是正式的说法) ,在浏览器按下F12打开“应用程序”,你可以看到存在浏览器里的Cookie。
setMaxAge方法其实就是设置Cookie的保存时间,你不设定的话一关浏览器就没了。它的单位是秒,我们这里设置了一周。
最后如果想在login.html把Cookie中存的数据填入登录栏,作为缺省值,我们怎么做呢?
<script>
var index = document.cookie.indexOf(";");
//alert(document.cookie);
var first = document.cookie.substring(0,index);
var second = document.cookie.substring(index+1);
var usename = first.substring(first.indexOf("=")+1);
var password = second.substring(second.indexOf("=")+1);
//html上的属性或者文本(例如h1这种)才用innerText,如果是向input元素中输入内容,就使用value
document.getElementById("usename").value = usename;
document.getElementById("password").value = password;
</script>
Cookie其实就是一串字符串,你可以试着用Alert来打开它看看。
按照上面的方法慢慢切分即可。