AI 摘要

本文记录基于 Spring Boot + JSP 构建车辆质量投诉管理系统的全过程:先设计 MySQL 品牌与投诉表,再用 Spring Initializr 搭建项目,集成 MyBatis、Redis、JSP;借助插件生成实体与 Mapper,完成多表联查、条件筛选、分页列表展示;最后引入 Redis 缓存品牌数据,实现序列化并验证缓存命中,显著提升查询性能。

概述

本项目选取车辆质量投诉管理系统中的车辆质量投诉的基础功能进行编码实现,作为阶段性的练习案例进行学习。

项目主要功能:资产列表查询、资产列表分页查询、资产列表模糊查询、新增资产、修改资产、删除资产。

技术选型:Spring Boot + JSP + MySQL + MyBatis + Redis + Ajax。

数据库设计

brand(车辆品牌表):

字段名
类型备注
brandIdint(11)品牌 id,主键自增
brandNamevarchar(255)品牌名称

vehiclecomplaints(车辆质量投诉信息表):

字段名类型备注
idint(11)投诉 id,主键自增
problemvarchar(255)投诉问题
brandIdint(11)关联车辆品牌
statusint(11)投诉状态,0 信息审核 / 1 厂家受理
carTypevarchar(255)投诉车系
complainsTimedatatime投诉时间

开发笔记

打开数据库连接软件,连接到本地 MySQL 数据库,并新建数据库。

在数据库中,根据项目要求,创建车辆品牌表、车辆质量投诉信息表,并分别为车辆品牌表、车辆质量投诉信息表初始化几条测试数据。

打开 IDEA,新建 Spring Boot 项目。

选择 Spring Boot 2.6.13,并选择项目依赖 Lombok、Spring Web、MyBatis Framework、MySQL Driver。

项目创建完成后,修改 Maven 项目配置文件(pom.xml),导入 JSP 相关依赖。Maven 项目配置文件(pom.xml)修改后,需要刷新以加载相关依赖。

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
</dependency>

修改 Spring Boot 项目配置文件(classpath:/application.properties),编写 MySQL 数据库配置、MyBatis 配置、JSP 视图解析器配置。

# 服务器端口号
server.port=8080
# SQL映射文件路径,在resources文件夹下的mappers文件夹里
mybatis.mapper-locations=classpath:mappers/*xml
# 配置自动映射级别(多表联查时自动映射)
mybatis.configuration.auto-mapping-behavior=FULL

# MySQL 数据库连接信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/clts_project?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456..

# 配置视图解析器
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

在项目中创建基本的包结构和文件夹结构。

在 IDEA 中连接到本地 MySQL 数据库。

借助 MyBatisCodeHelperPro 插件,快捷为车辆品牌表和车辆质量投诉信息表生成实体类、数据访问层、业务逻辑层的代码片段。

代码片段生成完成,注意检查实体类中相关的数据类型,以及代码完整性。

由于项目需要使用 jQuery,故提前将 jQuery 复制到静态资源文件夹中。

前置工作完成,正式开始编写业务代码。

由于车辆质量投诉信息和车辆品牌是主外键关联的关系,故修改车辆质量投诉信息实体类(package.entity.Vehiclecomplains),加入对车辆品牌实体类的关联。

由于车辆质量投诉信息和车辆品牌是主外键关联的关系,故修改车辆质量投诉信息实体类(package.entity.Vehiclecomplains),加入对车辆品牌实体类的关联。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Vehiclecomplaints {
    // ...

    private Brand brand;

}

修改车辆质量投诉信息数据访问接口(package.dao.VehiclecomplaintsMapper),编写查询车辆质量投诉信息列表的方法。

@Mapper
public interface VehiclecomplaintsMapper {
    // ...

    List<Vehiclecomplaints> getList();

}

修改车辆质量投诉信息数据访问接口的 SQL 映射文件(classpath:/mappers/VehiclecomplaintsMapper.xml),为查询车辆质量投诉信息列表的方法绑定多表联查的 SQL 语句。

<?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="cn.duozai.clts.dao.VehiclecomplaintsMapper">
    <!-- ... -->

    <!--
        多表联查使用resultMap进行手动映射
        <association>:一对一关系映射

        字段名与实体类属性名不一致时,需要使用<result>手动映射
        字段名与实体类属性名一致时,自动映射级别为FULL,不需要手动映射
    -->
    <resultMap id="jointResultMap" type="cn.duozai.clts.entity.Vehiclecomplaints">
        <association property="brand" javaType="cn.duozai.clts.entity.Brand">
        </association>
    </resultMap>

    <!--
        定义多表联查的SQL语句,使用resultMap指定映射规则
        两表无重复字段,故不需要对字段取别名
    -->
    <select id="getList" resultMap="jointResultMap">
        SELECT * FROM vehiclecomplaints AS vc, brand AS b
        WHERE vc.brandId = b.brandId
    </select>
</mapper>

修改车辆质量投诉信息业务逻辑接口(package.service.VehiclecomplaintsService)和车辆质量投诉信息业务逻辑接口实现类(package.service.VehiclecomplaintsServiceImpl),编写查询车辆质量投诉信息列表的方法,业务逻辑层调用数据访问层查询数据。

@Service
public class VehiclecomplaintsServiceImpl implements VehiclecomplaintsService{
    // ...

    @Override
    public List<Vehiclecomplaints> getList() {
        return vehiclecomplaintsMapper.getList();
    }

}

新建车辆质量投诉信息控制器(package.controller.IndexController),编写查询车辆质量投诉信息列表的视图方法,控制器调用业务逻辑层查询数据。

@Controller
public class IndexController {

    @Resource
    VehiclecomplaintsService vehiclecomplaintsService;

    @GetMapping("/")
    public String index(Model model) {
        // 调用Service层查询数据,并将查询到的车辆质量投诉信息列表保存到模型对象中
        model.addAttribute("tsList", vehiclecomplaintsService.getList());

        // 返回视图页面
        return "index";
    }

}

新建车辆质量投诉信息列表的视图页面(webapp/WEB-INF/jsp/index.jsp),渲染车辆质量投诉信息列表。

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title></title>
    <style>
        table, th, tr, td {
            border: black solid 1px;
        }

        tr:nth-child(even) {
            background: beige;
        }
    </style>
</head>
<body>
<table>
    <thead>
    <tr>
        <th>投诉编号</th>
        <th>投诉品牌</th>
        <th>投诉问题</th>
        <th>投诉车系</th>
        <th>投诉时间</th>
        <th>投诉状态</th>
        <th>操作</th>
    </tr>
    </thead>
    <tbody>
    <!--
        c:forEach:遍历列表
        items:要遍历的列表,使用$选择器选择模型中保存的列表
        var:列表中元素的别名
        遍历时,注意数据的属性名与相应实体类的属性名一致
    -->
    <c:forEach items="${tsList}" var="item">
        <tr>
            <td>${item.id}</td>
            <td>${item.brand.brandname}</td>
            <td>${item.problem}</td>
            <td>${item.cartype}</td>
            <td>${item.complainstime}</td>
            <td>
                <!--
                   投诉状态:0表示信息审核,1表示厂家受理
                   需要使用c:if进行判断,以显示不同的文本
                -->
                <c:if test="${item.status == 0}">
                    信息审核
                </c:if>
                <c:if test="${item.status == 1}">
                    厂家受理
                </c:if>
            </td>
            <td>
                <button>修改</button>
                <button>删除</button>
            </td>
        </tr>
    </c:forEach>
    </tbody>
</table>
</body>
</html>

运行项目,查看效果。

在查询车辆质量投诉信息列表的功能中,还要求能够根据投诉品牌查询对应的车辆质量投诉信息列表。

修改车辆质量投诉信息数据访问接口(package.dao.VehiclecomplaintsMapper),优化查询车辆质量投诉信息列表的方法。

@Mapper
public interface VehiclecomplaintsMapper {
    // ...

    List<Vehiclecomplaints> getList(@Param("brandId") Integer brandId);

}

修改车辆质量投诉信息数据访问接口的 SQL 映射文件(classpath:/mappers/VehiclecomplaintsMapper.xml),优化查询车辆质量投诉信息列表的方法绑定的 SQL 语句。

<?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="cn.duozai.clts.dao.VehiclecomplaintsMapper">
    <!-- ... -->

    <select id="getList" resultMap="jointResultMap">
        SELECT * FROM vehiclecomplaints AS vc, brand AS b
        <!-- 动态SQL+条件查询 -->
        <where>
            vc.brandId = b.brandId
            <if test="brandId != null">
                AND vc.brandId = #{brandId}
            </if>
        </where>
    </select>
</mapper>

修改车辆质量投诉信息业务逻辑接口(package.service.VehiclecomplaintsService)和车辆质量投诉信息业务逻辑接口实现类(package.service.VehiclecomplaintsServiceImpl),优化查询车辆质量投诉信息列表的方法。

@Service
public class VehiclecomplaintsServiceImpl implements VehiclecomplaintsService{
    // ...

    @Override
    public List<Vehiclecomplaints> getList(Integer brandId) {
        return vehiclecomplaintsMapper.getList(brandId);
    }

}

修改车辆品牌数据访问接口(package.dao.BrandMapper),编写查询车辆品牌列表的方法。

@Mapper
public interface BrandMapper {
    // ...

    List<Brand> getList();

}

修改车辆品牌数据访问接口的 SQL 映射文件(classpath:/mappers/BrandMapper.xml),为查询车辆品牌列表的方法绑定 SQL 语句。

<?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="cn.duozai.clts.dao.BrandMapper">
    <!-- ... -->

    <select id="getList" resultType="cn.duozai.clts.entity.Brand">
        SELECT * FROM brand
    </select>
</mapper>

修改车辆品牌业务逻辑接口(package.service.BrandService)和车辆品牌业务逻辑接口实现类(package.service.BrandServiceImpl),编写一个查询车辆品牌列表的方法,业务逻辑层调用数据访问层查询数据。

@Service
public class BrandServiceImpl implements BrandService{

    // ...

    @Override
    public List<Brand> getList() {
        return brandMapper.getList();
    }

}

修改车辆质量投诉信息控制器(package.controller.IndexController),为查询车辆质量投诉信息列表的视图方法添加条件查询。

@Controller
public class IndexController {

    @Resource
    VehiclecomplaintsService vehiclecomplaintsService;
    @Resource
    BrandService brandService;

    @GetMapping("/")
    public String index(Model model,
                        @RequestParam(name = "brandId", required = false) Integer brandId) {
        // 查询参数brandId允许为null

        // 调用Service层查询数据,并将查询到的车辆质量投诉信息列表保存到模型对象中
        model.addAttribute("tsList", vehiclecomplaintsService.getList(brandId));

        // 调用Service查询数据,并将查询到的车辆品牌列表保存到模型对象中
        model.addAttribute("brandList", brandService.getList());

        // 把查询条件brandId保存到模型对象中
        model.addAttribute("brandId", brandId);

        // 返回视图页面
        return "index";
    }

}

修改车辆质量投诉信息列表的视图页面(webapp/WEB-INF/jsp/index.jsp),编写条件查询表单。

<!-- 条件查询表单 -->
<form action="/" method="GET">
    <select name="brandId">
        <option value="">请选择投诉品牌</option>
        <c:forEach items="${brandList}" var="item">
            <!--
                表单数据回显的条件:查询的brandId等于当前遍历到的brand的id
            -->
            <option value="${item.brandid}" 
                <c:if test="${item.brandid == brandId}">selected</c:if>
            >
                ${item.brandname}
            </option>
        </c:forEach>
    </select>
    <button type="submit">查询</button>
</form>
<table>
    <!-- ... -->
</table>

运行项目,查看效果。

在车辆质量投诉信息列表功能中,需要实现投诉品牌的条件查询。车辆品牌一般是固定的数据,不会频繁修改。为了提高性能,可以将车辆品牌列表缓存到 Redis 服务器中,在需要使用车辆品牌列表时,从 Redis 服务器中将数据取出,以减小 MySQL 的压力。

修改 Maven 项目配置文件(pom.xml),引入 Redis 的依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

修改 Spring Boot 项目配置文件(classpath:/application.properties),配置 Redis 数据库信息。

# 配置Redis
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database=2

修改车辆品牌业务逻辑接口实现类(package.service.BrandServiceImpl),优化查询车辆品牌列表的方法,注入 RedisTemplate,实现 Redis 缓存服务。

@Service
public class BrandServiceImpl implements BrandService {

    @Resource
    RedisTemplate redisTemplate;

    // ..

    @Override
    public List<Brand> getList() {
        // 从Redis数据库中读取车辆品牌列表
        // 通过redisTemplate.opsForValue().get()方法获取缓存数据
        List<Brand> brandList = (List<Brand>) redisTemplate.opsForValue().get("brandList");

        // 车辆品牌列表不存在或为空,从MySQL中获取数据
        if (brandList == null || brandList.size() == 0) {
            brandList = brandMapper.getList();
            // 将MySQL中查询到的车辆品牌列表保存到Redis数据库中
            redisTemplate.opsForValue().set("brandList", brandList);
        }

        // 返回车辆列表
        return brandList;
    }

}

在项目中,可以开启 MyBatis 的日志服务,以测试查询车辆品牌列表时是从 MySQL 获取的,还是 Redis 获取的。

修改 Spring Boot 项目配置文件(classpath:/application.properties),配置 MyBatis 日志服务。

mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

运行项目,查看效果。

访问车辆质量投诉信息列表页面,此时控制台会报序列化错误,原因在于 Redis 要求所有的实体类需要实现序列化接口。

修改车辆品牌实体类(package.entity.Brand),实现序列化接口。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Brand implements Serializable {
    // ...
}

运行项目,查看效果。

第一次访问车辆质量投诉列表页面时,会从 MySQL 数据库中查询车辆品牌列表并缓存到 Redis 数据库中。再次访问车辆质量投诉列表页面时,会从 Redis 数据库中查询车辆品牌列表,而不会从 MySQL 数据库中查询车辆品牌列表。