当前位置: 首页 > news >正文

网站常用特效电影视频网站建设费用

网站常用特效,电影视频网站建设费用,建设网站书籍pdf,微信分销小程序开发文章目录 一、MyBatis-Plus简介二、快速入门1、环境准备2、将mybatis项目改造成mybatis-plus项目#xff08;1#xff09;引入MybatisPlus依赖#xff0c;代替MyBatis依赖#xff08;2#xff09;配置Mapper包扫描路径#xff08;3#xff09;定义Mapper接口并继承BaseM… 文章目录 一、MyBatis-Plus简介二、快速入门1、环境准备2、将mybatis项目改造成mybatis-plus项目1引入MybatisPlus依赖代替MyBatis依赖2配置Mapper包扫描路径3定义Mapper接口并继承BaseMapperT 3、常见注解1TableName2TableId3TableField4使用案例 4、常见配置 三、核心功能1、条件构造器1QueryWrapper2UpdateWrapper3基于Lambda的Wrapper 2、自定义SQL1自定义SQL片段2多表联查 3、IService接口1常用方法介绍2基本用法3Restful案例4LambdaQuery和LambdaUpdate5批量新增 批处理方案性能测试 四、扩展功能1、代码生成1安装插件2使用步骤3代码生成器配置 2、静态工具1案例一2案例二3案例三 3、逻辑删除1介绍2使用步骤3TableLogic 4、枚举处理器1定义枚举标记EnumValue2配置枚举处理器 5、JSON类型处理器1定义接收Json的实体类2指定类型处理器 6、yaml配置加密1生成密钥2修改配置3配置密钥运行参数4实现原理 7、自动填充字段1配置自动填充处理器2添加TableField的fill属性 五、插件功能1、分页插件1引入依赖2配置分页内置拦截器3分页API 2、通用分页实体1实体类设计2开发接口3改造PageDTO实体4改造PageResult实体 一、MyBatis-Plus简介 MyBatis-Plus 是一个 MyBatis 的增强工具在 MyBatis 的基础上只做增强不做改变为简化开发、提高效率而生。 支持的数据库 框架结构 MyBatis-Plus官网https://baomidou.com/ 参考文档https://mybatis.plus/ 网站访问速度稍慢建议直接看官网文档 二、快速入门 1、环境准备 导入数据库表结构mp.sql一共三张表user和address还有测试MP注解的表tb_user。 -- -------------------------------------------------------- -- 主机: 127.0.0.1 -- 服务器版本: 8.0.28 - MySQL Community Server - GPL -- 服务器操作系统: Win64 -- HeidiSQL 版本: 12.2.0.6576 -- --------------------------------------------------------/*!40101 SET OLD_CHARACTER_SET_CLIENTCHARACTER_SET_CLIENT */; /*!40101 SET NAMES utf8 */; /*!50503 SET NAMES utf8mb4 */; /*!40103 SET OLD_TIME_ZONETIME_ZONE */; /*!40103 SET TIME_ZONE00:00 */; /*!40014 SET OLD_FOREIGN_KEY_CHECKSFOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS0 */; /*!40101 SET OLD_SQL_MODESQL_MODE, SQL_MODENO_AUTO_VALUE_ON_ZERO */; /*!40111 SET OLD_SQL_NOTESSQL_NOTES, SQL_NOTES0 */;-- 导出 mp 的数据库结构 CREATE DATABASE IF NOT EXISTS mp /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */ /*!80016 DEFAULT ENCRYPTIONN */; USE mp;-- 导出 表 mp.address 结构 CREATE TABLE IF NOT EXISTS address (id bigint NOT NULL AUTO_INCREMENT,user_id bigint DEFAULT NULL COMMENT 用户ID,province varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 省,city varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 市,town varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 县/区,mobile varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 手机,street varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 详细地址,contact varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 联系人,is_default bit(1) DEFAULT b0 COMMENT 是否是默认 1默认 0否,notes varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 备注,deleted bit(1) DEFAULT b0 COMMENT 逻辑删除,PRIMARY KEY (id) USING BTREE,KEY user_id (user_id) USING BTREE ) ENGINEInnoDB AUTO_INCREMENT71 DEFAULT CHARSETutf8mb3 ROW_FORMATCOMPACT;-- 正在导出表 mp.address 的数据~11 rows (大约)INSERT INTO address (id, user_id, province, city, town, mobile, street, contact, is_default, notes, deleted) VALUES(59, 2, 北京, 北京, 朝阳区, 13900112222, 金燕龙办公楼, Rose, b1, NULL, b0),(60, 1, 北京, 北京, 朝阳区, 13700221122, 修正大厦, Jack, b0, NULL, b0),(61, 1, 上海, 上海, 浦东新区, 13301212233, 航头镇航头路, Jack, b1, NULL, b0),(63, 2, 广东, 佛山, 永春, 13301212233, 永春武馆, Rose, b0, NULL, b0),(64, 3, 浙江, 杭州, 拱墅区, 13567809102, 浙江大学, Hope, b1, NULL, b0),(65, 3, 浙江, 杭州, 拱墅区, 13967589201, 左岸花园, Hope, b0, NULL, b0),(66, 4, 湖北, 武汉, 汉口, 13967519202, 天天花园, Thomas, b1, NULL, b0),(67, 3, 浙江, 杭州, 拱墅区, 13967589201, 左岸花园, Hopey, b0, NULL, b0),(68, 4, 湖北, 武汉, 汉口, 13967519202, 天天花园, Thomas, b1, NULL, b0),(69, 3, 浙江, 杭州, 拱墅区, 13967589201, 左岸花园, Hopey, b0, NULL, b0),(70, 4, 湖北, 武汉, 汉口, 13967519202, 天天花园, Thomas, b1, NULL, b0);-- 导出 表 mp.user 结构 CREATE TABLE user (id BIGINT(19) NOT NULL AUTO_INCREMENT COMMENT 用户id,username VARCHAR(50) NOT NULL COMMENT 用户名 COLLATE utf8_general_ci,password VARCHAR(128) NOT NULL COMMENT 密码 COLLATE utf8_general_ci,phone VARCHAR(20) NULL DEFAULT NULL COMMENT 注册手机号 COLLATE utf8_general_ci,info JSON NOT NULL COMMENT 详细信息,status INT(10) NULL DEFAULT 1 COMMENT 使用状态1正常 2冻结,balance INT(10) NULL DEFAULT NULL COMMENT 账户余额,create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间,update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间,PRIMARY KEY (id) USING BTREE,UNIQUE INDEX username (username) USING BTREE ) COMMENT用户表 COLLATEutf8_general_ci ENGINEInnoDB ROW_FORMATCOMPACT AUTO_INCREMENT5 ;-- 正在导出表 mp.user 的数据~4 rows (大约)INSERT INTO user (id, username, password, phone, info, status, balance, create_time, update_time) VALUES(1, Jack, 123, 13900112224, {age: 20, intro: 佛系青年, gender: male}, 1, 1600, 2023-05-19 20:50:21, 2023-06-19 20:50:21),(2, Rose, 123, 13900112223, {age: 19, intro: 青涩少女, gender: female}, 1, 600, 2023-05-19 21:00:23, 2023-06-19 21:00:23),(3, Hope, 123, 13900112222, {age: 25, intro: 上进青年, gender: male}, 1, 100000, 2023-06-19 22:37:44, 2023-06-19 22:37:44),(4, Thomas, 123, 17701265258, {age: 29, intro: 伏地魔, gender: male}, 1, 800, 2023-06-19 23:44:45, 2023-06-19 23:44:45);/*!40103 SET TIME_ZONEIFNULL(OLD_TIME_ZONE, system) */; /*!40101 SET SQL_MODEIFNULL(OLD_SQL_MODE, ) */; /*!40014 SET FOREIGN_KEY_CHECKSIFNULL(OLD_FOREIGN_KEY_CHECKS, 1) */; /*!40101 SET CHARACTER_SET_CLIENTOLD_CHARACTER_SET_CLIENT */; /*!40111 SET SQL_NOTESIFNULL(OLD_SQL_NOTES, 1) */;create table tb_user (user_id bigint auto_increment comment 用户id,username varchar(20) null comment 用户名,password varchar(20) null comment 密码,is_deleted TINYINT default 0 null comment 逻辑删除,order TINYINT null comment 排序序号,constraint tb_user_pkprimary key (user_id),constraint tb_user_pk2unique (username) ) comment 用户表测试mp注解;数据库表结构如下 导入项目结构mp-demo 在application.yaml中修改jdbc参数为自己的数据库参数 spring:datasource:url: jdbc:mysql://127.0.0.1:3307/mp?useUnicodetruecharacterEncodingUTF-8autoReconnecttrueserverTimezoneAsia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: 123456 logging:level:com.itheima: debugpattern:dateformat: HH:mm:ss mybatis:mapper-locations: classpath*:mapper/*.xml2、将mybatis项目改造成mybatis-plus项目 基于现有的mybatis项目将其改造成mybatis-plus实现如下功能 新增用户功能根据id查询用户根据id批量查询用户根据id更新用户根据id删除用户 比如我们要实现User表的CRUD只需要下面几步。 1引入MybatisPlus依赖代替MyBatis依赖 MyBatisPlus官方提供了starter其中集成了Mybatis和MybatisPlus的所有功能并且实现了自动装配效果。 !-- springboot2的mybatis-plus依赖 -- dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.5.9/version /dependency注意如果是springboot3引入的是mybatis-plus-spring-boot3-starter依赖。 !-- springboot3的mybatis-plus依赖 -- dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-spring-boot3-starter/artifactIdversion3.5.9/version /dependency由于这个starter包含对mybatis的自动装配因此完全可以替换掉Mybatis的starter。 最终项目的依赖如下 !-- mysql -- dependencygroupIdcom.mysql/groupIdartifactIdmysql-connector-j/artifactIdscoperuntime/scope /dependency !-- lombok -- dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional /dependency !-- 单元测试 -- dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope /dependency !-- mybatis-plus -- dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.5.9/version /dependency2配置Mapper包扫描路径 SpringBoot启动类上添加MapperScan注解 import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;MapperScan(com.itheima.mp.mapper) SpringBootApplication public class MpDemoApplication {public static void main(String[] args) {SpringApplication.run(MpDemoApplication.class, args);} }3定义Mapper接口并继承BaseMapper 升级前的MyBatis版本增删改查 之前MyBatis的Mapper接口 public interface UserMapper {void saveUser(User user);void deleteUser(Long id);void updateUser(User user);User queryUserById(Param(id) Long id);ListUser queryUserByIds(Param(ids) ListLong ids); }之前MyBatis的Mapper接口的xml映射文件 ?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.itheima.mp.mapper.UserMapper!-- 新增用户 --insert idsaveUser parameterTypecom.itheima.mp.domain.po.UserINSERT INTO user (id, username, password, phone, info, balance)VALUES(#{id}, #{username}, #{password}, #{phone}, #{info}, #{balance});/insert!-- 修改用户 --update idupdateUser parameterTypecom.itheima.mp.domain.po.UserUPDATE usersetif testusername ! nullusername#{username}/ifif testpassword ! nullpassword#{password}/ifif testphone ! nullphone#{phone}/ifif testinfo ! nullinfo#{info}/ifif teststatus ! nullstatus#{status}/ifif testbalance ! nullbalance#{balance}/if/setWHERE id#{id};/update!-- 删除用户 --delete iddeleteUser parameterTypecom.itheima.mp.domain.po.UserDELETE FROM user WHERE id #{id}/delete!-- 根据用户id查询单个用户 --select idqueryUserById resultTypecom.itheima.mp.domain.po.UserSELECT *FROM userWHERE id #{id}/select!-- 根据用户id数组批量查询多个用户 --select idqueryUserByIds resultTypecom.itheima.mp.domain.po.UserSELECT *FROM userif testids ! nullWHERE id INforeach collectionids open( close) itemid separator,#{id}/foreach/ifLIMIT 10/select /mapper之前MyBatis的CRUD测试类 SpringBootTest class MyBatisUserMapperTests {Autowiredprivate UserMapper userMapper;Testvoid testInsert() {User user new User();user.setId(5L);user.setUsername(Lucy);user.setPassword(123);user.setPhone(18688990011);user.setBalance(200);user.setInfo({\age\: 24, \intro\: \英文老师\, \gender\: \female\});user.setCreateTime(LocalDateTime.now());user.setUpdateTime(LocalDateTime.now());userMapper.saveUser(user);}Testvoid testSelectById() {User user userMapper.queryUserById(5L);System.out.println(user user);}Testvoid testQueryByIds() {ListUser users userMapper.queryUserByIds(List.of(1L, 2L, 3L, 4L));users.forEach(System.out::println);}Testvoid testUpdateById() {User user new User();user.setId(5L);user.setBalance(20000);userMapper.updateUser(user);}Testvoid testDeleteUser() {userMapper.deleteUser(5L);} }升级后的MyBatisPlus版本的增删改查 为了简化单表CRUDMybatisPlus提供了一个基础的BaseMapper接口其中已经实现了单表的CRUD MyBatisPlus的Mapper接口 import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.itheima.mp.domain.po.User;// 只需要继承BaseMapper就能省去所有的单表CRUD public interface UserMapper extends BaseMapperUser { }MyBatisPlus的CRUD测试类 SpringBootTest class MyBatisPlusUserMapperTests {Autowiredprivate UserMapper userMapper;Testvoid testInsert() {User user new User();user.setId(5L);user.setUsername(Lucy);user.setPassword(123);user.setPhone(18688990011);user.setBalance(200);user.setInfo({\age\: 24, \intro\: \英文老师\, \gender\: \female\});user.setCreateTime(LocalDateTime.now());user.setUpdateTime(LocalDateTime.now());userMapper.insert(user);}Testvoid testSelectById() {User user userMapper.selectById(5L);System.out.println(user user);}Testvoid testSelectByIds() {ListUser users userMapper.selectBatchIds(List.of(1L, 2L, 3L, 4L, 5L));users.forEach(System.out::println);}Testvoid testUpdateById() {User user new User();user.setId(5L);user.setBalance(20000);userMapper.updateById(user);}Testvoid testDelete() {userMapper.deleteById(5L);} }可以看到在运行过程中打印出的SQL日志以字段名进行查询而不是用*非常标准。 11:52:03 DEBUG 22712 --- [ main] com.itheima.mp.mapper.UserMapper.insert : Preparing: INSERT INTO user ( id, username, password, phone, info, balance, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ? ) 11:52:03 DEBUG 22712 --- [ main] com.itheima.mp.mapper.UserMapper.insert : Parameters: 5(Long), Lucy(String), 123(String), 18688990011(String), {age: 24, intro: 英文老师, gender: female}(String), 200(Integer), 2024-11-13T11:52:02.904878200(LocalDateTime), 2024-11-13T11:52:02.904878200(LocalDateTime) 11:52:03 DEBUG 22712 --- [ main] com.itheima.mp.mapper.UserMapper.insert : Updates: 111:54:23 DEBUG 8964 --- [ main] c.i.mp.mapper.UserMapper.selectBatchIds : Preparing: SELECT id,username,password,phone,info,status,balance,create_time,update_time FROM user WHERE id IN ( ? , ? , ? , ? , ? ) 11:54:23 DEBUG 8964 --- [ main] c.i.mp.mapper.UserMapper.selectBatchIds : Parameters: 1(Long), 2(Long), 3(Long), 4(Long), 5(Long) 11:54:23 DEBUG 8964 --- [ main] c.i.mp.mapper.UserMapper.selectBatchIds : Total: 5 User(id1, usernameJack, password123, phone13900112224, info{age: 20, intro: 佛系青年, gender: male}, status1, balance1600, createTime2023-05-19T20:50:21, updateTime2023-06-19T20:50:21) User(id2, usernameRose, password123, phone13900112223, info{age: 19, intro: 青涩少女, gender: female}, status1, balance600, createTime2023-05-19T21:00:23, updateTime2023-06-19T21:00:23) User(id3, usernameHope, password123, phone13900112222, info{age: 25, intro: 上进青年, gender: male}, status1, balance100000, createTime2023-06-19T22:37:44, updateTime2023-06-19T22:37:44) User(id4, usernameThomas, password123, phone17701265258, info{age: 29, intro: 伏地魔, gender: male}, status1, balance800, createTime2023-06-19T23:44:45, updateTime2023-06-19T23:44:45) User(id5, usernameLucy, password123, phone18688990011, info{age: 24, intro: 英文老师, gender: female}, status1, balance200, createTime2024-11-13T11:52:03, updateTime2024-11-13T11:52:03)3、常见注解 在刚刚的入门案例中我们仅仅引入了依赖继承了BaseMapper就能使用MybatisPlus非常简单。但是问题来了 MybatisPlus如何知道我们要查询的是哪张表表中有哪些字段呢 UserMapper在继承BaseMapper的时候指定了一个泛型 泛型中的User就是与数据库对应的PO实体类。 MybatisPlus底层通过反射根据PO实体的信息来推断出表的信息从而生成SQL的。默认情况下约定 MybatisPlus会把PO实体的类名驼峰转下划线作为表名MybatisPlus会把PO实体的所有变量名驼峰转下划线作为表的字段名并根据变量类型推断字段类型MybatisPlus会把名为id的字段作为主键 但很多情况下默认的实现与实际场景不符实际情况与MP的约定不符合时使用因此MybatisPlus提供了一些注解便于我们声明表信息。 TableName用来指定表名Tableld用来指定表中的主键字段信息TableField用来指定表中的普通字段信息 1TableName 描述表名注解标识实体类对应的表使用位置实体类 TableName注解除了指定表名以外还可以指定很多其它属性 属性类型必须指定默认值描述valueString否“”表名schemaString否“”schemakeepGlobalPrefixboolean否false是否保持使用全局的 tablePrefix 的值当全局 tablePrefix 生效时resultMapString否“”xml 中 resultMap 的 id用于满足特定类型的实体类对象绑定autoResultMapboolean否false是否自动构建 resultMap 并使用如果设置 resultMap 则不会进行 resultMap 的自动构建与注入excludePropertyString[]否{}需要排除的属性名 since 3.3.1 2TableId 描述主键注解标识实体类中的主键字段使用位置实体类的主键字段 TableId注解支持两个属性 属性类型必须指定默认值描述valueString否“”表名typeEnum否IdType.NONE指定主键类型 枚举IdType支持的类型有 值描述AUTO数据库 ID 自增NONE无状态该类型为未设置主键类型如果全局配置中有 IdType 相关的配置则会跟随全局配置。当我们设置 TableId 类型为NONE 时且不手动设置主键值MyBatisPlus将默认给出一个 Long 类型的字符串因为全局配置默认为ASSIGN_ID。INPUTinsert 前自行 set 主键值。当我们没有设置主键值时MyBatisPlus并不设置 Long 类型的值而是插入为null。ASSIGN_ID分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)ASSIGN_UUID分配 UUID,主键类型为 String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认 default 方法)ID_WORKER分布式全局唯一 ID 长整型类型 (please use ASSIGN_ID)UUID32 位 UUID 字符串 (please use ASSIGN_UUID)ID_WORKER_STR分布式全局唯一 ID 字符串类型 (please use ASSIGN_ID) 这里比较常见的有三种 AUTO利用数据库的id自增长INPUT手动生成idASSIGN_ID雪花算法生成Long类型的全局唯一id这是默认的ID策略 3TableField 描述普通字段注解 一般情况下我们并不需要给字段添加TableField注解一些特殊情况除外 成员变量名与数据库字段名不一致成员变量是以isXXX命名按照JavaBean的规范MybatisPlus识别字段时会把is去除这就导致与数据库不符。成员变量名与数据库一致但是与数据库的关键字冲突。使用TableField注解给字段名添加转义字符两个反引号 支持的其它属性如下 属性类型必填默认值描述valueString否“”数据库字段名existboolean否true是否为数据库表字段conditionString否“”字段 where 实体查询比较条件有值设置则按设置的值为准没有则为默认全局的 %s#{%s}参考(opens new window)updateString否“”字段 update set 部分注入例如当在version字段上注解update“%s1” 表示更新时会 set versionversion1 该属性优先级高于 el 属性insertStrategyEnum否FieldStrategy.DEFAULT举例NOT_NULL insert into table_a(column) values (#{columnProperty})updateStrategyEnum否FieldStrategy.DEFAULT举例IGNORED update table_a set column#{columnProperty}whereStrategyEnum否FieldStrategy.DEFAULT举例NOT_EMPTY where column#{columnProperty}fillEnum否FieldFill.DEFAULT字段自动填充策略selectboolean否true是否进行 select 查询keepGlobalFormatboolean否false是否保持使用全局的 format 进行处理jdbcTypeJdbcType否JdbcType.UNDEFINEDJDBC 类型 (该默认值不代表会按照该值生效)typeHandlerTypeHander否类型处理器 (该默认值不代表会按照该值生效)numericScaleString否“”指定小数点后保留的位数 4使用案例 tb_user为MP这三个常用注解的演示表 MpUser为PO实体类 Data TableName(tb_user) public class MpUser {// 用户idTableId(value user_id, type IdType.AUTO)private Long id;// 用户名TableField(username)private String name;// 密码private String password;// 是否被逻辑删除TableField(is_deleted)private Boolean isDeleted;// 排序字段TableField(order)private Integer order;// 地址TableField(exist false)private String address; }MpUserMapper public interface MpUserMapper extends BaseMapperMpUser { }MpUserMapperTests测试类 SpringBootTest public class MpUserMapperTests {Autowiredprivate MpUserMapper mpUserMapper;Testvoid testInsert() {MpUser mpUser new MpUser();//mpUser.setId(1L);mpUser.setName(MP用户1);mpUser.setPassword(123);mpUser.setIsDeleted(false);mpUser.setOrder(1);mpUser.setAddress(https://baomidou.com/);mpUserMapper.insert(mpUser);}Testvoid testSelectById() {MpUser mpUser mpUserMapper.selectById(1L);System.out.println(mpUser mpUser);}Testvoid testSelectAll() {// selectList()方法的参数为MP内置的条件封装器Wrapper所以不填写就是无任何条件即查询全部ListMpUser mpUserList mpUserMapper.selectList(null);mpUserList.forEach(System.out::println);}Testvoid testUpdateById() {MpUser mpUser new MpUser();mpUser.setId(1L);mpUser.setIsDeleted(true);mpUserMapper.updateById(mpUser);}Testvoid testDelete() {mpUserMapper.deleteById(1L);} }在新增时无论是否手动设置id主键字段都被忽略由数据库自增长 在查询时字段名会添加别名冲突字段加了转移符并且查数据库不存在的字段也不会报错而是null 4、常见配置 MybatisPlus也支持基于yaml文件的自定义配置详见官方文档使用配置。 mybatis-plus:type-aliases-package: com.itheima.mp.domain.po # 别名包扫描这项无默认值需要自己指定mapper-locations: classpath*:/mapper/**/*.xml # mapper.xml映射文件地址默认值configuration:map-underscore-to-camel-case: true # 是否开启下划线和驼峰命名的映射默认开启cache-enabled: true # mybatis二级缓存默认开启global-config:db-config:id-type: assign_id # 默认全局id生成策略为雪花算法update-strategy: not_null # 默认更新策略只更新非null字段mapper-locations指定 MyBatis Mapper 对应的 XML 文件位置。如果在 Mapper 中有自定义方法手写SQL或多表联查需要配置此项。默认值为classpath*:/mapper/**/*.xml也就是说我们只要把mapper.xml文件放置这个目录下就一定会被加载。 对于 Maven 多模块项目扫描路径应以 classpath*: 开头以加载多个 JAR 包中的 XML 文件。 大多数的配置都有默认值因此我们都无需配置。但还有一些是没有默认值的例如实体类的别名扫描包type-aliases-package默认值为null用于给包中的类注册别名注册后在Mapper对应的XML文件中可以直接使用类名无需使用全限定类名。 另外如果数据库的表大多数为主键自增可以在全局配置中设置id-type为auto之后如果有实体类的id属性是其他主键生成策略再通过TableId注解配置即可优先级指定注解 全局配置。 mybatis-plus:type-aliases-package: com.itheima.mp.domain.poglobal-config:db-config:id-type: auto # 全局id类型为自增长三、核心功能 刚才的案例中都是以id为条件的简单CRUD一些复杂条件的SQL语句就要用到一些更高级的功能了。 1、条件构造器 除了新增以外修改、删除、查询的SQL语句都需要指定where条件。因此BaseMapper中提供的相关方法除了以id作为where条件以外还支持更加复杂的where条件。 参数中的Wrapper就是条件构造的抽象类其下有很多默认实现继承关系如图 Wrapper的子类AbstractWrapper提供了where中包含的所有条件构造方法 而QueryWrapper在AbstractWrapper的基础上拓展了一个select方法允许指定查询字段 而UpdateWrapper在AbstractWrapper的基础上拓展了一个set方法允许指定SQL中的SET部分 接下来我们就来看看如何利用Wrapper实现复杂查询。 1QueryWrapper 无论是修改、删除、查询都可以使用QueryWrapper来构建查询条件。接下来看一些例子 查询查询出名字中带o的存款大于等于1000元的人的id、username、info、balance字段。 // 查询出名字中带o的存款大于等于1000元的人的id、username、info、balance字段 Test void testQueryWrapper() {// 1.构建查询条件 where username like %o% AND balance 1000QueryWrapperUser queryWrapper new QueryWrapper();queryWrapper.select(id, username, info, balance).like(username, o).ge(balance, 1000);// 2.查询数据ListUser userList userMapper.selectList(queryWrapper);userList.forEach(System.out::println); }更新更新用户名为jack的用户的余额为2000。 // 更新用户名为jack的用户的余额为2000。 Test void testUpdateByQueryWrapper() {// 1.设置要更新的数据User user new User();user.setBalance(2000);// 2.构建更新条件 where username JackQueryWrapperUser queryWrapper new QueryWrapperUser().eq(username, Jack);// 3.执行更新user中非null字段都会作为set语句System.out.println(userMapper.update(user, queryWrapper) 0); }2UpdateWrapper 基于BaseMapper中的update方法更新时只能直接赋值对于一些复杂的需求就难以实现。 例如更新id为1,2,4的用户的余额扣200对应的SQL应该是 UPDATE user SET balance balance - 200 WHERE id in (1, 2, 4)SET的赋值结果是基于字段现有值的这个时候就要利用UpdateWrapper中的setSql功能了 Test void testUpdateWrapper() {ListLong ids List.of(1L, 2L, 4L);// 1.生成SQLUpdateWrapperUser updateWrapper new UpdateWrapperUser().setSql(balance balance - 200) // SET balance balance - 200.in(id, ids); // WHERE id in (1, 2, 4)// 2.基于UpdateWrapper中的setSql来更新System.out.println(userMapper.update(updateWrapper) 0); }3基于Lambda的Wrapper 无论是QueryWrapper还是UpdateWrapper在构造条件的时候都需要写死字段名称会出现字符串魔法值。这在编程规范中显然是不推荐的。那怎么样才能不写字段名又能知道字段名呢 其中一种办法是基于变量的getter方法结合反射技术我们只要将条件对应的字段的getter方法传递给MybatisPlus它就能计算出对应的变量名了。而传递方法可以使用JDK8中的方法引用和Lambda表达式。 因此MybatisPlus又提供了一套基于Lambda的Wrapper包含两个 LambdaQueryWrapper对应QueryWrapperLambdaUpdateWrapper对应UpdateWrapper 其使用方式如下 Test void testLambdaUpdateWrapper() {ListLong ids List.of(1L, 2L, 4L);// 1.生成SQLLambdaUpdateWrapperUser lambdaUpdateWrapper new LambdaUpdateWrapperUser().setSql(balance balance - 200) // SET balance balance - 200.in(User::getId, ids); // WHERE id in (1, 2, 4)// 2.基于UpdateWrapper中的setSql来更新System.out.println(userMapper.update(lambdaUpdateWrapper) 0); }Test void testLambdaQueryWrapper() {// 1.构建查询条件 where username like %o% AND balance 1000LambdaQueryWrapperUser queryWrapper new LambdaQueryWrapper();queryWrapper.select(User::getId, User::getUsername, User::getInfo, User::getBalance).like(User::getUsername, o).ge(User::getBalance, 1000);// 2.查询数据ListUser userList userMapper.selectList(queryWrapper);userList.forEach(System.out::println); }总结 QueryWrapper和LambdaQueryWrapper通常用来构建select、delete、update的where条件部分UpdateWrapper和LambdaUpdateWrapper通常只有在set语句比较特殊的情况才使用尽量使用LambdaQueryWrapper和LambdaUpdateWrapper避免硬编码 2、自定义SQL 1自定义SQL片段 问题引出 在演示的UpdateWrapper和LambdaUpdateWrapper的案例中我们在代码中编写了更新的SQL语句 其中balance balance - 200现在这种写法相当于把Mapper层的sql语句写在Service层了这在某些企业也是不允许的因为SQL语句最好都维护在持久层而不是业务层。就当前案例来说由于条件是in语句只能将SQL写在Mapper.xml文件利用foreach来生成动态SQL。 假如查询条件更复杂动态SQL的编写也会更加复杂。 所以MybatisPlus提供了自定义SQL片段功能可以让我们利用Wrapper生成查询条件再结合Mapper.xml编写SQL。 以当前案例来说我们可以这样写 Test void testCustomSQLUpdate() {// 更新条件ListLong ids List.of(1L, 2L, 4L);int amount 200;// 定义条件LambdaQueryWrapperUser wrapper new LambdaQueryWrapperUser().in(User::getId, ids);// 调用自定义SQL方法userMapper.updateBalanceByIds(wrapper, amount); }然后在UserMapper中自定义SQL import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.toolkit.Constants; import com.itheima.mp.domain.po.User; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select;import java.util.List;public interface UserMapper extends BaseMapperUser {Select(UPDATE user SET balance balance - #{amount} ${ew.customSqlSegment})//void updateBalanceByIds(Param(ew) LambdaQueryWrapperUser wrapper, Param(amount) int amount);void updateBalanceByIds(Param(Constants.WRAPPER) LambdaQueryWrapperUser wrapper, Param(amount) int amount); }${ew.customSqlSegment}为自定义SQL片段Param(ew)其中参数ew必须叫这个如果忘记了也可以用baomidou包下的常量类Constants.WRAPPER其值等于ew。 这样就省去了编写复杂查询条件的烦恼了总结一下自定义SQL片段的使用场景 更新时的特殊场景不是更新具体不变的值而是在原有值的基础上动态做增减例如balance balance - amount完全使用MP只能在业务层拼接这条SQL语句。此时可以使用自定义SQL传值更新更新的SQL定义在Mapper接口或Mapper.xml中MP则更擅长处理where更新条件。查询时的特殊场景如下图查询的字段结果是个别字段而MP默认查询所有字段只能通过QueryWrapper或LambdaQueryWrapper的select()方法去在业务层拼出查询字段有时为了不违背企业开发规范此时也可以使用自定义SQL片段 2多表联查 理论上来讲MyBatisPlus是不支持多表查询的不过我们可以利用Wrapper中自定义条件结合自定义SQL来实现多表查询的效果。 例如我们要查询出所有收货地址在北京的并且用户id在1、2、4之中的用户信息要是自己基于mybatis实现SQL大概是这样的 select idqueryUserByIdAndAddr resultTypecom.itheima.mp.domain.po.UserSELECT *FROM user uINNER JOIN address a ON u.id a.user_idWHERE u.idforeach collectionids separator, itemid openIN ( close)#{id}/foreachAND a.city #{city} /select可以看出其中最复杂的就是WHERE条件的编写如果业务复杂一些这里的SQL会更变态。 但是基于自定义SQL结合Wrapper的玩法我们就可以利用Wrapper来构建查询条件然后手写SELECT及FROM部分实现多表查询。 查询条件这样来构建 Test void testCustomJoinWrapper() {// 1.准备自定义查询条件QueryWrapperUser wrapper new QueryWrapperUser().in(u.id, List.of(1L, 2L, 4L)).eq(a.city, 北京);// 2.调用mapper的自定义方法ListUser users userMapper.queryUserByWrapper(wrapper);users.forEach(System.out::println); }然后在UserMapper中自定义方法 Select(SELECT u.* FROM user u INNER JOIN address a ON u.id a.user_id ${ew.customSqlSegment}) ListUser queryUserByWrapper(Param(ew)QueryWrapperUser wrapper);当然也可以在UserMapper.xml中写SQL select idqueryUserByIdAndAddr resultTypecom.itheima.mp.domain.po.UserSELECT * FROM user u INNER JOIN address a ON u.id a.user_id ${ew.customSqlSegment} /select3、IService接口 MybatisPlus不仅提供了BaseMapper还提供了通用的Service接口及默认实现封装了一些常用的service模板方法。 通用接口为IService默认实现为ServiceImpl其中封装的方法可以分为以下几类 save新增remove删除update更新get查询单个结果list查询集合结果count计数page分页查询 1常用方法介绍 新增 save新增单个元素saveBatch批量新增saveOrUpdate根据id判断如果实体类中存在id就更新不存在则新增saveOrUpdateBatch批量的新增或修改 删除 removeById根据id删除removeByIds根据ids集合批量删除removeByMap根据Map中的键值对为条件删除remove(WrapperT)根据Wrapper条件删除removeBatchByIds暂不支持 修改 updateById根据id修改只更新不为null的值update(WrapperT)根据UpdateWrapper修改Wrapper中包含set和where部分update(TWrapper)按照T内的数据修改与Wrapper匹配到的数据updateBatchById根据id批量修改 查一条 getById根据id查询1条数据getOne(WrapperT)根据Wrapper查询1条数据getBaseMapper获取Service内的BaseMapper实现某些时候需要直接调用Mapper内的自定义SQL时可以用这个方法获取到Mapper 查多条 listByIds根据id批量查询list(WrapperT)根据Wrapper条件查询多条数据list()查询所有 查条数 count()统计所有数量count(WrapperT)统计符合Wrapper条件的数据数量 2基本用法 由于Service中经常需要定义与业务有关的自定义方法因此我们不能直接使用IService而是自定义Service接口然后继承MP的IService接口以拓展方法。让自定义的ServiceImpl实现类实现自定义的Service接口同时继承MP的默认实现类 ServiceImpl同时这样就不用自己实现IService接口中的方法了。 自定义Service接口继承IService接口。定义IUserService继承IService import com.baomidou.mybatisplus.extension.service.IService; import com.itheima.mp.domain.po.User;public interface IUserService extends IServiceUser { }自定义Service实现类实现自定义接口并继承ServiceImpl类。创建UserServiceImpl类继承ServiceImpl实现UserService import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.itheima.mp.domain.po.User; import com.itheima.mp.mapper.UserMapper; import com.itheima.mp.service.IUserService; import org.springframework.stereotype.Service;Service public class UserServiceImpl extends ServiceImplUserMapper, User implements IUserService { }ServiceImplM, T接口的泛型参数中M是继承了BaseMapper的Mapper接口T是PO实体类。 3Restful案例 案例基于Restful风格实现下面的接口 编号接口请求方式请求路径请求参数返回值1新增用户POST/users用户表单实体无2删除用户DELETE/users/{id}用户id无3根据id查询用户GET/users/{id}用户id用户VO4根据id批量查询GET/users用户id集合用户VO集合5根据id扣减余额PUT/users/{id}/deduction/{money} 用户id 扣减金额无 首先我们在项目中引入Swagger和Web依赖 !-- swagger -- dependencygroupIdcom.github.xiaoymin/groupIdartifactIdknife4j-openapi2-spring-boot-starter/artifactIdversion4.1.0/version /dependency !-- web -- dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId /dependency在yaml中配置swagger信息 knife4j:enable: trueopenapi:title: 用户管理接口文档description: 用户管理接口文档email: Aizenqq.comconcat: 蓝染url: https://www.itcast.cnversion: v1.0.0group:default:group-name: defaultapi-rule: packageapi-rule-resources:- com.itheima.mp.controller然后接口的接收和返回值分别需要定义两个实体 UserFormDTO代表新增时的用户表单 package com.itheima.mp.domain.dto;import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data;Data ApiModel(description 用户表单实体) public class UserFormDTO {ApiModelProperty(id)private Long id;ApiModelProperty(用户名)private String username;ApiModelProperty(密码)private String password;ApiModelProperty(注册手机号)private String phone;ApiModelProperty(详细信息JSON风格)private String info;ApiModelProperty(账户余额)private Integer balance; }UserVO代表查询的返回结果 package com.itheima.mp.domain.vo;import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data;Data ApiModel(description 用户VO实体) public class UserVO {ApiModelProperty(用户id)private Long id;ApiModelProperty(用户名)private String username;ApiModelProperty(详细信息)private String info;ApiModelProperty(使用状态1正常 2冻结)private Integer status;ApiModelProperty(账户余额)private Integer balance; }最后按照Restful风格编写Controller接口方法 Api(tags 用户管理接口) RestController RequestMapping(/users) RequiredArgsConstructor // lombok注解在构造方法中只会注入必须需要初始化的成员变量例如加了final且未初始化的变量将来不需要做注入的变量不加final即可 public class UserController {// Spring不推荐我们使用Autowired进行属性注入推荐我们使用构造器注入但当需要注入的成员变量很多的时候构造方法会显得特别长因此我们可以将需要注入的变量加上final并且使用lombok的RequiredArgsConstructor注解提供必要参数构造器private final IUserService userService;ApiOperation(新增用户接口)PostMappingpublic void saveUser(RequestBody UserFormDTO userDTO) {// 将DTO拷贝到POUser user BeanUtil.copyProperties(userDTO, User.class);// 新增userService.save(user);}ApiOperation(删除用户接口)DeleteMapping(/{id})public void deleteUserById(ApiParam(用户id) PathVariable(id) Long id) {userService.removeById(id);}ApiOperation(根据id查询用户接口)GetMapping(/{id})public UserVO queryUserById(ApiParam(用户id) PathVariable(id) Long id) {// 查询用户POUser user userService.getById(id);// 将PO拷贝到VOreturn BeanUtil.copyProperties(user, UserVO.class);}ApiOperation(根据id批量查询用户接口)GetMappingpublic ListUserVO queryUserById(ApiParam(用户id集合) RequestParam(ids) ListLong ids) {// 查询用户PO集合ListUser users userService.listByIds(ids);// 将PO集合拷贝到VO集合return BeanUtil.copyToList(users, UserVO.class);}ApiOperation(根据id扣减用户余额接口)PutMapping(/{id}/deduction/{money})public void deductBalanceById(ApiParam(用户id) PathVariable(id) Long id,ApiParam(扣减的金额) PathVariable(money) Integer money) {userService.deductBalanceById(id, money);} }可以看到前四个接口都直接在Controller实现即可无需编写任何Service代码非常方便。不过一些带有业务逻辑的接口比如第五个deductBalanceById接口MP的Service没有提供业务逻辑所以这些业务逻辑都要在Service层来做。另外更新余额需要自定义SQL要在mapper中来实现。 UserService接口和UserServiceImpl实现类 public interface IUserService extends IServiceUser {void deductBalanceById(Long id, Integer money); }Service public class UserServiceImpl extends ServiceImplUserMapper, User implements IUserService {Overridepublic void deductBalanceById(Long id, Integer money) {// 查询用户User user getById(id);// 校验用户状态if (user null || user.getStatus() 2) {throw new RuntimeException(用户状态异常);}// 校验余额是否充足if (user.getBalance() money) {throw new RuntimeException(用户余额不足);}// 扣减余额baseMapper.deductBalance(id, money);} }mapper接口 public interface UserMapper extends BaseMapperUser {Update(UPDATE user SET balance balance - #{money} WHERE id #{id})void deductBalance(Param(id) Long id, Param(money) Integer money); }访问http://localhost:8080/doc.html测试接口 4LambdaQuery和LambdaUpdate IService中还提供了Lambda功能来简化我们的复杂查询及更新功能。我们通过两个案例来学习一下。 案例一实现一个根据复杂条件查询用户的接口查询条件如下 name用户名关键字可以为空status用户状态可以为空minBalance最小余额可以为空maxBalance最大余额可以为空 可以理解成一个用户的后台管理界面管理员可以自己选择条件来筛选用户因此上述条件不一定存在需要动态做判断。 我们首先需要定义一个查询条件实体UserQueryDTO实体 Data ApiModel(description 用户查询条件实体) public class UserQueryDTO {ApiModelProperty(用户名关键字)private String name;ApiModelProperty(用户状态1-正常2-冻结)private Integer status;ApiModelProperty(余额最小值)private Integer minBalance;ApiModelProperty(余额最大值)private Integer maxBalance; }在UserController中定义方法 ApiOperation(根据条件查询用户接口) GetMapping(/condition) public ListUserVO queryUserByCondition(UserQueryDTO queryDTO) {// 查询用户PO集合ListUser users userService.queryUserByCondition(queryDTO);// 将PO集合拷贝到VO集合return BeanUtil.copyToList(users, UserVO.class); }UserService接口和UserServiceImpl实现类基于lambdaQuery实现 public interface IUserService extends IServiceUser {ListUser queryUserByCondition(UserQueryDTO queryDTO); }// 基于Lambda查询 Override public ListUser queryUserByCondition(UserQueryDTO queryDTO) {String name queryDTO.getName();Integer status queryDTO.getStatus();Integer minBalance queryDTO.getMinBalance();Integer maxBalance queryDTO.getMaxBalance();return lambdaQuery().like(name ! null, User::getUsername, name).eq(status ! null, User::getStatus, status).ge(minBalance ! null, User::getBalance, minBalance).le(maxBalance ! null, User::getBalance, maxBalance).list(); }MP对LambdaQueryWrapper和LambdaUpdateWrapper的用法进一步做了简化。我们无需自己通过new的方式来创建Wrapper而是直接调用lambdaQuery和lambdaUpdate方法。在组织查询条件的时候我们加入了name ! null这样的参数意思就是当条件成立时才会添加这个查询条件类似Mybatis的mapper.xml文件中的if标签。这样就实现了动态查询条件效果了。 MybatisPlus会根据链式编程的最后一个方法来判断最终的返回结果。lambdaQuery方法中除了可以构建条件还需要在链式编程的最后添加一个查询结果list()表示查询结果返回一个List集合。可选的常用方法有 one()最多1个结果list()返回集合结果count()返回计数结果exist()返回查询的结果是否存在 与lambdaQuery方法类似IService中的lambdaUpdate方法可以非常方便的实现复杂更新业务。 案例二改造根据id修改用户余额的接口要求如下 完成对用户状态校验完成对用户余额校验如果扣减后余额为0则将用户status修改为2表示冻结状态update语句的set部分是动态的 基于lambdaUpdate实现 Override Transactional public void deductBalanceById(Long id, Integer money) {// 查询用户User user getById(id);// 校验用户状态if (user null || user.getStatus() 2) {throw new RuntimeException(用户状态异常);}// 校验余额是否充足if (user.getBalance() money) {throw new RuntimeException(用户余额不足);}// 扣减余额//baseMapper.deductBalance(id, money);int remainBalance user.getBalance() - money;lambdaUpdate().set(User::getBalance, remainBalance) // 更新余额.set(remainBalance 0, User::getStatus, 2) // 动态判断是否更新status.eq(User::getBalance, user.getBalance()) // CAS乐观锁.eq(User::getId, id) // 根据id扣减对应用户的余额.update(); // 注意LambdaUpdate做复杂更新时最后必须记得加上.update()进行更新操作 }5批量新增 批处理方案性能测试 IService中的批量新增功能使用起来非常方便但有一点注意事项。 需求批量插入10万条用户数据并作出对比。 方式一普通for循环逐条插入方式二IService的批量插入默认不开启 jdbc 批处理参数方式三开启rewriteBatchedStatementstrue参数 首先我们测试方式一逐条插入数据 /*** 10w次插入意味着10w次网络请求耗时最慢*/ Test void testSaveOneByOne() {long b System.currentTimeMillis();for (int i 1; i 100000; i) {userService.save(buildUser(i));}long e System.currentTimeMillis();System.out.println(耗时 (e - b)); }private User buildUser(int i) {User user new User();user.setUsername(user_ i);user.setPassword(123);user.setPhone( (18688190000L i));user.setBalance(2000);user.setInfo({\age\: 24, \intro\: \英文老师\, \gender\: \female\});user.setCreateTime(LocalDateTime.now());user.setUpdateTime(user.getCreateTime());return user; }执行结果耗时大约为551.9秒 可以看到速度非常慢。 再测试一下方式二MybatisPlus的批处理 /*** MP批处理采用的是JDBC底层的预编译方案PreparedStatement将1000条数据统一打包执行save一并提交到MySQL每1000条发送一次网络请求插入100次共发送100次网络请求* MP如果不加JDBC连接参数rewriteBatchedStatementstrue底层还是打包逐条插入只不过是从网络请求数量上减少了耗时* 而加上了MySQL的这个开启批处理参数后MP调用的JDBC底层的批处理才能真正变成一次性批量插入多条数据*/ Test void testSaveBatch() {// 因为一次性new 10万条数据占用内存太多并且向数据库请求的数据包有上限大小限制一次网络传输的数据量是有限的// 所以我们每次批量插入1000条件插入100次即10万条数据// 准备一个容量为1000的集合ListUser list new ArrayList(1000);long b System.currentTimeMillis();for (int i 1; i 100000; i) {// 添加一个userlist.add(buildUser(i));// 每1000条批量插入一次if (i % 1000 0) {// 批量插入userService.saveBatch(list);// 清空集合准备下一批数据list.clear();}}long e System.currentTimeMillis();System.out.println(耗时 (e - b)); }执行结果耗时大约为27.6秒打包逐条插入从网络请求层面大大减少了耗时。 虽然上面的方式二减少了网络请求次数但是底层还是打包逐条SQL插入。如果想要真正实现批量处理有下面两种办法。 第一种实现就是利用MyBatis自定义SQL语句用foreach标签遍历组装成下面一条SQL的形式 !-- MyBatis批量插入 -- insert idbatchInsertUsersINSERT INTO user (username, password, phone, info, balance, create_time, update_time)VALUESforeach collectionlist itemuser separator,(#{name}, #{password}, #{phone}, #{info}, #{balance}, #{createTime}, #{updateTime})/foreach /insert第二种实现就是还是利用MP的jdbc批处理只不过MySQL本身默认没有开启这个批处理参数rewriteBatchedStatementstrue该参数在MySQL 3.1.13版本开始引入默认值为false不开启。因此这个批处理操作其实底层是由MySQL驱动去做的不是由MP来做的。所以我们只需在jdbc的url连接参数后添加该参数MP的批处理才能成效。 spring:datasource:url: jdbc:mysql://127.0.0.1:3307/mp?useUnicodetruecharacterEncodingUTF-8autoReconnecttrueserverTimezoneAsia/ShanghairewriteBatchedStatementstruedriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: 123456jdbc批处理相比于Mybatis批处理效率更高 开启参数后测试耗时大约为6.5秒 拓展rewriteBatchedStatementstrue 和 allowMultiQueriestrue 的区别 rewriteBatchedStatements是重写sql语句达到发送一次sql的请求效果allowMultiQueries是在mapper.xml中使用;分号分隔多条sql允许多条语句一起发送执行的。对于insert批处理操作开启rewriteBatchedStatementstrue驱动则会把多条sql语句重写成一条sql语句然后再发出去而对于update和delete批处理操作开启allowMultiQueriestrue驱动所做的事就是把多条sql语句累积起来再一次性发出去。 批处理方案总结 普通for循环逐条插入速度极差不推荐MP的批量新增基于预编译的批处理性能不错配置jdbc参数开启rewriteBatchedStatements性能最好 四、扩展功能 1、代码生成 在使用MybatisPlus以后基础的Mapper、Service、PO代码相对固定重复编写也比较麻烦。因此MybatisPlus官方提供了代码生成器根据数据库表结构生成PO、Mapper、Service等相关代码。只不过代码生成器同样要编码使用也很麻烦。 这里推荐大家使用一款MybatisPlus的插件它可以基于图形化界面完成MybatisPlus的代码生成非常简单。 1安装插件 在idea的plugins市场中搜索并安装MyBatisPlus插件 2使用步骤 刚好数据库中还有一张address表尚未生成对应的实体和mapper等基础代码。我们利用插件生成一下。 首先需要配置数据库地址在Idea顶部菜单中找到other选择Config Database在弹出的窗口中填写数据库连接的基本信息 点击OK保存。然后再次点击Idea顶部菜单中的other然后选择Code Generator在弹出的表单中填写信息 最终代码自动生成到指定的位置了 3代码生成器配置 如果不想用图形化界面方式配置生成代码使用MyBatis-Plus官网提供的代码生成器模板也是可以的但需要自己填写配置信息。 因为MP代码生成更新迭代速度很快若本文的API被弃用请以官网最新版本API为准 MyBatis-Plus新代码生成器https://baomidou.com/guides/new-code-generator/ 代码生成器配置https://baomidou.com/reference/new-code-generator-configuration/ 引入依赖 !-- MP代码生成器 -- dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-generator/artifactIdversion3.5.9/version /dependency代码生成模板配置示例 import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.generator.FastAutoGenerator; import com.baomidou.mybatisplus.generator.config.OutputFile;import java.util.Collections; import java.util.List;public class CodeGenerator {/*** 数据库链接地址**/private static final String JDBC_URL_MAN jdbc:mysql://xxxxx:3306/xxx?useUnicodetruecharacterEncodingUTF-8;/*** 数据库登录账号**/private static final String JDBC_USER_NAME xx;/*** 数据库登录密码**/private static final String JDBC_PASSWORD xxxx;public static void main(String[] args) {String dir \\xx\\xxx;String tablePrefix tb_;ListString tables List.of(tb_user);FastAutoGenerator.create(JDBC_URL_MAN, JDBC_USER_NAME, JDBC_PASSWORD).globalConfig(builder - {builder.author(Aizen) // 作者.outputDir(System.getProperty(user.dir) dir \\src\\main\\java) // 输出路径(写到java目录).enableSwagger() // 开启swagger.commentDate(yyyy-MM-dd); // 设置注释日期格式默认值: yyyy-MM-dd}).packageConfig(builder - {builder.parent(com.{company}) // 设置父包名.moduleName({model}) // 设置父包模块名.entity(domain) // 设置实体类包名.service(service) // 设置Service接口包名.serviceImpl(service.impl)// 设置Service实现类包名.controller(controller) // 设置Controller包名.mapper(mapper) // 设置Mapper接口文件包名.xml(mappers) // 设置Mapper XML文件包名.pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty(user.dir) dir \\src\\main\\resources\\mapper));}).strategyConfig(builder - {builder.addInclude(tables) // 设置需要生成的表名.addTablePrefix(tablePrefix) // 设置表前缀.serviceBuilder() // 设置 Service 层模板.formatServiceFileName(%sService).formatServiceImplFileName(%sServiceImpl).entityBuilder() // 设置实体类模板.enableLombok() // 启用 Lombok.logicDeleteColumnName(deleted) // 逻辑删除字段名(数据库字段).enableTableFieldAnnotation() // 开启生成实体时生成字段注解.controllerBuilder().formatFileName(%sController).enableRestStyle() // 启用 REST 风格.mapperBuilder() // Mapper 策略配置.enableBaseResultMap() // 生成通用的resultMap.superClass(BaseMapper.class) // 设置父类.formatMapperFileName(%sMapper).enableMapperAnnotation().formatXmlFileName(%sMapper);})//.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板默认的是Velocity引擎模板.execute(); // 执行生成} }2、静态工具 有的时候Service之间也会相互调用为了避免出现循环依赖问题MybatisPlus提供一个静态工具类Db其中的一些静态方法与IService中方法签名基本一致也可以帮助我们实现CRUD功能 因为静态方法无法读取类上的泛型所以MP在使用静态工具读取表信息时需要传入PO实体类的Class字节码MP再通过反射获取到表信息。其中新增和修改的方法由于需要传入实体类对象因此不用传入实体类的Class字节码。下面是使用示例 Test void testDbGet() {User user Db.getById(1L, User.class);System.out.println(user); }Test void testDbList() {// 利用Db实现复杂条件查询ListUser list Db.lambdaQuery(User.class).like(User::getUsername, o).ge(User::getBalance, 1000).list();list.forEach(System.out::println); }Test void testDbUpdate() {Db.lambdaUpdate(User.class).set(User::getBalance, 2000).eq(User::getUsername, Rose); }1案例一 案例一改造根据id用户查询的接口查询用户的同时返回用户收货地址列表 首先我们要添加一个收货地址的VO对象AddressVO Data ApiModel(description 收货地址VO) public class AddressVO{ApiModelProperty(id)private Long id;ApiModelProperty(用户ID)private Long userId;ApiModelProperty(省)private String province;ApiModelProperty(市)private String city;ApiModelProperty(县/区)private String town;ApiModelProperty(手机)private String mobile;ApiModelProperty(详细地址)private String street;ApiModelProperty(联系人)private String contact;ApiModelProperty(是否是默认 1默认 0否)private Boolean isDefault;ApiModelProperty(备注)private String notes; }然后改造原来的UserVO添加一个用户的收获地址集合对多属性 Data ApiModel(description 用户VO实体) public class UserVO {ApiModelProperty(用户id)private Long id;ApiModelProperty(用户名)private String username;ApiModelProperty(详细信息)private String info;ApiModelProperty(使用状态1正常 2冻结)private Integer status;ApiModelProperty(账户余额)private Integer balance;ApiModelProperty(用户的收获地址)private ListAddressVO addresses; }修改UserController中根据id查询用户的业务接口 ApiOperation(根据id查询用户接口) GetMapping(/{id}) public UserVO queryUserById(ApiParam(用户id) PathVariable(id) Long id) {return userService.queryUserAndAddressById(id); }IUserService和UserServiceImpl public interface IUserService extends IServiceUser {UserVO queryUserAndAddressById(Long id); }Override public UserVO queryUserAndAddressById(Long id) {// 查询用户POUser user this.getById(id);if (user null || user.getStatus() 2) {throw new RuntimeException(用户不存在或用户状态异常);}// 查询地址POListAddress addresses Db.lambdaQuery(Address.class).eq(Address::getUserId, id).list();// 封装VO将用户PO转为VOUserVO userVO BeanUtil.copyProperties(user, UserVO.class);// 如果地址PO不为空将地址PO集合转化为地址VO集合设置到用户VO中if (CollUtil.isNotEmpty(addresses)) {ListAddressVO addressVOList BeanUtil.copyToList(addresses, AddressVO.class);userVO.setAddresses(addressVOList);}return userVO; }在查询地址时我们采用了Db的静态方法因此避免了注入AddressService减少了循环依赖的风险。 2案例二 案例二改造根据id批量查询用户的接口查询用户的同时查询出用户对应的所有地址 代码实现 ApiOperation(根据id批量查询用户接口) GetMapping public ListUserVO queryUserByIds(ApiParam(用户id集合) RequestParam(ids) ListLong ids) {return userService.queryUserAndAddressByIds(ids); }public interface IUserService extends IServiceUser {ListUserVO queryUserAndAddressByIds(ListLong ids); }Override public ListUserVO queryUserAndAddressByIds(ListLong ids) {// 查询用户ListUser users this.listByIds(ids);if (CollUtil.isEmpty(users)) {return Collections.emptyList();}// 查询地址// 获取用户id集合ListLong userIds users.stream().map(User::getId).collect(Collectors.toList());// 根据用户id集合查询地址PO集合ListAddress addresses Db.lambdaQuery(Address.class).in(Address::getUserId, userIds).list();// 地址PO集合转地址VO集合ListAddressVO addressVOList BeanUtil.copyToList(addresses, AddressVO.class);// 用户地址集合分组处理相同用户的放入一个集合(组)中MapLong, ListAddressVO addressMap new HashMap(0);if (CollUtil.isNotEmpty(addressVOList)) {addressMap addressVOList.stream().collect(Collectors.groupingBy(AddressVO::getUserId));}// PO集合转VO集合返回ListUserVO userVOList new ArrayList(users.size());for (User user : users) {// 转换用户PO为VOUserVO userVO BeanUtil.copyProperties(user, UserVO.class);// 为每个用户VO设置自己的地址集合userVO.setAddresses(addressMap.get(user.getId()));userVOList.add(userVO);}return userVOList; }3案例三 案例三实现根据用户id查询收货地址功能需要验证用户状态冻结用户抛出异常 代码实现 Api(tags 地址管理接口) RestController RequestMapping(/address) RequiredArgsConstructor public class AddressController {private final IAddressService addressService;ApiOperation(根据用户id查询地址接口)GetMapping(/{userId})public ListAddressVO queryAddressByUserId(ApiParam(用户id) PathVariable(userId) Long userId) {return addressService.queryAddressByUserId(userId);} }public interface IAddressService extends IServiceAddress {ListAddressVO queryAddressByUserId(Long userId); }Service public class AddressServiceImpl extends ServiceImplAddressMapper, Address implements IAddressService {Overridepublic ListAddressVO queryAddressByUserId(Long userId) {// 查询用户验证用户状态冻结用户抛出异常User user Db.getById(userId, User.class);if (user null || user.getStatus() 2) {throw new RuntimeException(用户不存在或用户状态异常);}// 查询该用户的收获地址ListAddress addresses lambdaQuery().eq(Address::getUserId, userId).list();return BeanUtil.copyToList(addresses, AddressVO.class);} }3、逻辑删除 1介绍 对于一些比较重要的数据我们往往会采用逻辑删除的方案即 在表中添加一个字段标记数据是否被删除逻辑删除字段的属性通常是Integer或Boolean类型。当删除数据时把标记置为11表示已删除查询时只查询标记为0的数据0表示未删除 同理更新操作也需要加上deleted 0所以一旦采用了逻辑删除所有的查询、删除、更新逻辑都要跟着变化非常麻烦。 为了解决这个问题MybatisPlus就添加了对逻辑删除的支持。无需改变方法调用的方式而是在底层帮我们自动修改CRUD的语句。我们只需要在application.yaml文件中配置逻辑删除的字段名称和值即可。 注意只有MybatisPlus生成的SQL语句才支持自动的逻辑删除自定义SQL需要自己手动处理逻辑删除。 2使用步骤 例如给address表添加一个逻辑删除字段 alter table address add deleted bit default b0 null comment 逻辑删除;给Address实体类添加deleted属性 在application.yaml中配置MP逻辑删除字段 mybatis-plus:global-config:db-config:logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)logic-delete-value: 1 # 逻辑已删除值(默认为 1)logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)测试删除和查询 SpringBootTest class IAddressServiceTest {Autowiredprivate IAddressService addressService;Testvoid testLogicDelete() {// 删除addressService.removeById(59L);// 查询Address address addressService.getById(59L);System.out.println(address address);} }对于没有逻辑删除字段的表不受影响删除和查询还和之前一样。 3TableLogic TableLogic注解用于标记实体类中的逻辑删除字段。 Data EqualsAndHashCode(callSuper false) Accessors(chain true) TableName(address) public class Address implements Serializable {private static final long serialVersionUID 1L;TableId(value id, type IdType.AUTO)private Long id;// 省略.../*** 逻辑删除*/TableLogic // TableLogic注解用于标记实体类中的逻辑删除字段//TableLogic(value 0, delval 1) // value表示默认逻辑未删除值delval表示默认逻辑删除值 (这两个值可无、会自动获取全局配置)TableField(deleted)private Boolean deleted; }使用这种方式就不用在application.yaml中配置MP逻辑删除字段了直接在逻辑删除字段属性上加该注解即可。 总结逻辑删除本身也有自己的问题比如 会导致数据库表垃圾数据越来越多从而影响查询效率SQL中全都需要对逻辑删除字段做判断影响查询效率 因此不太推荐采用逻辑删除功能如果数据不能删除可以采用把数据迁移到其它表的办法。 4、枚举处理器 当实体类属性是枚举类型在与数据库的字段类型做转换时底层默认使用的是MyBatis提供的EnumOrdinalTypeHandler枚举类型处理器。 但是这个并不好用所以MP对类型处理器做了增强其中增强后的枚举处理器叫MybatisEnumTypeHandlerJSON处理器叫AbstractJsonTypeHandler。 1定义枚举标记EnumValue 定义一个用户状态的枚举 import com.baomidou.mybatisplus.annotation.EnumValue; import lombok.Getter;Getter public enum UserStatus {NORMAL(1, 正常),FREEZE(2, 冻结);EnumValueprivate final int value;private final String desc;UserStatus(int value, String desc) {this.value value;this.desc desc;} }要让MybatisPlus处理枚举与数据库类型自动转换我们必须告诉MybatisPlus枚举中的哪个字段的值作为数据库值。 MybatisPlus提供了EnumValue注解来标记枚举属性值。因此我们需要给枚举中与数据库字段类型对应的属性值添加EnumValue注解。 把User和UserVO类中的status字段类型改为UserStatus 枚举类型。 2配置枚举处理器 在application.yaml文件中配置枚举处理器 mybatis-plus:configuration:default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler测试发现查询出的status字段会是枚举类型默认显示的是枚举项的名称 如果我们想向前端返回指定的枚举属性例如value状态值或desc描述SpringMVC负责处理响应数据它在底层处理Json时用的是jackson所以我们只需要使用jackson提供的JsonValue注解来标记JSON序列化后展示的字段。 import com.baomidou.mybatisplus.annotation.EnumValue; import com.fasterxml.jackson.annotation.JsonValue; import lombok.Getter;Getter public enum UserStatus {NORMAL(1, 正常),FREEZE(2, 冻结);EnumValueprivate final int value;JsonValueprivate final String desc;UserStatus(int value, String desc) {this.value value;this.desc desc;} }响应效果 5、JSON类型处理器 数据库的user表中有一个info字段是JSON类型 info的格式像这样 {age: 20, intro: 佛系青年, gender: male}这样一来我们要读取info中的属性时就非常不方便。如果要方便获取info的类型最好是一个Map或者实体类。而一旦我们把info改为对象类型就需要在写入数据库时手动转为String再读取数据库时手动转换为对象这会非常麻烦。 因此MybatisPlus提供了很多特殊类型字段的类型处理器解决特殊字段类型与数据库类型转换的问题。例如处理JSON就可以使用JacksonTypeHandler处理器SpringMVC底层默认也是使用的这个类。 1定义接收Json的实体类 首先定义一个单独实体类UserInfo来与info字段的属性匹配 Data NoArgsConstructor AllArgsConstructor(staticName of) // 为了方便构建对象为有参构造提供静态方法名为ofUserInfo.of() public class UserInfo {private Integer age;private String intro;private String gender; }2指定类型处理器 将User和UserVO类的info字段修改为UserInfo类型并声明类型处理器TableField(typeHandler JacksonTypeHandler.class)。另外将info改为对象类型后出现对象嵌套在复杂嵌套查询时需要使用resultMap结果集映射否则无法映射。所以还需要再TableName注解中添加autoResultMaptrue确保能够正常映射。 Data TableName(value user, autoResultMap true) public class User {/*** 用户id*///TableId(type IdType.AUTO)private Long id;/*** 用户名*/private String username;/*** 密码*/private String password;/*** 注册手机号*/private String phone;/*** 详细信息*/TableField(typeHandler JacksonTypeHandler.class)private UserInfo info;/*** 使用状态1正常 2冻结*/private UserStatus status;/*** 账户余额*/private Integer balance;/*** 创建时间*/private LocalDateTime createTime;/*** 更新时间*/private LocalDateTime updateTime; }如果启动mapper.xml报错在info字段后加上, typeHandler com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler就解决了。 测试效果 6、yaml配置加密 目前我们配置文件中的很多参数都是明文如果开发人员发生流动很容易导致敏感信息的泄露。所以MybatisPlus支持配置文件的加密和解密功能。 我们以数据库的用户名和密码为例。 1生成密钥 首先我们利用MP提供的AES工具生成一个随机秘钥然后对用户名、密码加密。 import com.baomidou.mybatisplus.core.toolkit.AES; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest;import static org.junit.jupiter.api.Assertions.*;SpringBootTest class MpDemoApplicationTest {public static final String USERNAME root;public static final String PASSWORD 123456;Testvoid testEncrypt() {// 生成 16 位随机 AES 密钥String randomKey AES.generateRandomKey();System.out.println(randomKey randomKey); // randomKey 7pSEa6F9TnYacTNJ// 利用密钥对用户名加密String username AES.encrypt(USERNAME, randomKey);System.out.println(username username); // username O4YqWKYGlPW5t8QvgrhUQ// 利用密钥对用户名加密String password AES.encrypt(PASSWORD, randomKey);System.out.println(password password); // password cDYHnWysq07zUIAy1tcbRQ} }2修改配置 修改application.yaml文件把jdbc的用户名、密码修改为刚刚加密生成的密文。 spring:datasource:url: jdbc:mysql://127.0.0.1:3307/mp?useUnicodetruecharacterEncodingUTF-8autoReconnecttrueserverTimezoneAsia/ShanghairewriteBatchedStatementstruedriver-class-name: com.mysql.cj.jdbc.Driverusername: mpw:O4YqWKYGlPW5t8QvgrhUQ # 密文要以 mpw:开头password: mpw:cDYHnWysq07zUIAy1tcbRQ # 密文要以 mpw:开头3配置密钥运行参数 在启动项目的时候需要把刚才生成的秘钥添加到Jar启动参数中像这样 --mpw.key7pSEa6F9TnYacTNJ新版本idea添加Program arguments中设置界面如下 单元测试的时候不能添加启动参数所以要在测试类的注解上配置SpringBootTest(args --mpw.key7pSEa6F9TnYacTNJ) 然后随意运行一个单元测试可以发现数据库查询正常以上就是给SpringBoot的application.yaml配置文件中的敏感重要数据加密的实现步骤。 4实现原理 SpringBoot提供修改Spring环境后置处理器【EnvironmentPostProcessor】允许在应用程序之前操作环境属性值MyBatisPlus对其进行了重写实现。 package com.baomidou.mybatisplus.autoconfigure;import com.baomidou.mybatisplus.core.toolkit.AES; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import org.springframework.boot.SpringApplication; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.OriginTrackedMapPropertySource; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.env.SimpleCommandLinePropertySource;import java.util.HashMap;/*** 安全加密处理器** author hubin* since 2020-05-23*/ public class SafetyEncryptProcessor implements EnvironmentPostProcessor {Overridepublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {/*** 命令行中获取密钥*/String mpwKey null;for (PropertySource? ps : environment.getPropertySources()) {if (ps instanceof SimpleCommandLinePropertySource) {SimpleCommandLinePropertySource source (SimpleCommandLinePropertySource) ps;mpwKey source.getProperty(mpw.key);break;}}/*** 处理加密内容*/if (StringUtils.isNotBlank(mpwKey)) {HashMapString, Object map new HashMap();for (PropertySource? ps : environment.getPropertySources()) {if (ps instanceof OriginTrackedMapPropertySource) {OriginTrackedMapPropertySource source (OriginTrackedMapPropertySource) ps;for (String name : source.getPropertyNames()) {Object value source.getProperty(name);if (value instanceof String) {String str (String) value;if (str.startsWith(mpw:)) {map.put(name, AES.decrypt(str.substring(4), mpwKey));}}}}}// 将解密的数据放入环境变量并处于第一优先级上if (CollectionUtils.isNotEmpty(map)) {environment.getPropertySources().addFirst(new MapPropertySource(custom-encrypt, map));}}} }7、自动填充字段 MyBatis-Plus提供了一个便捷的自动填充功能用于在插入或更新数据时自动填充某些字段如创建时间、更新时间等。 1配置自动填充处理器 自动填充功能通过实现 com.baomidou.mybatisplus.core.handlers.MetaObjectHandler 接口来实现。我们需要创建一个类来实现这个接口并在其中定义插入和更新时的填充逻辑。添加Component配置自动填充处理器类被Spring管理。 MyMetaObjectHandler实现MetaObjectHandler接口 Slf4j Component public class MyMetaObjectHandler implements MetaObjectHandler {Overridepublic void insertFill(MetaObject metaObject) {log.info(开始插入填充...);// 起始版本 3.3.3(推荐)this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now());// Date类型填充//this.strictInsertFill(metaObject, createTime, () - new Date(), Date.class);// 起始版本 3.3.0(推荐使用)//this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now());// 也可以使用(3.3.0 该方法有bug)//this.fillStrategy(metaObject, createTime, LocalDateTime.now());}Overridepublic void updateFill(MetaObject metaObject) {log.info(开始更新填充...);// 起始版本 3.3.3(推荐)this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now());// Date类型填充//this.strictUpdateFill(metaObject, updateTime, Date::new, Date.class);// 起始版本 3.3.0(推荐)//this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now());// 也可以使用(3.3.0 该方法有bug)//this.fillStrategy(metaObject, updateTime, LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)} }2添加TableField的fill属性 在实体类中你需要使用 TableField 注解来标记哪些字段需要自动填充并通过fill属性指定填充的策略。 User实体类 Data TableName(value user, autoResultMap true) public class User {// 省略.../*** 创建时间*/TableField(fill FieldFill.INSERT)private LocalDateTime createTime;/*** 更新时间*/TableField(fill FieldFill.INSERT_UPDATE)private LocalDateTime updateTime; }FieldFill 枚举类 public enum FieldFill {DEFAULT, // 默认不处理INSERT, // 插入填充字段UPDATE, // 更新填充字段INSERT_UPDATE // 插入和更新填充字段 }注意事项 自动填充是直接给实体类的属性设置值如果属性没有值入库时会是null。MetaObjectHandler 提供的默认方法策略是如果属性有值则不覆盖如果填充值为 null 则不填充。字段必须声明 TableField 注解并设置 fill 属性来选择填充策略。在 update(T entity, WrapperT updateWrapper) 时entity 不能为空否则自动填充失效。在 update(WrapperT updateWrapper) 时不会自动填充需要手动赋值字段条件。使用 strictInsertFill 或 strictUpdateFill 方法可以根据注解 FieldFill.xxx、字段名和字段类型来区分填充逻辑。如果不需区分可以使用 fillStrategy 方法。 五、插件功能 MybatisPlus提供了很多的插件功能进一步拓展其功能。目前已有的插件有 PaginationInnerInterceptor自动分页TenantLineInnerInterceptor多租户DynamicTableNameInnerInterceptor动态表名OptimisticLockerInnerInterceptor乐观锁IllegalSQLInnerInterceptorsql 性能规范BlockAttackInnerInterceptor防止全表更新与删除 注意使用多个分页插件的时候需要注意插件定义顺序建议使用顺序如下 多租户动态表名分页乐观锁sql 性能规范防止全表更新与删除 这里我们以最常用的分页插件为例来学习插件的用法。 1、分页插件 在未引入分页插件的情况下MybatisPlus是不支持分页功能的IService和BaseMapper中的分页方法都无法正常起效。 所以我们必须配置分页插件。 1引入依赖 ⚠ 注意MyBatisPlus于 v3.5.9 起PaginationInnerInterceptor已分离出来。如需使用则需单独引入mybatis-plus-jsqlparser依赖 !-- MP分页插件 jdk 11 引入可选模块 -- dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-jsqlparser/artifactIdversion3.5.9/version /dependency !-- MP分页插件 jdk 8 引入可选模块 -- dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-jsqlparser-4.9/artifactId /dependency2配置分页内置拦截器 在项目中新建一个配置类MyBatisConfig import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;/*** MyBatis配置类*/ Configuration public class MybatisConfig {/*** MP拦截器*/Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {// 初始化核心插件MybatisPlusInterceptor interceptor new MybatisPlusInterceptor();// 添加分页插件PaginationInnerInterceptor paginationInnerInterceptor new PaginationInnerInterceptor(DbType.MYSQL);paginationInnerInterceptor.setMaxLimit(1000L); // 设置单页分页条数最大限制interceptor.addInnerInterceptor(paginationInnerInterceptor);return interceptor;} }3分页API 测试分页查询 Test void testPageQuery() {// 准备分页条件int pageNo 1, pageSize 2; // 页码、每页查询条数// 查询条件无// 分页条件PageUser page Page.of(pageNo, pageSize);// 排序条件//page.addOrder(new OrderItem(id, true)); // MP老版本排序条件写法true为升序false为降序page.addOrder(OrderItem.desc(balance)); // 新版MP直接用OrderItem的静态方法page.addOrder(OrderItem.asc(id)); // 可以添加多个排序条件// 分页查询page userService.page(page); // 这里返回的page对象其实和上面是同一个地址只不过是封装好分页查询结果的page对象// 获取page中的查询结果long total page.getTotal(); // 总条数System.out.println(total total);long pages page.getPages(); // 总页数System.out.println(pages pages);ListUser records page.getRecords(); // 分页数据records.forEach(System.out::println); }分页查询效果 2、通用分页实体 现在要实现一个用户分页查询的接口接口规范如下 参数说明请求方式GET请求路径/users/page请求参数json { pageNo: 1, pageSize: 5, sortBy: balance, isAsc: false, name: o, status: 1 } 返回值json { total: 100006, pages: 50003, list: [ { id: 1685100878975279298, username: user_9****, info: { age: 24, intro: 英文老师, gender: female }, status: 正常, balance: 2000 } ] } 特殊说明(1) 如果排序字段为空默认按照更新时间排序(2) 排序字段不为空则按照排序字段排序 这里需要用到4个实体 PageDTO通用分页查询条件的实体包含分页页码、每页查询条数、排序字段、是否升序UserQueryDTO接收用户查询条件实体为了实现分页功能去继承PageDTOPageResult分页结果实体包含总条数、总页数、当前页数据UserVO响应用户页面视图实体将用户VO集合封装到PageResult中返回 1实体类设计 分页条件不仅仅用户分页查询需要以后其它业务也都有分页查询的需求。因此建议将分页查询条件单独定义为一个PageDTO实体 PageDTO 通用分页查询条件的实体 Data ApiModel(description 通用分页查询实体) public class PageDTO {ApiModelProperty(页码)private Integer pageNo;ApiModelProperty(每页查询条数)private Integer pageSize;ApiModelProperty(排序字段)private String sortBy;ApiModelProperty(是否升序)private Boolean isAsc; }PageDTO是接收前端提交的查询参数一般包含四个属性 pageNo页码pageSize每页数据条数sortBy排序字段isAsc是否升序 UserQueryDTO 接收用户查询条件实体继承PageDTO EqualsAndHashCode(callSuper true) // 当判断相等时先考虑父类属性再考虑子类属性就是分页的时候把分页条件作为数据请求的的前提然后再考虑查到了哪些匹配的数据 Data ApiModel(description 用户查询条件实体) public class UserQueryDTO extends PageDTO {ApiModelProperty(用户名关键字)private String name;ApiModelProperty(用户状态1-正常2-冻结)private Integer status;ApiModelProperty(余额最小值)private Integer minBalance;ApiModelProperty(余额最大值)private Integer maxBalance; }PageResult 分页结果实体 Data ApiModel(description 分页结果) public class PageResultT {ApiModelProperty(总条数)private Long total;ApiModelProperty(总页数)private Long pages;ApiModelProperty(结果集合)private ListT list; }UserVO 响应用户页面视图实体将用户VO集合封装到PageResult中返回之前已经定义过了 Data ApiModel(description 用户VO实体) public class UserVO {ApiModelProperty(用户id)private Long id;ApiModelProperty(用户名)private String username;ApiModelProperty(详细信息)private UserInfo info;ApiModelProperty(使用状态1正常 2冻结)private UserStatus status;ApiModelProperty(账户余额)private Integer balance;ApiModelProperty(用户的收获地址)private ListAddressVO addresses; }2开发接口 UserController中定义分页条件查询用户的接口 // in UserController ApiOperation(根据条件分页查询用户接口) GetMapping(/condition/page) public PageResultUserVO queryUserByConditionAndPage(UserQueryDTO queryDTO) {return userService.queryUserByConditionAndPage(queryDTO); }IUserService接口和UserServiceImpl实现类 // in IUserService PageResultUserVO queryUserByConditionAndPage(UserQueryDTO queryDTO);// in UserServiceImpl Override public PageResultUserVO queryUserByConditionAndPage(UserQueryDTO queryDTO) {// 构建分页条件PageUser page Page.of(queryDTO.getPageNo(), queryDTO.getPageSize());// 构建排序条件if (StrUtil.isNotBlank(queryDTO.getSortBy())) { // 如果排序字段不为空page.addOrder(new OrderItem().setColumn(queryDTO.getSortBy()).setAsc(queryDTO.getIsAsc()));}else { // 如果排序字段为空默认按照更新时间排序page.addOrder(new OrderItem().setColumn(update_time).setAsc(false));}// 分页查询String name queryDTO.getName();Integer status queryDTO.getStatus();PageUser p lambdaQuery().like(name ! null, User::getUsername, name).eq(status ! null, User::getStatus, status).page(page);// 封装VO结果PageResultUserVO result new PageResult();result.setTotal(p.getTotal()); // 总条数result.setPages(p.getPages()); // 总页数// 当前页数据ListUser records p.getRecords();// 其实也可以不用判断因为如果查到的是空集合转换完还是空集合不影响最后的结果if (CollUtil.isEmpty(records)) {result.setList(Collections.emptyList());return result;}// 将用户集合拷贝为用户VO集合ListUserVO userVOList BeanUtil.copyToList(records, UserVO.class);result.setList(userVOList);return result; }测试根据条件和分页查询用户接口 发送的分页请求 响应的分页数据 3改造PageDTO实体 在刚才的代码中从PageDTO到MybatisPlus的Page之间转换的过程还是比较麻烦的。 对于PageDTO构建为MP的分页对象的部分我们完全可以在PageDTO这个实体内部中定义一个转换方法简化开发。 PageDTO import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data;Data ApiModel(description 通用分页查询实体) public class PageDTO {ApiModelProperty(页码)private Integer pageNo;ApiModelProperty(每页查询条数)private Integer pageSize;ApiModelProperty(排序字段)private String sortBy;ApiModelProperty(是否升序)private Boolean isAsc;/*** 在PageDTO内部将PageDTO构建为MP的分页对象并设置排序条件* param items 排序条件可以有一个或多个* return MP的分页对象* param T MP的分页对象的泛型*/public T PageT toMpPage(OrderItem... items) {// 构建分页条件PageT page Page.of(pageNo, pageSize);// 构建排序条件if (sortBy ! null sortBy.trim().length() 0) { // 如果排序字段不为空page.addOrder(new OrderItem().setColumn(sortBy).setAsc(isAsc));}else if (items ! null) { // 如果排序字段为空且传入的默认OrderItem不为空按照调用者传入的默认OrderItem排序page.addOrder(items);}return page;}/*** 将PageDTO构建为MP的分页对象* 如果调用者不想new OrderItem对象可以调用该方法传入默认的排序字段和排序规则即可* param defaultSortBy 排序字段* param defaultAsc 排序规则true为ascfalse为desc* return MP的分页对象* param T MP的分页对象的泛型*/public T PageT toMpPage(String defaultSortBy, Boolean defaultAsc) {return toMpPage(new OrderItem().setColumn(defaultSortBy).setAsc(defaultAsc));}/*** 将PageDTO构建为MP的分页对象排序条件按update_time更新时间降序* return MP的分页对象* param T MP的分页对象的泛型*/public T PageT toMpPageDefaultSortByUpdateTimeDesc() {return toMpPage(update_time, false);}/*** 将PageDTO构建为MP的分页对象排序条件按create_time创建时间降序* return MP的分页对象* param T MP的分页对象的泛型*/public T PageT toMpPageDefaultSortByCreateTimeDesc() {return toMpPage(create_time, false);} }这样我们在开发也时就可以省去对从PageDTO到Page的的转换 // 构建分页条件对象 PageUser page queryDTO.toMpPageDefaultSortByUpdateTimeDesc();4改造PageResult实体 在查询出分页结果后数据的非空校验数据的VO转换都是模板代码编写起来很麻烦。 我们完全可以将 PO分页对象转换为VO分页结果对象 的逻辑封装到 PageResult 的内部方法中简化整个过程。 PageResult import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data;import java.util.Collections; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors;Data ApiModel(description 分页结果) public class PageResultT {ApiModelProperty(总条数)private Long total;ApiModelProperty(总页数)private Long pages;ApiModelProperty(结果集合)private ListT list;/*** 将PO分页对象转换为VO分页结果对象* param page PO分页对象* param clazz 目标VO的字节码对象* return VO分页结果对象* param PO PO实体* param VO VO实体*/public static PO, VO PageResultVO of(PagePO page, ClassVO clazz) {// PO分页对象封装为VO结果PageResultVO result new PageResult();result.setTotal(page.getTotal()); // 总条数result.setPages(page.getPages()); // 总页数ListPO records page.getRecords(); // 当前页数据// 其实也可以不用判断因为如果查到的是空集合转换完还是空集合不影响最后的结果if (CollUtil.isEmpty(records)) {result.setList(Collections.emptyList());return result;}// 将PO集合拷贝为VO集合ListVO userVOList BeanUtil.copyToList(records, clazz);result.setList(userVOList);return result;}/*** 将PO分页对象转换为VO分页结果对象* param page PO分页对象* param convertor 自定义规则转换器* return VO分页结果对象* param PO PO实体* param VO VO实体*/public static PO, VO PageResultVO of(PagePO page, FunctionPO, VO convertor) {// PO分页对象封装为VO结果PageResultVO result new PageResult();result.setTotal(page.getTotal()); // 总条数result.setPages(page.getPages()); // 总页数ListPO records page.getRecords(); // 当前页数据// 其实也可以不用判断因为如果查到的是空集合转换完还是空集合不影响最后的结果if (CollUtil.isEmpty(records)) {result.setList(Collections.emptyList());return result;}// 将PO集合转换为VO集合转换动作由调用者来传递ListVO voList records.stream().map(convertor).collect(Collectors.toList());result.setList(voList);return result;} }这两个改造都相当于定义通用业务工具类。最终业务层的代码可以简化为 Override public PageResultUserVO queryUserByConditionAndPage(UserQueryDTO queryDTO) {// 构建分页条件对象PageUser page queryDTO.toMpPageDefaultSortByUpdateTimeDesc();// 分页查询String name queryDTO.getName();Integer status queryDTO.getStatus();PageUser p lambdaQuery().like(name ! null, User::getUsername, name).eq(status ! null, User::getStatus, status).page(page);// PO分页对象封装为VO结果return PageResult.of(p, UserVO.class); }如果是希望自定义PO到VO的转换过程可以调用重载方法of(PagePO page, FunctionPO, VO convertor)convertor的转换器逻辑由调用者去编写传递 Override public PageResultUserVO queryUserByConditionAndPage(UserQueryDTO queryDTO) {// 构建分页条件对象PageUser page queryDTO.toMpPageDefaultSortByUpdateTimeDesc();// 分页查询String name queryDTO.getName();Integer status queryDTO.getStatus();PageUser p lambdaQuery().like(name ! null, User::getUsername, name).eq(status ! null, User::getStatus, status).page(page);// PO分页对象封装为VO结果return PageResult.of(p, user - {// PO拷贝基础属性得到VOUserVO userVO BeanUtil.copyProperties(user, UserVO.class);// 对VO进行处理特殊逻辑String username userVO.getUsername();// 例如用户名脱敏处理userVO.setUsername(username.length() 2 ? StrUtil.fillAfter(username.substring(0, 2), *, username.length()) : username.charAt(0) *);return userVO;}); }自定义转换规则的场景例如 ① PO字段和VO字段不是包含关系出现字段不一致。② 对VO中的属性做一些过滤、数据脱敏、加密等操作。③ 将VO中的属性继续设置数据例如VO中的address属性可以查询出用户所属的收获地址设置后一并返回。 最终的查询结果如下
文章转载自:
http://www.morning.mcpdn.cn.gov.cn.mcpdn.cn
http://www.morning.fswml.cn.gov.cn.fswml.cn
http://www.morning.fdzzh.cn.gov.cn.fdzzh.cn
http://www.morning.bpp999.com.gov.cn.bpp999.com
http://www.morning.lrzst.cn.gov.cn.lrzst.cn
http://www.morning.bhqlj.cn.gov.cn.bhqlj.cn
http://www.morning.gthc.cn.gov.cn.gthc.cn
http://www.morning.pqcbx.cn.gov.cn.pqcbx.cn
http://www.morning.ywpwq.cn.gov.cn.ywpwq.cn
http://www.morning.wgxtz.cn.gov.cn.wgxtz.cn
http://www.morning.pxmyw.cn.gov.cn.pxmyw.cn
http://www.morning.gqwbl.cn.gov.cn.gqwbl.cn
http://www.morning.qwmdx.cn.gov.cn.qwmdx.cn
http://www.morning.mcpby.cn.gov.cn.mcpby.cn
http://www.morning.rfwrn.cn.gov.cn.rfwrn.cn
http://www.morning.ahscrl.com.gov.cn.ahscrl.com
http://www.morning.hlppp.cn.gov.cn.hlppp.cn
http://www.morning.ksgjn.cn.gov.cn.ksgjn.cn
http://www.morning.bhwll.cn.gov.cn.bhwll.cn
http://www.morning.wwdlg.cn.gov.cn.wwdlg.cn
http://www.morning.nzms.cn.gov.cn.nzms.cn
http://www.morning.bwqcx.cn.gov.cn.bwqcx.cn
http://www.morning.ljqd.cn.gov.cn.ljqd.cn
http://www.morning.rnlx.cn.gov.cn.rnlx.cn
http://www.morning.trrrm.cn.gov.cn.trrrm.cn
http://www.morning.yhywr.cn.gov.cn.yhywr.cn
http://www.morning.bpmtr.cn.gov.cn.bpmtr.cn
http://www.morning.pffqh.cn.gov.cn.pffqh.cn
http://www.morning.osshjj.cn.gov.cn.osshjj.cn
http://www.morning.bmssj.cn.gov.cn.bmssj.cn
http://www.morning.hwxxh.cn.gov.cn.hwxxh.cn
http://www.morning.kfqzd.cn.gov.cn.kfqzd.cn
http://www.morning.mqxrx.cn.gov.cn.mqxrx.cn
http://www.morning.cwqln.cn.gov.cn.cwqln.cn
http://www.morning.lhrwy.cn.gov.cn.lhrwy.cn
http://www.morning.tkzrh.cn.gov.cn.tkzrh.cn
http://www.morning.hmsong.com.gov.cn.hmsong.com
http://www.morning.jpmcb.cn.gov.cn.jpmcb.cn
http://www.morning.bjndc.com.gov.cn.bjndc.com
http://www.morning.kyfrl.cn.gov.cn.kyfrl.cn
http://www.morning.coatingonline.com.cn.gov.cn.coatingonline.com.cn
http://www.morning.kmkpm.cn.gov.cn.kmkpm.cn
http://www.morning.fnpmf.cn.gov.cn.fnpmf.cn
http://www.morning.hmqmm.cn.gov.cn.hmqmm.cn
http://www.morning.yfrlk.cn.gov.cn.yfrlk.cn
http://www.morning.mdpkf.cn.gov.cn.mdpkf.cn
http://www.morning.fqyxb.cn.gov.cn.fqyxb.cn
http://www.morning.tpssx.cn.gov.cn.tpssx.cn
http://www.morning.kfyqd.cn.gov.cn.kfyqd.cn
http://www.morning.1000sh.com.gov.cn.1000sh.com
http://www.morning.jxmjr.cn.gov.cn.jxmjr.cn
http://www.morning.dlrsjc.com.gov.cn.dlrsjc.com
http://www.morning.jlpdc.cn.gov.cn.jlpdc.cn
http://www.morning.rtspr.cn.gov.cn.rtspr.cn
http://www.morning.nshhf.cn.gov.cn.nshhf.cn
http://www.morning.kynf.cn.gov.cn.kynf.cn
http://www.morning.ksjmt.cn.gov.cn.ksjmt.cn
http://www.morning.xnltz.cn.gov.cn.xnltz.cn
http://www.morning.rqwmt.cn.gov.cn.rqwmt.cn
http://www.morning.kngqd.cn.gov.cn.kngqd.cn
http://www.morning.cwyfs.cn.gov.cn.cwyfs.cn
http://www.morning.gnkbf.cn.gov.cn.gnkbf.cn
http://www.morning.pswqx.cn.gov.cn.pswqx.cn
http://www.morning.tkcz.cn.gov.cn.tkcz.cn
http://www.morning.dyxlj.cn.gov.cn.dyxlj.cn
http://www.morning.jrslj.cn.gov.cn.jrslj.cn
http://www.morning.fksyq.cn.gov.cn.fksyq.cn
http://www.morning.bylzr.cn.gov.cn.bylzr.cn
http://www.morning.jwxmn.cn.gov.cn.jwxmn.cn
http://www.morning.rdlong.com.gov.cn.rdlong.com
http://www.morning.rrcxs.cn.gov.cn.rrcxs.cn
http://www.morning.zkqsc.cn.gov.cn.zkqsc.cn
http://www.morning.hpkr.cn.gov.cn.hpkr.cn
http://www.morning.lxfdh.cn.gov.cn.lxfdh.cn
http://www.morning.tdzxy.cn.gov.cn.tdzxy.cn
http://www.morning.nftzn.cn.gov.cn.nftzn.cn
http://www.morning.hjjhjhj.com.gov.cn.hjjhjhj.com
http://www.morning.zdnrb.cn.gov.cn.zdnrb.cn
http://www.morning.xdmsq.cn.gov.cn.xdmsq.cn
http://www.morning.gthgf.cn.gov.cn.gthgf.cn
http://www.tj-hxxt.cn/news/249343.html

相关文章:

  • 网站建设忄金手指花总网站积分系统方案
  • 桂林景区网站建设策划方案国内永久免费crm系统网站推荐大全
  • 广西网站建设培训wordpress与微信连接
  • 西安网站开发外包公司有上海网站开发有限公司
  • 做特色线路的旅游网站企业邮箱域名解析
  • 宁波做网站的大公司排名做域名不做网站可以吗
  • 北京网站制作设计价格官网网站设计
  • 网站怎么认证wordpress评论表情不显示不出来
  • 内江市建设信息网站wordpress文章页打不开
  • 石家庄网站建设价格公司网站制作苏州
  • 网站建设评审会阳泉软件定制网站建设
  • 墙蛙网站谁家做的网线制作实训总结
  • 深圳网站建设的公商标logo图案大全图片
  • 红色网站建设的作用和意义云南做网站
  • 建设银行官方网站客户资料修改网站建设的SOWT分析
  • 网站横条广告化妆品网站设计报告
  • 官方网站的必要性四川网站建设方案
  • 网站后台密码修改望野原文翻译
  • 心理医院网站优化服务商全国招聘网站排名
  • 网站开发算固定资产建e室内设计网官网全景效果图
  • 南宁网站建站公司国家域名注册
  • 外贸怎么做公司网站刚做的网站怎么
  • 做视频网站服务器配置建行系统
  • 网站克隆 有后台登录.net响应式网站模板
  • 青岛注册公司核名在哪个网站天津建设工程信息网登录不了
  • 网站制作苏州企业遵义网站建设服务
  • 智慧农业网站开发相关文档查找网站域名
  • 西部数码网站建设教程wordpress的底部找不到版权信息
  • 徐州网站建设大前端dedecms购物网站模板
  • 为什么建设旅游网站seo网站建设流程