SpringBoot+MyBatisPlus技术总结

SpringBoot+MyBatisPlus

官网简介 | MyBatis-Plus (baomidou.com)

MyBatis-Plus (简称 MP)是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

只需简单的配置,就能实现对单表的CURD。

其核心有两个接口:BaseMapper和IService

BaseMapper中封装了大量数据访问层的方法

IServcie中封装了大量业务流程层的方法

SpringBoot+MyBatisPlus

通过IDEA创建

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WGaNT9sj-1676451781113)(C:\Users\20525\AppData\Roaming\marktext\images\2023-02-15-14-49-13-image.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-emslZ4s8-1676451781114)(C:\Users\20525\AppData\Roaming\marktext\images\2023-02-15-15-06-44-image.png)]

热部署

项目在开发过程中,可以不需要每次都重启,等待一段时间后会自动更新编译运行

使用

添加依赖,可以在创建的项目的时候选择,也可以中途添加

<!--热部署-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <version>2.7.8</version>
</dependency>

开启热部署

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3aBZKgVR-1676451781114)(C:\Users\20525\AppData\Roaming\marktext\images\2023-02-15-15-09-16-image.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JO2K8d5D-1676451781114)(C:\Users\20525\AppData\Roaming\marktext\images\2023-02-15-15-09-51-image.png)]

Lombok

用于简化实体类中模板代码的工具

使用

添加依赖,可以在创建的项目的时候选择,也可以中途添加

<!--Lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
    <scope>provided</scope>
</dependency>

安装插件(IDEA2020.2之后的版本会内置Lombok插件,无需安装)

在某个实体类上添加注解

lombok常用注解作用
@AllArgsConstructor自动生成全参构造方法
@Data以下注解之和
@Setter自动生成set方法
@Getter自动生成get方法
@NoArgsConstructor自动生成无参构造方法
@ToString自动生成toString方法
@EqualsAndHashcode自动生成equals和hashcode方法

SpringBoot+MyBatis实现单表查询

在springboot配置文件application.properties中

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/gamedb?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

# 开启sql日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 无需加入开启驼峰命名映射,MyBatisPlus默认使用驼峰命名进行属性-字段映射
#mybatis-plus.configuration.map-underscore-to-camel-case=true

mybatis的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">
<!--设置该文件对应的dao层接口全限定名-->
<mapper namespace="">

</mapper>

导入SpringBoot集成MyBatisPlus依赖

<!-- SpringBoot集成MyBatisPlus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>

测试

在SpringBoot自带的单元测试类中,注入HeroMapper对象,调用BaseMapper中定义的方法即可实现CURD。

BaseMapper接口

BaseMapper接口中定义了常用的增删改查方法,

在数据访问层接口中继承该接口

方法列表

使用

public interface HeroMapper extends BaseMapper<Hero> {

}

BaseMapper接口中的常用方法

方法名参数作用
selectList(Wrapper wrapper)条件构造器根据条件查询集合,如果实参为null表示查询所有,返回List集合
selectById(Serializable id)主键根据主键查询单个对象,返回单个对象
selectOne(Wrapper wrapper)条件构造器条件查询单个对象,返回单个对象
insert(T entity)实体对象添加单个实体
updateById(T entity)实体对象根据实体对象单个修改,对象必须至少有一个属性和主键
update(T entity,Wrapper wrapper)实体对象和条件构造器根据条件修改全部,对象必须至少有一个属性
deleteById(Serializable id/T entity)主键/实体对象根据主键删除单个对象
deleteBatchIds(Collection ids)主键集合根据集合删除
delete(Wrapper wrapper)条件构造器根据条件删除,如果实参为null表示无条件删除所有

IService接口中的常用方法

方法作用
list()无条件查询所有
list(Wrapper wrapper)条件查询素有
page(Page page)无条件分页查询,Page是分页模型对象
page(Page page,Wrapper wrapper)条件分页查询,Page是分页模型对象
getById(Serializable id)根据主键查询单个对象
getOne(Wrapper wrapper)条件查询单个对象
save(T entity)添加单个对象
save(Collection col)批量添加对象的集合
updateById(T entity)修改,参数至少有一个属性值和主键
saveOrUpdate(T entity)添加或修改。如果实参对象的主键值不存在则添加,存在则修改
update(T entity,Wrapper wrapper)条件修改,条件为null则修改全部数据
removeById(Serializable id/T entity)根据主键或包含主键的对象删除
removeBatchByIds(Collection ids)根据集合删除
remove(Wrapper wrapper)根据条件删除,条件为null则删除全部

IService接口

IService接口减少业务逻辑层的代码,并对BaseMapper进行了拓展

在业务流程类中继承该接口

部分方法列表

使用

  • 1.创建一个业务层接口,继承IService接口,设置泛型

    /*
    * 创建业务逻辑层接口,继承IService<T>接口,设置泛型
    * */
    public interface HeroService extends IService<Hero> {
    }
    
  • 2.创建一个业务层接口的实现类,添加@Service注解,继承 ServiceImpl<M, T>,实现上一步创建的接口

    • M是数据访问层接口
    • T是实体类
    /*
    * 1.添加@Service
    * 2.继承ServiceImpl<M, T> M是Mapper类 T是实体类
    * 3.实现自定义Service接口
    * */
    @Service
    public class ImpHeroService extends ServiceImpl<HeroMapper, Hero> implements HeroService {
    
    }
    

IService接口中的常用方法

方法作用
list()无条件查询所有
list(Wrapper wrapper)条件查询素有
page(Page page)无条件分页查询,Page是分页模型对象
page(Page page,Wrapper wrapper)条件分页查询,Page是分页模型对象
getById(Serializable id)根据主键查询单个对象
getOne(Wrapper wrapper)条件查询单个对象
save(T entity)添加单个对象
save(Collection col)批量添加对象的集合
updateById(T entity)修改,参数至少有一个属性值和主键
saveOrUpdate(T entity)添加或修改。如果实参对象的主键值不存在则添加,存在则修改
update(T entity,Wrapper wrapper)条件修改,条件为null则修改全部数据
removeById(Serializable id/T entity)根据主键或包含主键的对象删除
removeBatchByIds(Collection ids)根据集合删除
remove(Wrapper wrapper)根据条件删除,条件为null则删除全部

条件构造器Wrapper

BaseMapper和IService接口中有很多方法都有这个参数,表示一个条件构造器对象。

如果该参数实际传递的值为null,表示没有任何条件,

这个Wrapper是一个抽象类,如果想要带条件,就要创建一个该类的子类对象。

常用子类为QueryWrapper和UpdateWrapper,

查询是创建QueryWrapper对象,更新时创建UpdateWrapper,实际使用无区别。

Wrapper对象带参数

Wrapper<T> wrapper =  new QueryWrapper(T entity);
QueryWrapper<T> wrapper =  new QueryWrapper(T entity);

Wrapper构造方法的参数如果是一个实体对象,只要该对象的属性不为空,就会将所有属性用and拼接起来作为条件。

这种适合已知某个字段为某个值时使用。

@Test
void test1() {
    //创建实体对象,只带两个属性值
    Hero hero = new Hero();
    hero.setHeroName("瑞兹");
    hero.setSex("女");
    //创建一个带实体参数的条件构造器对象
    Wrapper<Hero> wrapper = new QueryWrapper<>(hero);
    System.out.println(heroService.getOne(wrapper));
}

Wrapper对象不带参数

Wrapper<T> wrapper =  new QueryWrapper();
QueryWrapper<T> wrapper =  new QueryWrapper();

当条件构造器不带参数时,就需要自定义条件。

这种适用于自定义查询条件。

默认多个条件时,用and连接,如果条件之间要使用or,调用or()方法

指定或范围

@Test
void test2() {
    /*
    * eq(String column,Object val)      equals          column = val
    * ne(String column,Object val)      not equals      column <> val
    * lt(String column,Object val)      less then       column < val
    * gt(String column,Object val)      great then      column > val
    * le(String column,Object val)      less equals     column <= val
    * ge(String column,Object val)      great equals    column >= val
    * between(String column Object val1,Object val2)    between and          column between val1 and bal2
    * notBetween(String column Object val1,Object val2) not between and      column not between val1 and bal2
    * */


    //创建一个不带实体参数的条件构造器对象
    QueryWrapper<Hero> wrapper = new QueryWrapper<>();
    //自定义条件:指定值
    //查询性别为男的数据  eq equals    =
    //wrapper.eq("sex","男");
    //查询性别不为男的数据 ne not equals   <>
    //wrapper.ne("sex","男");

    //wrapper.between("price",1000,4000);

    //查询价格小于5000的战士
    //多个条件默认用and拼接
    /*wrapper.lt("price",5000);
    wrapper.eq("position","战士");*/

    //查询性别为女或价格大于3000
    //or()方法会将前后的条件使用or拼接
    /*wrapper.gt("price",3000);
    wrapper.or();
    wrapper.eq("sex","女");*/

    //查询性别为男的战士或价格大于3000的法师
    //支持链式写法
    wrapper.eq("sex","男")
            .eq("position","战士")
            .or()
            .eq("position","法师")
            .gt("price","3000");

    heroService.list(wrapper).forEach(System.out::println);
}

空值和模糊查询

@Test
void test3() {
    /*
         * isNull(String column)                            column is null
         * isNotNull(String column)                         column is not null
         * like(String column,Object val)                   column like '%val%'
         * likeLeft(String column,Object val)               column like '%val'
         * likeRight(String column,Object val)              column like 'val%'
         * notLike(String column,Object val)                column not like '%val%'
         * notLikeLeft(String column,Object val)            column not like '%val'
         * notLikeRight(String column,Object val)           column not like 'val%'
         * */
    QueryWrapper<Hero> wrapper = new QueryWrapper<>();

    //字段为空
    //wrapper.isNull("shelf_date");
    //带有指定关键字
    //wrapper.like("name","琳");
    //以指定关键字结尾
    //wrapper.likeLeft("name","琳");
    //以指定关键字开头
    //wrapper.likeRight("name","伊");

    //查询名字中带有琳字且上架时间为空
    wrapper.like("name","琳").isNull("shelf_date");

    heroService.list(wrapper).forEach(System.out::println);
}

集合和排序

@Test
    void test4() {
        /*
        *   集合
        *   in(String column,Object... vals)            column in (val1,val2...)
        *   in(String column,Collection vals)           column in (val1,val2...)
        *   notIn(String column,Object... vals)         column not in (val1,val2...)
        *   notIn(String column,Collection vals)        column not in (val1,val2...)
        *
        *   排序
        *   orderByAsc(String column)                   order by column asc
        *   orderByDesc(String column)                  order by column desc
        *
        * */


        QueryWrapper<Hero> wrapper = new QueryWrapper<>();
        //职业为战士或法师
        //wrapper.eq("position","战士").or().eq("position","法师");
        //可变参数
        //wrapper.in("position","战士","法师");
        //集合
        //wrapper.in("position", Arrays.asList("战士","法师"));

        //所有男性角色,按价格降序
        wrapper.eq("sex","男").orderByDesc("price");

        heroService.list(wrapper).forEach(System.out::println);
    }

分组和聚合函数(自定义查询)

分组通常会和聚合函数(count、sum、min、max、avg)一起使用,但MyBatisPlus中没有现成的聚合函数。

需要使用**select(String… fields)**自定义查询字段。

如查询每个每个位置的总数

条件构造器对象.select(“position”,“count(id)”),对应的sql查询部分为select “position”,“count(id)” from hero

@Test
void test() {
    /*
    * select(String... fields)          select field1,field2...
    * groupBy(String column)            group by column
    * */

    QueryWrapper<Hero> wrapper = new QueryWrapper<>();
    //无参数时,查询所有字段
    //wrapper.select();
    //查询指定字段
    //wrapper.select("name");
    //使用聚合函数
    wrapper.select("count(id)");

    //查询不同性别的人数
    //select sex,count(id) from hero group by sex
    //wrapper.select("sex","count(id)");
    //根据指定字段分组
    //wrapper.groupBy("sex");

    //当前的sql语句查询的结果是一个订制数据,如查询总数只会得到一个数据,所以使用listMaps()方法
    //List<Map<String, Object>> maps = heroService.listMaps(wrapper);

    //根据定位分组,查询值为法师、战士的人数,按人数降序
    wrapper.select("position","count(id) as count")
            .in("position","战士","法师")
            .groupBy("position")
            .orderByDesc("count");

    List<Map<String, Object>> maps = heroService.listMaps(wrapper);
    System.out.println(maps);
}

分页查询

MyBatisPlus中集成了分页功能,只需在项目的启动类中注入一个分页拦截器对象

/*
        注入一个MyBatisPlus提供的分页拦截器对象
        定义一个方法,在方法上加入@Bean注解,将该方法的返回值注入到Spring容器中
    */
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
    //创建一个MybatisPlus拦截器对象
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    //通过拦截器对象添加分页拦截器对象,设置数据库类型
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    return interceptor;
}
@Test
void test1(){
    //无条件分页查询page(Page pageInfo)
    //1.创建一个分页模型对象,设置泛型,参数为当前页和每页显示的记录数
    Page<Hero> pageInfo = new Page<>(1, 5);
    //2.调用IService中的page方法,参数为分页模型对象,返回值也是该对象
    pageInfo = heroService.page(pageInfo);
    //分页的相关信息都保存在分页模型对象pageInfo中
    System.out.println("总页数"+pageInfo.getPages());
    System.out.println("当前页"+pageInfo.getCurrent());
    System.out.println("每页显示的记录数"+pageInfo.getSize());
    System.out.println("总记录数"+pageInfo.getTotal());
    System.out.println("分页后的集合"+pageInfo.getRecords());
    System.out.println("是否有下一页"+pageInfo.hasNext());
    System.out.println("是否有上一页"+pageInfo.hasPrevious());


    //条件分页page(Page pageInfo,Wrapper wrapper)
    //分页查询所有法师
    pageInfo= heroService.page(pageInfo,new QueryWrapper<Hero>().eq("position","法师"));
    pageInfo.getRecords().forEach(System.out::println);
}

代码生成器

MyBatisPlus中用于自动生成entity、mapper、service、controller这些类和接口的工具

1.添加依赖

<!-- 代码生成器 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.3</version>
</dependency>
<!--代码生成器所需引擎-->
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.3</version>
</dependency>

2.代码生成器类

修改以下几处

  • 数据库信息
  • 输出目录为本地硬盘目录
  • 父包名
  • 要生成的表名

3.thymeleaf具体使用

  • 双标签中的输出变量: th:text

    <h1 th:text="${作用域中某个对象名}"></h1>
    
    <h1 th:text="${username}"></h1>
    
  • 单标签中设置value的值:th:value

    <input type="text" th:value="${作用域中某个对象名}">
    
    <input type="text" th:value="${变量.属性名}">
    
  • 遍历:th:each

    <table>
        <tr th:each="变量:${作用域中的集合名}">
            <td th:text="${变量.属性名}"></td>
        </tr>
    </table>
    
    <table>
        <tr th:each="hero:${heroList}">
            <td th:text="${hero.id}"></td>
            <td th:text="${hero.name}"></td>
            <td th:text="${hero.sex}"></td>
        </tr>
    </table>
    
  • 超链接获取全局路径:th:href=“@{/}”

    • 不带参数

      <link th:href="@{/bootstrap-3.4.1-dist/css/bootstrap.css}" rel="stylesheet">
      
    • 带参数

      <a th:href="@{'/hero/delete?id='+${hero.id}">删除</a>
      
      <a th:href="@{/question/findById(id=${question.quesCode})}">编辑</a>
      
  • 表单提交路径:th:action=“@{/}”

    <form methos="post" th:action="@{/hero/insert}">
    
    </form>
    
  • 判断:th:if

    <h1 th:if="判断逻辑">用户信息</h1>
    
    <h1 th:if="${userinfo.username==null}">请登录</h1>
    
  • 选中单选按钮或复选框:th:checked

    <input type="radio" value="" name="sex" th:checked="逻辑判断">
    
    <input type="radio" value="" name="sex" th:checked="${user.sex=='男'}"><input type="radio" value="" name="sex" th:checked="${user.sex eq '女'}">
  • 选中下拉菜单:th:selected

    <select>
        <option th:selected="逻辑判断"></option>
    </select>
    
    <select>
        <option th:selected="${book.typeId==bookType.typeId}" th:each="bookType:${list}" th:value="${bookType.typeId}" th:text="${bookType.typeName}"></option>
    </select>
    
  • src属性:th:src

    <script th:src="@{/bootstrap-3.4.1-dist/js/jquery-3.6.2.min.js}"></script>
    
  • 内联表达式

    Thymeleaf页面中使用[[]]可以在script标签中使用EL表达式,这两对中括号拼接的内容称为内联表达式。

    在给script标签加入**th:inline=“javascript”**后使用

    <script th:inline="javascript">
        $(function () {
            //遍历页码
            $(".pno").each(function (){
                //如果页码和当前页一致,高亮显示
                if($(this).text()==[[${pageInfo.current}]]){
                    $(this).addClass("active");
                }
            });
        });
    </script>
    
    <script th:inline="javascript">
        $(function () {
            //使用ajax读取所有的题目类型对象,遍历成option
            $.ajax({
                url:[[@{/qc/queryAll}]],
                success:function (res){
                    for(var i=0;i<res.length;i++){
                        let qcId = res[i].qcId;
                        let qcName = res[i].qcName;
                        $("<option></option>").text(qcName).val(qcId).appendTo($("select[name=qcId]"));
                    }
                }
            });
        });
    </script>
    
package com.hqyj.question_sys.util;


import com.baomidou.mybatisplus.generator.FastAutoGenerator;

public class CodeGenerator {
    public static void main(String[] args) {
        //***数据库的url, 用户名和密码
        FastAutoGenerator.create("jdbc:mysql://localhost:3306/question_sys?serverTimezone=Asia/Shanghai",
                                 "root", "root")
            .globalConfig(builder -> {
                builder.author("HQYJ") // 设置作者,生成文件的注释里的作者
                    .outputDir("F:\\框架\\question_sys\\src\\main\\java"); //***指定输出目录
            }).packageConfig(builder -> {
            builder.parent("com.hqyj.question_sys"); //***设置父包名
            // .controller("controller") //控制层包名
            // .service("service") //service接口包名
            // .serviceImpl("service.impl")  //service接口实现包名
            // .mapper("mapper")  //mapper接口包名
            // .xml("mapper.xml") //映射文件的包名(如果要生成的话,加上这句,去掉下面的.xml方法)
            // .entity("entity"); //实体类的包名
        }).strategyConfig(builder -> {
            builder.addInclude("question_catalog","question")  //***设置需要生成的表名
                .controllerBuilder();//只生成Controller
            //.enableRestStyle(); //生成的Controller类添加@RestController;
        }).templateConfig(builder -> {
            builder.xml(null); //禁止生成xml映射文件
        }).execute();
    }
}

运行该类即可自动生成

MyBatisPlus中的逻辑删除和物理删除

物理删除:将数据真正删除。delete from 表 where id=值

逻辑删除:数据不删除,而是打个"被删除"的标记。额外添加一个是否被删除的字段"deleted",其值为0或1,0表示未删除,1表示已删除。

​ 当删除某条数据时,执行修改 update 表 set deleted=1 where id=值

添加了"deleted"字段后,查询要更改为select * from 表 where deleted=0

根据数据表创建实体类、dao层接口、service、controller

查询所有蔬菜

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EGq3mw2R-1676451781115)(C:\Users\20525\AppData\Roaming\marktext\images\2023-02-15-15-13-06-image.png)]

entity

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AA2aHGNh-1676451781115)(C:\Users\20525\AppData\Roaming\marktext\images\2023-02-15-15-14-18-image.png)]

dao层

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LOpxaCxI-1676451781116)(C:\Users\20525\AppData\Roaming\marktext\images\2023-02-15-15-15-33-image.png)]

service层

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hfYZPpTB-1676451781116)(C:\Users\20525\AppData\Roaming\marktext\images\2023-02-15-15-15-51-image.png)]

controller层

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jJ4ySSWV-1676451781116)(C:\Users\20525\AppData\Roaming\marktext\images\2023-02-15-15-16-39-image.png)]

查询所有

在接口中写下方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jG0pshLB-1676451781116)(C:\Users\20525\AppData\Roaming\marktext\images\2023-02-15-15-18-43-image.png)]

在vegetable中重写接口方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cXsjPOhG-1676451781117)(C:\Users\20525\AppData\Roaming\marktext\images\2023-02-15-15-19-32-image.png)]

在控制层controller中调用该方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qhM0NDSq-1676451781117)(C:\Users\20525\AppData\Roaming\marktext\images\2023-02-15-15-19-57-image.png)]

在spring boot的启动类上,加上@MapperScan注解,扫描Dao层所在的包

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ogEp9hMi-1676451781117)(C:\Users\20525\AppData\Roaming\marktext\images\2023-02-15-15-21-45-image.png)]

启动项目,按自定的项目上下文路径和端口号访问某个controller中的方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E2DEkCF0-1676451781117)(C:\Users\20525\AppData\Roaming\marktext\images\2023-02-15-15-22-53-image.png)]

LayUI数据表格

数据表格所需的数据接口格式为

{
    code:0,
    msg:"",
    count:1000,
    data:[{},{}]
}

构造满足LayUI数据表格的数据模型类ResultData

package com.bjxy.vegetable.utils;

import lombok.Data```java
/*
* 定义一个用于LayUI数据表格对应格式的模板类
* code  状态码         0成功
* msg   提示文字
* count 数据总量
* data  数据集合
* */
```;


@Data
public class ResultData<T>  {

  private  int code=0;
    private String msg="";
    private int count;
    private T data;

    public ResultData(T data, int count) {
        this.count = count;
        this.data=data;
    }
    public static <T> ResultData<T> ok(T data,int count){
        return new ResultData<>(data,count);
    }
}

最终页面

<!DOCTYPE html >
<html >
<head>
    <meta charset="UTF-8">
    <title>vegetableList</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="../layui/css/layui.css" media="all">
    <!-- 注意:如果你直接复制所有代码到本地,上述css路径需要改成你本地的 -->
</head>
<body>
<table class="layui-hide" id="test" lay-filter="test"></table>

<script type="text/html" id="toolbarDemo">

    <div class="layui-btn-container">
        <button class="layui-btn layui-btn-sm" lay-event="getCheckData">获取选中行数据</button>
        <button class="layui-btn layui-btn-sm" lay-event="getCheckLength">获取选中数目</button>
        <button class="layui-btn layui-btn-sm" lay-event="isAll">验证是否全选</button>
        <button class="layui-btn layui-btn-sm" lay-event="addVe">添加蔬菜</button>
    </div>
</script>

<script type="text/html" id="barDemo">

    <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>

</script>


<script src="../layui/layui.js" charset="utf-8"></script>
<!-- 注意:如果你直接复制所有代码到本地,上述 JS 路径需要改成你本地的 -->

<script>
    layui.use('table', function () {
        var table = layui.table;

        //引入jquery
        var $ = layui.$;

        table.render({
            elem: '#test'
            , url: "http://localhost:8081/vegetable/vegetableAll"
            , toolbar: '#toolbarDemo' //开启头部工具栏,并为其绑定左侧模板
            , defaultToolbar: ['filter', 'exports', 'print', { //自定义头部工具栏右侧图标。如无需自定义,去除该参数即可
                title: '提示'
                , layEvent: 'LAYTABLE_TIPS'
                , icon: 'layui-icon-tips'
            }]
            , title: '用户数据表'
            //两个[之间要加空格
            , cols: [ [
                {type: 'checkbox', fixed: 'left'}
                , {field: 'vId', title: '蔬菜编号', cellminwidth: 100, fixed: 'left', unresize: true, sort: true}

                , {field: 'vName', title: '蔬菜名', cellminwidth: 100, edit: 'text'}
                , {field: 'vPrice', title: '蔬菜价格', cellminwidth: 100, edit: 'text', sort: true}
                , {field: 'vNum', title: '蔬菜数量', cellminwidth: 100, edit: 'text', sort: true}
                , {field: 'vegetableImg', title: '蔬菜图片', cellminwidth: 100, edit: 'text', sort: true}
                , {fixed: 'right', title: '操作', toolbar: '#barDemo', cellminwdth: 150}

            ] ]
            //开启分页组件
            , page: true
            // 设置每页显示记录数的下拉选项
            ,limits:[5,10,20]
            //     显示每页显示条数
            ,limit:5
            //     解析数据表格url请求后的数据
            ,parseData:function (res){//res就是url对应的数据
                /* var result;
                 if (this.page.curr){
                     result = res.data.slice(this.limit*(this.page.curr-1),this.limit*this.page.curr);

                 }
                 else {
                     result=res.data.slice(0,this.limit);
                 }*/
                return{
                    "code":res.code,
                    "msg":res.msg,
                    "count":res.count,
                    "data":res.data

                }
            }
        });

        //头工具栏事件
        table.on('toolbar(test)', function (obj) {
            var checkStatus = table.checkStatus(obj.config.id);
            switch (obj.event) {
                case 'getCheckData':
                    var data = checkStatus.data;
                    layer.alert(JSON.stringify(data));
                    break;
                case 'getCheckLength':
                    var data = checkStatus.data;
                    layer.msg('选中了:' + data.length + ' 个');
                    break;
                case 'isAll':
                    layer.msg(checkStatus.isAll ? '全选' : '未全选');
                    break;
                case 'addVe':
                    layer.open({
                        title: "添加客房",
                        type: 2,
                        content: "addVe.html",
                        area: ['400px', '300px'],
                        resize: false,
                        anim: 2
                    })
                    break;

                //自定义头工具栏右侧图标 - 提示
                case 'LAYTABLE_TIPS':
                    layer.alert('这是工具栏右侧自定义的一个图标按钮');
                    break;
            }
            ;
        });

        //监听单元格编辑事件
        table.on('edit(test)', function (obj) {
            layer.confirm('确定要修改吗', function (index) {
                $.ajax({
                    url: "http://localhost:8081/vegetable/updateVe",
                    data: {
                        "vId": obj.data.vId,
                        "field": obj.field,
                        "value": obj.value
                    },
                    type: "POST",
                    success: function (res) {
                        layer.close(index)
                    }
                })
            }, function () {
                location.reload()
            })
        })


        //监听行工具事件
        table.on('tool(test)', function (obj) {
            //当前行中的数据
            var data = obj.data;
            //console.log(obj)
            if (obj.event === 'del') {
                layer.confirm('真的删除行么', function (index) {
                    $.ajax({
                        url: "http://localhost:8081/vegetable/deleteVe",
                        type: "POST",
                        data: {
                            "vId": data.vId
                        },
                        success: function (res) {
                            if (res) {
                                obj.del();
                            } else {
                                alert("删除失败")
                            }
                            layer.close(index);
                        }
                    })


                });
            }

        })
    })

</script>


</body>
</html>

添加页面

<!DOCTYPE html >
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>addVe</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="../layui/css/layui.css" media="all">
</head>
<body>
<form class="layui-form" action="">
    <div class="layui-form-item">
        <label class="layui-form-label">蔬菜名</label>
        <div class="layui-input-block">
            <input type="text" name="vName" required lay-verify="required" placeholder="请输入蔬菜名" autocomplete="off"
                   class="layui-input">
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">蔬菜价格</label>
        <div class="layui-input-inline">
            <input type="text" name="vPrice" required lay-verify="required" placeholder="请输入价格"
                   autocomplete="off" class="layui-input">
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">蔬菜数量</label>
            <div class="layui-input-inline">
                <input type="text" name="vNum" required lay-verify="required" placeholder="请输入价格"
                       autocomplete="off" class="layui-input">
            </div>
            <div class="layui-form-item">
                <label class="layui-form-label">蔬菜图片</label>
                <div class="layui-input-inline">
                    <input type="text" name="vegetableImg" required lay-verify="required" placeholder="请输入图片链接"
                           autocomplete="off" class="layui-input">
                </div>
                <div class="layui-form-item">
                    <div class="layui-input-block">
                        <button class="layui-btn" lay-submit lay-filter="formDemo">立即提交</button>
                        <button type="reset" class="layui-btn layui-btn-primary">重置</button>
                    </div>
                </div>
</form>


<script src="../layui/layui.js" charset="utf-8"></script>
<!-- 注意:如果你直接复制所有代码到本地,上述 JS 路径需要改成你本地的 -->
<script>
    //Demo
    layui.use('form', function () {
        var form = layui.form;
        var $=layui.$;

        //监听提交
        form.on('submit(formDemo)', function (data) {
            $.ajax({
                url:'http://localhost:8081/vegetable/addVe',
                data:data.field,
                success:function (res){
                    if (res){
                        parent.location.reload();
                    }
                }
            })

            return false;
        });
    });
</script>

</body>
</html>

单表条件分页

原理:调用IService接口中的**page(Page page,Wrapper wrapper)**方法,创建分页模型对象和条件构造器对象

在Springboot启动类中加入mybatis分页拦截器

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    return interceptor;
}

搜索框

<form class="navbar-form navbar-left" th:action="@{/question/queryAll}">
    <div class="form-group">
        <input type="text" class="form-control" name="keyword" placeholder="请输入题目关键字">
    </div>
    <button type="submit" class="btn btn-default">搜索</button>
</form>

控制层

@RequestMapping("/queryAll")
public String queryAll(@RequestParam(defaultValue = "1") Integer page,
                       @RequestParam(defaultValue = "5") Integer size,
                       @RequestParam(defaultValue = "") String keyword,
                       Model model) {
    //1.创建分页模型对象
    Page<Question> pageInfo = new Page<>(page, size);

    //创建条件构造器对象
    QueryWrapper<Question> wrapper = new QueryWrapper<>();
    wrapper.like("ques_title",keyword);
    //2.调用条件分页查询的方法
    questionService.page(pageInfo,wrapper);
    // 将分页模型对象保存到请求中
    model.addAttribute("pageInfo", pageInfo);
    //构造页数
    List<Long> pageList = new ArrayList<>();
    //每次最多显示5页
    //定义最大数字
    long maxPage = page + 4 > pageInfo.getPages() ? pageInfo.getPages() : page + 4;
    //定义最小数字
    long minPage = page - 4 < 1 ? page : maxPage - 4;

    for (long i = minPage; i <= maxPage; i++) {
        pageList.add(i);
    }

    model.addAttribute("pageList", pageList);

    return "questionList";
}

分页组件

<nav aria-label="Page navigation">
    <ul class="pagination">
        <li>
            <a th:if="${pageInfo.hasPrevious}" th:href="@{/question/queryAll(keyword=${param.keyword},page=${pageInfo.current-1})}"
               aria-label="Previous">
                <span aria-hidden="true">«</span>
            </a>
        </li>
        <li class="pno" th:each="pno:${pageList}">
            <a th:href="@{/question/queryAll(keyword=${param.keyword},page=${pno})}" th:text="${pno}"></a>
        </li>
        <li>
            <a th:if="${pageInfo.hasNext}" th:href="@{/question/queryAll(keyword=${param.keyword},page=${pageInfo.current+1})}"
               aria-label="Next">
                <span aria-hidden="true">»</span>
            </a>
        </li>
    </ul>
</nav>

MyBatisPlus关联查询

可以通过创建xml文件来实现,过程同MyBatis关联查询。

这里使用注解实现。

主表question_catalog

从表question

多对一

查询时以从表数据为主体,关联对应的主表信息。

多个question对象对应一个question_catalog对象。

在查询question表的同时,显示关联question_catalog表中的数据。

实体类

在从表的实体类中,额外添加一个外键对应的主表实体对象

@Data
public class Question implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.ASSIGN_UUID)
    private String quesCode;
    private Integer qcId;
    private String quesTitle;
    private String quesOptA;
    private String quesOptB;
    private String quesOptC;
    private String quesAns;
    @TableLogic//该注解表示该字段是逻辑删除字段
    private Integer deleted;

    /*
    * 如果在实体类中添加了一个不属于该表中字段的属性
    * 要在该属性上加@TableField(exist = false)表示该属性没有对应的字段
    * */
    @TableField(exist = false)
    private QuestionCatalog qc;
}

原理

如果直接通过IService接口中的list()方法查询,实际调用的是BaseMapper接口中的selectList()方法,默认查询自身表。

如果重写业务层中的list()方法,或在业务层中自定义一个方法,让其调用数据访问层中自定义的某个方法,重新定制sql语句,就能得到想要的数据

实现过程

1.在Mapper(QuestionMapper)接口中自定义一个方法,使用注解编写sql语句

有两种方式:

  • 方式一:构造自定义的sql语句

    • 建议外键关联字段比较少的情况下使用
    • sql语句需要连接查询,将外键关联的表的字段重命名为"外键实体对象名.属性名"
    @Select("select q.*,qc_name as 'qc.qcName' from question q,question_catalog qc where q.qc_id=qc.qc_id ")
    List<Question> mySelect();
    
  • 方式二:使用子查询,自定义结果集映射

    • 建议外键关联字段比较多的情况下使用
    • 先查询从表,再使用外键字段查询主表
    • 外键字段需要重新映射一次
    //1.查询自身表
    @Select("select * from question")
    //2.自定义结果集映射
    @Results({
        //qc_id字段在下面的子查询中已经使用了,将外键字段qc_id重新映射
        @Result(property = "qcId",column = "qc_id"),
        //遇到qc属性,使用qc_id进行子查询
        //执行QuestionCatalogMapper中的selectById方法
        //该方法是BaseMapper接口中默认就存在的方法
        @Result(property ="qc" ,column ="qc_id",one =@One(select = "com.hqyj.question_sys.mapper.QuestionCatalogMapper.selectById"))
    })
    List<Question> mySelect();
    

2.在Service层中调用Mapper层中自定义的方法

@Service
public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, Question> implements IQuestionService {

    /*
    * 如果直接调用该方法查询,会最终调用BaseMapper中的selectList()方法
    * 这里重写该方法,让其调用BaseMapper中的自定义方法
    * */
    @Override
    public List<Question> list() {
        return baseMapper.mySelect();
    }
}

一对多

以主表为主体,查询时显示关联的从表对象集合。

查询question_catalog表,同时显示qc_id对应的question表中的数据集合。

实体类

在主表实体类中,额外添加一个从表实体对象集合。

@TableName("question_catalog")
@Data
public class QuestionCatalog implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "qc_id", type = IdType.AUTO)
    private Integer qcId;

    private String qcName;

    /*
    * 一对多查询,定义关联的从表对象集合
    * @TableField(exist = false)标明该属性没有对应的字段
    * */
    @TableField(exist = false)
    private List<Question> questionList;
}

原理同多对一查询,重写service层中的list()方法,改变原本方法中调用的内容

实现过程

1.在Mapper(QuestionCatalogMapper)接口中自定义一个方法,使用注解编写sql语句

/*
     * 自定义查询
     * 使用子查询实现一对多
     * */
//1.查询question_catalog表
@Select("select * from question_catalog")
//2.自定义结果集映射
@Results({
    //由于在下面使用qc_id进行了子查询,对其重新映射
    @Result(property = "qcId",column = "qc_id"),
    //3.使用qc_id进行子查询,查询question表,调用根据qc_id查询对应数据集合的方法,没有现成的方法,需要自定义
    @Result(property = "questionList", column = "qc_id", many = @Many(select = "com.hqyj.question_sys.mapper.QuestionMapper.queryByQcId"))
})
List<QuestionCatalog> mySelect();

2.在Mapper(QuestionMapper)接口中定义方法

/*
    * 根据qc_id查询对应数据集合
    * */
@Select("select * from question where qc_id=#{qcId}")
List<Question> queryByQcId();

3.在Service层中调用Mapper层中自定义的方法

/*
        * 重写原本的查询所有,执行自定义的查询
        * */
@Override
public List<QuestionCatalog> list() {
    return baseMapper.mySelect();
}

多对一关联查询条件分页

原理

参考原本的条件分页查询方法selectPage(P page, Wrapper<Question> queryWrapper)

page用于分页,queryWrapper用于构造条件。通过自定义的sql语句关联两张表实现。
在自定义的sql语句中,

通过{ew.sqlSegment}或{ew.customSqlSegment}使用条件构造器Wrapper对象

${ew.sqlSegment} 将条件原样拼接

${ew.customSqlSegment} 在所有条件前自动加入where关键字

实现过程

1.在Mapper(QuestionMapper)中自定义关联查询的sql

  • ${ew.sqlSegment} 将条件原样拼接
  • ${ew.customSqlSegment} 在所有条件前加入where关键字
//${ew.sqlSegment}   将条件原样拼接
//@Select("SELECT q.*,qc_name AS 'qc.qcName' FROM question q,question_catalog qc WHERE q.qc_id = qc.qc_id and ${ew.sqlSegment}")

//${ew.customSqlSegment}  在所有条件前加入where关键字
@Select("SELECT q.*,qc_name AS 'qc.qcName' FROM question q inner join question_catalog qc on q.qc_id = qc.qc_id ${ew.customSqlSegment}")
<P extends IPage<Question>> P queryByCondition(P page, @Param("ew") Wrapper<Question> queryWrapper);

2.在service层中重写或自定义方法,调用mapper层中自定义的方法

@Override
public <E extends IPage<Question>> E page(E page, Wrapper<Question> queryWrapper) {
    return baseMapper.queryByCondition(page,queryWrapper);
}

3.controller层

  • 接收所需参数
  • 创建分页模型对象
  • 创建条件构造器对象
@RequestMapping("/queryAll")
public String queryAll(@RequestParam(defaultValue = "1") Integer page,
                       @RequestParam(defaultValue = "5") Integer size,
                       @RequestParam(defaultValue = "") String keyword,
                       @RequestParam(defaultValue = "0") Integer qcId,
                       Model model) {
    //1.创建分页模型对象
    Page<Question> pageInfo = new Page<>(page, size);
    //创建条件构造器对象
    QueryWrapper<Question> wrapper = new QueryWrapper<>();
    wrapper.like("ques_title", keyword);
    if (qcId != 0) {
        wrapper.eq("q.qc_id", qcId);
    }
    //2.调用条件分页查询的方法
    questionService.page(pageInfo, wrapper);

    model.addAttribute("pageInfo", pageInfo);
    //构造页数
    List<Long> pageList = new ArrayList<>();
    //每次最多显示5页
    //定义最大数字
    long maxPage = page + 4 > pageInfo.getPages() ? pageInfo.getPages() : page + 4;
    //定义最小数字
    long minPage = page - 4 < 1 ? page : maxPage - 4;

    for (long i = minPage; i <= maxPage; i++) {
        pageList.add(i);
    }

    model.addAttribute("pageList", pageList);

    return "questionList";
}

4.页面表单

<form class="navbar-form navbar-left" th:action="@{/question/queryAll}">
    <div class="form-group">
        <input type="text" class="form-control" name="keyword" placeholder="请输入题目关键字">
        <select class="form-control" name="qcId">

        </select>
    </div>
    <button type="submit" class="btn btn-default">搜索</button>
</form>
<script th:inline="javascript">
    $(function () {
        $.ajax({
            url:[[@{/qc/queryAll}]],
            success:function (res){
                for(var i=0;i<res.length;i++){
                    let qcId = res[i].qcId;
                    let qcName = res[i].qcName;
                    $("<option></option>").text(qcName).val(qcId).appendTo($("select[name=qcId]"));
                }
            }
        });
    });
</script>

版权声明:本文为weixin_45735754原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。