JSP 概述
JSP 是什么?
- 全称:Java Server Pages(Java 服务端页面)
- 本质:一种动态网页技术,允许在 HTML 中嵌入 Java 代码(可简单理解为 JSP = HTML + Java)
- 示例代码:
<html> <body> <h1>JSP, Hello World</h1> <!-- HTML静态内容 --> <% System.out.println("hello,jsp~"); // Java动态代码 %> </body> </html> - 执行位置:
- HTML 内容 → 展示在浏览器页面
- Java 代码 → 运行在服务器(在IDE的控制台输出)
JSP 解决什么问题?
-
Servlet 的痛点:
当需要在页面动态展示数据时(例如登录后显示登陆者用户名),Servlet 需用writer逐行输出 HTML 标签:// Servlet 实现动态内容(伪代码) protected void doPost(HttpServletRequest request, HttpServletResponse response) { response.setContentType("text/html;charset=utf-8"); PrintWriter writer = response.getWriter(); writer.write("<html>"); writer.write("<body>"); writer.write("<h1>" + username + ",欢迎您</h1>"); // 动态拼接HTML writer.write("</body>"); writer.write("</html>"); }❌ 问题:
代码臃肿、维护困难(标签与Java强耦合)、排错麻烦。 -
JSP 的解决方案:
直接在 HTML 中嵌入 Java 代码,分离展示与逻辑:<!-- JSP 实现动态内容 --> <body> <h1><%= username %>, 欢迎您</h1> <!-- 动态插入用户名 --> </body>✅ 优势:
代码简洁、可读性强、便于维护。
JSP 的核心作用
- 核心目标:简化动态网页开发
- 关键能力:
- 定义静态内容:原生支持 HTML/CSS/JavaScript
- 嵌入动态内容:通过
<% ... %>标签插入 Java 代码
- 核心价值:避免在 Servlet 中手动拼接 HTML 标签
JSP 工作流程
- 用户访问
.jsp页面(如login.jsp) - 服务器将 JSP 翻译为 Servlet(第一次访问时)
- Servlet 编译执行,动态生成 HTML
- 服务器返回纯 HTML 内容到浏览器
💡 初学者提示:
JSP 本质是 Servlet 的封装,最终仍会转换成 Servlet 执行,但开发者无需直接操作 Servlet 的response.getWriter()。
JSP 快速入门
环境搭建步骤
- 创建 Maven Web 项目
创建一个maven的 web 项目,项目结构如下:
pom.xml 文件内容如下:
<!-- pom.xml 核心配置 -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>jsp-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging> <!-- 打包方式为war -->
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- Servlet 依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Tomcat Maven 插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
</plugin>
</plugins>
</build>
</project>
导入 JSP 依赖
在 dependencies 标签中添加 JSP 的依赖:
<dependencies>
<!-- JSP 依赖 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope> <!-- 关键配置:使用服务器提供的JAR -->
</dependency>
</dependencies>
scope=provided 说明:Tomcat 服务器自带 JSP 实现,此配置避免依赖冲突,避免将该依赖打进到我们工程的war包中。
创建 JSP 页面
在项目的 webapp 下创建JSP页面
通过上面方式创建一个名为 hello.jsp 的页面,路径:src/main/webapp/hello.jsp。
编写 JSP 代码
在 JSP 页面中书写 HTML 标签和 Java 代码,如下
//页面指令:设置内容类型和编码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>JSP入门</title>
</head>
<body>
<%-- HTML内容 --%>
<h1>hello jsp</h1>
<%-- 脚本片段:嵌入Java代码 --%>
<%
// 服务器端控制台输出(非浏览器显示)
System.out.println("hello,jsp~");
%>
</body>
</html>
测试
启动服务器并在浏览器地址栏输入 http://localhost:8080/jsp-demo/hello.jsp,我们可以在页面上看到如下内容
同时也可以看到在 idea 的控制台看到输出的 hello,jsp~ 内容。
JSP 原理
核心结论:JSP 本质上就是一个 Servlet
执行流程
- 首次访问
浏览器第一次请求hello.jsp页面时,触发转换过程。 - JSP → Servlet 源码
Tomcat 将hello.jsp转换为名为hello_jsp.java的 Servlet 源文件。 - 源码 → 字节码
Tomcat 编译hello_jsp.java,生成可执行的字节码文件hello_jsp.class。 - 提供服务
Tomcat 加载并执行hello_jsp.class,向浏览器返回响应结果。
📌 验证路径
生成的文件位于:
项目所在磁盘目录/target/tomcat/work/Tomcat/localhost/应用名/org/apache/jsp/\
查看、分析生成的 Servlet (hello_jsp.java)
public final class hello_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {
// 核心方法:处理请求
public void _jspService(
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// 自动生成的响应处理逻辑
out.write("<html>");
out.write("<body>");
// ... 其他HTML/JSP代码被转换为write语句
}
}
关键继承链解析
flowchart TB
A[hello_jsp] --> B[HttpJspBase]
B --> C[HttpServlet]
C --> D[GenericServlet]
D --> E[Servlet]
hello_jsp:自动生成的 Servlet 类HttpJspBase:Tomcat 提供的基类(源码位置:tomcat源码/org/apache/jasper/runtime/HttpJspBase)- 核心继承关系(前者继承后者)
HttpJspBase→HttpServlet→GenericServlet→Servlet
✅ 证明 JSP 本质是 Servlet
核心方法:_jspService()
-
功能类比
等同于 Servlet 中的service()方法,是处理请求的核心入口。 -
自动生成内容
- JSP 中的 HTML 标签 → 自动转换为
out.write("<html>...") - JSP 中的 Java 代码 → 直接嵌入到方法体中执行
- 示例代码片段:
out.write("<h1>Hello JSP!</h1>"); // HTML部分 String name = "Jack"; // Java代码段 out.write("Welcome: " + name); // 混合输出
- JSP 中的 HTML 标签 → 自动转换为
JSP 脚本
核心作用:在 JSP 页面中直接嵌入 Java 代码,实现动态内容生成。
JSP 脚本分类及作用
- <%...%>(普通脚本)
- 作用:将
<%...%>中代码插入到_jspService()方法内部(相当于 Servlet 的service()方法)。 - 用途:定义局部变量、编写逻辑代码(如循环、条件判断)。
- 在 hello.jsp 中书写:
<% System.out.println("Hello JSP~"); // 控制台输出 int i = 3; // 局部变量 %> - 查看转换的 hello_jsp.java 文件:
public void _jspService(...) { ... System.out.println("Hello JSP~"); int i = 3; // 变量 i 定义在 _jspService() 方法内 ... }
- <%=...%>(表达式脚本)
- 作用:将
<%=...%>中代码插入到out.print()中作为参数。 - 用途:向页面输出动态内容(如变量、表达式结果,无需分号,
<%= "Hello"; %>错误!)。 - 在 hello.jsp 中书写:
<%="Hello"%> <!-- 输出字符串 --> <%=i%> <!-- 输出变量值 --> - 查看转换的 hello_jsp.java 文件:
out.print("Hello"); // 直接输出到响应 out.print(i); // 输出变量 i 的值
- <%!...%>(声明脚本)
- 作用:将
<%=...%>中代码插入到_jspService()方法之外(类成员位置)。 - 用途:定义成员变量或成员方法(属于 Servlet 类本身,仅在
<%! %>中可定义方法,普通脚本中定义方法会破坏 Java 语法结构。)。 - 在 hello.jsp 中书写:
<%! String name = "zhangsan"; // 成员变量 void show() { // 成员方法 System.out.println(name); } %> - 查看转换的 hello_jsp.java 文件:
public final class hello_jsp extends HttpJspBase { String name = "zhangsan"; // 类成员变量 void show() { // 类成员方法 System.out.println(name); } public void _jspService(...) { ... } // 服务方法 }
案例:使用JSP脚本展示品牌数据
需求分析
- 目标:使用JSP脚本动态展示品牌数据表格
- 数据来源:数据硬编码在JSP中(非数据库查询)
- 效果图:
实现与测试
1. 准备POJO类
- 创建包路径:
src/main/java/com/itheima/pojo(包命名:com.itheima.pojo;pojo:专门存放数据模型的包) - 将
Brand.java文件 放置到pojo目录中 - 确保第
Brand.java第一行有正确的包声明:package com.itheima.pojo;
Brand.java内代码:
package com.itheima.pojo;
public class Brand {
private Integer id; // 品牌ID
private String brandName; // 品牌名称
private String companyName; // 企业名称
private Integer ordered; // 排序字段
private String description; // 描述信息
private Integer status; // 状态:0-禁用,1-启用
// 无参构造器
public Brand() {}
// 带参构造器
public Brand(Integer id, String brandName, String companyName,
Integer ordered, String description, Integer status) {
this.id = id;
this.brandName = brandName;
this.companyName = companyName;
this.ordered = ordered;
this.description = description;
this.status = status;
}
// Getter和Setter方法
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getBrandName() { return brandName; }
public void setBrandName(String brandName) { this.brandName = brandName; }
public String getCompanyName() { return companyName; }
public void setCompanyName(String companyName) { this.companyName = companyName; }
public Integer getOrdered() { return ordered; }
public void setOrdered(Integer ordered) { this.ordered = ordered; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public Integer getStatus() { return status; }
public void setStatus(Integer status) { this.status = status; }
}
2. 创建JSP页面
- 在项目的 webapp 中创建 brand.jsp文件
- 将 brand.html 代码复制到 brand.jsp文件
代码如下:现在页面中的数据都是假数据。<%@ page contentType="text/html;charset=UTF-8" language="java" %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <input type="button" value="新增"><br> <hr> <table border="1" cellspacing="0" width="800"> <tr> <th>序号</th> <th>品牌名称</th> <th>企业名称</th> <th>排序</th> <th>品牌介绍</th> <th>状态</th> <th>操作</th> </tr> <tr align="center"> <td>1</td> <td>三只松鼠</td> <td>三只松鼠</td> <td>100</td> <td>三只松鼠,好吃不上火</td> <td>启用</td> <td><a href="#">修改</a> <a href="#">删除</a></td> </tr> <tr align="center"> <td>2</td> <td>优衣库</td> <td>优衣库</td> <td>10</td> <td>优衣库,服适人生</td> <td>禁用</td> <td><a href="#">修改</a> <a href="#">删除</a></td> </tr> <tr align="center"> <td>3</td> <td>小米</td> <td>小米科技有限公司</td> <td>1000</td> <td>为发烧而生</td> <td>启用</td> <td><a href="#">修改</a> <a href="#">删除</a></td> </tr> </table> </body> </html> - 在 JSP 文件的 第一行开始 添加JSP指令-导包:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="com.itheima.pojo.Brand" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
3. 在 <body> 内添加品牌数据(硬编码)
<%
// 创建品牌数据列表
List<Brand> brands = new ArrayList<>();
brands.add(new Brand(1, "三只松鼠", "三只松鼠", 100, "三只松鼠,好吃不上火", 1));
brands.add(new Brand(2, "优衣库", "优衣库", 200, "优衣库,服适人生", 0));
brands.add(new Brand(3, "小米", "小米科技有限公司", 1000, "为发烧而生", 1));
%>
4. 动态生成表格
for循环需要将 tr 标签包裹起来,这样才能实现循环的效果;
td 标签中的数据也需要是动态的。
最终代码
<%@ page import="com.itheima.pojo.Brand" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// 查询数据库
List<Brand> brands = new ArrayList<Brand>();
brands.add(new Brand(1,"三只松鼠","三只松鼠",100,"三只松鼠,好吃不上火",1));
brands.add(new Brand(2,"优衣库","优衣库",200,"优衣库,服适人生",0));
brands.add(new Brand(3,"小米","小米科技有限公司",1000,"为发烧而生",1));
%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input type="button" value="新增"><br>
<hr>
<table border="1" cellspacing="0" width="800">
<tr>
<th>序号</th>
<th>品牌名称</th>
<th>企业名称</th>
<th>排序</th>
<th>品牌介绍</th>
<th>状态</th>
<th>操作</th>
</tr>
// 开始JSP脚本:动态生成表格数据行
// 遍历brands集合,brands是之前准备好的品牌列表
<% for (int i = 0; i < brands.size(); i++) {%>
<%
Brand brand = brands.get(i); // 获取当前索引位置的Brand对象
%>
<tr align="center"> // 表格数据行,内容居中显示
<td><%= brand.getId() %></td> // 输出品牌ID
<td><%= brand.getBrandName() %></td> // 输出品牌名称
<td><%= brand.getCompanyName() %></td> // 输出企业名称
<td><%= brand.getOrdered() %></td> // 输出排序字段值
<td><%= brand.getDescription() %></td> // 输出品牌介绍描述
<td>
// 使用三元表达式转换状态值:1->"启用", 0->"禁用"
<%= brand.getStatus() == 1 ? "启用" : "禁用" %>
</td>
<td>
// 操作列:修改和删除链接(目前是占位符)
<a href="#">修改</a> // 修改链接
<a href="#">删除</a> // 删除链接
</td>
</tr>
<% } %> // 结束for循环
</table>
</body>
</html>
测试
在浏览器地址栏输入 http://localhost:8080/jsp-demo/brand.jsp ,页面展示效果如下
JSP 核心缺点与演进
一、JSP 的核心缺点
-
开发效率低:需同时编写 HTML 标签 和 Java 代码,页面复杂度高时开发繁琐。
-
可读性差: HTML 结构与 Java 逻辑混杂,后期维护时需花费大量时间梳理逻辑。
-
环境依赖复杂:运行依赖 JRE、JSP 容器(如 Tomcat)、JavaEE 环境,部署门槛高。
-
资源占用大:JSP 会自动生成
.java和.class文件,占用磁盘空间;运行时加载.class文件到内存,增加服务器内存消耗。 -
调试困难:错误发生时需定位到自动生成的
.java文件调试,流程繁琐。 -
团队协作障碍:前端人员不熟悉 Java,后端人员不精通 HTML/CSS;页面修改流程低效:前端改静态页面 → 后端转为 JSP → 重新整合逻辑。
二、技术演进:为何 JSP 被淘汰?
替代方案:现代开发采用 HTML + Ajax 实现前后端分离
核心优势:前端专注页面,后端提供数据接口,技术栈解耦。
| 阶段 | 技术方案 | 核心问题 |
|---|---|---|
| 1. 原始阶段 | Servlet 拼接 HTML | 逻辑与视图强耦合,拼接代码复杂 |
| 2. JSP 阶段 | JSP 内嵌 Java 代码 | HTML/Java 混杂,可维护性差 |
| 3. 优化阶段 | Servlet + JSP(EL/JSTL) | 用 EL 表达式/JSTL 减少 Java 代码 |
| 4. 现代阶段 | HTML + Ajax | 前后端分离,动态数据通过 API 交互 |
graph LR
A[Servlet 拼接 HTML] --> B[JSP 内嵌 Java]
B --> C[Servlet + JSP + EL/JSTL]
C --> D[HTML + Ajax 前后端分离]
三、为什么仍需学习 JSP?
- 历史项目维护
- 部分企业遗留系统仍使用 JSP,需具备维护能力。
- 理解技术演进
- 通过对比 JSP 的缺陷,深入体会前后端分离架构的优势。
EL 表达式
核心概念
- 定义:
EL(Expression Language)表达式语言,用于简化 JSP 页面中的 Java 代码编写。 - 核心作用:
从域对象中获取数据,并将数据展示在页面上。 - 语法格式:
${expression}
示例:${brands}表示获取域对象中键为brands的数据。
EL 的获取、存储与转发
-
pageContext
pageContext是 JSP 页面的域对象,其作用范围仅限于当前 JSP 页面。<%@ page contentType="text/html;charset=UTF-8" language="java" %> <% // 存储数据到 pageContext 域对象 pageContext.setAttribute("pageData", "这是 pageContext 域的数据"); %> -
request
request域对象的作用范围是一次请求(转发可共享),常用于在 Servlet 和 JSP 之间传递数据。@WebServlet("/requestDemo") public class RequestDemoServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 准备数据 List<String> requestData = new ArrayList<>(); requestData.add("请求数据 1"); requestData.add("请求数据 2"); // 存储到 request 域 request.setAttribute("requestData", requestData); // 转发到 JSP 页面 request.getRequestDispatcher("/requestDemo.jsp").forward(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } } -
session
session域对象的作用范围是一次会话,即从用户打开浏览器到关闭浏览器的整个过程。@WebServlet("/sessionDemo") public class SessionDemoServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取 session 对象 HttpSession session = request.getSession(); // 存储数据到 session 域 session.setAttribute("sessionData", "这是 session 域的数据"); // 转发到 JSP 页面 request.getRequestDispatcher("/sessionDemo.jsp").forward(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } } -
application
application域对象的作用范围是整个 Web 应用程序(服务器重启失效),所有用户共享该域对象的数据。@WebServlet("/applicationDemo") public class ApplicationDemoServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取 application 对象 javax.servlet.ServletContext application = getServletContext(); // 存储数据到 application 域 application.setAttribute("applicationData", "这是 application 域的数据"); // 转发到 JSP 页面 request.getRequestDispatcher("/applicationDemo.jsp").forward(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
转发的两种写法
中间变量:// 步骤1:获取转发器对象 javax.servlet.RequestDispatcher dispatcher = request.getRequestDispatcher("/forwardDemo.jsp"); // 步骤2:执行转发 dispatcher.forward(request, response);链式调用:
// 合并步骤1和步骤2 request.getRequestDispatcher("/forwardDemo.jsp").forward(request, response);
在 JSP 页面中获取数据
在 JSP 页面中,可以使用 EL 表达式从域对象中获取数据。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>转发示例</title>
</head>
<body>
${forwardData} <!-- 从域对象中获取键为 forwardData 的数据 -->
</body>
</html>
通过以上方法,可以将数据存储到不同的域对象,并将请求转发到 JSP 页面进行数据展示。需要注意的是,不同域对象的作用范围不同,应根据实际需求选择合适的域对象来存储数据。
基础代码演示
Servlet 代码(数据准备与转发)
定义servlet,在 servlet 中封装一些数据并存储到 request 域对象中并转发到 el-demo.jsp 页面。
@WebServlet("/demo1")
public class ServletDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 准备数据
List<Brand> brands = new ArrayList<>();
brands.add(new Brand(1, "三只松鼠", "三只松鼠", 100, "三只松鼠,好吃不上火", 1));
brands.add(new Brand(2, "优衣库", "优衣库", 200, "优衣库,服适人生", 0));
brands.add(new Brand(3, "小米", "小米科技有限公司", 1000, "为发烧而生", 1));
// 2. 存储到 request 域
request.setAttribute("brands", brands);
// 3. 转发到 el-demo.jsp(必须使用转发)
request.getRequestDispatcher("/el-demo.jsp").forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
关键点:
- 必须通过
request.setAttribute()存储数据到域对象。- 必须使用 转发(
forward),转发才能共享 request 域对象数据。
JSP 页面(使用 EL 获取数据)
在 el-demo.jsp 中通过 EL表达式 获取数据
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>EL 示例</title>
</head>
<body>
${brands} <!-- 从域对象中获取键为 brands 的数据 -->
</body>
</html>
访问效果:
浏览器访问 http://localhost:8080/jsp-demo/demo1,页面效果如下:\
四大域对象与查找机制
JavaWeb中有四大域对象,分别是:
- page:当前页面有效
- request:当前请求有效
- session:当前会话有效
- application:当前应用有效
域对象范围
这四个域对象的作用范围如下图所示:
EL 查找数据顺序
EL 表达式获取数据,会依次从这4个域中寻找,找到即停止。
1.page 域 → 2.request 域 → 3.session 域 → 4.application 域
JSTL 标签库
概述
JSTL(JSP Standard Tag Library)是标准标签库,用标签替代 JSP 中的 Java 代码逻辑。如下代码就是JSTL标签:
<c:if test="${flag == 1}">
男
</c:if>
<c:if test="${flag == 2}">
女
</c:if>
JSTL 提供了很多标签,如下图
常用标签分类:
| 标签库 | 前缀 | 功能 |
|---|---|---|
| 核心库 | c | 流程控制、迭代等 |
| 格式化 | fmt | 日期、数字格式化 |
| 函数库 | fn | 字符串处理函数 |
本章重点讲解核心库中的
<c:if>和<c:forEach>标签
使用流程
- 导入依赖
<!-- pom.xml -->
<!-- JSTL API 依赖 -->
<dependency>
<groupId>jstl</groupId> <!-- 定义JSTL规范接口 -->
<artifactId>jstl</artifactId> <!-- 标准标签库的实现 -->
<version>1.2</version> <!-- 使用1.2版本(稳定版) -->
</dependency>
<!-- Apache Standard Taglib 实现 -->
<dependency>
<groupId>taglibs</groupId> <!-- Apache提供的标签库实现组 -->
<artifactId>standard</artifactId> <!-- 符合JSP标准的标签库实现 -->
<version>1.1.2</version> <!-- 实现库版本(与JSTL 1.2兼容) -->
</dependency>
- JSP 页面引入核心库
<%--
引入 JSTL 核心标签库
- taglib 指令:声明要使用的标签库
- prefix="c":设置标签前缀(类似命名空间),后续标签使用 <c:xxx> 格式
- uri="http://java.sun.com/jsp/jstl/core":官方标准库的唯一标识符
--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
- 使用标签
<%--
使用 JSTL 标签的基本格式
- <c:标签名>:以声明的前缀开头(此处为 c)
- 属性="值":设置标签属性,值通常使用 EL 表达式 ${}
- 内容:标签包裹的 HTML/文本内容
示例:<c:if test="${condition}">显示内容</c:if>
--%>
<c:标签名 属性="值">内容</c:标签名>
if 标签
<c:if>:相当于 if 判断,- 属性:test,用于定义条件表达式(如
${status == 1})
基本语法:
<c:if test="布尔表达式">
<!-- 表达式为true时显示的内容 -->
</c:if>
注意:JSTL 没有直接的 else 标签,需多个
<c:if>配合实现
代码演示
Servlet 准备数据
定义一个 servlet ,在该 servlet 中向 request 域对象中添加 键是 status ,值为 1 的数据
// 定义Servlet,映射路径为/demo2
@WebServlet("/demo2")
public class ServletDemo2 extends HttpServlet {
// 处理GET请求
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
// 核心功能:向request域对象中添加数据,键:status,值:1(表示启用状态)
request.setAttribute("status", 1);
// 请求转发到jstl-if.jsp页面
// 注意:必须使用转发(forward)才能共享request域中的数据
request.getRequestDispatcher("/jstl-if.jsp").forward(request, response);
}
// 处理POST请求(直接调用doGet方法)
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
JSP 使用 <c:if>
定义 jstl-if.jsp 页面,在该页面使用 <c:if> 标签
<%-- 页面基础设置 --%>
<%@ page contentType="text/html;charset=UTF-8" %> <%-- 设置编码 --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%-- 引入JSTL核心库 --%>
<html>
<body>
<%--
状态显示逻辑
- 使用<c:if>根据status值显示不同内容
--%>
<c:if test="${status == 1}"> <%-- 当status等于1时 --%>
<span style="color:green">启用</span> <%-- 显示绿色"启用" --%>
</c:if>
<c:if test="${status == 0}"> <%-- 当status等于0时 --%>
<span style="color:red">禁用</span> <%-- 显示红色"禁用" --%>
</c:if>
<%--
复杂条件示例(扩展用法)
- 组合条件:user对象存在且年龄≥18
--%>
<c:if test="${not empty user && user.age >= 18}">
成年用户 <%-- 满足条件时显示 --%>
</c:if>
</body>
</html>
执行效果
当访问 http://localhost:8080/demo2 时:
- 页面显示 绿色"启用" 文本
- 若修改 Servlet 中
status=0,则显示 红色"禁用"
forEach 标签
<c:forEach>:相当于 for 循环。java中有增强for循环和普通for循环,JSTL 中的 <c:forEach> 也有两种用法。
varStatus 对象是 JSTL <c:forEach> 标签内部自动创建的循环状态对象,用于在遍历过程中获取循环的元信息(如索引、计数、首尾元素等)。
varStatus 对象属性:
| 属性 | 说明 | 示例 |
|---|---|---|
index | 当前索引(从0开始的整数) | ${status.index} |
count | 当前计数(从1开始的整数) | ${status.count} |
first | 是否是第一个元素(布尔值) | ${status.first} |
last | 是否是最后一个元素(布尔值) | ${status.last} |
current | 当前元素对象(对象) | ${status.current} |
集合遍历(增强for循环)
核心属性:
| 属性 | 作用 | 示例值 |
|---|---|---|
items | 要遍历的集合/数组对象 | ${brands} |
var | 循环中当前元素的变量名 | brand |
varStatus | 循环状态对象(可选) | status |
基本语法:
<c:forEach items="集合对象" var="临时变量名">
${临时变量名.属性} <!-- 访问元素属性 -->
</c:forEach>
代码演示:
如下代码,是从域对象中获取名为 brands 数据,该数据是一个集合;遍历遍历,并给该集合中的每一个元素起名为 brand,是 Brand对象。在循环里面使用 EL表达式获取每一个Brand对象的属性值
<%-- 页面基础设置 --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%-- 引入JSTL核心标签库 --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>品牌列表</title>
</head>
<body>
<%-- 操作按钮 --%>
<input type="button" value="新增"><br>
<hr>
<%-- 品牌数据表格 --%>
<table border="1" cellspacing="0" width="800">
<tr>
<th>序号</th>
<th>品牌名称</th>
<th>企业名称</th>
<th>排序</th>
<th>品牌介绍</th>
<th>状态</th>
<th>操作</th>
</tr>
<%--
JSTL forEach 标签:遍历品牌集合
- items="${brands}":从域对象获取名为brands的集合
- var="brand":为每个元素命名为brand(Brand对象)
- varStatus="status":获取循环状态对象
--%>
<c:forEach items="${brands}" var="brand" varStatus="status">
<tr align="center">
<%--
序号列:使用循环状态对象
${status.count}:显示当前循环计数(从1开始)
--%>
<td>${status.count}</td>
<td>${brand.brandName}</td>
<td>${brand.companyName}</td>
<td>${brand.ordered}</td>
<td>${brand.description}</td>
<%-- 状态列:根据品牌状态值显示不同文本 --%>
<c:if test="${brand.status == 1}">
<td>启用</td> <%-- 状态值为1时显示"启用" --%>
</c:if>
<c:if test="${brand.status != 1}">
<td>禁用</td> <%-- 状态值不为1时显示"禁用" --%>
</c:if>
<%-- 操作列:修改和删除链接(暂为占位符) --%>
<td>
<a href="#">修改</a>
<a href="#">删除</a>
</td>
</tr>
</c:forEach>
</table>
</body>
</html>
总结
- 数据获取与遍历:
- 从域对象中获取名为
brands的数据集合(通常由Servlet转发时设置) - 使用
<c:forEach>标签遍历该集合 - 为集合中的每个元素命名为
brand(Brand类型对象)
- 数据展示:
- 在循环体内使用 EL 表达式
${}访问每个 Brand 对象的属性 - 使用
${status.count}显示行序号(从1开始计数)
- 状态条件显示:使用
<c:if>标签根据brand.status的值显示不同状态 - 操作功能:
- 提供"新增"按钮(顶部)
- 每行包含"修改"和"删除"链接(目前为占位符)
数值迭代(普通for循环)
核心属性:
| 属性 | 作用 |
|---|---|
begin | 起始值(包含) |
end | 结束值(包含) |
step | 步长(默认1) |
var | 循环变量名 |
基本语法:
<c:forEach begin="起始值" end="结束值" step="步长" var="变量名">
${变量名} <!-- 显示当前值 -->
</c:forEach>
代码演示:
<!-- 输出:0 2 4 6 8 10 -->
<c:forEach begin="0" end="10" step="2" var="i">
${i}
</c:forEach>
<!-- 创建下拉菜单选项 -->
<select>
<c:forEach begin="18" end="60" step="1" var="age">
<option value="${age}">${age}岁</option>
</c:forEach>
</select>
选择原则:
数据来自集合/数组 → 用items
需要生成数字序列 → 用begin/end
MVC 模式与三层架构
MVC 模式
核心概念:
| 组件 | 职责 | 对应实现 |
|---|---|---|
| Model(模型) | 业务数据处理 | JavaBean/Service |
| View(视图) | 数据展示 | JSP/HTML |
| Controller(控制器) | 请求调度 | Servlet |
工作流程:
sequenceDiagram
autonumber
participant User as 用户/浏览器
participant Controller
participant Model
participant DataStore as 数据存储
participant View
User->>Controller: 发送请求
activate Controller
Controller->>Model: 调用业务处理
activate Model
Model->>DataStore: 读写数据
activate DataStore
DataStore-->>Model: 返回数据
deactivate DataStore
Model-->>Controller: 返回业务结果
deactivate Model
Controller->>View: 传递模型数据
activate View
View->>Controller: 请求数据绑定
Controller-->>View: 提供模型数据
View-->>User: 渲染最终响应
deactivate View
deactivate Controller
控制器(serlvlet)用来接收浏览器发送过来的请求,控制器调用模型(JavaBean)来获取数据,比如从数据库查询数据;控制器获取到数据后再交由视图(JSP)进行数据展示。
核心优势:
- 职责分离:
- 模型专注业务逻辑
- 视图专注展示效果
- 控制器专注请求调度
- 组件复用:同一模型可被多个视图复用
- 协作开发:前端(视图)与后端(模型)可并行开发
三层架构(系统架构)
三层架构是将项目分成了三个层面,分别是 表现层 、 业务逻辑层 、 数据访问层 。
-
表现层 (Web/Controller)
职责:接收用户请求参数;数据封装与校验;调用Service处理业务;响应结果(转发/重定向)
包命名规范:com.xxx.controller、com.xxx.web
技术实现:Servlet/JSP、Spring MVC、Struts、JSF -
业务逻辑层 (Service)
职责:对业务逻辑进行封装,组合多个数据访问层中DAO操作,形成复杂的业务逻辑功能。
包命名规范:com.xxx.service、com.xxx.biz
技术实现:Service类、Spring @Service、EJB、业务规则引擎
- 示例:注册业务
public void register(User user) { // 1. 调用DAO的 selectByName() 判断用户名是否存在 if(userDao.selectByName(user.getName()) == null) { // 2. 不存在则调用DAO的 insert() 插入数据 userDao.insert(user); } else { throw new RuntimeException("用户名已存在"); } }
- 数据访问层 (DAO/Mapper)
职责:数据库CRUD操作;数据库连接管理;SQL执行与优化;结果集映射
包命名规范:com.xxx.dao、com.xxx.mapper、com.xxx.repository
技术实现:JDBC、MyBatis、JPA/Hibernate、Spring Data
整个流程:
浏览器发送请求,表现层的Servlet接收请求并调用业务逻辑层的方法进行业务逻辑处理,而业务逻辑层方法调用数据访问层方法进行数据的操作,依次返回到serlvet,然后servlet将数据交由 JSP 进行展示。
MVC 与 三层架构的关系
- MVC 是表现层的实现方式,而三层架构是整个应用的分层框架。
- MVC 的 Controller 和 View 属于 三层架构的表现层,用于处理用户交互;
MVC 的 Model 仅作为表现层内部的视图数据载体(通常是 DTO) - 三层架构的业务逻辑层(Service)和数据访问层(DAO)是独立于MVC的、三层架构的核心组成部分,承担业务规则与数据持久化职责。
graph LR
A[三层架构] --> B[表现层]
A --> C[业务层]
A --> D[数据层]
B --> E[MVC模式]
E --> F[Controller]
E --> G[View]
E --> H[Model]
H[Model] -.->|仅包含| I[展示数据]
C[业务层] --> J[业务规则]
D[数据层] --> K[数据库操作]
组件对应:
| MVC 组件 | 在三层架构中的位置 | 实质内容 |
|---|---|---|
| Controller | 表现层 | 请求处理器 |
| View | 表现层 | 页面模板/JSP |
| Model | 表现层 | 传给View的DTO对象 |
| - | 业务层(独立) | Service类 |
| - | 数据层(独立) | DAO/Mapper类 |
技术演进:
- 表现层技术:Servlet → SpringMVC
- 业务层技术:JavaBean → Spring
- 数据层技术:JDBC → MyBatis
按照要求将不同层的代码写在不同的包下,每一层里功能职责做到单一,将来如果将表现层的技术换掉,而业务逻辑层和数据访问层的代码不需要发生变化。 选择原则:
- 简单项目:MVC模式足够
- 复杂系统:采用三层架构
- 现代开发:SpringBoot + MVC + 三层架构
案例
需求:完成品牌数据的增删改查操作
这个功能我们前面一直在做,而这个案例将本章节学习的所有的内容(包含 MVC模式 和 三层架构)进行应用,并将整个流程贯穿起来。
环境准备
环境准备工作,我们分以下步骤实现:
- 创建新的模块 brand_demo,引入坐标
- 创建三层架构的包结构
- 数据库表 tb_brand
- 实体类 Brand
- MyBatis 基础环境
- Mybatis-config.xml
- BrandMapper.xml
- BrandMapper接口
创建工程
创建模块:brand-demo
打包方式:war(Web应用)
JDK版本:8
引入坐标。分析出要用到哪些技术,就明确了需要哪些坐标:
- 需要操作数据库。mysql的驱动包
- 要使用mybatis框架。mybaits的依赖包
- web项目需要用到servlet和jsp。servlet和jsp的依赖包
- 需要使用 jstl 进行数据展示。jstl的依赖包
pom.xml 内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- 项目基本信息 -->
<modelVersion>4.0.0</modelVersion> <!-- Maven模型版本 -->
<groupId>org.example</groupId> <!-- 组织标识 -->
<artifactId>brand-demo</artifactId> <!-- 项目名称 -->
<version>1.0-SNAPSHOT</version> <!-- 版本号(快照版) -->
<packaging>war</packaging> <!-- 打包方式:Web应用 -->
<!-- JDK编译版本设置 -->
<properties>
<maven.compiler.source>8</maven.compiler.source> <!-- 源代码使用JDK8 -->
<maven.compiler.target>8</maven.compiler.target> <!-- 编译目标为JDK8 -->
</properties>
<!-- 项目依赖配置 -->
<dependencies>
<!-- MyBatis核心依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version> <!-- MyBatis框架核心库 -->
</dependency>
<!-- MySQL数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version> <!-- 用于连接MySQL数据库 -->
</dependency>
<!-- Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version> <!-- Servlet开发基础API -->
<scope>provided</scope> <!-- 由服务器提供,不打包进WAR -->
</dependency>
<!-- JSP API -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version> <!-- JSP开发基础API -->
<scope>provided</scope> <!-- 由服务器提供,不打包进WAR -->
</dependency>
<!-- JSTL核心库 -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version> <!-- JSP标准标签库核心功能 -->
</dependency>
<!-- JSTL标准库 -->
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version> <!-- JSTL的辅助库 -->
</dependency>
</dependencies>
<!-- 构建配置 -->
<build>
<plugins>
<!-- Tomcat Maven插件 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version> <!-- 用于在开发时运行Web应用 -->
<!--
配置示例(可选):
<configuration>
<port>8080</port> Tomcat端口
<path>/brand-demo</path> 应用上下文路径
</configuration>
-->
</plugin>
</plugins>
</build>
</project>
创建包
创建不同的包结构,用来存储不同的类。包结构如下
web 包的功能与 controller 完全相同,只是命名不同
util 包的作用:工具类集合,用于存放项目中重复使用的工具类
创建表
-- 删除tb_brand表
drop table if exists tb_brand;
-- 创建品牌表
CREATE TABLE tb_brand (
id INT PRIMARY KEY AUTO_INCREMENT, -- 主键ID
brand_name VARCHAR(20), -- 品牌名称
company_name VARCHAR(20), -- 企业名称
ordered INT, -- 排序字段
description VARCHAR(100), -- 描述信息
status INT -- 状态:0-禁用 1-启用
);
-- 插入示例数据
INSERT INTO tb_brand (brand_name, company_name, ordered, description, status)
VALUES
('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0),
('华为', '华为技术有限公司', 100, '华为致力于把数字世界带入每个人、每个家庭、每个组织,构建万物互联的智能世界', 1),
('小米', '小米科技有限公司', 50, 'are you ok', 1);
创建实体类
在 pojo 包下创建名为 Brand 的类。
package com.itheima.pojo;
public class Brand {
private Integer id; // 主键ID
private String brandName; // 品牌名称
private String companyName; // 企业名称
private Integer ordered; // 排序字段
private String description; // 描述信息
private Integer status; // 状态:0-禁用 1-启用
// 无参构造
public Brand() {}
// 带参构造
public Brand(Integer id, String brandName, String companyName,
Integer ordered, String description, Integer status) {
this.id = id;
this.brandName = brandName;
this.companyName = companyName;
this.ordered = ordered;
this.description = description;
this.status = status;
}
// Getter和Setter方法
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getBrandName() { return brandName; }
public void setBrandName(String brandName) { this.brandName = brandName; }
public String getCompanyName() { return companyName; }
public void setCompanyName(String companyName) { this.companyName = companyName; }
public Integer getOrdered() { return ordered; }
public void setOrdered(Integer ordered) { this.ordered = ordered; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public Integer getStatus() { return status; }
public void setStatus(Integer status) { this.status = status; }
// toString方法
@Override
public String toString() {
return "Brand{" +
"id=" + id +
", brandName='" + brandName + '\'' +
", companyName='" + companyName + '\'' +
", ordered=" + ordered +
", description='" + description + '\'' +
", status=" + status +
'}';
}
}
MyBatis环境配置
核心配置文件
定义核心配置文件 mybatis-config.xml ,并将该文件放置在 resources 下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置别名 -->
<typeAliases>
<!-- 扫描pojo包下的所有类,自动设置别名 -->
<package name="com.itheima.pojo"/>
</typeAliases>
<!-- 配置数据库环境 -->
<environments default="development">
<environment id="development">
<!-- 事务管理器 - JDBC -->
<transactionManager type="JDBC"/>
<!-- 数据源 - 连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<!-- 映射器配置 -->
<mappers>
<!-- 扫描mapper包下的所有接口 -->
<package name="com.itheima.mapper"/>
</mappers>
</configuration>
创建Mapper接口
package com.itheima.mapper;
import com.itheima.pojo.Brand;
import java.util.List;
public interface BrandMapper {
// 品牌操作接口方法
// (后续实现CRUD操作)
}
创建Mapper映射文件
在 resources 下创建放置映射配置文件的目录结构 com/itheima/mapper ,并在该目录下创建映射配置文件BrandMapper.xml。
路径:resources/com/itheima/mapper/BrandMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.BrandMapper">
<!-- 后续添加SQL映射 -->
</mapper>
查询所有
当我们点击 index.html 页面中的 查询所有 这个超链接时,就能查询到上图右半部分的数据。
对于上述的功能,整个流程如下:
- 用户点击 "查询所有" 超链接
- 请求发送到后端 Servlet
- Servlet 调用 MyBatis 查询数据库(BD)
- 将查询结果展示在页面上
BrandMapper 接口实现(数据访问层)
在 mapper 包下创建创建 BrandMapper 接口,在接口中定义 selectAll() 方法
package com.itheima.mapper;
import com.itheima.pojo.Brand;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface BrandMapper {
/**
* 查询所有品牌信息
* 使用注解方式定义SQL
*
* @return 品牌列表
*/
@Select("SELECT * FROM tb_brand") //查询所有字段
List<Brand> selectAll();
}
编写SqlSessionFactory工具类
在 com.itheima 包下创建 utils 包,并在该包下创建名为 SqlSessionFactoryUtils 工具类
package com.itheima.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class SqlSessionFactoryUtils {
// 静态变量保证全局唯一
private static SqlSessionFactory sqlSessionFactory;
// 静态代码块:类加载时自动执行
static {
try {
// 1. 加载MyBatis核心配置文件
String resource = "mybatis-config.xml";
// 2. 获取配置文件输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 3. 创建SqlSessionFactory对象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
// 4. 异常处理(实际项目应记录日志)
e.printStackTrace();
throw new RuntimeException("初始化SqlSessionFactory失败", e);
}
}
/**
* 获取SqlSessionFactory实例
*
* @return SqlSessionFactory对象
*/
public static SqlSessionFactory getSqlSessionFactory() {
return sqlSessionFactory;
}
}
BrandService 实现(业务逻辑层)
在 service 包下创建 BrandService 类
public class BrandService {
SqlSessionFactory factory = SqlSessionFactoryUtils.getSqlSessionFactory();
/**
* 查询所有
* @return
*/
public List<Brand> selectAll(){
//调用BrandMapper.selectAll()
//2. 获取SqlSession
SqlSession sqlSession = factory.openSession();
//3. 获取BrandMapper
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
//4. 调用方法
List<Brand> brands = mapper.selectAll();
sqlSession.close();
return brands;
}
}
Servlet 实现(表现层,Controller)
在 web 包下创建名为 SelectAllServlet 的 servlet,该 servlet 的逻辑如下:
- 调用
BrandService的selectAll()方法进行业务逻辑处理,并接收返回的结果 - 将上一步返回的结果存储到
request域对象中 - 跳转到
brand.jsp页面进行数据的展示
具体的代码如下:
@WebServlet("/selectAllServlet")
public class SelectAllServlet extends HttpServlet {
// 品牌服务实例(单例)
private BrandService service = new BrandService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 调用业务层BrandService完成查询
List<Brand> brands = service.selectAll();
//2. 将数据存入request域中
request.setAttribute("brands",brands);
//3. 转发到展示页面 brand.jsp
request.getRequestDispatcher("/brand.jsp").forward(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
brand.jsp页面实现(表现层,View)
将 brand.html 页面复制到项目的 webapp 目录下,并将该页面改成 brand.jsp 页面,而 brand.jsp 页面在表格中使用 JSTL 和 EL表达式 从request域对象中获取名为 brands 的集合数据并展示出来。页面内容如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> //JSTL核心库引入
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<hr>
<table border="1" cellspacing="0" width="80%">
<tr>
<th>序号</th>
<th>品牌名称</th>
<th>企业名称</th>
<th>排序</th>
<th>品牌介绍</th>
<th>状态</th>
<th>操作</th>
</tr>
//数据遍历,从request域获取品牌列表
<c:forEach items="${brands}" var="brand" varStatus="status">
<tr align="center">
<%--<td>${brand.id}</td>--%> //序号显示
<td>${status.count}</td>
<td>${brand.brandName}</td>
<td>${brand.companyName}</td>
<td>${brand.ordered}</td>
<td>${brand.description}</td>
<c:if test="${brand.status == 1}">
<td>启用</td>
</c:if>
<c:if test="${brand.status != 1}">
<td>禁用</td>
</c:if>
<td><a href="/brand-demo/selectByIdServlet?id=${brand.id}">修改</a> <a href="#">删除</a></td>
</tr>
</c:forEach>
</table>
</body>
</html>
测试
启动服务器,并在浏览器输入 http://localhost:8080/brand-demo/index.html,看到如下 查询所有 的超链接,点击该链接就可以查询出所有的品牌数据,如下图:
但是此时有一个问题:品牌名称和企业名称无显示。
原因:查询到的字段名和实体类对象的属性名没有一一对应。
解决办法:在映射配置文件中使用 resultMap 标签定义映射关系。
在 BrandMapper.xml 中定义结果映射:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.BrandMapper">
<resultMap id="brandResultMap" type="brand">
<!-- 特殊字段映射 -->
<result column="brand_name" property="brandName"></result>
<result column="company_name" property="companyName"></result>
</resultMap>
</mapper>
并且在 BrandMapper 接口中的 selectAll() 上使用 @ResuleMap 注解指定使用该映射
@Select("select * from tb_brand") // MyBatis 注解:指定要执行的SQL
@ResultMap("brandResultMap") // MyBatis 注解:指定结果映射规则
List<Brand> selectAll(); // 方法声明:返回Brand对象的列表
重启服务器,再次访问就能看到我们想要的数据了
添加
上图是做 添加 功能流程。点击 新增 按钮后,会先跳转到 addBrand.jsp 新增页面,在该页面输入要添加的数据,输入完毕后点击 提交 按钮,需要将数据提交到后端,而后端进行数据添加操作,并重新将所有的数据查询出来。整个流程如下:
接下来我们根据流程来实现功能:
BrandMapper 接口实现(数据访问层)
在 BrandMapper 接口,在接口中定义 add(Brand brand) 插入方法
@Insert("insert into tb_brand values(null,#{brandName},#{companyName},#{ordered},#{description},#{status})")
void add(Brand brand);
代码说明:
@Insert:MyBatis 插入操作注解#{}:MyBatis 参数占位符,自动从 Brand 对象获取属性值- 参数顺序:对应数据库表字段顺序
null:主键自增,由数据库自动生成
BrandService 实现(业务逻辑层)
在 BrandService 类中定义添加 品牌数据 方法 add(Brand brand)
public void add(Brand brand) {
SqlSession sqlSession = null;
try {
// 1. 获取SqlSession对象
sqlSession = factory.openSession();
// 2. 获取Mapper接口代理对象
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
// 3. 调用插入方法
mapper.add(brand);
// 4. 提交事务
sqlSession.commit();
} finally {
// 5. 资源释放
if(sqlSession != null) {
sqlSession.close();
}
}
}
改进brand.jsp页面
我们需要在该页面表格的上方添加 新增 按钮
<input type="button" value="新增" id="add"><br>
并给该按钮绑定单击事件,当点击了该按钮需要跳转到 brand.jsp 添加品牌数据的页面
<script>
document.getElementById("add").onclick = function (){ // 绑定按钮点击事件
location.href = "/brand-demo/addBrand.jsp"; // 跳转到添加页面
}
</script>
注意:该
script标签建议放在body结束标签前面。
编写addBrand.jsp页面(创建添加页面)
将 addBrand.html 页面拷贝到项目的 webapp 下,并改成 addBrand.jsp 动态页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加品牌</title>
</head>
<body>
<h3>添加品牌</h3>
<form action="/brand-demo/addServlet" method="post">
品牌名称:<input name="brandName"><br>
企业名称:<input name="companyName"><br>
排序:<input name="ordered"><br>
描述信息:<textarea rows="5" cols="20" name="description"></textarea><br>
状态:
<input type="radio" name="status" value="0">禁用
<input type="radio" name="status" value="1">启用<br>
<input type="submit" value="提交">
</form>
</body>
</html>
Servlet 实现(表现层,Controller)
在 web 包下创建 AddServlet 的 servlet,该 servlet 的逻辑如下:
- 设置处理post请求乱码的字符集
- 接收客户端提交的数据
- 将接收到的数据封装到
Brand对象中 - 调用 Service 层
BrandService的add()方法进行添加的业务逻辑处理 - 跳转到
selectAllServlet查询页面展示添加结果
具体的代码如下:
@WebServlet("/addServlet")
public class AddServlet extends HttpServlet {
// 实例化业务逻辑层对象
private BrandService service = new BrandService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//处理POST请求的乱码问题
request.setCharacterEncoding("utf-8");
//1. 接收表单提交的数据,封装为一个Brand对象
String brandName = request.getParameter("brandName");
String companyName = request.getParameter("companyName");
String ordered = request.getParameter("ordered");
String description = request.getParameter("description");
String status = request.getParameter("status");
//封装为一个Brand对象
Brand brand = new Brand();
brand.setBrandName(brandName);
brand.setCompanyName(companyName);
brand.setOrdered(Integer.parseInt(ordered));
brand.setDescription(description);
brand.setStatus(Integer.parseInt(status));
//2. 调用service 完成添加
service.add(brand);
//3. 转发到查询所有Servlet
request.getRequestDispatcher("/selectAllServlet").forward(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
测试
- 访问品牌列表页
http://localhost:8080/brand-demo/selectAllServlet - 点击
brand.jsp页面的新增按钮,会跳转到addBrand.jsp页面,可以进行填写品牌信息 - 点击
提交按钮,就能看到如下页面,里面就包含刚添加的数据
修改
点击每条数据后面的 编辑 按钮会跳转到修改页面,如下图:
在该修改页面我们可以看到将 编辑 按钮所在行的数据 回显 到表单,然后在表单中对该行数据进行修改,然后点击 提交 按钮将数据提交到后端,后端再将数据存储到数据库中。
从上面的例子可知,当用户点击数据列表中的编辑按钮时,系统需完成两个关键操作:
- 数据回显:用户编辑前,将当前行的数据展示在修改表单中
- 数据更新:用户编辑后,将修改后的数据提交到后端存储到数据库
数据回显
数据回显流程
- 用户触发
点击数据行的编辑按钮(携带该行数据的唯一标识ID) - 请求处理
前端向后端发送请求,格式示例:GET /update?id=<数据ID> - 数据查询
后端根据ID查询数据库,获取完整数据对象 - 数据传递
将查询结果存入请求域:request.setAttribute("item", dataObject); - 页面跳转
后端转发请求到修改页面(如:update.jsp) - 数据展示
修改页面从域对象提取数据并填充表单:<input value="${item.name}">
整体流程如下:
BrandMapper 接口实现(数据访问层)
在 BrandMapper 接口,在接口中定义 selectById(int id) 方法,根据ID查询品牌的数据库操作
public interface BrandMapper {
/**
* @param id 品牌唯一标识
* @return 品牌对象(包含所有字段)
*/
@Select("SELECT * FROM tb_brand WHERE id = #{id}")
@ResultMap("brandResultMap") // 复用结果映射配置
Brand selectById(int id);
}
BrandService 实现(业务逻辑层)
在 BrandService 类中定义根据id查询数据方法 selectById(int id),调用Mapper执行查询
public class BrandService {
// 获取SqlSessionFactory(通常通过构造器或静态块初始化)
private final SqlSessionFactory factory = SqlSessionFactoryUtils.getSqlSessionFactory();
/**
* 根据ID查询品牌
* @param id 品牌ID
* @return 品牌对象
*/
public Brand selectById(int id){
//调用BrandMapper.selectAll()
//2. 获取SqlSession
SqlSession sqlSession = factory.openSession();
//3. 获取Mapper代理对象
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
//4. 调用方法,执行查询并返回结果
Brand brand = mapper.selectById(id);
sqlSession.close(); //释放资源
return brand;
}
}
Servlet 实现(表现层,Controller)
在 web 包下创建 SelectByIdServlet 的 servlet,该 servlet 的逻辑如下:
- 获取请求数据
id - 调用
BrandService的selectById()方法进行数据查询的业务逻辑 - 将查询到的数据存储到 request 域对象中
- 跳转到
update.jsp页面进行数据真实
具体代码如下:
@WebServlet("/selectByIdServlet")
public class SelectByIdServlet extends HttpServlet {
private BrandService service = new BrandService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 获取请求参数(品牌ID)
String id = request.getParameter("id");
//2. 调用service查询
Brand brand = service.selectById(Integer.parseInt(id));
//3. 存储数据到请求域request中
request.setAttribute("brand",brand);
//4. 转发到更新页面update.jsp
request.getRequestDispatcher("/update.jsp").forward(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
update.jsp 页面实现(表现层,View)
拷贝 前面的addBrand.jsp 页面,改名为 update.jsp 并做出以下修改:
-
title标签内容改为修改品牌,让用户明确当前页面的功能 -
form标签的action属性值改为/brand-demo/updateServlet,将表单提交的数据将发送到该 Servlet 进行处理 -
为
input标签设置value属性,使用 EL 表达式获取brand对象的对应属性值,实现数据回显品牌名称:<input name="brandName" value="${brand.brandName}"><br> 企业名称:<input name="companyName" value="${brand.companyName}"><br> 排序:<input name="ordered" value="${brand.ordered}"><br> -
在
textarea标签体中使用 EL 表达式获取brand对象的description属性值,实现描述信息的回显描述信息:<textarea rows="5" cols="20" name="description">${brand.description} </textarea><br> -
使用 JSTL 的
c:if标签判断brand.status的值是 1 还是 0,根据判断结果为相应的单选框添加checked属性,表示该选项是否被选中状态: <c:if test="${brand.status == 0}"> <input type="radio" name="status" value="0" checked>禁用 <input type="radio" name="status" value="1">启用<br> </c:if> <c:if test="${brand.status == 1}"> <input type="radio" name="status" value="0" >禁用 <input type="radio" name="status" value="1" checked>启用<br> </c:if>
综上,update.jsp 代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>修改品牌</title>
</head>
<body>
<h3>修改品牌</h3>
<form action="/brand-demo/updateServlet" method="post"> <%-- 提交表单到更新Servlet --%>
品牌名称:<input name="brandName" value="${brand.brandName}"><br> <%-- 品牌名称输入框,预填充数据 --%>
企业名称:<input name="companyName" value="${brand.companyName}"><br>
排序:<input name="ordered" value="${brand.ordered}"><br>
描述信息:<textarea rows="5" cols="20" name="description">${brand.description} </textarea><br>
状态:
<c:if test="${brand.status == 0}"> <%-- 当状态为0(禁用)时 --%>
<input type="radio" name="status" value="0" checked>禁用 <%-- 默认选中禁用 --%>
<input type="radio" name="status" value="1">启用<br>
</c:if>
<c:if test="${brand.status == 1}"> <%-- 当状态为1(启用)时 --%>
<input type="radio" name="status" value="0" >禁用
<input type="radio" name="status" value="1" checked>启用<br> <%-- 默认选中启用 --%>
</c:if>
<input type="submit" value="提交">
</form>
</body>
</html>
数据更新
完成数据回显后,进入修改数据环节,下图是修改数据的效果:
后端处理流程:
- 数据修改:后端程序接收到提交的数据后,对表中的相应数据进行修改操作。
- 数据查询:完成数据修改后,后端重新进行数据查询操作,更新数据状态。
BrandMapper 接口实现(数据访问层)
在 BrandMapper 接口,在接口中定义 update(Brand brand) 方法,根据ID修改品牌的数据库操作
/**
* 修改品牌数据
* @param brand 包含更新字段和条件ID的品牌对象
*/
@Update("update tb_brand set brand_name = #{brandName}, company_name = #{companyName}, ordered = #{ordered}, description = #{description}, status = #{status} where id = #{id}")
void update(Brand brand);
BrandService 实现(业务逻辑层)
在 BrandService 类中定义 根据ID查询数据方法 update(Brand brand)
/**
* 修改品牌信息
* @param brand 包含更新数据的品牌对象
*/
public void update(Brand brand) {
// 1. 获取SqlSession对象(从SqlSessionFactory工厂获取新会话)
SqlSession sqlSession = factory.openSession();
// 2. 获取BrandMapper接口的代理实现(MyBatis动态代理机制)
BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
// 3. 调用Mapper接口的update方法执行SQL更新
mapper.update(brand);
// 4. 提交事务(确保数据库更改生效)
sqlSession.commit();
// 5. 释放资源(关闭数据库连接,归还连接池)
sqlSession.close();
}
Servlet 实现(表现层,Controller)
在 web 包下创建 AddServlet 的 servlet,该 servlet 的逻辑如下:
- 设置处理post请求乱码的字符集
- 接收客户端提交的数据
- 将接收到的数据封装到
Brand对象中 - 调用
BrandService的update()方法进行添加的业务逻辑处理 - 跳转到
selectAllServlet资源重新查询数据
具体的代码如下:
@WebServlet("/updateServlet") // 注册Servlet访问路径
public class UpdateServlet extends HttpServlet {
private BrandService service = new BrandService(); // 实例化业务逻辑层
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 处理POST请求的乱码问题(设置请求体字符编码)
request.setCharacterEncoding("utf-8");
// 1. 接收表单提交的数据,封装为一个Brand对象
String id = request.getParameter("id"); // 获取隐藏的主键ID
String brandName = request.getParameter("brandName");
String companyName = request.getParameter("companyName");
String ordered = request.getParameter("ordered");
String description = request.getParameter("description");
String status = request.getParameter("status");
// 封装为Brand对象
Brand brand = new Brand();
brand.setId(Integer.parseInt(id)); // 字符串ID转为整数
brand.setBrandName(brandName);
brand.setCompanyName(companyName);
brand.setOrdered(Integer.parseInt(ordered)); // 字符串排序值转为整数
brand.setDescription(description);
brand.setStatus(Integer.parseInt(status)); // 字符串状态值转为整数
// 2. 调用service完成修改操作
service.update(brand);
// 3. 转发到查询所有Servlet(重新加载数据)
request.getRequestDispatcher("/selectAllServlet").forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response); // POST请求统一由doGet处理
}
}
update.jsp页面实现(表现层,View)
关键问题解决:主键ID传递:
后台修改数据需要根据ID进行修改,因此update.jsp 页面提交数据时需要携带ID数据,而且表单页面不应展示ID给用户。
解决方案:在JSP表单中添加 隐藏域 传递ID
<!-- 在表单开始位置添加 -->
<input type="hidden" name="id" value="${brand.id}">
隐藏域特点:对用户不可见,不干扰界面;通过request.getParameter("id")可获取值。
update.jsp 页面的最终代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>修改品牌</title>
</head>
<body>
<h3>修改品牌</h3>
<form action="/brand-demo/updateServlet" method="post">
<%--隐藏域,提交id--%>
<input type="hidden" name="id" value="${brand.id}">
品牌名称:<input name="brandName" value="${brand.brandName}"><br>
企业名称:<input name="companyName" value="${brand.companyName}"><br>
排序:<input name="ordered" value="${brand.ordered}"><br>
描述信息:<textarea rows="5" cols="20" name="description">${brand.description} </textarea><br>
状态:
<c:if test="${brand.status == 0}">
<input type="radio" name="status" value="0" checked>禁用
<input type="radio" name="status" value="1">启用<br>
</c:if>
<c:if test="${brand.status == 1}">
<input type="radio" name="status" value="0" >禁用
<input type="radio" name="status" value="1" checked>启用<br>
</c:if>
<input type="submit" value="提交">
</form>
</body>
</html>