一、Tomcat
1.1 介绍
Tomcat是一个web容器项目可以部署到里面。
通过context root来定位某个项目。比如http://localhost:8080/baidu/demo09.html ip地址+端口号定位某一台服务器的tomcat,然后通过root定位资源。
1.2 IDEA(2021)里完成部署
- 选java Enterprise项目
- 新建与WEB-INF同级的HTML
- 完成部署前先进行配置tomcat,选tomcat-local
- 完成部署
完全可以改的方便些
最终结果:
1.3 IDEA特殊
idea把项目部署到这个位置,然后在tomacat里面指明位置,所以tomcat的webapps里面看不到
1.4 动态web工程目录结构介绍
二、Servlet(服务器端应用程序)
2.1 Servlet在整个web应用中起到的作用
在整个Web应用中,Servlet主要负责处理请求、协调调度功能。我们可以把Servlet称为Web应用中的控制器
2.2 第一次使用servlet
服务器端把请求封装成request
add.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="add" method="post">
名称:<input type="text" name="fname"/><br/>
价格:<input type="text" name="price"/><br/>
库存:<input type="text" name="fcount"/><br/>
备注:<input type="text" name="remark"/><br/>
<input type="submit" value="添加" />
</form>
</body>
</html>
编写AddServlet,响应post请求
public class AddServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String fname = request.getParameter("trfname");
String priceStr = request.getParameter("price");
Integer price = Integer.parseInt(priceStr);
String fcountStr = request.getParameter("fcount");
Integer fcount = Integer.parseInt(fcountStr);
String remark = request.getParameter("remark");
//soutv
System.out.println("fname = " + fname);
}
}
对于第一个图的疑问:add是怎么能对应上AddServlet?
我们需要设置。在web.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>
<!--第一行是随便起的名字,第二行对应的是AddServlet类的全类名-->
<servlet-name>AddServlet</servlet-name>
<servlet-class>com.example.servlets.AddServlet</servlet-class>
</servlet>
<servlet-mapping>
<!--对应的映射-->
<servlet-name>AddServlet</servlet-name>
<url-pattern>/add</url-pattern>
</servlet-mapping>
</web-app>
执行顺序
1. 用户发请求,action=add
2. 项目中,web.xml中找到url-pattern = /add
3. 找第11行的servlet-name = AddServlet
4. 找和servlet-mapping中servlet-name一致的servlet
5. 找servlet-class -> com.atguigu.servlets.AddServlet
6. 用户发送的是post请求(method=post) , 因此 tomcat会执行AddServlet中的doPost方法
运行成功后是这样的,如果出现404则是找不到对应的资源
用开发者模式观察提交表单后
填入信息后即可在idea中看到结果。
乱码在doPost方法里加上 req.setCharacterEncoding(“UTF-8”);
注意:设置编码这一行代码必须放在所有的获取参数动作之前,也就是第一行。并且,tomcat8之后,get方法不需要设置编码,只有post需要。
2.3 连接数据库
效果展示:
资料:下载尚硅谷的文件即可,建议先自学jdbc后再应用此部分。
实现过程中的问题:
- 我采用的是maven来进行mysql jar包的安装,没有这个绝对跑不起来。
- 因为版本是6以后的,所以我们的driver要变一变(加个cj)
2.4 Servlet的继承关系
1、提出问题
为什么IDEA创建Servlet之后不再实现Servlet接口而是继承HttpServlet类?
Servlet接口和HttpServlet类有什么关系?
doXxx()方法和service()方法有什么关系?
2、类型与方法关系
Servlet中service方法,当客户端发送请求,此方法就会被自动调用(其实是tomcat容器调用的)。具体做的什么事情可以到GenericServlet实现类里的service。
发现仍然是抽象类。最后去看看他的抽象子类(HttpServlet)
public class HelloServlet extends HttpServlet {
// 用户第一次请求,创建HelloServlet对象的时候,会执行这个无参数构造方法。
public HelloServlet() {
}
//override 重写 doGet方法
//override 重写 doPost方法
}
public abstract class GenericServlet implements Servlet, ServletConfig,
java.io.Serializable {
// 用户第一次请求的时候,HelloServlet对象第一次被创建之后,这个init方法会执行。
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
// 用户第一次请求的时候,带有参数的init(ServletConfig config)执行之后,会执行这个没有参数的init()
public void init() throws ServletException {
// NOOP by default
}
}
// HttpServlet模板类。
public abstract class HttpServlet extends GenericServlet {
用户发送第一次请求的时候这个service会执行
用户发送第N次请求的时候,这个service方法还是会执行。
用户只要发送一次请求,这个service方法就会执行一次。
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
// 将ServletRequest和ServletResponse向下转型为带有Http的HttpServletRequest和HttpServletResponse
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException(lStrings.getString("http.non_http"));
}
// 调用重载的service方法。
service(request, response);
}
这个service方法的两个参数都是带有Http的。
这个service是一个模板方法。
在该方法中定义核心算法骨架,具体的实现步骤延迟到子类中去完成。
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 获取请求方式
// 这个请求方式最终可能是:""
// 注意:request.getMethod()方法获取的是请求方式,可能是七种之一:
// GET POST PUT DELETE HEAD OPTIONS TRACE
String method = req.getMethod();
// 如果请求方式是GET请求,则执行doGet方法。
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// Invalid date header - proceed as if none was set
ifModifiedSince = -1;
}
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
// 如果请求方式是POST请求,则执行doPost方法。
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException{
// 报405错误
String msg = lStrings.getString("http.method_get_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 报405错误
String msg = lStrings.getString("http.method_post_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
}
再HttpServlet中这些do方法默认都是405的实现风格-要我们的子类去实现对应的方法,否则默认会报405的错误。因此我们在新建Servlet,要考虑请求方法,从而决定重写哪个do方法
2.5 Servlet的生命周期
//演示Servlet的生命周期
public class Demo02Servlet extends HttpServlet {
public Demo02Servlet(){
System.out.println("正在实例化....");
}
@Override
public void init() throws ServletException {
System.out.println("正在初始化.....");
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("正在服务.....");
}
@Override
public void destroy() {
System.out.println("正在销毁......");
}
}
1) 生命周期:从出生到死亡的过程就是生命周期。对应Servlet中的三个方法:init(),service(),destroy()
2) 默认情况下:
第一次接收请求时,这个Servlet会进行实例化(调用构造方法)、初始化(调用init())、然后服务(调用service())
从第二次请求开始,每一次都是服务
当容器关闭时,其中的所有的servlet实例会被销毁,调用销毁方法
3) 通过案例我们发现:
- Servlet实例tomcat只会创建一个,所有的请求都是这个实例去响应。
- 默认情况下,第一次请求时,tomcat才会去实例化,初始化,然后再服务.这样的好处是什么? 提高系统的启动速度 。 这样的缺点是什么? 第一次请求时,耗时较长。
- 因此得出结论: 如果需要提高系统的启动速度,当前默认情况就是这样。如果需要提高响应速度,我们应该设置Servlet的初始化时机。
4) Servlet的初始化时机:
分为两种情况:
- 默认情况下第一次请求servlet对象时实例化,初始化;
- 可以通过load-on-startup 节点( load-on-startup属性值只能为整数 ) 来指定Servlet启动顺序,:
(1)当节点值为负数 (相当于不写时) , 表示当用户第一次请求时, 创建此Servlet !
(2)当节点值大于0或等于0时, 在服务器启动时, 创建此Servlet !
启动的顺序是: 值越小越早创建,0时最早创建 !
值相同时, 自上而下顺序创建 !
<servlet>
<servlet-name>Demo02Servlet</servlet-name>
<servlet-class>com.example.servlets.Demo02Servlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
5) Servlet在容器中是:单例的、线程不安全的
- 单例:所有的请求都是同一个实例去响应
- 线程不安全:一个线程需要根据这个实例中的某个成员变量值去做逻辑判断。但是在中间某个时机,另一个线程改变了这个成员变量的值,从而导致第一个线程的执行路径发生了变化
- 我们已经知道了servlet是线程不安全的,给我们的启发是: 尽量的不要在servlet中定义成员变量。如果不得不定义成员变量,那么不要去:①不要去修改成员变量的值 ②不要去根据成员变量的值做一些逻辑判断
三、 HTTP协议
1) Http 称之为 超文本传输协议
2) Http是无状态的
3) Http请求响应包含两个部分:请求和响应
1. 请求
请求包含三个部分: 1.请求行 ; 2.请求消息头 ; 3.请求主体
![在这里插入图片描述]()
1)请求行包含是三个信息: 1. 请求的方式 ; 2.请求的URL ; 3.请求的协议(一般都是HTTP1.1)
2)请求消息头中包含了很多客户端需要告诉服务器的信息,比如:我的浏览器型号、版本、我能接收的内容的类型、我给你发的内容的类型、内容的长度等等
3)请求体,三种情况
get方式,没有请求体,但是有一个queryString
post方式,有请求体,form data
json格式,有请求体,request payload
2. 响应
响应也包含三个部分: 1. 响应行 ; 2.响应头 ; 3.响应体
![在这里插入图片描述]()
1)响应行包含三个信息:1.协议 2.响应状态码(200) 3.响应状态(ok)
2)响应头:包含了服务器的信息;服务器发送给浏览器的信息(内容的媒体类型、编码、内容长度等)
3)响应体:响应的实际内容(比如请求add.html页面时,响应的内容就是<form…)
4)响应状态码:
四、会话(Session)
1) Http是无状态的
- HTTP 无状态 :服务器无法判断这两次请求是同一个客户端发过来的,还是不同的客户端发过来的
- 无状态带来的现实问题:第一次请求是添加商品到购物车,第二次请求是结账;如果这两次请求服务器无法区分是同一个用户的,那么就会导致混乱
- 通过会话跟踪技术来解决无状态的问题。
2) 会话跟踪技术
- 客户端第一次发请求给服务器,服务器获取session,获取不到,则创建新的,然后响应给客户端
- 下次客户端给服务器发请求时,会把sessionID带给服务器,那么服务器就能获取到了,那么服务器就判断这一次请求和上次某次请求是同一个客户端,从而能够区分开客户端
- 常用的API:
request.getSession() -> 获取当前的会话,没有则创建一个新的会话
request.getSession(true) -> 效果和不带参数相同(即和上面一样)
request.getSession(false) -> 获取当前会话,没有则返回null,不会创建新的
session.getId() -> 获取sessionID
session.isNew() -> 判断当前session是否是新的
session.getMaxInactiveInterval() -> session的非激活间隔时长,默认1800秒
session.setMaxInactiveInterval() -> 设置时长的
session.invalidate() -> 强制性让会话立即失效
…
再次发起请求
3) session保存作用域–一次会话范围都有效
- session保存作用域是和具体的某一个session对应的
- 常用的API:
void session.setAttribute(k,v)
Object session.getAttribute(k)
void removeAttribute(k)
代码测试(用断点形式来测试)
//演示向HttpSession保存数据
public class Demo04Servlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getSession().setAttribute("uname","lina");
}
}
public class Demo05Servlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Object unameObj = req.getSession().getAttribute("uname");
System.out.println(unameObj);
}
}
第一个测试:先访问demo04在访问demo05
第二个测试:直接访问demo05
五、 服务器内部转发以及客户端重定向
1) 服务器内部转发 : request.getRequestDispatcher(“…”).forward(request,response);
- 一次请求响应的过程,对于客户端而言,内部经过了多少次转发,客户端是不知道的
- 地址栏没有变化
2) 客户端重定向: response.sendRedirect(“…”);
- 两次请求响应的过程。客户端肯定知道请求URL有变化
- 地址栏有变化
测试:
//演示服务器端内部转发以及客户端重定向
public class Demo06Servlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("demo06......");
//服务器端内部转发
//request.getRequestDispatcher("Demo07").forward(request,response);
//客户端重定向
response.sendRedirect("Demo07");
}
}
public class Demo07Servlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("demo07......");
}
}
最终控制台结果是一样的,但是用户摁下回车后可以看到网址的变化。
内部转发:
重定向:
六、Thymeleaf - 视图模板技术
让网页中显示的数据是从数据库中查出来的
大致流程:
基础准备:
连接数据库等等操作。
1) 添加thymeleaf的jar包
2) 新建一个Servlet类ViewBaseServlet(因为里面有两个方法,其中第二个方法是读取前后缀,然后渲染页面)
package com.example.myssm.myspringmvc;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.WebContext;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ViewBaseServlet extends HttpServlet {
private TemplateEngine templateEngine;
@Override
public void init() throws ServletException {
// 1.获取ServletContext对象
ServletContext servletContext = this.getServletContext();
// 2.创建Thymeleaf解析器对象
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext);
// 3.给解析器对象设置参数
// ①HTML是默认模式,明确设置是为了代码更容易理解
templateResolver.setTemplateMode(TemplateMode.HTML);
// ②设置前缀
String viewPrefix = servletContext.getInitParameter("view-prefix");
templateResolver.setPrefix(viewPrefix);
// ③设置后缀
String viewSuffix = servletContext.getInitParameter("view-suffix");
templateResolver.setSuffix(viewSuffix);
// ④设置缓存过期时间(毫秒)
templateResolver.setCacheTTLMs(60000L);
// ⑤设置是否缓存
templateResolver.setCacheable(true);
// ⑥设置服务器端编码方式
templateResolver.setCharacterEncoding("utf-8");
// 4.创建模板引擎对象
templateEngine = new TemplateEngine();
// 5.给模板引擎对象设置模板解析器
templateEngine.setTemplateResolver(templateResolver);
}
protected void processTemplate(String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 1.设置响应体内容类型和字符集
resp.setContentType("text/html;charset=UTF-8");
// 2.创建WebContext对象
WebContext webContext = new WebContext(req, resp, getServletContext());
// 3.处理模板数据
templateEngine.process(templateName, webContext, resp.getWriter());
}
}
3) 在web.xml文件中添加配置
- 配置前缀 view-prefix
- 配置后缀 view-suffix
4) 使得我们的Servlet继承ViewBaseServlet,为了让子类使用processTemplate方法
5) 根据逻辑视图名称 得到 物理视图名称
//此处的视图名称是 index
//那么thymeleaf会将这个 逻辑视图名称 对应到 物理视图 名称上去
//逻辑视图名称 : index
//物理视图名称 : view-prefix + 逻辑视图名称 + view-suffix
//所以真实的视图名称是: / index .html
super.processTemplate(“index”,request,response);
<!-- 配置上下文参数 -->
<context-param>
<param-name>view-prefix</param-name>
<param-value>/</param-value>
</context-param>
<context-param>
<param-name>view-suffix</param-name>
<param-value>.html</param-value>
</context-param>
//Servlet从3.0版本开始支持注解方式的注册
@WebServlet("/index")
public class IndexServlet extends ViewBaseServlet {
@Override
public void doGet(HttpServletRequest request , HttpServletResponse response)throws IOException, ServletException {
FruitDAO fruitDAO = new FruitDAOImpl();
List<Fruit> fruitList = fruitDAO.getFruitList();
//保存到session作用域
HttpSession session = request.getSession();
session.setAttribute("fruitList",fruitList);
//此处的视图名称是 index
//那么thymeleaf会将这个 逻辑视图名称 对应到 物理视图 名称上去
//逻辑视图名称 : index
//物理视图名称 : view-prefix + 逻辑视图名称 + view-suffix
//所以真实的视图名称是: / index .html
super.processTemplate("index",request,response);
}
}
在保存session处下断点查看
6) 使用thymeleaf的标签
th:if , th:unless , th:each , th:text
在index.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">
<p class="center f30">欢迎使用水果库存后台管理系统</p>
<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>
七、Servlet保存作用域与路径问题
原始情况下,保存作用域我们可以认为有四个: page(页面级别,现在几乎不用) , request(一次请求响应范围) , session(一次会话范围) , application(整个应用程序范围)
1) request:一次请求响应范围
2) session:一次会话范围有效
3) application: 一次应用程序范围有效
7.1 Request保存作用域
测试:
//演示request保存作用域(demo01和demo02)
@WebServlet("/demo01")
public class Demo01Servlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.向request保存作用域保存数据
request.setAttribute("uname","lili");
//2.客户端重定向
response.sendRedirect("demo02");
//3.服务器端转发
//request.getRequestDispatcher("demo02").forward(request,response);
}
}
@WebServlet("/demo02")
public class Demo02Servlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取request保存作用域里面保存的数据,key为uname
Object uname = req.getAttribute("uname");
System.out.println("uname = " + uname);
}
}
重定向后输入demo01,刷新后是demo02
服务器端转发方式
7.2 session保存作用域
无论是跳转还是服务器端转发都可以,因为是一次会话有效。代码没啥差距,只需要在set和get改一下即可
request.getSession().setAttribute("uname","lili");
request.getSession().getAttribute("uname");
7.3 application保存作用域
代码也只需要在set这里改动一点即可测试
//ServletContext:Servlet上下文(张三和李四聊的很开心,王五去问,张三和李四就介绍上下文给他)
// tomcat已运行表示这一次的应用程序就开始启动了
ServletContext application = request.getServletContext();
application.setAttribute("uname","lili");
ServletContext application = req.getServletContext();
7.4 路径问题
八、实现库存系统的功能
前提:此实现网址都放在web目录下,而不是web-inf下,尽管后者更为安全常见。此项目只为简单实现。
8.1 编辑和修改特定库存信息
首先我们先将前端页面写好,并针对特定信息形成特定网址,这样才可以对特定信息进行修改
初始的index页面之前就已经写好。而修改这部分是由 th:href=“@{/edit.do(fid=${fruit.fid})}” 来进行实现。@{}的作用是在字符串前附加『上下文路径』
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/index.css">
<script language="JavaScript" src="js/index.js"></script>
</head>
<body>
<div id="div_container">
<div id="div_fruit_list">
<p class="center f30">欢迎使用水果库存后台管理系统</p>
<div style="border:0px solid red;width:60%;margin-left:20%;text-align:right;">
<a th:href="@{/add.html}" style="border:0px solid blue;margin-bottom:4px;">添加新库存记录</a>
</div>
<table id="tbl_fruit">
<tr>
<th class="w20">名称1</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><a th:text="${fruit.fname}" th:href="@{'/edit.do?fid='+${fruit.fid}}">苹果</a></td> -->
<td><a th:text="${fruit.fname}" th:href="@{/edit.do(fid=${fruit.fid})}">苹果</a></td>
<td th:text="${fruit.price}">5</td>
<td th:text="${fruit.fcount}">20</td>
<!-- <td><img src="imgs/del.jpg" class="delImg" th:οnclick="'delFruit('+${fruit.fid}+')'"/></td> -->
<td><img src="imgs/del.jpg" class="delImg" th:onclick="|delFruit(${fruit.fid})|"/></td>
</tr>
</table>
</div>
</div>
</body>
</html>
此时后端要对请求作出响应(ViewBaseServlet这个基类直接复制粘贴就好,后面都会被框架代替)
@WebServlet("/edit.do")
public class EditServlet extends ViewBaseServlet {
private FruitDAO fruitDAO = new FruitDAOImpl();
@Override
public void doGet(HttpServletRequest request , HttpServletResponse response)throws IOException, ServletException {
String fidStr = request.getParameter("fid");
if(StringUtil.isNotEmpty(fidStr)){
int fid = Integer.parseInt(fidStr);
Fruit fruit = fruitDAO.getFruitByFid(fid);
request.setAttribute("fruit",fruit);
//此处的视图名称是 index
//那么thymeleaf会将这个 逻辑视图名称 对应到 物理视图 名称上去
//逻辑视图名称 : index
//物理视图名称 : view-prefix + 逻辑视图名称 + view-suffix
//所以真实的视图名称是: / index .html
super.processTemplate("edit",request,response);
}
}
}
因为常常需要判断是否为空,我们直接写一个判断是否是空的工具类
public class StringUtil {
//判断字符串是否为null或者""
public static boolean isEmpty(String str){
return str==null || "".equals(str);
}
public static boolean isNotEmpty(String str){
return !isEmpty(str);
}
}
查询数据库的方法要建立
public interface FruitDAO {
//获取所有的库存列表信息
List<Fruit> getFruitList();
//根据fid获取特定的水果库存信息
Fruit getFruitByFid(Integer fid);
}
public class FruitDAOImpl extends BaseDAO<Fruit> implements FruitDAO {
@Override
public List<Fruit> getFruitList() {
return super.executeQuery("select * from t_fruit");
}
@Override
public Fruit getFruitByFid(Integer fid) {
return super.load("select * from t_fruit where fid = ? " , fid);
}
}
写好edit页面,将用户填入的信息以表单的形式发给后端。
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/edit.css">
</head>
<body>
<div id="div_container">
<div id="div_fruit_list">
<p class="center f30">编辑库存信息3</p>
<form th:action="@{/update.do}" method="post" th:object="${fruit}">
<!-- 隐藏域 : 功能类似于文本框 , 它的值会随着表单的发送也会发送给服务器,但是界面上用户看不到 -->
<input type="hidden" name="fid" th:value="*{fid}"/>
<table id="tbl_fruit">
<tr>
<th class="w20">名称:</th>
<!-- <td><input type="text" name="fname" th:value="${fruit.fname}"/></td> -->
<td><input type="text" name="fname" th:value="*{fname}"/></td>
</tr>
<tr>
<th class="w20">单价:</th>
<td><input type="text" name="price" th:value="*{price}"/></td>
</tr>
<tr>
<th class="w20">库存:</th>
<td><input type="text" name="fcount" th:value="*{fcount}"/></td>
</tr>
<tr>
<th class="w20">备注:</th>
<td><input type="text" name="remark" th:value="*{remark}"/></td>
</tr>
<tr>
<th colspan="2">
<input type="submit" value="修改" />
</th>
</tr>
</table>
</form>
</div>
</div>
</body>
</html>
后端将数据库进行更新,然后跳转到index页面
@WebServlet("/update.do")
public class UpdateServlet extends ViewBaseServlet {
private FruitDAO fruitDAO = new FruitDAOImpl();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.设置编码
request.setCharacterEncoding("utf-8");
//2.获取参数
String fidStr = request.getParameter("fid");
Integer fid = Integer.parseInt(fidStr);
String fname = request.getParameter("fname");
String priceStr = request.getParameter("price");
int price = Integer.parseInt(priceStr);
String fcountStr = request.getParameter("fcount");
Integer fcount = Integer.parseInt(fcountStr);
String remark = request.getParameter("remark");
//3.执行更新
fruitDAO.updateFruit(new Fruit(fid,fname, price ,fcount ,remark ));
//4.资源跳转
//super.processTemplate("index",request,response);
//上面这句话相当于request.getRequestDispatcher("index.html").forward(request,response);刚刚更新完,这条语句执行的是从session里面展示数据,但session里的数据是更新之前的。所以这只在数据库里面更新
//此处需要重定向,目的是重新给IndexServlet发请求,重新获取furitList,然后覆盖到session中,这样index.html页面上显示的session中的数据才是最新的
response.sendRedirect("index");
//注意:这两个index是两码事(super的和这个的)。前者是会自动添加前后缀,后者是发给后端indexServlet来进行重新查询的
}
}
8.2 删除和添加
首先在index页面与删除的图片进行js绑定
<td><img src="imgs/del.jpg" class="delImg" th:onclick="|delFruit(${fruit.fid})|"/></td>
新建js文件
function delFruit(fid){
if (confirm('是否确认删除?')){
window.location.href = 'del.do?fid='+fid;
}
}
编写对应的deleteServlet
@WebServlet("/del.do")
public class DelServlet extends ViewBaseServlet {
private FruitDAO fruitDAO = new FruitDAOImpl();
@Override
public void doGet(HttpServletRequest request , HttpServletResponse response)throws IOException, ServletException {
String fidStr = request.getParameter("fid");
if(StringUtil.isNotEmpty(fidStr)){
int fid = Integer.parseInt(fidStr);
fruitDAO.delFruit(fid);
//super.processTemplate("index",request,response);
response.sendRedirect("index");
}
}
}
添加也是类似,在index页面里面添加
<div style="border:0px solid red;width:60%;margin-left:20%;text-align:right;">
<a th:href="@{/add.html}" style="border:0px solid blue;margin-bottom:4px;">添加新库存记录</a>
</div>
然后创建AddServlet。但是你就会发现出现自己不想要的结果。
执行完后,应该是跳转到index页面才对。并且添加的数据没进入数据库,页面也不会更新。
我们在index页面里,这行这么写是错误的。点完超链接,直接跳转到对应页面,而没有经过Thymeleaf渲染的。
因为是没有通过Servlet的,访问的只是一个静态的页面罢了,并没有执行super.processTemplate(),也就是Thymeleaf没有生效。
<div style="border:0px solid red;width:60%;margin-left:20%;text-align:right;">
<a th:href="@{/add.html}" style="border:0px solid blue;margin-bottom:4px;">添加新库存记录</a>
</div>
解决方法:缺啥补啥
我们只要让此请求的页面资源经过Thymeleaf渲染即可。那么就是让页面执行super.processTemplate()方法。最简单的方式就是在AddServlet里面添加doget方法,里面就写个super.processTemplate()方法。同时在index.html里面替换掉add.html变成add.do
<a th:href="@{/add.do}" style="border:0px solid blue;margin-bottom:4px;">添加新库存记录</a>
@WebServlet("/add.do")
public class AddServlet extends ViewBaseServlet {
private FruitDAO fruitDAO = new FruitDAOImpl();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
super.processTemplate("add",request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, 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");
Fruit fruit = new Fruit(0,fname , price , fcount , remark ) ;
fruitDAO.addFruit(fruit);
response.sendRedirect("index");
}
}
这样的流程就变味:点击新增库存信息,先跳到AddServlet方法里,执行将add渲染,变成动态页面,而不知直接请求一个add静态页面。然后再执行将表单记录到数据库和页面跳转。
写在前面:关于idea中的tomcat配置的URL和Application Context联系
URL指的是服务器默认打开路径。
Application Context是web应用的路径。
部署项目时若制定了Application Context,按以下路径访问自己的项目即可。
所以Application Context一定是/fruit2。而url我们为了方便,又添加了/index,这样就能直接访问我们的indexServlet,去数据库查询,便可以看到我们水果的表。
提示:若没有特殊情况,我们并不需要主动在idea中配置tomcat的url,因为设置的Application Context会被自动填写至url中,且url会随着Application的改变自动改变。这样一来,看起来似乎url是服务器默认打开的url,Application Context是默认打开的项目。
8.3 分页功能
sql语句:
@Override
public List<Fruit> getFruitList(Integer pageNo) {
return super.executeQuery("select * from t_fruit limit ? , 5" , (pageNo-1)*5);
}
在index.html上添加分页对应图标。同时上一页和下一页都应该拿到当前所在页
<div style="width:60%;margin-left:20%;">
<input type="button" value="首 页" class="btn" th:onclick="|page(1)|" th:disabled="${session.pageNo==1}"/>
<input type="button" value="上一页" class="btn" th:onclick="|page(${session.pageNo-1})|" th:disabled="${session.pageNo==1}"/>
<input type="button" value="下一页" class="btn" th:onclick="|page(${session.pageNo+1})|" th:disabled="${session.pageNo==session.pageCount}"/>
<input type="button" value="尾 页" class="btn" th:onclick="|page(${session.pageCount})|" th:disabled="${session.pageNo==session.pageCount}"/>
</div>
page的js方法写在index.js
function page(pageNo){
window.location.href = "index?pageNo="+pageNo;
}
mysql
@Override
public int getFruitCount() {
return ((Long)super.executeComplexQuery("select count(*) from t_fruit")[0]).intValue();
}
indexServlet
//Servlet从3.0版本开始支持注解方式的注册
@WebServlet("/index")
public class IndexServlet extends ViewBaseServlet {
@Override
public void doGet(HttpServletRequest request , HttpServletResponse response)throws IOException, ServletException {
//把pageNo保存到session作用域,这样在页面上就能获取到
Integer pageNo = 1;
String pageNoStr = request.getParameter("pageNo");
if (StringUtil.isNotEmpty(pageNoStr)){
pageNo = Integer.parseInt(pageNoStr);
}
HttpSession session = request.getSession();
session.setAttribute("pageNo",pageNo);
FruitDAO fruitDAO = new FruitDAOImpl();
List<Fruit> fruitList = fruitDAO.getFruitList(pageNo);
session.setAttribute("fruitList",fruitList);
//总记录条数
int fruitCount = fruitDAO.getFruitCount();
//总页数
int pageCount = (fruitCount+5-1)/5 ;
/*
总记录条数 总页数
1 1
5 1
6 2
10 2
11 3
fruitCount (fruitCount+5-1)/5
*/
session.setAttribute("pageCount",pageCount);
//此处的视图名称是 index
//那么thymeleaf会将这个 逻辑视图名称 对应到 物理视图 名称上去
//逻辑视图名称 : index
//物理视图名称 : view-prefix + 逻辑视图名称 + view-suffix
//所以真实的视图名称是: / index .html
super.processTemplate("index",request,response);
}
}
8.4 关键字查询
通过sql的like进行模糊查询
<form th:action="@{/index}" method="post" style="float:left;width:60%;margin-left:20%;">
<input type="hidden" name="oper" value="search"/>
请输入关键字:<input type="text" name="keyword" th:value="${session.keyword}"/>
<input type="submit" value="查询" class="btn"/>
</form>
@Override
public List<Fruit> getFruitList(String keyword, Integer pageNo) {
return super.executeQuery("select * from t_fruit where fname like ? or remark like ? limit ? , 5" , "%"+keyword+"%", "%"+keyword+"%", (pageNo-1)*5);
}
@Override
public int getFruitCount(String keyword) {
return ((Long)super.executeComplexQuery("select count(*) from t_fruit where fname like ? or remark like ?", "%"+keyword+"%", "%"+keyword+"%")[0]).intValue();
}
}
//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
public void doGet(HttpServletRequest request , HttpServletResponse response)throws IOException, ServletException {
request.setCharacterEncoding("UTF-8");
HttpSession session = request.getSession() ;
Integer pageNo = 1 ;
String oper = request.getParameter("oper");
//如果oper!=null 说明 通过表单的查询按钮点击过来的
//如果oper是空的,说明 不是通过表单的查询按钮点击过来的
String keyword = null ;
if(StringUtil.isNotEmpty(oper) && "search".equals(oper)){
//说明是点击表单查询发送过来的请求
//此时,pageNo应该还原为1 , keyword应该从请求参数中获取
pageNo = 1 ;
keyword = request.getParameter("keyword");
if(StringUtil.isEmpty(keyword)){
keyword = "" ;
}
session.setAttribute("keyword",keyword);
}else{
//说明此处不是点击表单查询发送过来的请求(比如点击下面的上一页下一页或者直接在地址栏输入网址)
//此时keyword应该从session作用域获取
String pageNoStr = request.getParameter("pageNo");
if(StringUtil.isNotEmpty(pageNoStr)){
pageNo = Integer.parseInt(pageNoStr);
}
Object keywordObj = session.getAttribute("keyword");
if(keywordObj!=null){
keyword = (String)keywordObj ;
}else{
keyword = "" ;
}
}
session.setAttribute("pageNo",pageNo);
FruitDAO fruitDAO = new FruitDAOImpl();
List<Fruit> fruitList = fruitDAO.getFruitList(keyword , pageNo);
session.setAttribute("fruitList",fruitList);
//总记录条数
int fruitCount = fruitDAO.getFruitCount(keyword);
//总页数
int pageCount = (fruitCount+5-1)/5 ;
/*
总记录条数 总页数
1 1
5 1
6 2
10 2
11 3
fruitCount (fruitCount+5-1)/5
*/
session.setAttribute("pageCount",pageCount);
//此处的视图名称是 index
//那么thymeleaf会将这个 逻辑视图名称 对应到 物理视图 名称上去
//逻辑视图名称 : index
//物理视图名称 : view-prefix + 逻辑视图名称 + view-suffix
//所以真实的视图名称是: / index .html
super.processTemplate("index",request,response);
}
}