目录
2. 根据水果javabean和JDBC写出FruitDaoJDBC
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协议
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包到库中,并添加到组件。
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部分了