JSP

50 阅读18分钟

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 的核心作用

  • 核心目标:简化动态网页开发
  • 关键能力
    1. 定义静态内容:原生支持 HTML/CSS/JavaScript
    2. 嵌入动态内容:通过 <% ... %> 标签插入 Java 代码
  • 核心价值:避免在 Servlet 中手动拼接 HTML 标签

JSP 工作流程

  1. 用户访问 .jsp 页面(如 login.jsp
  2. 服务器将 JSP 翻译为 Servlet(第一次访问时)
  3. Servlet 编译执行,动态生成 HTML
  4. 服务器返回纯 HTML 内容到浏览器

💡 初学者提示
JSP 本质是 Servlet 的封装,最终仍会转换成 Servlet 执行,但开发者无需直接操作 Servlet 的 response.getWriter()

JSP 快速入门

环境搭建步骤

  1. 创建 Maven Web 项目

创建一个maven的 web 项目,项目结构如下:

image.png

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页面

image.png

通过上面方式创建一个名为 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,我们可以在页面上看到如下内容

image.png

同时也可以看到在 idea 的控制台看到输出的 hello,jsp~ 内容。

JSP 原理

核心结论:JSP 本质上就是一个 Servlet

执行流程

image.png
  1. 首次访问
    浏览器第一次请求 hello.jsp 页面时,触发转换过程。
  2. JSP → Servlet 源码
    Tomcat 将 hello.jsp 转换为名为 hello_jsp.java 的 Servlet 源文件。
  3. 源码 → 字节码
    Tomcat 编译 hello_jsp.java,生成可执行的字节码文件 hello_jsp.class
  4. 提供服务
    Tomcat 加载并执行 hello_jsp.class,向浏览器返回响应结果。

📌 验证路径
生成的文件位于:
项目所在磁盘目录/target/tomcat/work/Tomcat/localhost/应用名/org/apache/jsp/\ image.png

查看、分析生成的 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
  • 核心继承关系(前者继承后者)
    HttpJspBaseHttpServletGenericServletServlet
    ✅ 证明 JSP 本质是 Servlet

核心方法:_jspService()

  1. 功能类比
    等同于 Servlet 中的 service() 方法,是处理请求的核心入口。

  2. 自动生成内容

    • JSP 中的 HTML 标签 → 自动转换为 out.write("<html>...")
    • JSP 中的 Java 代码 → 直接嵌入到方法体中执行
    • 示例代码片段:
      out.write("<h1>Hello JSP!</h1>");  // HTML部分
      String name = "Jack";              // Java代码段
      out.write("Welcome: " + name);     // 混合输出
      

JSP 脚本

核心作用:在 JSP 页面中直接嵌入 Java 代码,实现动态内容生成。

JSP 脚本分类及作用

  1. <%...%>(普通脚本)
  • 作用:将<%...%>中代码插入到 _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() 方法内
      ...
    }
    
  1. <%=...%>(表达式脚本)
  • 作用:将<%=...%>中代码插入到 out.print() 中作为参数。
  • 用途:向页面输出动态内容(如变量、表达式结果,无需分号<%= "Hello"; %>错误!)。
  • 在 hello.jsp 中书写
    <%="Hello"%>  <!-- 输出字符串 -->
    <%=i%>        <!-- 输出变量值 -->
    
  • 查看转换的 hello_jsp.java 文件
    out.print("Hello");  // 直接输出到响应
    out.print(i);        // 输出变量 i 的值
    
  1. <%!...%>(声明脚本)
  • 作用:将<%=...%>中代码插入到 _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中(非数据库查询)
  • 效果图image.png

实现与测试

1. 准备POJO类

  1. 创建包路径:src/main/java/com/itheima/pojo(包命名:com.itheima.pojopojo:专门存放数据模型的包)
  2. 将 Brand.java文件 放置到 pojo 目录中
  3. 确保第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页面

  1. 在项目的 webapp 中创建 brand.jsp文件
  2. 将 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>
    
    现在页面中的数据都是假数据。
  3. 在 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 ,页面展示效果如下 image.png

JSP 核心缺点与演进

一、JSP 的核心缺点

  1. 开发效率低:需同时编写 HTML 标签 和 Java 代码,页面复杂度高时开发繁琐。

  2. 可读性差: HTML 结构与 Java 逻辑混杂,后期维护时需花费大量时间梳理逻辑。

  3. 环境依赖复杂:运行依赖 JRE、JSP 容器(如 Tomcat)、JavaEE 环境,部署门槛高。

  4. 资源占用大:JSP 会自动生成 .java.class 文件,占用磁盘空间;运行时加载 .class 文件到内存,增加服务器内存消耗。

  5. 调试困难:错误发生时需定位到自动生成的 .java 文件调试,流程繁琐。

  6. 团队协作障碍:前端人员不熟悉 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?

  1. 历史项目维护
    • 部分企业遗留系统仍使用 JSP,需具备维护能力。
  2. 理解技术演进
    • 通过对比 JSP 的缺陷,深入体会前后端分离架构的优势。

EL 表达式

核心概念

  • 定义
    EL(Expression Language)表达式语言,用于简化 JSP 页面中的 Java 代码编写。
  • 核心作用
    从域对象中获取数据,并将数据展示在页面上。
  • 语法格式
    ${expression}
    示例:${brands} 表示获取域对象中键为 brands 的数据。

EL 的获取、存储与转发

  1. pageContext
    pageContextJSP 页面的域对象,其作用范围仅限于当前 JSP 页面。

    <%@ page contentType="text/html;charset=UTF-8" language="java" %> 
    <% 
        // 存储数据到 pageContext 域对象 
        pageContext.setAttribute("pageData", "这是 pageContext 域的数据"); 
    %> 
    
  2. 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); 
        } 
    } 
    
  3. 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); 
        } 
    } 
    
  4. 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,页面效果如下:\ image.png

四大域对象与查找机制

JavaWeb中有四大域对象,分别是:

  • page:当前页面有效
  • request:当前请求有效
  • session:当前会话有效
  • application:当前应用有效

域对象范围
这四个域对象的作用范围如下图所示:

image.png

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 提供了很多标签,如下图

image.png

常用标签分类:

标签库前缀功能
核心库c流程控制、迭代等
格式化fmt日期、数字格式化
函数库fn字符串处理函数

本章重点讲解核心库中的 <c:if><c:forEach> 标签

使用流程

  1. 导入依赖
<!-- 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>
  1. 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" %>
  1. 使用标签
<%--
  使用 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>

总结

  1. 数据获取与遍历
  • 从域对象中获取名为 brands 的数据集合(通常由Servlet转发时设置)
  • 使用 <c:forEach> 标签遍历该集合
  • 为集合中的每个元素命名为 brand(Brand类型对象)
  1. 数据展示
  • 在循环体内使用 EL 表达式 ${} 访问每个 Brand 对象的属性
  • 使用 ${status.count} 显示行序号(从1开始计数)
  1. 状态条件显示:使用 <c:if> 标签根据 brand.status 的值显示不同状态
  2. 操作功能
    • 提供"新增"按钮(顶部)
    • 每行包含"修改"和"删除"链接(目前为占位符)

数值迭代(普通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
image.png

工作流程

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)进行数据展示。

核心优势

  1. 职责分离
    • 模型专注业务逻辑
    • 视图专注展示效果
    • 控制器专注请求调度
  2. 组件复用:同一模型可被多个视图复用
  3. 协作开发:前端(视图)与后端(模型)可并行开发

三层架构(系统架构)

三层架构是将项目分成了三个层面,分别是 表现层 、 业务逻辑层 、 数据访问层 。 image.png

  1. 表现层 (Web/Controller)
    职责:接收用户请求参数;数据封装与校验;调用Service处理业务;响应结果(转发/重定向)
    包命名规范:com.xxx.controllercom.xxx.web
    技术实现:Servlet/JSP、Spring MVC、Struts、JSF

  2. 业务逻辑层 (Service)
    职责:对业务逻辑进行封装,组合多个数据访问层中DAO操作,形成复杂的业务逻辑功能。
    包命名规范:com.xxx.servicecom.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("用户名已存在");
        }
    }
    
  1. 数据访问层 (DAO/Mapper)
    职责:数据库CRUD操作;数据库连接管理;SQL执行与优化;结果集映射
    包命名规范:com.xxx.daocom.xxx.mappercom.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 + 三层架构

案例

需求:完成品牌数据的增删改查操作

image.png

这个功能我们前面一直在做,而这个案例将本章节学习的所有的内容(包含 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>

创建包

创建不同的包结构,用来存储不同的类。包结构如下

image.png

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&amp;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>

查询所有

image.png

当我们点击 index.html 页面中的 查询所有 这个超链接时,就能查询到上图右半部分的数据。

对于上述的功能,整个流程如下:

  1. 用户点击 "查询所有" 超链接
  2. 请求发送到后端 Servlet
  3. Servlet 调用 MyBatis 查询数据库(BD)
  4. 将查询结果展示在页面上
image.png

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 包下创建名为 SelectAllServletservlet,该 servlet 的逻辑如下:

  1. 调用 BrandServiceselectAll() 方法进行业务逻辑处理,并接收返回的结果
  2. 将上一步返回的结果存储到 request 域对象中
  3. 跳转到 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 页面在表格中使用 JSTLEL表达式 从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,看到如下 查询所有 的超链接,点击该链接就可以查询出所有的品牌数据,如下图:

image.png

但是此时有一个问题:品牌名称和企业名称无显示。
原因:查询到的字段名和实体类对象的属性名没有一一对应。
解决办法:在映射配置文件中使用 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对象的列表

重启服务器,再次访问就能看到我们想要的数据了 image.png

添加

image.png

上图是做 添加 功能流程。点击 新增 按钮后,会先跳转到 addBrand.jsp 新增页面,在该页面输入要添加的数据,输入完毕后点击 提交 按钮,需要将数据提交到后端,而后端进行数据添加操作,并重新将所有的数据查询出来。整个流程如下:

image.png

接下来我们根据流程来实现功能:

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 包下创建 AddServletservlet,该 servlet 的逻辑如下:

  1. 设置处理post请求乱码的字符集
  2. 接收客户端提交的数据
  3. 将接收到的数据封装到 Brand 对象中
  4. 调用 Service 层BrandServiceadd() 方法进行添加的业务逻辑处理
  5. 跳转到 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);
    }
}

测试

  1. 访问品牌列表页 http://localhost:8080/brand-demo/selectAllServlet
  2. 点击 brand.jsp 页面的 新增 按钮,会跳转到 addBrand.jsp页面,可以进行填写品牌信息 image.png
  3. 点击 提交 按钮,就能看到如下页面,里面就包含刚添加的数据 image.png

修改

image.png

点击每条数据后面的 编辑 按钮会跳转到修改页面,如下图: image.png

在该修改页面我们可以看到将 编辑 按钮所在行的数据 回显 到表单,然后在表单中对该行数据进行修改,然后点击 提交 按钮将数据提交到后端,后端再将数据存储到数据库中。

从上面的例子可知,当用户点击数据列表中的编辑按钮时,系统需完成两个关键操作:

  1. 数据回显:用户编辑前,将当前行的数据展示在修改表单中
  2. 数据更新:用户编辑后,将修改后的数据提交到后端存储到数据库

数据回显

image.png

数据回显流程

  1. 用户触发
    点击数据行的编辑按钮(携带该行数据的唯一标识ID)
  2. 请求处理
    前端向后端发送请求,格式示例:GET /update?id=<数据ID>
  3. 数据查询
    后端根据ID查询数据库,获取完整数据对象
  4. 数据传递
    将查询结果存入请求域:
    request.setAttribute("item", dataObject);
    
  5. 页面跳转
    后端转发请求到修改页面(如:update.jsp)
  6. 数据展示
    修改页面从域对象提取数据并填充表单:
    <input value="${item.name}">
    

整体流程如下: image.png

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 包下创建 SelectByIdServletservlet,该 servlet 的逻辑如下:

  1. 获取请求数据 id
  2. 调用 BrandServiceselectById() 方法进行数据查询的业务逻辑
  3. 将查询到的数据存储到 request 域对象中
  4. 跳转到 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>

数据更新

完成数据回显后,进入修改数据环节,下图是修改数据的效果: image.png

后端处理流程:

  1. 数据修改:后端程序接收到提交的数据后,对表中的相应数据进行修改操作。
  2. 数据查询:完成数据修改后,后端重新进行数据查询操作,更新数据状态。
image.png
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 包下创建 AddServletservlet,该 servlet 的逻辑如下:

  1. 设置处理post请求乱码的字符集
  2. 接收客户端提交的数据
  3. 将接收到的数据封装到 Brand 对象中
  4. 调用 BrandServiceupdate() 方法进行添加的业务逻辑处理
  5. 跳转到 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>