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插件,无需安装)
IDEA插件官网Versions: Lombok - IntelliJ IDEs Plugin | Marketplace (jetbrains.com)
IDEA内置插件市场搜索
在某个实体类上添加注解
| 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>