ssm框架整合(尚硅谷)

SSM—CRUD

一、项目介绍:

二、环境搭建

三、普通分页查询

四、优化Ajax分页查询

五、新增员工

六、修改员工

七、删除员工

八、条件查询

在这里插入图片描述

使用SSM框架搭建出一套简单的CRUD项目示例,包括分页查询、Ajax请求、数据校验等。
在这里插入图片描述
在这里插入图片描述

功能点

  1. 分页查询
  2. 数据校验:JQuery前端校验+JSR-303后端校验
  3. Ajax请求
  4. REST风格的URI:GET查询、POST新增、DELETE删除、PUT修改

技术点

  • 基础框架-SSM(Spring+SpringMVC+Mybatis)
  • 数据库-MySQL
  • 前端框架-Bootstrap
  • 依赖管理-Maven
  • 分页查询-PageHelper
  • 逆向工程-Mybatis Generator

相关配置文件的创建请见SSM整合配置模板,这里主要写下不同的地方。

1. 准备数据库ssm_crud

创建数据库ssm_crud,然后创建员工表tb_emp和部门表tb_dept,并插入一些数据。

tb_emp表:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kJvXVLSR-1657276618652)(https://secure2.wostatic.cn/static/3sSncx5bFnuYMvw86MiKfd/image.png)]

tb_dept表:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BgI6y0Ip-1657276618653)(https://secure2.wostatic.cn/static/9j1UJNrusqh9WK7bXVGPdN/image.png)]

2. 使用MBG自动生成mapper代码

使用MBG自动生成mapper代码(具体用法详见MBG (Mybatis Generator) ),配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>

    <!-- 数据库驱动包(如果classpath下已有则可以忽略) -->
    <!--<classPathEntry location="D:/SQL/mysql-8.0.22-winx64/mysql-connector-java-8.0.15.jar"/>-->

    <!--MBG上下文配置
        id: 上下文id
        TODO: 选择合适的targetRuntime
        targetRuntime: 指定要按照哪种形式去生成Java代码, 默认值Mybatis3
                        Mybatis3 生成基本的增删改查, 还会生成"xxxByExample"方法的动态SQL
                        MyBatis3Simple 只生成基本的增删改查
        defaultModelType: 要如何生成实体类, 默认值conditional
                          conditional 和hierarchical类似, 只是当主键列只有一个时, 不会生成只包含主键的实体类
                          flat 只为每张表生成一个实体类(推荐使用)
                          hierarchical 生成三个实体类, 一个只包含主键, 一个只包含BLOB字段, 一个包含其他剩余字段
    -->
    <context id="MyGenerator" targetRuntime="Mybatis3" defaultModelType="flat">
        <!-- 自动给关键字添加分隔符 -->
        <property name="autoDelimitKeywords" value="true"/>
        <!-- 前缀分隔符 -->
        <property name="beginningDelimiter" value="`"/>
        <!-- 后置分隔符 -->
        <property name="endingDelimiter" value="`"/>
        <!-- Java文件编码 -->
        <property name="javaFileEncoding" value="UTF-8"/>
        <!-- Java文件格式 -->
        <property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
        <!-- XML文件格式 -->
        <property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>

        <!-- TODO: 根据个人需求选择合适的插件 -->
        <!-- 生成xxxMapper.xml时覆盖原文件, 而不是追加 -->
        <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/>
        <!-- 生成Equals和HashCode方法 -->
        <!--<plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin"/>-->
        <!-- 实现Serializable接口 -->
        <plugin type="org.mybatis.generator.plugins.SerializablePlugin"/>
        <!-- 生成toString方法 -->
        <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>

        <!--生成注释-->
        <commentGenerator>
            <!-- 完全禁止生成注释 -->
            <property name="suppressAllComments" value="true"/>
            <!-- 禁止生成时间戳注释 -->
            <property name="suppressDate" value="true"/>
            <!-- 时间戳格式, 要符合SimpleDateFormat -->
            <!--<property name="dateFormat" value="yyyy/MM/dd HH:mm:ss"/>-->
            <!-- 注释是否包含对应表名或列名信息 -->
            <!--<property name="addRemarkComments" value="true"/>-->
        </commentGenerator>

        <!-- TODO: 配置MySQL数据库连接 -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/ssm_crud?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT%2B8"
                        userId="root"
                        password="mysql123">
            <!-- 避免MySQL多次生成SQL映射文件 -->
            <property name="nullCatalogMeansCurrent" value="true"/>
        </jdbcConnection>

        <!-- 设置JDBC类型和Java类型的映射 -->
        <javaTypeResolver>
            <!--  true:使用BigDecimal对应DECIMAL和NUMERIC数据类型
                  false:默认值
                    scale>0;length>18:使用BigDecimal;
                    scale=0;length[10,18]:使用Long;
                    scale=0;length[5,9]:使用Integer;
                    scale=0;length<5:使用Short -->
            <property name="forceBigDecimals" value="false"/>
            <!-- 是否应该符合JSR-310日期类型, 还是说直接将日期映射成java.util.Date -->
            <property name="useJSR310Types" value="false"/>
        </javaTypeResolver>

        <!-- TODO: 生成Model实体类
            targetProject: 项目源码根目录
            targetPackage: 生成的实体类放在哪个包里
        -->
        <javaModelGenerator targetPackage="pers.oneice.ssm.crud.pojo" targetProject="src/main/java">
            <!-- 是否直接将实体类放在targetPackage包中(废话...) -->
            <property name="enableSubPackages" value="true"/>
            <!-- 是否生成有参构造函数 -->
            <property name="constructorBased" value="true"/>
            <!-- 是否删除查询结果的前后空格(体现在实体类的set方法中) -->
            <property name="trimStrings" value="false"/>
            <!-- 生成的实体类属性是否不可变 -->
            <property name="immutable" value="false"/>
            <!-- 设置所有实体类的基类 -->
            <!--<property name="rootClass" value=""/>-->
        </javaModelGenerator>

        <!-- TODO: 生成SQL映射文件 -->
        <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>

        <!-- TODO: 生成映射器接口 -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="pers.oneice.ssm.crud.dao" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>

        <!-- TODO: 要逆向分析的表, 多张表需要配置多个table标签
             tableName: 表名
             domainObjectName: 要生成的实体类名称, 会影响【实体类/映射器接口/映射文件】的名称
             可选属性:
                alias: 设置【表别名】和【列别名前缀】
                mapperName: 设置【映射器接口】和【映射文件】的名称
                enableXxx: 是否要为映射器生成Xxx方法, 默认true
        -->
        <table tableName="tb_dept" domainObjectName="Department">
            <!-- 插入数据之后获取自增主键值 -->
            <generatedKey column="dept_id" identity="true" type="post" sqlStatement="MySql"/>
            <!-- 重写列和属性的映射 -->
            <!--<columnOverride property="propertyName" column="LONG_VARCHAR_FIELD" javaType="java.lang.String" jdbcType="VARCHAR"/>-->
            <!-- 忽略某些列的映射 -->
            <!--<ignoreColumn column=""/>-->
        </table>

        <table tableName="tb_emp" domainObjectName="Employee">
            <!-- 插入数据之后获取自增主键值 -->
            <generatedKey column="emp_id" identity="true" type="post" sqlStatement="MySql"/>
            <!-- 重写列和属性的映射 -->
            <columnOverride property="gender" column="gender" javaType="java.lang.Integer"
                            jdbcType="TINYINT"/>
        </table>

    </context>

</generatorConfiguration>

MBG自动生成的POJO并不完美,所以还需要我们手动完善一下,补充添加下面高亮的代码。

public class Employee implements Serializable {
    
    
    private Integer empId;

    private String empName;

    private Integer gender;

    private String email;

    private Integer dId;
    private Department department;

    private static final long serialVersionUID = 1L;

    public Employee() {
    
    
    }

    public Employee(Integer empId, String empName, Integer gender, String email, Integer dId) {
    
    
        this.empId = empId;
        this.empName = empName;
        this.gender = gender;
        this.email = email;
        this.dId = dId;
    }
public class Department implements Serializable {
    
    
    private Integer deptId;

    private String deptName;

    private static final long serialVersionUID = 1L;

    public Department() {
    
    
    }

    public Department(Integer deptId, String deptName) {
    
    
        this.deptId = deptId;
        this.deptName = deptName;
    }

为DepartmentMapper.xml和EmployeeMapper.xml添加全局缓存:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zIlklLVp-1657276618654)(https://secure2.wostatic.cn/static/skyppUD1CbYmEBb38ZoLRG/image.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tg5Rbkwr-1657276618655)(https://secure2.wostatic.cn/static/6r4Wf7wJej9bT4w3HTJVR/image.png)]

3. 编写自定义mapper代码

MBG生成的mapper代码不包含关联查询,所以我还们需要编写一些关联查询的代码。为了尽量不破坏MBG生成的代码,这里采用继承mapper接口的方式进行拓展。

部门表相关:

@Resource
public interface DepartmentMapperExt extends DepartmentMapper {

}
<?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="pers.oneice.ssm.crud.dao.DepartmentMapperExt">
    <cache-ref namespace="pers.oneice.ssm.crud.dao.DepartmentMapper"/>
</mapper>

员工表相关:

@Resource
public interface EmployeeMapperExt extends EmployeeMapper {
    
    
    
    /** 查询满足example条件的员工, 包括所属部门信息 */
    List<Employee> queryByExampleWithDept(EmployeeExample example);

    /** 根据员工id查询员工, 包括所属部门信息 */
    Employee queryByIdWithDept(int empId);
}
<?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="pers.oneice.ssm.crud.dao.EmployeeMapperExt">
    <cache-ref namespace="pers.oneice.ssm.crud.dao.EmployeeMapper"/>

    <!-- Employee的结果映射, 包括对内部的department属性的映射 -->
    <resultMap id="EmpResultMapWithDept" type="pers.oneice.ssm.crud.pojo.Employee"
               extends="pers.oneice.ssm.crud.dao.EmployeeMapper.BaseResultMap">
        <association property="department" resultMap="pers.oneice.ssm.crud.dao.DepartmentMapper.BaseResultMap"/>
    </resultMap>
    <!-- tb_emp表连接查询tb_dept表时, 要展示的字段列表 -->
    <sql id="Emp_With_Dept_Column_list">
        e.emp_id,
        e.emp_name,
        e.gender,
        e.email,
        e.d_id,
        d.dept_id,
        d.dept_name
    </sql>
    <!-- tb_emp表的where子句条件 -->
    <sql id="Example_Where_Clause">
        <where>
            <foreach collection="oredCriteria" item="criteria" separator="or">
                <if test="criteria.valid">
                    <trim prefix="(" prefixOverrides="and" suffix=")">
                        <foreach collection="criteria.criteria" item="criterion">
                            <choose>
                                <when test="criterion.noValue">
                                    and ${
    
    criterion.condition}
                                </when>
                                <when test="criterion.singleValue">
                                    and ${
    
    criterion.condition} #{
    
    criterion.value}
                                </when>
                                <when test="criterion.betweenValue">
                                    and ${
    
    criterion.condition} #{
    
    criterion.value} and #{
    
    criterion.secondValue}
                                </when>
                                <when test="criterion.listValue">
                                    and ${
    
    criterion.condition}
                                    <foreach close=")" collection="criterion.value" item="listItem" open="("
                                             separator=",">
                                        #{
    
    listItem}
                                    </foreach>
                                </when>
                            </choose>
                        </foreach>
                    </trim>
                </if>
            </foreach>
        </where>
    </sql>

    <select id="queryByExampleWithDept" parameterType="pers.oneice.ssm.crud.pojo.EmployeeExample"
            resultMap="EmpResultMapWithDept">
        select
        <if test="distinct">
            distinct
        </if>
        <include refid="Emp_With_Dept_Column_list"/>
        from tb_emp e
                     left join tb_dept d on e.d_id = d.dept_id
        <if test="_parameter != null">
            <include refid="Example_Where_Clause"/>
        </if>
        <if test="orderByClause != null">
            order by ${
    
    orderByClause}
        </if>
    </select>

    <select id="queryByIdWithDept" parameterType="java.lang.Integer" resultMap="EmpResultMapWithDept">
        select
        <include refid="Emp_With_Dept_Column_list"/>
        from tb_emp e
                     left join tb_dept d on e.d_id = d.dept_id
        where emp_id = #{
    
    empId,jdbcType=INTEGER}
    </select>
</mapper>

4. 在list.jsp页面中引入Bootstrap框架

基本上所有页面都需要引入Booststrap框架、Jquery库等,考虑将它们抽取到一个公共页面中,然后在需要的页面中使用jsp:include元素引入即可。

抽取页眉header.jsp:

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!-- 设置页面编码 -->
<meta charset="utf-8">
<!-- 向浏览器传递参数和值
X-UA-Compatible用于兼容IE8, 让IE8使用特定版本的渲染模式, "IE=edge"表示使用最高版本的渲染模式
-->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 用于适配手机浏览器的页面缩放 -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->

<!-- HTML5 shim 和 Respond.js 是为了让 IE8 支持 HTML5 元素和媒体查询(media queries)功能 -->
<!-- 警告:通过 file:// 协议(就是直接将 html 页面拖拽到浏览器中)访问页面时 Respond.js 不起作用 -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dest/respond.min.js"></script>
<![endif]-->

<!-- 新 Bootstrap 核心 CSS 文件 -->
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
 
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
 
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>

<%
    request.setAttribute("APP_PATH", request.getContextPath());
%>

抽取页脚footer.jsp:

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
<!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"></script>

在list.jsp页面中引入:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <jsp:include page="/WEB-INF/views/common/header.jsp"/>
</head>
<body>
<jsp:include page="/WEB-INF/views/common/footer.jsp"/>
</body>
</html>

因为我们最终是在list.jsp页面中进行展示,所以当用户访问主页index.jsp时,可以直接将请求转发到 “/emps”,由控制器private static final String PAGE_LIST = “list”; private EmployeeService employeeService;

/**

  • 分页查询员工数据
  • @param page 页码
  • @param rows 记录数
  • @return 转发至PAGE_LIST页面进行展示 */ @GetMapping(“/emps”) public String getEmps(@RequestParam(value = “page”, defaultValue = “1”) int page, @RequestParam(value = “rows”, defaultValue = “10”) int rows, Model model) { PageInfo pageInfo = employeeService.getEmps(page, rows); model.addAttribute(“pageInfo”, pageInfo); return PAGE_LIST; }查询第一页员工,然后跳转到list.jsp页面进行展示。
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
</head>
<body>
<jsp:forward page="/emps"/>
</body>
</html>

至此,基础环境就搭建好了。

在这里插入图片描述

查询步骤:

URI:/emps?page=xxx&rows=xxx GET

1、访问index.jsp页面

2、index.jsp页面发送Ajax查询员工列表的请求

4、转发到list.jsp页面进行展示

1. 引入PageHelper分页插件

接下来在 Spring 配置文件中配置该分页插件。

2. 服务端处理分页请求

EmployeeController根据请求参数中的page和rows,分页查询员工数据,然后转发至list.jsp页面进行展示。

EmployeeController#getEmps:

private static final String PAGE_LIST = "list";
private EmployeeService employeeService;

/**
 * 分页查询员工数据
 * @param page 页码
 * @param rows 记录数
 * @return 转发至PAGE_LIST页面进行展示
 */
@GetMapping("/emps")
public String getEmps(@RequestParam(value = "page", defaultValue = "1") int page,
                      @RequestParam(value = "rows", defaultValue = "10") int rows,
                      Model model) {
    PageInfo<Employee> pageInfo = employeeService.getEmps(page, rows);
    model.addAttribute("pageInfo", pageInfo);
    return PAGE_LIST;
}

EmployeeService#getEmps:

private EmployeeMapperExt employeeMapper;

/**
 * 根据要查询的页码和页大小, 分页查询员工数据
 * @param page  当前页码
 * @param rows 当前页大小, 即查询多少条记录
 * @return 所有员工信息
 */
public PageInfo<Employee> getEmps(int page, int rows) {
    
    
    PageHelper.startPage(page, rows);
    List<Employee> emps = employeeMapper.queryByExampleWithDept(null);
    return new PageInfo<>(emps, 5);
}

EmployeeMapperExt:

@Resource
public interface EmployeeMapperExt extends EmployeeMapper {
    
    
    
    /** 查询满足example条件的员工, 包括所属部门信息 */
    List<Employee> queryByExampleWithDept(EmployeeExample example);

    /** 根据员工id查询员工, 包括所属部门信息 */
    Employee queryByIdWithDept(int empId);
}
<?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="pers.oneice.ssm.crud.dao.EmployeeMapperExt">
    <cache-ref namespace="pers.oneice.ssm.crud.dao.EmployeeMapper"/>

    <!-- Employee的结果映射, 包括对内部的department属性的映射 -->
    <resultMap id="EmpResultMapWithDept" type="pers.oneice.ssm.crud.pojo.Employee"
               extends="pers.oneice.ssm.crud.dao.EmployeeMapper.BaseResultMap">
        <association property="department" resultMap="pers.oneice.ssm.crud.dao.DepartmentMapper.BaseResultMap"/>
    </resultMap>
    <!-- tb_emp表连接查询tb_dept表时, 要展示的字段列表 -->
    <sql id="Emp_With_Dept_Column_list">
        e.emp_id,
        e.emp_name,
        e.gender,
        e.email,
        e.d_id,
        d.dept_id,
        d.dept_name
    </sql>
    <!-- tb_emp表的where子句条件 -->
    <sql id="Example_Where_Clause">
        <where>
            <foreach collection="oredCriteria" item="criteria" separator="or">
                <if test="criteria.valid">
                    <trim prefix="(" prefixOverrides="and" suffix=")">
                        <foreach collection="criteria.criteria" item="criterion">
                            <choose>
                                <when test="criterion.noValue">
                                    and ${
    
    criterion.condition}
                                </when>
                                <when test="criterion.singleValue">
                                    and ${
    
    criterion.condition} #{
    
    criterion.value}
                                </when>
                                <when test="criterion.betweenValue">
                                    and ${
    
    criterion.condition} #{
    
    criterion.value} and #{
    
    criterion.secondValue}
                                </when>
                                <when test="criterion.listValue">
                                    and ${
    
    criterion.condition}
                                    <foreach close=")" collection="criterion.value" item="listItem" open="("
                                             separator=",">
                                        #{
    
    listItem}
                                    </foreach>
                                </when>
                            </choose>
                        </foreach>
                    </trim>
                </if>
            </foreach>
        </where>
    </sql>

    <select id="queryByExampleWithDept" parameterType="pers.oneice.ssm.crud.pojo.EmployeeExample"
            resultMap="EmpResultMapWithDept">
        select
        <if test="distinct">
            distinct
        </if>
        <include refid="Emp_With_Dept_Column_list"/>
        from tb_emp e
                     left join tb_dept d on e.d_id = d.dept_id
        <if test="_parameter != null">
            <include refid="Example_Where_Clause"/>
        </if>
        <if test="orderByClause != null">
            order by ${
    
    orderByClause}
        </if>
    </select>

    <select id="queryByIdWithDept" parameterType="java.lang.Integer" resultMap="EmpResultMapWithDept">
        select
        <include refid="Emp_With_Dept_Column_list"/>
        from tb_emp e
                     left join tb_dept d on e.d_id = d.dept_id
        where emp_id = #{
    
    empId,jdbcType=INTEGER}
    </select>
</mapper>

3. list.jsp页面展示数据

  • 在list.jsp页面中取出pageInfo域数据
  • c:foreach元素遍历出每条记录,进行展示
  • 导航条采用Bootstrap提供的nav元素,详见
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <jsp:include page="/WEB-INF/views/common/header.jsp"/>
</head>
<body>
<%-- 定义一个栅格布局 --%>
<div class="container">
    <%-- 标题行 --%>
    <div class="row">
        <%-- 占满整行(每行最多12列) --%>
        <div class="col-md-12"><h1>SSM-CRUD</h1></div>
    </div>

    <%-- 查询行 --%>
    <div class="row">
        <div class="col-md-3"></div>
        <div class="col-md-6">
            <form>
                <div class="input-group">
                    <div class="input-group-btn">
                        <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown"
                                aria-haspopup="true" aria-expanded="false">姓名 <span class="caret"></span></button>
                        <ul class="dropdown-menu">
                            <li><a href="#">姓名</a></li>
                            <li><a href="#">邮箱</a></li>
                            <li><a href="#">部门</a></li>
                        </ul>
                    </div>

                    <input type="text" class="form-control" placeholder="Search for...">

                    <span class="input-group-btn"><button class="btn btn-primary" type="submit">查询</button></span>
                </div>
            </form>
        </div>
    </div>

    <%-- 新增/删除行 --%>
    <div class="row">
        <%-- 移到最右边 --%>
        <div class="col-md-2 col-md-offset-10">
            <button class="btn btn-success" id="add_emp_btn"><span class="glyphicon glyphicon-plus"></span> 新增</button>
            <button class="btn btn-danger">
                <span class="glyphicon glyphicon-trash" aria-hidden="true" id="delete_all_check"></span> 删除
            </button>
        </div>
    </div>

    <%-- 表格行 --%>
    <div class="row">
        <%-- 占满整行 --%>
        <div class="col-md-12">
            <table class="table table-bordered table-striped table-hover">
                <tr>
                    <td><input id="check_all" type="checkbox"></td>
                    <th>编号</th>
                    <th>姓名</th>
                    <th>性别</th>
                    <th>邮箱</th>
                    <th>部门</th>
                    <th>操作</th>
                </tr>
                <%-- 遍历出当前页的员工数据 --%>
                <c:forEach var="emp" items="${pageInfo.list}">
                    <tr>
                        <td><input name="#" type="checkbox"></td>
                        <td>${emp.empId}</td>
                        <td>${emp.empName}</td>
                        <td>${emp.gender==1?"男":"女"}</td>
                        <td>${emp.email}</td>
                        <td>${emp.department.deptName}</td>
                        <td>
                            <button class="btn btn-info btn-sm">
                                <span class="glyphicon glyphicon-edit" aria-hidden="true"></span> 编辑
                            </button>
                            <button class="btn btn-danger btn-sm">
                                <span class="glyphicon glyphicon-trash" aria-hidden="true"></span> 删除
                            </button>
                        </td>
                    </tr>
                </c:forEach>
            </table>
        </div>
    </div>

    <%-- 分页信息 --%>
    <div class="row">
        <%-- 基本文字说明 --%>
        <div class="col-md-6">
            当前第${pageInfo.pageNum}页, 共${pageInfo.pages}页, 共${pageInfo.total}条记录
        </div>
        <%-- 导航条 --%>
        <div class="col-md-6">
            <nav aria-label="Page navigation">
                <ul class="pagination">
                    <c:choose>
                        <%-- 首页+上一页 --%>
                        <c:when test="${pageInfo.hasPreviousPage}">
                            <li>
                                <a href="${APP_PATH}/emps?page=1&rows=${pageInfo.pageSize}">首页</a>
                            </li>
                            <li>
                                <a href="${APP_PATH}/emps?page=${pageInfo.prePage}&rows=${pageInfo.pageSize}"
                                   aria-label="Previous">
                                    <span aria-hidden="true">&laquo;</span>
                                </a>
                            </li>
                        </c:when>
                        <c:otherwise>
                            <li class="disabled">
                                <span aria-hidden="true">首页</span>
                            </li>
                            <li class="disabled">
                                <span aria-hidden="true">&laquo;</span>
                            </li>
                        </c:otherwise>
                    </c:choose>

                    <%-- 页码展示 --%>
                    <c:forEach var="navigatepageNum" items="${pageInfo.navigatepageNums}">
                        <c:choose>
                            <c:when test="${pageInfo.pageNum!=navigatepageNum}">
                                <li>
                                    <a href="${APP_PATH}/emps?page=${navigatepageNum}&rows=${pageInfo.pageSize}">
                                            ${navigatepageNum}
                                    </a>
                                </li>
                            </c:when>
                            <c:otherwise>
                                <li class="active">
                                    <span>${navigatepageNum} <span class="sr-only">(current)</span></span>
                                </li>
                            </c:otherwise>
                        </c:choose>
                    </c:forEach>

                    <%-- 下一页+末页 --%>
                    <c:choose>
                        <c:when test="${pageInfo.hasNextPage}">
                            <li>
                                <a href="${APP_PATH}/emps?page=${pageInfo.nextPage}&rows=${pageInfo.pageSize}"
                                   aria-label="Next">
                                    <span aria-hidden="true">&raquo;</span>
                                </a>
                            </li>
                            <li>
                                <a href="${APP_PATH}/emps?page=${pageInfo.pages}&rows=${pageInfo.pageSize}">末页</a>
                            </li>
                        </c:when>
                        <c:otherwise>
                            <li class="disabled">
                              <span>
                                <span aria-hidden="true">&raquo;</span>
                              </span>
                            </li>
                            <li class="disabled">
                              <span>
                                <span aria-hidden="true">末页</span>
                              </span>
                            </li>
                        </c:otherwise>
                    </c:choose>
                </ul>
            </nav>
        </div>
    </div>

</div>
<jsp:include page="/WEB-INF/views/common/footer.jsp"/>
</body>
</html>

最终效果如下:

在这里插入图片描述

这样我们就实现了分页查询的功能。但实际开发中我们不会这么做,因为每次切换页面都要刷新页面,用户体验并不是很好,更优的方案是用JS发送Ajax请求,获取Json数据,使用JS解析Json数据并显示到页面上。ok,接下来我们就来一波优化—优化-Ajax分页查询。

优化-Ajax分页查询

在这里插入图片描述

Ajax请求的优点是:避免客户端多次刷新,同时也更好地兼容多平台。所以本文采用Ajax请求对前面《普通分页查询》进行优化,大致步骤是:用JS发送Ajax请求,服务端返回Json数据,客户端用JS解析Json数据并显示到页面上。

后端

1. 引入Jackson依赖

对于Ajax请求通常是返回Json数据,这里我们使用Jackson实现。在pom.xml中添加如下依赖:

2. 创建一个通用的【返回信息】Msg类

我们返回的Json数据通常不只是包含分页信息,还要包括错误码和说明信息等。为此,我们可以创建一个通用的【返回信息】类Msg,如下所示:

/**
 * 这是一个通用的【返回信息】类
 * @author OneIce
 * @since 2021/3/22 22:45
 */
public class Msg {
    
    

    /** 错误码 */
    private ErrorCodeEnum errorCode;
    /** 要返回给用户的数据 */
    private Map<String, Object> dataMap = new HashMap<>();

    public Msg(ErrorCodeEnum errorCode) {
    
    
        this.errorCode = errorCode;
    }

    /** 方便地获取一个表示"OK"的Msg对象 */
    public static Msg success() {
    
    
        Msg msg = new Msg(ErrorCodeEnum.OK);
        return msg;
    }

    /**
     * 添加返回数据
     * @param name  数据的名称
     * @param value 数据的内容
     * @return this本身
     */
    public Msg add(String name, Object value) {
    
    
        dataMap.put(name, value);
        return this;
    }

    public ErrorCodeEnum getErrorCode() {
    
    
        return errorCode;
    }

    public void setErrorCode(ErrorCodeEnum errorCode) {
    
    
        this.errorCode = errorCode;
    }

    public Map<String, Object> getDataMap() {
    
    
        return dataMap;
    }

    public void setDataMap(Map<String, Object> dataMap) {
    
    
        this.dataMap = dataMap;
    }

    @Override
    public String toString() {
    
    
        return "Msg{" +
                "errorCode=" + errorCode +
                ", dataMap=" + dataMap +
                '}';
    }
}

其中ErrorCodeEnum是个枚举类,封装了错误码和描述信息。

/**
 * 封装了错误码和错误描述信息
 * @author OneIce
 * @since 2021/3/22 23:10
 */
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum ErrorCodeEnum {
    
    
    OK("00000", "一切正确"),
    USER_ERROR("A0001", "用户端错误"),
    USER_REQUEST_PARAM_ERROR("A0400", "用户请求参数错误");

    /** 错误码 */
    private String value;
    /** 错误描述信息 */
    private String desc;

    ErrorCodeEnum(String value, String desc) {
    
    
        this.value = value;
        this.desc = desc;
    }

    @Override
    public String toString() {
    
    
        return "[" + value + "]" + desc;
    }

    public String getValue() {
    
    
        return value;
    }

    public String getDesc() {
    
    
        return desc;
    }
}

3. 控制器查询分页数据, 返回Msg对象

在控制器方法上声明@ResponseBody注解,然后将查询到的分页数据添加到Msg对象中并返回。

EmployeeController#getEmpsWithJson:

/**
 * 分页查询员工信息, 返回Json数据
 * @param page 页码
 * @param rows 记录数
 * @return 分页数据(包含查询到的员工数据), 由MappingJackson2CborHttpMessageConverter解析成Json字符串
 */
@ResponseBody
@GetMapping("/emps")
public Msg getEmpsWithJson(@RequestParam(value = "page", defaultValue = "1") int page,
                           @RequestParam(value = "rows", defaultValue = "10") int rows) {
    
    
    PageInfo<Employee> pageInfo = employeeService.getEmps(page, rows);
    Msg msg = Msg.success();
    msg.add("pageInfo", pageInfo);
    return msg;
}

要注意的是@ResponseBody注解必不可少,这样SpringMVC才会使用RequestResponseBodyProcessor这个返回值处理器来处理Msg对象,它会选取一个适当的HttpMessageConverter—MappingJackson2CborHttpMessageConverter,将返回值转换成Json字符串,然后输出到HTTP响应流中。详见SpringMVC如何解析方法参数和返回值?。

前端

普通的分页查询已经写在list.jsp中了,所以我们将Ajax分页查询直接写在index.jsp里好了。

1. 页面一加载好就发送Ajax查询请求

//页面一加载好就请求一次分页数据
$(function () {
    toPage(1, 10)
})

//发送Ajax请求, 请求分页数据
function toPage(page, rows) { //页码, 记录数
    $.ajax({
        type: "GET",
        url: "${APP_PATH}/emps",
        data: {"page": page, "rows": rows},
        dataType: "json",
        success: function (result) {
            //构建员工列表
            buildEmpsTable(result.dataMap.pageInfo.list)
            //构建分页基本信息
            buildPageInfo(result.dataMap.pageInfo)
            //构建导航条
            buildPageNavigate(result.dataMap.pageInfo)
        }
    })
}

2. 解析Json数据, 构建员工列表和导航条

//构建员工表格
function buildEmpsTable(list) {
    //清空原表格数据
    $("#emp_table tbody").empty()
    //获取每个员工信息, 追加到表格中显示
    $.each(list, function () {
        var empTr = $("<tr></tr>")
        var empCbTd = $('<td><input class="check_item" type="checkbox"></td>')
        var empIdTd = $("<td></td>").append(this.empId)
        var empNameTd = $("<td></td>").append(this.empName)
        var genderTd = $("<td></td>").append(this.gender === 1 ? "男" : "女")
        var emailTd = $("<td></td>").append(this.email)
        //避免部门为null
        var deptNameTd = $("<td></td>").append(this.department ? this.department.deptName : "")
        var editBtn = $("<button></button>").addClass("btn btn-info btn-sm edit_btn").append($("<span></span>")
            .addClass("glyphicon glyphicon-edit")).append(" 编辑").attr("emp_id", this.empId)
        var delBtn = $("<button></button>").addClass("btn btn-danger btn-sm delete_btn").append($("<span></span>")
            .addClass("glyphicon glyphicon-trash")).append(" 删除").attr("emp_id", this.empId)
        var operatorTd = $("<td></td>").append(editBtn).append(" ").append(delBtn)
        empTr.append(empCbTd).append(empIdTd).append(empNameTd).append(genderTd).append(emailTd).append(deptNameTd).append(operatorTd)
            .appendTo("#emp_table tbody")
    })
}
    
//构建分页的基本信息
function buildPageInfo(pageInfo) {
    $("#page_info_area").empty() //清空原信息
    pageNum = pageInfo.pageNum
    rows = pageInfo.pageSize
    pages = pageInfo.pages
    total = pageInfo.total
    $("#page_info_area").append(`当前第\${pageNum}页, 共\${pages}页, 共\${total}条记录`)
}

//构建导航条
function buildPageNavigate(pageInfo) {
    $("#page_navigate_area").empty() //清空原导航条

    var ul = $("<ul></ul>").addClass("pagination")
    //首页和上一页
    var firstPageLi = $("<li></li>").append($("<a></a>").attr("href", "#").append("首页"))
    var prePageLi = $("<li></li>").append($("<a></a>").attr("href", "#").append("&laquo;"))
    //没有上一页时禁用掉按钮
    if (!pageInfo.hasPreviousPage) {
        firstPageLi.addClass("disabled")
        prePageLi.addClass("disabled")
    }
    ul.append(firstPageLi).append(prePageLi)

    //遍历中间页码
    $.each(pageInfo.navigatepageNums, function () {
        var pageNumLi = $("<li></li>").append($("<a></a>").attr("href", "#").append(this))
        //高亮当前页的页码
        if (pageInfo.pageNum == this) {
            pageNumLi.addClass("active")
        }
        ul.append(pageNumLi)
    })
    //下一页和末页
    var nextPageLi = $("<li></li>").append($("<a></a>").attr("href", "#").append("&raquo;"))
    var lastPageLi = $("<li></li>").append($("<a></a>").attr("href", "#").append("末页"))
    //没有下一页时禁用掉按钮
    if (!pageInfo.hasNextPage) {
        nextPageLi.addClass("disabled")
        lastPageLi.addClass("disabled")
    }
    ul.append(nextPageLi).append(lastPageLi)
    //创建导航条, 显示到页面上
    var nav = $("<nav></nav>").append(ul)
    $("#page_navigate_area").append(nav)
}

3. 为导航条页码绑定单击事件 (发送Ajax请求)

//构建导航条
function buildPageNavigate(pageInfo) {
    $("#page_navigate_area").empty() //清空原导航条

    var ul = $("<ul></ul>").addClass("pagination")
    //首页和上一页
    var firstPageLi = $("<li></li>").append($("<a></a>").attr("href", "#").append("首页"))
    var prePageLi = $("<li></li>").append($("<a></a>").attr("href", "#").append("&laquo;"))
    //没有上一页时禁用掉按钮
    if (!pageInfo.hasPreviousPage) {
        firstPageLi.addClass("disabled")
        prePageLi.addClass("disabled")
    }
    //绑定单击事件
    clickToPage(firstPageLi, 1, pageInfo.pageSize)
    clickToPage(prePageLi, pageInfo.prePage, pageInfo.pageSize)
    ul.append(firstPageLi).append(prePageLi)

    //遍历中间页码
    $.each(pageInfo.navigatepageNums, function () {
        var pageNumLi = $("<li></li>").append($("<a></a>").attr("href", "#").append(this))
        //高亮当前页的页码
        if (pageInfo.pageNum == this) {
            pageNumLi.addClass("active")
        }
        //绑定单击事件
        clickToPage(pageNumLi, this, pageInfo.pageSize)
        ul.append(pageNumLi)
    })
    //下一页和末页
    var nextPageLi = $("<li></li>").append($("<a></a>").attr("href", "#").append("&raquo;"))
    var lastPageLi = $("<li></li>").append($("<a></a>").attr("href", "#").append("末页"))
    //绑定单击事件
    clickToPage(nextPageLi, pageInfo.nextPage, pageInfo.pageSize)
    clickToPage(lastPageLi, pageInfo.pages, pageInfo.pageSize)
    //没有下一页时禁用掉按钮
    if (!pageInfo.hasNextPage) {
        nextPageLi.addClass("disabled")
        lastPageLi.addClass("disabled")
    }
    ul.append(nextPageLi).append(lastPageLi)
    //创建导航条, 显示到页面上
    var nav = $("<nav></nav>").append(ul)
    $("#page_navigate_area").append(nav)
}

//为导航条页码绑定单击事件
function clickToPage(obj, page, rows) { //按钮对象, 页码, 记录数
    $(obj).click(function () {
        if (!$(this).hasClass("disabled") && !$(this).hasClass("active")) {
            toPage(page, rows)
        }
        return false
    })
} 

4. 效果演示

在这里插入图片描述

现在切换页码时不再会刷新页面了,浏览器后台会偷偷发送Ajax请求,然后更新页面数据。

新增员工

在这里插入图片描述

新增步骤:

URI:/emps POST

  1. 在index.jsp页面点击“新增”按钮
  2. 发送Ajax请求去查询部门列表,显示在对话框上
  3. 弹出新增对话框
  4. 用户输入数据,点击“保存”按钮
  5. 进行前端校验:JS校验+Ajax用户名校验
  6. 发送Ajax新增请求给服务端
  7. 服务端使用JSR 303校验注解进行完整校验
  8. 保存员工并返回Json数据,如有错误则显示错误信息

前端

1. 定义员工新增的模态框

根据Bootstrap模态框改一改:

<!-- 用于员工新增的模态框 -->
<div class="modal fade" id="add_emp_modal" tabindex="-1" role="dialog" aria-labelledby="addEmpModal">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span>
                </button>
                <h4 class="modal-title" id="myModalLabel">新增员工</h4>
            </div>
            <%-- 表单部分 --%>
            <div class="modal-body">
                <form id="add_emp_form" action="#" method="post" class="form-horizontal">
                    <div class="form-group">
                        <label for="emp_name_input" class="col-sm-2 control-label">姓名</label>
                        <div class="col-sm-10 has-feedback">
                            <input type="text" class="form-control" id="emp_name_input" placeholder="OneIce"
                                   name="empName">
                            <span class="glyphicon form-control-feedback" aria-hidden="true"></span>
                            <%-- 显示错误信息 --%>
                            <span class="help-block"></span>
                        </div>
                    </div>
                    <div class="form-group">
                        <label for="emp_email_input" class="col-sm-2 control-label">邮箱</label>
                        <div class="col-sm-10 has-feedback">
                            <input type="email" class="form-control has-feedback" id="emp_email_input"
                                   placeholder="[email protected]"
                                   name="email">
                            <span class="glyphicon form-control-feedback" aria-hidden="true"></span>
                            <%-- 显示错误消息 --%>
                            <span class="help-block"></span>
                        </div>
                    </div>
                    <div class="form-group">
                        <label for="emp_gender_input1" class="col-sm-2 control-label">性别</label>
                        <div class="col-sm-10">
                            <label class="radio-inline">
                                <input checked type="radio" name="gender" id="emp_gender_input1" value="1"> 男
                            </label>
                            <label class="radio-inline">
                                <input type="radio" name="gender" id="emp_gender_input2" value="2"> 女
                            </label>
                        </div>
                    </div>
                    <div class="form-group">
                        <label for="emp_dept_select" class="col-sm-2 control-label">部门</label>
                        <div class="col-sm-4">
                            <select id="emp_dept_select" name="dId" class="form-control">
                                <%-- 从数据库中查询部门信息 --%>
                            </select>
                        </div>
                    </div>
                </form>
            </div>
            <%-- 保存和关闭按钮 --%>
            <div class="modal-footer">
                <button id="emp_close_btn" type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
                <button id="emp_save_btn" type="button" class="btn btn-primary" disabled>保存</button>
                <%-- 默认禁用 --%>
            </div>
        </div>
    </div>
</div>

2. Ajax查询部门列表, 弹出新增模态框

//点击"新增"按钮, 弹出"新增"模态框
$("#add_emp_btn").click(function () {
    resetForm("#add_emp_form")//重置表单(内容+样式)
    getDepts() //查询所有部门信息显示在模态框上
    $("#add_emp_modal").modal() //展示模态框
})

//查询所有部门信息, 显示在"新增"模态框的下拉列表中
function getDepts() {
    $("#emp_dept_select").empty()
    $.ajax({
        type: "GET",
        url: "${APP_PATH}/depts",
        dataType: "json",
        success: function (result) {
            $.each(result.dataMap.depts, function () {
                var option = $("<option></option>").attr("value", this.deptId).append(this.deptName)
                $("#emp_dept_select").append(option)
            })
        }
    })
}

//点击"关闭"按钮, 清空下拉列表的部门信息
$("#emp_close_btn").click(function () {
    $("#emp_dept_select").empty()
})

3. JS前端校验+Ajax用户名校验

//重置表单(内容+样式), 避免再次打开时跳过校验
function resetForm(selector) {
    //重置表单内容
    $(selector)[0].reset()
    //重置表单样式
    $(selector).find("*").removeClass("has-error has-success")
    $(selector).find(".glyphicon").removeClass("glyphicon-remove glyphicon-ok")
    $(selector).find(".help-block").text("")
    //禁用保存按钮
    $("#emp_save_btn").prop("disabled", true)
}

//校验新增表单的姓名和邮箱
function validateAddForm() {
    //校验姓名
    var empName = $("#emp_name_input").val()
    var regName = /(^[a-zA-Z0-9_-]{3,16}$)|(^[\u2E80-\u9FFF]{2,8}$)/ //匹配2-8位中文, 或3-16字母下划线数字
    if (regName.test(empName)) { //姓名格式正确
        //Ajax再校验姓名是否存在
        $.ajax({
            type: "GET",
            url: "${APP_PATH}/checkEmpName",
            data: $("#emp_name_input").serialize(),
            dateType: "json",
            success: function (result) {
                if (result.errorCode.value == "00000") {
                    showValidateMsg("#emp_name_input", "success", "")
                } else if (result.errorCode.value == "A0111") { //姓名已存在
                    showValidateMsg("#emp_name_input", "error", "该员工姓名已存在, 请重新输入")
                } else {
                    showValidateMsg("#emp_name_input", "error", "姓名错误, 请重新输入")
                }
            }
        })
    } else { //姓名格式错误
        showValidateMsg("#emp_name_input", "error", "姓名格式错误, 必须是2-8位中文, 或3-16字母下划线数字")
        return false
    }

    //校验邮箱
    var empEmail = $("#emp_email_input").val()
    var regEmail = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/ //匹配电子邮箱
    if (regEmail.test(empEmail)) { //邮箱格式正确
        showValidateMsg("#emp_email_input", "success", "")
    } else { //邮箱格式错误
        showValidateMsg("#emp_email_input", "error", "邮箱格式错误")
        return false
    }
    //完全正确
    return true
}

//内容一改变就进行校验
$("#emp_name_input,#emp_email_input").change(function () {
    if (validateAddForm()) {
        $("#emp_save_btn").prop("disabled", false)
    } else { //无效时禁用按钮
        $("#emp_save_btn").prop("disabled", true)
    }
})

//显示校验结果信息
function showValidateMsg(selector, status, msg) { //选择器, 结果状态, 结果信息
    //初始化状态
    $(selector).parent().removeClass("has-error has-success")
    $(selector).nextAll(".glyphicon").removeClass("glyphicon-remove glyphicon-ok")
    $(selector).nextAll(".help-block").text("")
    if (status == "success") {
        $(selector).parent().addClass("has-success") //颜色
        $(selector).nextAll(".glyphicon").addClass("glyphicon-ok") //图标
        $(selector).nextAll(".help-block").text(msg) //提示信息
    } else if (status == "error") {
        $(selector).parent().addClass("has-error")
        $(selector).nextAll(".glyphicon").addClass("glyphicon-remove")
        $(selector).nextAll(".help-block").text(msg)
    }
}

4. 点击保存按钮, Ajax保存员工

//点击"保存"按钮:
//1. 发送Ajax新增员工的请求
//2. 关闭模态框
//3. 收到返回数据后, 跳转至末页
$("#emp_save_btn").click(function () {
    //保存前再校验一遍
    if (!validateAddForm()) {
        return false
    }
    //发送Ajax请求, 新增员工
    $.ajax({
        type: "POST",
        url: "${APP_PATH}/emps",
        data: $("#add_emp_form").serialize(),
        dataType: "json",
        success: function (result) {
            if (result.errorCode.value === "00000") {
                //关闭模态框
                $("#add_emp_modal").modal("hide")
                //跳转至最后一页
                toPage(total, rows) //将总记录数当作页码, 保证能跳到最后一页
            } else { //添加失败, 显示错误消息
                if (result.dataMap.empName) { //带有empName这个错误
                    showValidateMsg("#emp_name_input", "error", result.dataMap.empName);
                }
                if (result.dataMap.email) { //带有email这个错误
                    showValidateMsg("#emp_email_input", "error", result.dataMap.empName);
                }
            }
        }
    })
})

后端

1. 查询部门列表

DepartmentController#getDepts:

/** 查询部门列表 */
@GetMapping("/depts")
@ResponseBody
public Msg getDepts() {
    List<Department> depts = departmentService.getDepts();
    Msg msg = Msg.success();
    msg.add("depts", depts);
    return msg;
}

DepartmentService#getDepts:

/** 查询所有部门 */
public List<Department> getDepts() {
    return departmentMapper.selectByExample(null);
}

2. 保存员工

EmployeeController#saveEmp:

/** 保存员工 */
@PostMapping("/emps")
@ResponseBody
public Msg saveEmp(@Valid Employee employee, BindingResult result) {
    
    
    if (result.hasErrors()) {
    
     //校验有错误, 将错误存到Msg中, 返回给客户端
        Msg msg = new Msg(ErrorCodeEnum.REQUEST_PARAM_ERROR);
        for (FieldError fieldError : result.getFieldErrors()) {
    
    
            msg.add(fieldError.getField(), fieldError.getDefaultMessage());
        }
        return msg;
    } else {
    
     //没错误, 才保存员工
        employeeService.saveEmp(employee);
        return Msg.success();
    }
}

EmployeeService#saveEmp:

/** 保存员工 */
public void saveEmp(Employee employee) {
    
    
    if (!StringUtils.hasLength(employee.getEmpName())) {
    
    
        throw new BusinessException(ErrorCodeEnum.REQUIRED_REQUEST_PARAM_EMPTY);
    }
    employee.setEmpId(null);
    employeeMapper.insertSelective(employee);
}

3. 后端***JSR 303***数据校验

前端JS校验只能防君子,不防小人,所以后端需要重新进行一遍完整的校验。

1、首先引入hibernate-validator依赖:

<!-- hibernate-validator支持JSR303数据校验 -->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.2.0.Final</version>
</dependency>

注意:不要导入7.0以上的版本,否则会报错我也不知道为什么…

2、在 springmvc.xml 文件中配置校验器**HibernateValidator****:**

<!-- 注册自己配置的校验器 -->
<mvc:annotation-driven validator="validator"/>
        
<!-- 配置校验器 -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
    <!-- 校验器,使用hibernate校验器 -->
    <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
</bean>

3、给**Employee*和*Department**类的相应属性添加校验注解:

public class Employee implements Serializable {
    
    
    private static final long serialVersionUID = 1L;
    private Integer empId;

    @NotNull(message = "员工姓名不能为空")
    @Pattern(regexp = "(^[a-zA-Z0-9_-]{3,16}$)|(^[\\u2E80-\\u9FFF]{2,8}$)", message = "姓名必须是2-8位中文, 或3-16字母下划线数字")
    private String empName;

    @Range(min = 1, max = 2, message = "性别只能为男或女")
    private Integer gender;

    @Email(message = "邮箱格式错误")
    private String email;
    private Integer dId;
    private Department department;
public class Department implements Serializable {
    
    
    private static final long serialVersionUID = 1L;
    private Integer deptId;
    @NotBlank
    private String deptName;

注意,这些校验注解仅仅会在Controller层创建入参对象的时候生效,对于Service或Dao层中的对象是无效的。

4、通过BindingResult入参获取校验结果

/** 保存员工 */
@PostMapping("/emps")
@ResponseBody
public Msg saveEmp(@Valid Employee employee, BindingResult result) {
    
    
    if (result.hasErrors()) {
    
     //校验有错误, 将错误存到Msg中, 返回给客户端
        Msg msg = new Msg(ErrorCodeEnum.REQUEST_PARAM_ERROR);
        for (FieldError fieldError : result.getFieldErrors()) {
    
    
            msg.add(fieldError.getField(), fieldError.getDefaultMessage());
        }
        return msg;
    } else {
    
     //没错误, 才保存员工
        employeeService.saveEmp(employee);
        return Msg.success();
    }
}

4. 效果演示

点击 “新增” 按钮,输入员工信息,点击保存。
在这里插入图片描述
在这里插入图片描述

修改员工

在这里插入图片描述

  • 点击“编辑”按钮
  • 弹出员工修改的模态框(显示员工信息)
  • 用户输入数据,点击“更新”按钮
  • 对数据进行校验,然后发送Ajax修改请求,完成用户修改
  • 关闭模态框,重新请求当前页码,以显示修改后的数据

前端实现

1. 单击编辑按钮, 弹出员工修改的模态框

//点击"编辑"按钮, 弹出"修改员工"模态框
//因为编辑按钮是未来创建的, 所以需要委托给#emp_table
$("#emp_table").on("click", ".edit_btn", function () {
    //重置表单(内容+样式)
    resetForm("#update_emp_form")
    //将编辑按钮上的员工id传递到更新按钮上
    $("#emp_update_btn").attr("emp_id", $(this).attr("emp_id"))
    //查询所有部门信息显示在模态框上
    var defered = getDepts("#update_emp_dept_select")
    //查询该员工信息并显示在模态框上
    var editBtn = $(this)
    defered.done(function () {
        getEmp(editBtn.attr("emp_id"), "#update_emp_form")
    })
    //展示模态框
    $("#update_emp_modal").modal()
    return false //阻止事件继续冒泡
})

为了便于后面获取员工的id,我们可以在构建员工表格的时候,为“编辑”和“删除”按钮添加一个自定义属性emp_id来存放员工的id。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-28SXSqAa-1657276618662)(https://secure2.wostatic.cn/static/vrsoVaU36UcyBAENnuN174/image.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BEvvgWJH-1657276618662)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220708182759774.png)]

2. Ajax查询所有部门信息并显示在模态框上

//查询所有部门信息, 显示在selector所指定的下拉列表中
function getDepts(selector) {
    $(selector).empty()
    return $.ajax({
        type: "GET",
        url: "${APP_PATH}/depts",
        dataType: "json",
        success: function (result) {
            $.each(result.dataMap.depts, function () {
                var option = $("<option></option>").attr("value", this.deptId).append(this.deptName)
                $(selector).append(option)
            })
        }
    })
}

3. Ajax查询该员工信息并显示在模态框上

//Ajax查询该员工信息并显示在模态框上
function getEmp(empId, selector) { //员工id, 表单选择器
    $.ajax({
        type: "GET",
        url: "${APP_PATH}/emps/" + empId,
        dataType: "json",
        success: function (result) {
            //将员工数据显示到"修改员工"的模态框上
            var $form = $(selector)
            $form.find(".form-control-static").text(result.dataMap.emp.empName)
            console.log($form.find(":text[name=email]"))
            $form.find(":input[name=email]").val(result.dataMap.emp.email)
            $form.find(":radio[name=gender]").val([result.dataMap.emp.gender])
            $form.find("select[name=dId]").val([result.dataMap.emp.dId])
        }
    })
}

4. 校验【更新表单】

//校验【更新表单】
function validateUpdateForm() {
    var empEmail = $("#update_emp_email_input").val()
    var regEmail = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/ //匹配电子邮箱
    if (regEmail.test(empEmail)) { //邮箱格式正确
        showValidateMsg("#update_emp_email_input", "success", "")
        return true
    } else { //邮箱格式错误
        showValidateMsg("#update_emp_email_input", "error", "邮箱格式错误")
        return false
    }
}

//内容一改变就进行校验
$("#update_emp_email_input").change(function () {
    validateUpdateForm()
})

5. 点击更新, Ajax更新员工信息

//点击更新, Ajax更新员工信息
$("#emp_update_btn").click(function () {
    //校验【更新表单】
    if (!validateUpdateForm()) {
        return false
    }
    //Ajax更新员工信息
    $.ajax({
        type: "POST",
        url: "${APP_PATH}/emps/" + $(this).attr("emp_id"),
        data: $("#update_emp_form").serialize() + "&_method=PUT",
        dataType: "json",
        success: function (result) {
            if (result.errorCode.value == "00000") { //更新成功
                //关闭模态框
                $("#update_emp_modal").modal("hide")
                //重新请求当前页码, 获得更新后的员工信息
                toPage(pageNum, rows)
            } else {
                if (result.dataMap.email) { //带有email这个错误
                    showValidateMsg("#update_emp_email_input", "error", result.dataMap.email);
                }
            }
        }
    })
})

后端实现

1. 根据员工id查询员工

EmployeeController#getEmp:

/** 根据员工id查询员工 */
@GetMapping(value = "/emps/{id}")
@ResponseBody
public Msg getEmp(@PathVariable("id") int id) {
    
    
    Employee employee = employeeService.getEmpById(id);
    return Msg.success().add("emp", employee);
}

EmployeeService#getEmpById:

/** 根据员工id查询员工 */
public Employee getEmpById(int id) {
    EmployeeExample example = new EmployeeExample();
    example.createCriteria().andEmpIdEqualTo(id);
    return employeeMapper.queryByExampleWithDept(example).get(0);
}

2. 更新员工

EmployeeController#updateEmp:

/** 更新员工 */
//路径变量写成{empId}可以保证数据绑定器能正确将{empId}注入到Employee入参的empId属性中
@PutMapping(value = "/emps/{empId}")
@ResponseBody
public Msg updateEmp(Employee employee) {
    
    
    employeeService.updateEmp(employee);
    return Msg.success();
}

EmployeeService#updateEmp:

/** 更新员工 */
public void updateEmp(Employee employee) {
    if (employeeMapper.updateByPrimaryKeySelective(employee) != 1) {
        throw new BusinessException(ErrorCodeEnum.SYSTEM_EXECUTION_ERROR);
    }
}

3. 效果演示

点击“编辑”,输入数据,点击“更新”。

在这里插入图片描述

删除员工

在这里插入图片描述

删除步骤:

1、点击删除按钮,弹出确认框

2、Ajax删除员工

3、Ajax重新请求本页数据

前端—点击删除按钮,Ajax删除员工

//点击删除按钮, 弹出员工删除确认框, 确认后发送员工删除请求
$("#emp_table").on("click", ".delete_btn", function () {
    var empId = $(this).attr("emp_id")
    var empName = $(this).parents("tr").find("td:eq(2)").text()

    if (confirm("你确定要删除【" + empName + "】吗?")) {
        //Ajax发送员工删除请求
        $.ajax({
            type: "POST",
            url: "${APP_PATH}/emps/" + empId,
            data: "" + "_method=DELETE",
            dataType: "json",
            success: function (result) {
                if (result.errorCode.value == "00000") { //删除成功, 刷新页面
                    toPage(pageNum, rows)
                } else { //删除失败, 显示错误信息
                    //懒得写了...
                }
            }
        })
    }
    return false
})

后端—根据员工id删除员工

EmployeeController#deleteEmp:

/** 根据员工id删除员工 */
@DeleteMapping(value = "/emps/{empId}")
@ResponseBody
public Msg deleteEmp(@PathVariable("empId") int empId) {
    
    
    employeeService.deleteEmp(empId);
    return Msg.success();
}

EmployeeService#deleteEmp:

/** 根据员工id删除员工 */
public void deleteEmp(int empId) {
    if (employeeMapper.deleteByPrimaryKey(empId) != 1) {
        throw new BusinessException(ErrorCodeEnum.SYSTEM_EXECUTION_ERROR);
    }
}

批量删除

删除步骤:

1、用户勾选需要删除的条目

2、点击删除按钮,弹出确认框

3、发送Ajax批量删除请求

4、Ajax刷新当前页面

前端

1. 实现全选/全不选的复选框逻辑

//实现全选/全不选的复选框逻辑

//为全选框绑定单击事件
$("#check_all").click(function () {
    //这里必须用prop(), attr()只能获取显式指定的属性值
    var flag = $(this).prop("checked") 
    $(".check_item").prop("checked", flag)
})

//为每个复选框绑定单击事件
$("#emp_table").on("click", ".check_item", function () {
    //获取当前的选中个数
    var checkCount = $(".check_item:checked").length
    //如果选中个数和复选框总个数相等, 就把全选框勾上, 否则划掉
    var flag = checkCount == $(".check_item").length;
    if (flag) {
        $("#check_all").prop("checked", true)
    } else {
        $("#check_all").prop("checked", false)
    }
})

参考全选/全不选/反选。

2. 点击删除,发送Ajax批量删除请求

//点击删除, 发送Ajax批量删除请求
$("#delete_all_check").click(function () {
    //如果没有一个选中的, 则直接返回
    if ($(".check_item:checked").length == 0) {
        alert("请勾选要删除的条目!")
        return false
    }

    var empNames = "";
    var ids = ""
    $.each($(".check_item:checked"), function () {
        empNames += $(this).parents("tr").find("td:eq(2)").text() + ", " //拼接要删除的员工姓名
        ids += $(this).parents("tr").find("td:eq(1)").text() + "-" //拼接要删除的员工id
    })
    empNames = empNames.substring(0, empNames.length - 2) //去掉最后一个", "
    ids = ids.substring(0, ids.length - 1) //去掉最后一个"-"
    //弹出确认框
    if (confirm("你确定要删除【" + empNames + "】吗?")) {
        //发送Ajax批量删除请求
        $.ajax({
            type: "POST",
            url: "${APP_PATH}/emps/" + ids,
            data: "_method=DELETE",
            dataType: "json",
            success: function (result) {
                if (result.errorCode.value == "00000") { //删除成功, Ajax刷新页面
                    toPage(pageNum, rows)
                } else { //删除失败, 显示错误信息

                }
            }
        })
    }
})

后端

1. 处理批量删除请求

EmployeeController#deleteEmp:

对前面的*/** 根据员工id删除员工 */ @DeleteMapping(value = “/emps/{empId}”) @ResponseBody public Msg deleteEmp(@PathVariable(“empId”) int empId) { employeeService.deleteEmp(empId); return Msg.success(); }*进行改造,使其支持单个删除和批量删除。

/**
 * 根据员工id删除员工, 支持批量删除
 * 单个删除: /emps/id1
 * 批量删除: /emps/id1-id2-id3...
 */
@DeleteMapping(value = "/emps/{empIds}")
@ResponseBody
public Msg deleteEmp(@PathVariable("empIds") String empIds) {
    
    
    if (empIds.contains("-")) {
    
     //批量删除
        ArrayList<Integer> idList = new ArrayList<>();
        for (String id : empIds.split("-")) {
    
    
            idList.add(Integer.parseInt(id));
        }
        employeeService.deleteBatch(idList);
    } else {
    
     //单个删除
        int id = Integer.parseInt(empIds);
        employeeService.deleteEmp(id);
    }
    return Msg.success();
}

EmployeeService#deleteBatch:

/** 批量删除员工 */
public void deleteBatch(List<Integer> ids) {
    EmployeeExample example = new EmployeeExample();
    example.createCriteria().andEmpIdIn(ids);
    employeeMapper.deleteByExample(example);
}

上面的/**

  • 根据员工id删除员工, 支持批量删除
  • 单个删除: /emps/id1
  • 批量删除: /emps/id1-id2-id3… */ @DeleteMapping(value = “/emps/{empIds}”) @ResponseBody public Msg deleteEmp(@PathVariable(“empIds”) String empIds) { if (empIds.contains(“-”)) { //批量删除 ArrayList idList = new ArrayList<>(); for (String id : empIds.split(“-”)) { idList.add(Integer.parseInt(id)); } employeeService.deleteBatch(idList); } else { //单个删除 int id = Integer.parseInt(empIds); employeeService.deleteEmp(id); } return Msg.success(); }还可以再优化!!我们可以将拆分路径变量的过程封装起来,即自定义Converter (类型转换器)。这样未来如果需要重复应用这个转换逻辑时,我们就不必再写一遍了。

自定义的Converter代码如下:

/**
 * Spring到List<Integer>的转换器
 * @author OneIce
 * @since 2021/3/28 20:19
 */
public class StringToIntegerListConverter implements Converter<String, List<Integer>> {
    
    
    private String delimiter = "-";

    public StringToIntegerListConverter() {
    
    
    }

    public StringToIntegerListConverter(String delimiter) {
    
    
        this.delimiter = delimiter;
    }

    @Override
    public List<Integer> convert(String source) {
    
    
        ArrayList<Integer> list = new ArrayList<>();
        if (source.contains(delimiter)) {
    
    
            String[] params = source.split(delimiter);
            for (String param : params) {
    
    
                list.add(Integer.parseInt(param));
            }
        } else {
    
    
            list.add(Integer.parseInt(source));
        }
        return list;
    }
}

然后在springmvc.xml注册这个转换器:

<mvc:annotation-driven validator="validator" 
conversion-service="conversionService"/>

<!--配置指定的ConversionService, 用于类型转换和数据格式化-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <!--注册自定义的类型转换器-->
    <property name="converters">
        <set>
            <bean class="pers.oneice.ssm.crud.converter.StringToIntegerListConverter"/>
        </set>
    </property>
</bean> 

现在deleteEmp() 方法可以精简成下面这样:

/**
 * 根据员工id删除员工, 支持批量删除
 * 单个删除: /emps/id1
 * 批量删除: /emps/id1-id2-id3...
 */
@DeleteMapping(value = "/emps/{empIds}")
@ResponseBody
public Msg deleteEmp(@PathVariable("empIds") List<Integer> ids) {
    
    
    if (ids.size() == 1) {
    
     //单个删除
        employeeService.deleteEmp(ids.get(0));
    } else {
    
     //批量删除
        employeeService.deleteBatch(ids);
    }
    return Msg.success();
}

2. 效果演示

勾选要删除的条目,点击右上角“删除”:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eqFlWpHV-1657276618664)(https://secure2.wostatic.cn/static/sREEL4uJphad6VQYtZEfu2/image.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x23T0eGI-1657276618665)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220708183033131.png)]

点击“确定”:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bRXPNXoM-1657276618665)(https://secure2.wostatic.cn/static/8dVeLcrTWQgMQpzZRc11b7/image.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qRMYl0PJ-1657276618665)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220708183046960.png)]

消失不见了~

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fbDQ8A17-1657276618666)(https://secure2.wostatic.cn/static/3uU2r4pHVxyaduFZBi9Ehs/image.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ovq3Jops-1657276618666)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220708183102750.png)]

条件查询

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rGJhoZr6-1657276618667)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220708183151646.png)]

闲着没事,想尝试自己实现一个条件查询的功能。大致步骤如下:

  1. 编写一个输入框组,包含一个表单(查询条件+关键字)
  2. 当用户点击“搜索”时,发送Ajax条件查询请求
  3. 构建员工列表、分页信息、导航条

前端

为了重用之前写过的优化-Ajax分页查询代码,我增加了一个全局变量queryUrl,用来记录上一次查询时的url。每次Ajax查询时就使用这个queryUrl作为请求地址。queryUrl初始值是"${APP_PATH}/emps",在进行条件查询前,该变量会被赋为"${APP_PATH}/emps?type=xxx"。这样在切换页码的时候,就会继续按上一次的条件进行分页查询了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HAGVQOJj-1657276618667)(https://secure2.wostatic.cn/static/734PoZskrxM237EPK3pVDU/image.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hZkOFnsD-1657276618667)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220708183238321.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-74mnHm8n-1657276618667)(https://secure2.wostatic.cn/static/cTFDd8WjdPprWZEunLfz6m/image.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VvPMC1AY-1657276618668)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220708183253072.png)]

诚然,这种方式不太优雅。暂时我也想不到更好的法子了,凑合用吧!

接下来前端这里只需要编写一个输入框组,让用户输入搜索词。

<%-- 输入框组, 用于条件查询 --%>
<div class="row">
    <div class="col-md-3"></div>
    <div class="col-md-6">
        <form id="search_form" action="#">
            <div class="input-group">
                <div class="input-group-btn">
                    <button id="type_btn" type="button" class="btn btn-default dropdown-toggle"
                            data-toggle="dropdown"
                            aria-haspopup="true" aria-expanded="false">姓名 <span class="caret"></span></button>
                    <ul class="dropdown-menu">
                        <li><a name="byEmpName" href="javaScript:;">姓名</a></li>
                        <li><a name="byId" href="javaScript:;">编号</a></li>
                        <li><a name="byEmail" href="javaScript:;">邮箱</a></li>
                    </ul>
                </div>
                <input type="hidden" name="type" value="byEmpName">
                <input name="keyword" type="text" class="form-control" placeholder="Search for...">

                <span class="input-group-btn">
                    <button class="btn btn-primary" type="submit">
                        <span class="glyphicon glyphicon-search" aria-hidden="true"></span> 搜索
                    </button>
                </span>
            </div>
        </form>
    </div>
</div>

然后点击“搜索”后,发送Ajax条件查询请求。

//点击下拉列表时, 修改表单隐藏域的value值
$("#search_form .dropdown-menu a").click(function () {
    var text = $(this).text()
    var name = $(this).attr("name")
    $("#type_btn").html(text + ' <span class="caret"></span>')
    $("#search_form input[name=type]").val(name)
})

//点击"搜索", 发送Ajax查询请求
$("#search_form").submit(function () {
    //修改用于查询的url
    queryUrl = "${APP_PATH}/emps?" + $(this).serialize()
    //发送Ajax查询请求
    toPage(1, rows)
    return false
})

后端

至于后端,我对原本的/**

  • 分页查询员工信息, 返回Json数据
  • @param page 页码
  • @param rows 记录数
  • @return 分页数据(包含查询到的员工数据), 由MappingJackson2CborHttpMessageConverter解析成Json字符串 */ @ResponseBody @GetMapping(“/emps”) public Msg getEmpsWithJson(@RequestParam(value = “page”, defaultValue = “1”) int page, @RequestParam(value = “rows”, defaultValue = “10”) int rows) { PageInfo pageInfo = employeeService.getEmps(page, rows); Msg msg = Msg.success(); msg.add(“pageInfo”, pageInfo); return msg; }进行了改造,增加了两个参数用于接收查询条件。

EmployeeController#getEmpsWithJson:

/**
 * 分页查询员工信息, 返回Json数据
 * @param page    页码
 * @param rows    记录数
 * @param type    查询条件, 可以为null
 * @param keyword 搜索词, 可以为null
 * @return 分页数据(包含查询到的员工数据), 由MappingJackson2CborHttpMessageConverter解析成Json字符串
 */
@ResponseBody
@GetMapping("/emps")
public Msg getEmpsWithJson(@RequestParam(value = "page", defaultValue = "1") int page,
                           @RequestParam(value = "rows", defaultValue = "10") int rows,
                           @RequestParam(value = "type", required = false) String type,
                           @RequestParam(value = "keyword", required = false) String keyword) {
    
    
    PageInfo<Employee> pageInfo = employeeService.getEmps(page, rows, type, keyword);
    Msg msg = Msg.success();
    msg.add("pageInfo", pageInfo);
    return msg;
}

同样的,EmployeeService也要修改一下,主要就是增加了一个查询条件的判断逻辑,使之能根据查询条件来执行不同的SQL。EmployeeService#getEmps:

  /**
   * 根据要查询的页码和页大小, 分页查询员工数据
   * @param page    当前页码
   * @param rows    当前页大小, 即查询多少条记录
   * @param type    查询条件, 可以为null
   * @param keyword 搜索词, 可以为null
   * @return 所有员工信息
   */
  public PageInfo<Employee> getEmps(int page, int rows, String type, String keyword) {
    
    
      PageHelper.startPage(page, rows);
      EmployeeExample example = new EmployeeExample();
      if (StringUtils.hasLength(type) && StringUtils.hasLength(keyword)) {
    
    
          if (type.equals("byEmpName")) {
    
    
              example.createCriteria().andEmpNameLike("%" + keyword + "%");
          } else if (type.equals("byEmail")) {
    
    
              example.createCriteria().andEmailLike("%" +keyword + "%");
          } else if (type.equals("byId")) {
    
    
              example.createCriteria().andEmpIdEqualTo(Integer.parseInt(keyword));
          }
      }
      List<Employee> emps = employeeMapper.queryByExampleWithDept(example);
      return new PageInfo<>(emps, 5);
  }

效果演示

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_45936141/article/details/125684086