目录
1. 基础概念
1.1 BS架构和CS架构
1.2 Tomcat图解
2.TomCat
2.1 IDEA配置web项目和tomcat
2.2 idea启动TomCat因为端口号失败的问题
3.Servlet使用流程
3.1 Servlet简单图解
3.2 Servlet导入依赖
3.3 编写Servlet和add.html
3.4 试着使用Jdbc和Dao层连接水果库存数据库
0. 基础结构的代码
1. 数据库表创建
2. 根据水果javabean和JDBC写出FruitDaoJDBC
3. 重写此时servlets下的AddServlet
4.Servlet细节知识点
4.1 Servlet设置编码字符集
4.2 Servlet继承关系
1. 继承关系
2. 相关方法
4.3 Servlet生命周期
1.生命周期
2. 默认情况
3. Servlet初始化时机
4. Servlet在容器中是单例的,是线程不安全的
4.4 HTTP协议
1. HTTP基础概念
2. 请求报文
3. 响应报文
4.5 会话
1.Http是无状态的
2. 会话跟踪技术
3. session保存作用域
4.6 服务器内部转发以及客户端重定向
1. 服务器内部转发编辑
2. 客户端重定向
4.7 Thymeleaf视图技术
1. thymeleaf的部分标签
4.8 保存作用域
1. request:一次请求响应范围
2. session:一次会话范围有效
3. application: 一次应用程序范围有效
4. 补充:相对路径和绝对路径
4.9 水果管理系统项目(更新功能)
4.10 水果管理系统项目(删除功能)
4.11 水果管理系统项目(增加功能)
4.12 水果管理系统项目(分页功能)
4.13 水果管理系统项目(关键字查询功能)
1. 基础概念
1.1 BS架构和CS架构
CS:客户端服务器架构模式
优点︰充分利用客户端机器的资源,减轻服务器的负荷
(一部分安全要求不高的计算任务存储任务放在客户端执行,不需要把所有的计算和存储都在服务器端执行,从而能够减轻服务器的压力,也能够减轻网络负荷)
缺点∶需要安装;升级维护成本较高
BS∶浏览器服务器架构模式
优点︰客户端不需要安装;维护成本较低
缺点∶所有的计算和存储任务都是放在服务器端的,服务器的负荷较重﹔在服务端计算完成之后把结果再传输给客户端,因此客户端和服务器端会进行非常频繁的数据通信,从而网络负荷较重
1.2 Tomcat图解
2.TomCat
2.1 IDEA配置web项目和tomcat
创建一个java项目之后,右键项目文件夹,选择添加框架支持,然后选择javaee中的web即可添加web项目
在IDAE右上方选择编辑配置
点击加入工件,然后加入
服务器这里填写tomcat的根目录
2.2 idea启动TomCat因为端口号失败的问题
1099被占用,可以进入cmd控制台,然后进行搜索被占用的进程,然后将它关闭
netstat -aon | find "1099"
tasklist | find "5680"
在任务管理器中找到对应的这个进程然后关闭。
但是有可能出现搜索占用1099端口号但并没有任何进程占用的情况,这种时候,可以进行关闭hyper-v然后重启电脑并重新打开
1. 关闭hyper-v 它会询问是否立即重启,必须进行重启
dism.exe /Online /Disable-Feature:Microsoft-Hyper-V
2. 排除ipv4动态端口占用 startport 起始端口 numberofports 端口数
netsh int ipv4 add excludedportrange protocol=tcp startport=1099 numberofports=1
3. 启动hyper-v后重启电脑
dism.exe /Online /Enable-Feature:Microsoft-Hyper-V /All
3.Servlet使用流程
3.1 Servlet简单图解
3.2 Servlet导入依赖
我们使用AddServlet去承接add操作,需要继承自HttpServlet包,但是这个包在tomcat下,需要我们在idea的项目结构里面导入tomcat依赖
3.3 编写Servlet和add.html
我们需要完成一个,用户通过tomcat服务器访问add.html,通过add.html下的表单,输入对应的信息,这个操作在表单的声明中action = add,当我们提交表单信息的时候,服务器收取到这个post请求,通过servlet的mapping找到对应action=add映射的servlet映射名称对应同名的servlet,然后找到它所声明类的绝对路径,通过这个路径下的类中重写父类HttpServket的doPost方法,将用户Post请求下的要求按照我们所写的方法进行操作
1. 用户发请求,action=add
2. 项目中,web.xml中找到url-pattern = /add -----> 第12行
3. 找第11行的servlet-name = AddServlet
4. 找和servlet-mapping中servlet-name一致的servlet 找到第7行
5. 找第八行的servlet-class -> com.fanxy.servlets.AddServlet
6. 用户发送的是post请求(method = post) 因此tomcat会执行AddServlet中的doPost方法
add.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="add" method="post">
<label for="fname">名称:</label>
<input type="text" name="fname" id="fname" required><br>
<label for="price">价格:</label>
<input type="text" name="price" id="price" required><br>
<label for="fcount">库存:</label>
<input type="text" name="fcount" id="fcount" required><br>
<label for="remark">备注:</label>
<input type="text" name="remark" id="remark" required><br>
<input type="submit" value="添加">
</form>
</body>
</html>
com.fanxy.servlets.AddServlet类
public class AddServlet extends HttpServlet {
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String fname = request.getParameter("fname");
Integer price = Integer.parseInt(request.getParameter("price"));
Integer fcount = Integer.parseInt(request.getParameter("fcount"));
String remark = request.getParameter("remark");
System.out.println("fname = "+ fname);
System.out.println("price = "+ price);
System.out.println("fcount = "+ fcount);
System.out.println("remark = "+ remark);
}
}
xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>AddServlet</servlet-name>
<servlet-class>com.fanxy.servlets.AddServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AddServlet</servlet-name>
<url-pattern>/add</url-pattern>
</servlet-mapping>
</web-app>
3.4 试着使用Jdbc和Dao层连接水果库存数据库
如果想中途想导入包,需要删除out下的artifacts和production下的子文件夹,然后导入到项目外部的包,然后在项目结构里面的problem的fix中添加入web项目
0. 基础结构的代码
Fruit的javabean
public class Fruit {
private Integer fid ;
private String fname ;
private Integer price ;
private Integer fcount ;
private String remark ;
public Fruit(){}
public Fruit(Integer fid, String fname, Integer price, Integer fcount, String remark) {
this.fid = fid;
this.fname = fname;
this.price = price;
this.fcount = fcount;
this.remark = remark;
}
public Integer getFid() {
return fid;
}
public void setFid(Integer fid) {
this.fid = fid;
}
public String getFname() {
return fname;
}
public void setFname(String fname) {
this.fname = fname;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
public Integer getFcount() {
return fcount;
}
public void setFcount(Integer fcount) {
this.fcount = fcount;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
@Override
public String toString() {
return fid + "\t\t" + fname + "\t\t" + price +"\t\t" + fcount +"\t\t" + remark ;
}
}
FruitDAO的抽象接口
public interface FruitDAO {
//查询库存列表
List<Fruit> getFruitList() throws Exception;
//新增库存
boolean addFruit(Fruit fruit) throws SQLException;
//修改库存
boolean updateFruit(Fruit fruit) throws SQLException;
//根据名称查询特定库存
Fruit getFruitByFname(String fname) throws Exception;
//删除特定库存记录
boolean delFruit(String fname) throws SQLException;
}
1. 数据库表创建
CREATE DATABASE fruitdb charset utf8;
USE fruitdb ;
CREATE TABLE `t_fruit` (
`fid` int(11) NOT NULL AUTO_INCREMENT,
`fname` varchar(20) NOT NULL,
`price` int(11) DEFAULT NULL,
`fcount` int(11) DEFAULT NULL,
`remark` varchar(50) DEFAULT NULL,
PRIMARY KEY (`fid`)
) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8;
insert into `t_fruit`(`fid`,`fname`,`price`,`fcount`,`remark`)
values
(1,'红富士',5,16,'红富士也是苹果!'),
(2,'大瓜',5,100,'王校长的瓜真香'),
(3,'南瓜',4,456,'水果真好吃'),
(4,'苦瓜',5,55,'苦瓜很好吃'),
(5,'莲雾',9,99,'莲雾是一种神奇的水果'),
(6,'羊角蜜',4,30,'羊角蜜是一种神奇的瓜'),
(7,'啃大瓜',13,123,'孤瓜');
2. 根据水果javabean和JDBC写出FruitDao
JDBC数据库连接技术学习笔记https://blog.csdn.net/weixin_44981126/article/details/130424791
public class FruitDao extends BaseDao implements FruitDAO {
@Override
public List<Fruit> getFruitList() throws Exception {
List<Fruit> fruits = executeQuery(Fruit.class, "select * from t_fruit;");
return fruits;
}
@Override
public boolean addFruit(Fruit fruit) throws SQLException {
String sql = "INSERT INTO t_fruit(fid, fname, price, fcount, remark) values(0, ?, ?, ?, ?);";
int count = executeUpdate(sql, fruit.getFname(), fruit.getPrice(),
fruit.getFcount(), fruit.getRemark());
return count > 0;
}
@Override
public boolean updateFruit(Fruit fruit) throws SQLException {
String sql = "UPDATE t_fruit SET fcount = ? WHERE fid = ?;";
int count = executeUpdate(sql, fruit.getFcount(), fruit.getFid());
return count > 0;
}
@Override
public Fruit getFruitByFname(String fname) throws Exception {
String sql = "SELECT * FROM t_fruit WHERE fname = ?";
List<Fruit> fruits = executeQuery(Fruit.class, sql, fname);
if(fruits != null && fruits.size() > 0) {
return fruits.get(0);
}
return null;
}
@Override
public boolean delFruit(String fname) throws SQLException {
String sql = "DELETE FROM t_fruit WHERE fname LIKE ? ;";
int count = executeUpdate(sql, fname);
return count > 0;
}
}
3. 重写此时servlets下的AddServlet
public class AddServlet extends HttpServlet {
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.setCharacterEncoding("UTF-8");
String fname = request.getParameter("fname");
Integer price = Integer.parseInt(request.getParameter("price"));
Integer fcount = Integer.parseInt(request.getParameter("fcount"));
String remark = request.getParameter("remark");
FruitDao fruitDao = new FruitDao();
boolean flag = false;
try {
flag = fruitDao.addFruit(new Fruit(1, fname, price, fcount, remark));
} catch (SQLException e) {
throw new RuntimeException(e);
}
System.out.println(flag ? "添加成功!" : "添加失败!");
}
}
1. 新建项目 - 新建模块
2. 在模块中添加web
3. 创建artifact - 部署包
4. lib - artifact
先有artifact,后来才添加的mysql.jar。此时,这个jar包并没有添加到部署包中
那么在projectSettings中有一个Problems中会有提示的,我们点击fix选择add to...
另外,我们也可以直接把lib文件夹直接新建在WEB-INF下。
这样不好的地方是这个lib只能是当前这个moudle独享。如果有第二个moudle我们需要再次重复的新建lib。
5. 在部署的时候,修改application Context。然后再回到server选项卡,检查URL的值。
URL的值指的是tomcat启动完成后自动打开你指定的浏览器,然后默认访问的网址。
启动后,报错404.404意味着找不到指定的资源。
如果我们的网址是:http://localhost:8080/pro01/ , 那么表明我们访问的是index.html.
我们可以通过<welcome-file-list>标签进行设置欢迎页(在tomcat的web.xml中设置,或者在自己项目的web.xml中设置)
6. 405问题。当前请求的方法不支持。比如,我们表单method=post , 那么Servlet必须对应doPost。否则报405错误。
7. 空指针或者是NumberFormatException 。因为有价格和库存。如果价格取不到,结果你想对null进行Integer.parseInt()就会报错。错误的原因大部分是因为 name="price"此处写错了,结果在Servlet端还是使用request.getParameter("price")去获取。
8. <url-pattern>中以斜杠开头
4.Servlet细节知识点
4.1 Servlet设置编码字符集
POST提交方式:在继承HttpServlet的类中接受request的信息之前,先进行如下设置
request.setCharacterEncoding("UTF-8");
get提交方式,tomcat8之前设置比较麻烦, tomcat8之后无需设置:
String fname = request.getParameter("fname");
byte[] = bytes = fname.getBytes("iso-8859-1");
fname = new String(bytes, "UTF-8");
4.2 Servlet继承关系
1. 继承关系
javax.servlet.Servlet 接口
|----------javax.servlet.GenericServlet 抽象类
|----------javax.servlet.http.HttpServlet 抽象类
2. 相关方法
javax.servlet.Servlet 接口:
|---1. void init(config) - 初始化方法
|---2. void service(request, response) - 服务方法 抽象的
|---3. void destory() - 销毁方法
javax.servlet.GenericServlet 抽象类
|---1. void service(request, response) - 仍然是抽象的
javax.servlet.http.HttpServlet 抽象类
|---1. void service(request, response) - 不是抽象的,当有请求过来的时候,service方
| 法会自动响应(其实是tomcat容器调用的)在HttpSrvlet中我们会去分析请求的方
| 式:到底是get,post,head还是delete等等 然后决定调用哪个do开头的方法,
| 那么在HttpServlet中这些do方法默认都是405的实现风格-要我们子类去实现对应
| 的方法,否则默认会报405错误,因此我们新建Servlet时,我们才会考虑请求方法
|----1.1 String method = req.getMethod() - 获得请求的方式
|----1.2 各种 if 判断,根据请求方式的不同,决定调哪个do方法
|----1.3 在HttpServlet这个抽象类中,do方法都差不多,类似如下
if (method.equals("GET")) {
this.doGet(req, resp);
} else if (method.equals("HEAD")) {
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
}
4.3 Servlet生命周期
1.生命周期:
从出生到死亡的过程就是生命周期,对应Servlet的三个方法:init(),service(),destroy()
2. 默认情况:
第一次接收到请求的时候,这个Servlet会进行实例化(调用构造方法),初始化(调用init( )),然后服务(调用service( )
通过案例我们发现:Servlet实例tomcat只会创建一个,所有的请求都是这个实例去响应,默认情况下,第一次请求时,tomcat才会去实例化(通过反射创建),初始化,然后再服务
好处:提高系统启动速度
坏处:第一次启动时耗时太长
如果应该提高系统启动速度应该按此方法设置,当前默认情况就是这样。如果要提高响应速度,应该设置Servlet初始化时机
3. Servlet初始化时机
默认第一次接受请求时实例化,初始化
我们也可以通过<load-on-startup>来设置servlet启动的先后顺序,数字越小启动越靠前,最小为0
4. Servlet在容器中是单例的,是线程不安全的
单例:所有的请求都是同一个实例去响应
线程不安全:一个线程需要根据这个实例中的某个成员变量值做逻辑判断,但是在中间某个时机,另一个线程改变了这个值,导致前面的线程会进行错误的行为
启发:尽量不要在Servlet中定义成员变量,如果不得不定义成员变量
1. 不要修改成员变量的值
2. 不要根据成员变量的值进行逻辑判断
4.4 HTTP协议
HTTP详细笔记https://heavy_code_industry.gitee.io/code_heavy_industry/pro001-javaweb/note/note004-HTTP.html
1. HTTP基础概念
HTTP:Hyper Text Transfer Protocol超文本传输协议。HTTP最大的作用就是确定了请求和响应数据的格式。浏览器发送给服务器的数据:请求报文;服务器返回给浏览器的数据:响应报文。
HTTP是无状态的,请求响应包含两个部分:请求和响应
2. 请求报文
请求包含三个部分:
1.请求行 (一般包含:1.请求的方式 2.请求的URL 3.请求的协议,一般都是HTTP1.1)
2.请求消息头(包含了客户端告诉浏览器的信息,比如:浏览器型号,版本,能接受内容的类型,给你发的内容的类型,内容的长度等等)
3. 请求体
三种情况:
get方式,没有请求体,但有queryString
post方式,有请求体,form data
json格式,有请求体,request payload
3. 响应报文
响应也包含三个部分:
1.响应状态行 (一般包含:1.响应的协议,一般都是HTTP1.1 2.响应状态码 3.响应状态)
2.响应消息头(包含了服务器告诉客户端的信息,比如:内容的媒体类型,编码,内容的长度等等)
3. 响应体
服务器返回的数据主体,有可能是各种数据类型。
4.5 会话
1.Http是无状态的
1. HTTP 无状态 :服务器无法判断这两次请求是同一个客户端发过来的,还是不同的客户端发过来的
2. 无状态带来的现实问题:第一次请求是添加商品到购物车,第二次请求是结账;如果这两次请求服务器无法区分是同一个用户的,那么就会导致混乱
3. 通过会话跟踪技术来解决无状态的问题
2. 会话跟踪技术
1. 客户端第一次发请求给服务器,服务器获取session,获取不到,则创建新的,然后响应给客户端
2. 下次客户端给服务器发请求时,会把sessionID带给服务器,那么服务器就能获取到了,那么服务器就判断这一次请求和上次某次请求是同一个客户端,从而能够区分开客户端
3. 常用的API:
request.getSession() -> 获取当前的会话,没有则创建一个新的会话
request.getSession(true) -> 效果和不带参数相同
request.getSession(false) -> 获取当前会话,没有则返回null,不会创建新的
session.getId() -> 获取sessionID
session.isNew() -> 判断当前session是否是新的
session.getMaxInactiveInterval() -> 返回session的非激活间隔时长,默认1800秒
session.setMaxInactiveInterval() -> 设置session的非激活间隔时长,默认1800秒
session.invalidate() -> 强制性让会话立即失效
....
3. session保存作用域
1.session保存作用域是和具体的某一个session对应的
2. 常用的API:
void session.setAttribute(key, value) -> 向当前session保存作用域保存一个对应key为value的数据
Object session.getAttribute(key) -> 获取当前session中key对应的value值
void removeAttribute(key) -> 删除当前session中key对应的value值
4.6 服务器内部转发以及客户端重定向
1. 服务器内部转发
request.getRequestDispatcher("...").forward(request,response);
- 一次请求响应的过程,对于客户端而言,内部经过了多少次转发,客户端是不知道的
- 地址栏没有变化
2. 客户端重定向
response.sendRedirect("....");
- 两次请求响应的过程。客户端肯定知道请求URL有变化
- 地址栏有变化 302重定向
4.7 Thymeleaf视图技术
这里和前面的webmoudule1类似,使用水果数据库,使用尚硅谷jdbcUtilsV2和BaoseDao重写FruitDao,然后引入一些简单的css,imgs和index.html文件。同时添加thymeleaf的jar包到库中,并添加到组件。
Thymeleaf详细笔记https://heavy_code_industry.gitee.io/code_heavy_industry/pro001-javaweb/note/note006-Thymeleaf.html
1. thymeleaf的部分标签
1) 使用步骤: 添加thymeleaf的jar包
2) 新建一个Servlet类ViewBaseServlet (从上面的链接复制)
新建ViewBaseServlet(有两个方法)
3) 在web.xml添加配置(从上面的链接复制)
配置两个<context-param> : view-prefix , view-suffix
这里 / 代表web根目录
4) 使得我们的Servlet继承ViewBaseServlet
//Servlet从3.0版本支持注解方式的注册
@WebServlet("/index")
public class IndexServlet extends ViewBaseServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
FruitDao fruitDao = new FruitDao();
List<Fruit> fruitList = fruitDao.getFruitList();
HttpSession session = req.getSession();
session.setAttribute("fruitList", fruitList);
//此处的视图名称是 index
//那么thymeleaf会将这个 逻辑视图名称 对应到 物理视图 名称上
//逻辑视图名称 : index
//物理视图名称 : view-prefix + 逻辑视图名称 + view-suffix
//真实的视图名称是: / index .html
super.processTemplate("index", req, resp);
}
}
运用thymeleaf中的部分标签: <th:if> , <th:unless> , <th:each> , <th:text>重写index.html文件,即可完成把Servlet中通过session会话获取到的数据库数据存放的fruitlist,然后我们的Servlet写了存储作用域的句子,把它放入session作用域,我们html写入mxlns:th:http://www.thymeleaf.org的html下,写thymeleaf语法标签即可
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/index.css">
</head>
<body>
<div id="div_container">
<div id="div_fruit_list">
<table id="tbl_fruit">
<tr>
<th class="w20">名称</th>
<th class="w20">单价</th>
<th class="w20">库存</th>
<th>操作</th>
</tr>
<tr th:if="${#lists.isEmpty(session.fruitList)}">
<td colspan="4">对不起,库存为空</td>
</tr>
<tr th:unless="${#lists.isEmpty(session.fruitList)}" th:each="fruit : ${session.fruitList}">
<td th:text="${fruit.fname}">苹果</td>
<td th:text="${fruit.price}">5</td>
<td th:text="${fruit.fcount}">20</td>
<td><img src="imgs/del.jpg" class="delImg"/></td>
</tr>
</table>
</div>
</div>
</body>
</html>
4.8 保存作用域
原始情况下,保存作用域我们可以认为有四个: page(页面级别,现在几乎不用) , request(一次请求响应范围) , session(一次会话范围) , application(整个应用程序范围)
1. request:一次请求响应范围
这里使用demo01和demo02进行request保存作用域的判定,发现同一个request保存作用域在当前request下有效。客户端重定向因为发起了新的request导致无法取到之前保存的数据,而服务端内部转发,因为没有产生新的request,所以可以取到,但是客户端以为保存在demo01下,其实保存在demo02下,服务器内部帮助完成了转发任务
2. session:一次会话范围有效
使用session进行作用域的保存,可以发现无论是重定向还是服务器内部转发都可以获取到,只要是用一个session内即可
3. application: 一次应用程序范围有效
即便不同的浏览器访问demo05都可以获得作用域保存的数据
4. 补充:相对路径和绝对路径
从根目录一直写到文件的路径写法是绝对路径,从当前文件为目录起点,利用 .. 和 / 写的路径是相对路径。
<base href="http://localhost:8080/pro10/" /> 的作用是:当前页面上的所有路径都以此路径为基础,默认写别的绝对路径,可以从这个路径下来当作绝对路径的起点
在thymeleaf中,th:href="@{}" 和上面的base设置相同
例如可以: <link th:href="@{/css/shopping.css}">
4.9 水果管理系统项目(更新功能)
加入链接编辑Servlet,给水果加入链接,点水果名称跳转到edit.do的Servlet下,完成数据编辑,但是直接加超链接会因为thymeleaf的标签覆盖,导致里面的超链接被覆盖我们写的thymeleaf的数据,所以我们只需要把thymeleaf的表达式放到超链接就行
为了使得我们对网页的相应水果进行操作,我们实际情况下会在链接中填入 ?fid=xxx
为了使得我们填入对应信息可以选择相对应的水果,需要给html的水果栏的href修改参数,但thymeleaf无法判断字符串和参数,根据thymeleaf语法需要修改为:
但是我们参数数量多的情况下,这样比较繁琐,可以按语法如下写(不写问号,用小括号加键值对的书写来进行,如果参数不止一个,只需要用逗号作为分隔符,填入新的键值对):
为了使用到fid我们需要进行字符串判断
因为我们经常要进行这样的判断,这个方法不如封装到utils下作为StringUtil
同时为了Dao层的FruitDao能根据fid查询到对应的水果信息,我们可以给基础的FruitDAO接口加入一个抽象的getFruitById方法,然后在继承BaseDao和实现FruitDAO接口的FruitDao重写父接口的这个方法,完成Dao层方法的封装
此时我们的EditServlet方法写为,我们渲染到edit页面,所以我们需要重写一个edit.html的页面
这里按thymeleaf的语法,把request作用域保存的fruit的信息放入我们编辑页面的文本框中
此时效果图如下
但是每个属性都需要加fruit前缀,很冗余,我们只需要在祖先节点使用thymeleaf语法写入th:object="${fruit}",那么我们所有的后代节点只需要写th:value="*{属性值}"即可用*代表祖先节点声明的变量
同时我们需要把这个输入的内容存储到一个表单,以更新的方式进行修改我们的数据,所以我们把table写入一个表单中,action为 post
此时只需要重写update.do对应的Servlet类
但我们查询数据应该是按主键查询会快,但是主键此时没查,我们可以查询,但是没必要给用户看到主键或者修改主键,所以我们可以在edit.html中写入查询主键的input,但是使用type为hidden,这个时候用户端就不会显式的呈现这一个输入框,但是我们从后台拿到了主键fid,同时这里可以把th:object写到form中,还可以使用th:value="*{属性值}",如果不移动,就只能通过${}的书写方式
然后重写FruitDao的更新方法,使用在upDateServlet中的FruitDao的private实例对象,调用它这个方法,把我们从edit.html发送过来的表单数据提取并更新到数据库中
但是此时还是有问题的,我们修改数据发现,页面的数据没有变化,但是查看数据库,发现数据库的数据已经修改了,这是为什么呢?其实processTemplate的作用相当于服务器重定向,这个时候获取的数据还是Session的数据,此时的Session的数据还是之前查到的数据,没有把更新的数据放入
super.processTemplate("index", req, resp);
我们为了能够查到新的信息,可以使用客户端重定向,让客户端发一个新的请求给IndexServlet,重新获取fruitList,然后覆盖到Session中,这样就可以显示新的数据了。
4.10 水果管理系统项目(删除功能)
首先想要实现点击右侧删除图标的数据库数据删除功能
改写index.html的img部分,加入thymeleaf表达式,调用一会要给Dao层添加的delFruit方法
但是字符串拼接的方式可读性差,其实根据thymeleaf语法可以使用 | 单竖线去括起来,这样未有${}包裹的部分就会被识别为字符串
浏览器控制台可以看到已经做到了提取对应的id,因为我们之前写了隐藏域提取到对应fruit的fid
但这个js函数我们还没写,所以补上路径并在对应位置写js方法
显然我们应该再补充写上对应操作的Servlet:del.do,这里可能会疑惑为什么不像之前一样在onclick之后直接写对应的servlet,非要写一个js,这是因为需要给客户一个弹窗,这个是前端的层面,后端无法做到这样的操作
在此之前我们先在FruitDAO抽象接口写一个根据Fid删除记录的抽象方法,让继承于BaseDao实现这个接口的我们自己写的FruitDao重写相应的方法即可
下为FruitDao(我们自己写的)
这里我们需要在DelServlet类重写doGet方法,这里记住,除了表单的request是Post,其他的操作一般都是Get请求,有了前面的经验,我们这里也使用客户端重定向让客户端查询到最新的数据库数据,而不是之前Session作用域的内容
为了防止未在artifacts里面构建js文件夹,可以删除再重启项目,或者点上面的工具锤
此时就可以完成删除操作
4.11 水果管理系统项目(增加功能)
首先我们需要在我们的index.html下添加一个超链接,让我们能够点击跳转到add.html下,完成加入的操作
这里add的操作类似我们的edit页面,可以复制重命名后进行修改
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/add.css">
</head>
<body>
<div id="div_container">
<div id="div_fruit_list">
<p class="center f30">增加水果库存信息</p>
<form th:action="@{/add.do}" method="post">
<table id="tbl_fruit" th:object="${fruit}">
<tr>
<th class="w20">名称:</th>
<!-- <td><input type="text" name="fname" th:value="${fruit.fname}"/></td>-->
<td><input type="text" name="fname"/></td>
</tr>
<tr>
<th class="w20">单价:</th>
<td><input type="text" name="price" /></td>
</tr>
<tr>
<th class="w20">库存:</th>
<td><input type="text" name="fcount"/></td>
</tr>
<tr>
<th class="w20">备注:</th>
<td><input type="text" name="remark"/></td>
</tr>
<tr>
<th colspan="2">
<input type="submit" value="添加">
</th>
</tr>
</table>
</form>
</div>
</div>
</body>
</html>
显然我们应该给这个表单发送的目标Servlet add.do写一个类AddServlet来实现添加的功能,这个功能显然也应该操作Dao层去对数据库进行操作,所以我们按类似上面的操作,给FruitDao中写入这个方法
类AddServlet(add.do)实现添加的功能
此时出了bug,输入添加的数据,首先没有跳转回index页面,同时数据库也没有添加,仔细看前面的add.html,我们在表单中使用了th:action="@{/add.do}
但是我们最初访问的index页面的超链接,直接访问的add.html静态页面,所以根本没有进入addServlt,也就自然我们的操作仅仅是给服务器发送数据,但是也不会添加,还保留在原来的页面,因为thymeleaf语法是必须经过super.processTemplet()渲染的。静态页面它无法识别我们写的th代码是什么意思,也就无法进入我们的addServlet
所以我们应该把本身的add.html的action直接改为add.do
为什么我们之前编辑操作就可以?因为编辑操作我们是直接进入的EditServlet
而Servlet中已经使用thymeleaf渲染过了
为什么我们之前删除操作也可以?因为删除操作我们通过index的Servlet下渲染的index.html页面下的thymeleaf进入js函数,通过函数使得我们跳转到del.do的Servlet下,然后借助这个Servlet下写了的渲染函数完成了操作,然后提交的表单完成了数据库操作
至此,添加功能也完成了
4.12 水果管理系统项目(分页功能)
想要完成每页显示五条数据的分页,所以我们的最顶层接口应该添加一个重载的,查询指定页码的方法
同时在子类FruitDao中实现它
此时index的Servlet下我们就可以填入参数,让我们显示页数了,默认填入1,访问第一页的数据
然后我们需要加入换页的功能,在index.html的table下,加入四个按钮完成未来要完成的操作
为了完成我们按钮的功能,首先我们需要在index的Servlet下获取当前页码,并可以进行页码的转化,同时为了让我们会话过程中,能够保持相同页码,我们把页码放入session作用域
然后稍微补充一下我们之前的按钮,我们可以把当前页码传入一个js方法,实现到达指定的页码
此时可以完成基本的页码上下页和首页,但是到-1页会报错,数据库越界,到后面没有内容的页码会显示没有数据,这显然需要加入合法性的验证
我们先实现尾页的操作,需要我们加入一个Dao层的方法,返回当前有多少条记录
然后我们在index的Servlet下需要查询到总页数,传入session,方便我们调用,同时为了计算总页数,我们应该理解应该向上取整去除每页的规定页数,所以可以加入一个4的偏移量,完成向上取整的操作
此时彻底完成了尾页跳转
但是此时其实会报错500,因为我们最初封装的JDBCUtilsV2是用反射的方式把搜索到的字段赋值给我们javabean写的属性,然后以类的形式存储在List中,这里我们没法调用Object的内部属性值,因为是私有的,并且我们也不知道获取的Count(*)是什么类型(其实是Long)
这里老师用的Jdbc和我的不同,所以我在javabean中封装了一个类专门用来承接这个Long属性的值,然后再提取出来进行return
FruitDao类,查询方法
换页的非法判断,通过thymeleaf的页数判断和按钮的disable属性
至此,正式完成了换页和分页的功能
4.13 水果管理系统项目(关键字查询功能)
首先在index.html下加入表单,完成基础的入口
考虑到我们这个查询的逻辑本质和indexServlet没什么区别,只是搜索的时候增加了模糊查询的限制,同时这是表单查询,使用的是Post,可以直接还使用index的Servlet,给它添加写一个doPost的方法,但是业务逻辑相同,仅仅是入口不同,以及多了限制,我们不妨直接在doPost内部调用doGet把request和response传入,然后改写doGet的逻辑
为了区分到底是否使用关键字查询,老师这里提供一个思路,给form表单加入隐藏域,form表单在提交过程中也会被带入index的Servlet,如果通过req.getParameter()获取到不为空的隐藏域参数,说明使用了关键字查询
同时为了防止出现我们换页后进行查询,错用了session之前已经会保存的跳页的页码值,所以我们需要增加亿点点逻辑判断
//Servlet从3.0版本支持注解方式的注册
@WebServlet("/index")
public class IndexServlet extends ViewBaseServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
HttpSession session = req.getSession();
Integer pageNum = 1;
String oper = req.getParameter("oper");
//如果oper不为空,说明通过表单的查询按钮点击过来的
//如果oper为空,说明通过其他的操作进入
String keyword = null;
if(StringUtil.isNotEmpty(oper) && "serach".equals(oper)){
//说明是点击表单查询发送过来的请求
//此时,pageNum应该还原为1 , keyword应该从请求参数获取
pageNum = 1;
keyword = req.getParameter("keyword");
if(StringUtil.isEmpty(keyword)){
keyword = "";
}
session.setAttribute("keyword", keyword);
} else {
//说明此处是通过页面按钮跳转的,或者从网页直接输入网址
//keyword应该从session作用域获取
String pageNumStr = req.getParameter("pageNum");
if(StringUtil.isNotEmpty(pageNumStr)){
pageNum = Integer.parseInt(pageNumStr);
}
Object keywordObj = session.getAttribute("keyword");
if(keywordObj!= null){
keyword = keywordObj.toString();
}else {
keyword = "";
}
}
//保存页码到session作用域
session.setAttribute("pageNum", pageNum);
//保存水果库存到session作用域
FruitDao fruitDao = new FruitDao();
List<Fruit> fruitList = fruitDao.getFruitList(pageNum);
session.setAttribute("fruitList", fruitList);
//保存总页码到session作用域
int fruitCount = fruitDao.getFruitCount();
int pageCount = (fruitCount + 5 - 1) / 5;
session.setAttribute("pageCount", pageCount);
//此处的视图名称是 index
//那么thymeleaf会将这个 逻辑视图名称 对应到 物理视图 名称上
//逻辑视图名称 : index
//物理视图名称 : view-prefix + 逻辑视图名称 + view-suffix
//真实的视图名称是: / index .html
super.processTemplate("index", req, resp);
}
}
并且让我们查询页数和查询库存的Dao层函数增加一个keyword参数,Sql语句增加一个条件,这里要记住我们写Sql语句要填写?,而为了满足LIKE的语句条件,需要给参数部分用字符串拼接“%”
@Override
public List<Fruit> getFruitList(String keyword, Integer pageNum){
String sql = "SELECT * FROM t_fruit WHERE fname LIKE ? OR remark LIKE ? LIMIT ? , 5;";
List<Fruit> fruits = null;
try {
fruits = executeQuery(Fruit.class, sql,"%"+keyword+"%", "%"+keyword+"%", (pageNum - 1) * 5);
} catch (Exception e) {
throw new RuntimeException(e);
}
return fruits;
}
@Override
public int getFruitCount(String keyword) {
String sql = "SELECT COUNT(*) AS fruitCount FROM t_fruit WHERE fname LIKE ? OR remark LIKE ?;";
List<FruitData> counts = null;
try {
counts = super.executeQuery(FruitData.class, sql, "%" + keyword + "%", "%" + keyword + "%");
} catch (Exception e) {
throw new RuntimeException(e);
}
return Integer.parseInt(counts.get(0).getFruitCount().toString());
}
至此,水果管理系统的整体功能已经被我们使用Servlet+Thymeleaf和JDBC完成了
下一部分就是代码优化和MVC部分了