# springboot-mybatis-demo **Repository Path**: wangzz_felix/springboot-mybatis-demo ## Basic Information - **Project Name**: springboot-mybatis-demo - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-10-06 - **Last Updated**: 2021-10-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 条件构造器的应用 > 通过条件构造器可以实现R、U、D条件的构造 - updateWrapper用于给update条件传入条件参数 - queryWrapper用于给delete和select方法传入条件参数 ### BaseMapper中已经封装好的Mapper ```java import com.baomidou.mybatisplus.core.metadata.IPage;import java.sql.Wrapper;import java.util.List;import java.util.Map;public interface BaseMapper extends Mapper{ int delete(@Param("ew") Wrapper wrapper); int update(@Param("et") T entity, @Param("ew") Wrapper updateWrapper); T selectOne(@Param("ew") Wrapper queryWrapper); Integer selectCount(@Param("ew") Wrapper queryWrapper); List selectList(@Param("ew") Wrapper queryWrapper); List> selectMaps(@Param("ew") Wrapper queryWrapper); List selectObjs(@Param("ew") Wrapper queryWrapper); TPage selectPage(IPage page, @Param("ew") Wrapper queryWrapper); IPage> selectMapsPage(IPage page, @Param("ew") Wrapper queryWrapper); } ``` > 条件构造器相关: https://mp.baomidou.com/guide/wrapper.html#notin #### LIKE条件构造 ```java String name = "test"; // name不为空 String email = ""; // email为空串 QueryWrapper queryWrapper = new QueryWrapper(); queryWrapper.like(StringUtils.isNotEmpty(name), "name", name) // 因为email为空串,该条件未生效 .like(StringUtils.isNotEmpty(email), "email", email); List userList = userMapper.selectList(queryWrapper); userList.forEach(System.out::println); ``` #### allEq条件构造器 * all所有 * Eq是equals缩写表示相等 ```java // 构造条件 QueryWrapper queryWrapper = new QueryWrapper<>(); Map params = new HashMap(); params.put("name", "wangzz-test"); params.put("age", 18); params.put("email", null); // query.allEq(params, false); query.allEq((k, v) -> !k.equals("name"), params, false); List userList = userMapper.selectList(queryWrapper); userList.forEach(System.out::println); ``` #### Lambda条件构造器 ##### Eg01 ``` LambdaQueryWrapper lambdaQueryWrapper = Wrappers.lambdaQuery(); lambdaQueryWrapper.like(User::getName, "wangzz-test") .lt(User::getAge, 18); List userList = userMapper.selectList(lambdaQueryWrapper); ``` 转换执行代码 ```java SELECT id, name, age, email, create_time FROM user WHERE name LIKE "%wangzz-test%" AND age < 18 ``` # 自定义SQL MP虽然提供大量默认方法但是为了实现多表关联查询根据不同的查询条件传参 实现不同的动态SQL需要自定义SQL可以通过指定XXMapper.xml文件的存储位置 来实现自定义SQL ```java mybatis-plus.mapper-locations: classpath*:/mapper/*Mapper.xml ``` * 🌈多表关联查询或动态SQL写在XML文件里面进行维护 * 🌈单表的CRUD通过MP或Mybatis generator生成代码 # 主键生成策略精讲 ### 默认主键生成策略: 雪花算法 MP如果不做任何主键策略设置,默认使用雪花算法。该策略会根据雪花算法生成主键ID主键类型Long或String 自定义主键策略(5种) ```java public enum IDType{ /** * 数据库ID自增,数据库支持自增,并设置主键自增 */ AUTO(0), /** * 该类型未设置主键类型,默认采用雪花算法生成 */ NONE(1), /** * 用户输入ID,数据类型和数据库保持一致就行 *

该类型可以通过自己注册填充插件进行填充

*/ INPUT(2), /** * 以下3种类型,只有插入对象ID为空,才自动填充 */ ID_WORKER(3), // 全局唯一ID(idWorker)数值类型, UUID(4), // 全局唯一ID(UUID,不包含中划线) ID_WORKER_STR(5); // 字符串全局唯一ID(idWorker的字符串表示),数据库也要保证一样字符串类型 } ``` #### 局部注解配置策略 ```java @TableId(type = IdType.AUTO) private Long userId; ``` #### 全局配置策略 ```java mybatis-plus.global-config.db-config.id-type=auto ``` # 代码生成器的原理精讲以及使用方法 模板引擎使得视图和数据分离 HTML模板页面 + 页面数据 = 输出结果 代码生成器的实现原理与模板引擎实现页面渲染的逻辑几乎是一致的。 * 模板: 某语言的代码 + 模板引擎语法的占位符 * 模板引擎的输出结果在项目中是输出给浏览器进行页面渲染的,但是对于代码生成器而言,模板引擎的输出结果是保存到磁盘文件。 # 逻辑删除实现以及API细节 ### 物理删除与逻辑删除 物理删除: 文件存储所用到的磁盘存储区域被真正的擦除或清零,这样删除的文件是不可以恢复的,物理 删除是计算机处理数据的概念,直接使用DELETE、DROP删除表数据,如果没有备份,则数据就很难恢复了 逻辑删除: 只需要修改isDeleted等类似状态标识字段,这种方式会造成表的数据量增加而且查询的时候需要 考虑isDeleted状态标识字段,对索引都会有影响 > 一张表的数据是否采用逻辑删除,还要根据数据的重要性、数据量、查询性能以及业务需求等因素综合判断。 #### 准备工作 ```sql CREATE TABLE emp ( id BIGINT(20) NOT NULL COMMENT '主键ID', name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名', age INT(11) NULL DEFAULT NULL COMMENT '年龄', deleted TINYINT(4) NOT NULL DEFAULT '0' COMMENT '逻辑删除标记', PRIMARY KEY(id) ); ``` ```java @Data @EqualsAndHashCode(callSuper = true) public class Emp extends Model { private static final long serialVersionUID = 1L; private Long id; private String name; private Integer age; private String email; @TableLogic private Integer deleted; } ``` mysql> SELECT * -> FROM emp; +---------------------+-------------------+------+---------+ | id | name | age | deleted | +---------------------+-------------------+------+---------+ | 1446115543603662850 | Developer: wangzz | 22 | 0 | +---------------------+-------------------+------+---------+ 1 row in set (0.00 sec) deleted采用默认值0(未删除),新插入的数据都是未删除的数据 #### 全局配置参数 在项目实践中,逻辑删除字段不允许随意命名,所有表的逻辑删除字段使用相同的名称 eg: deleted ☆:当全局配置和@TableLogic局部配置同时存在,则以实体上注解为准,优先级更高。 ```xml # 全局逻辑删除字段值 mybatis-plus: global-config: db-config: logic-delete-field: deleted # 逻辑删除字段名称 logic-delete-value: 1 # 逻辑已删除值为1 logic-not-delete-value: 0 # 逻辑未删除值为0 ``` # 常用字段默认值自动填充 http://www.zimug.com/java/mybatis/小书mybatisplus第9篇-常用字段默认值自动填充/.html 【需求案例】 在插入数据的时候自动填充createTime和updateTime为当前插入数据的时间,在数据更新的时候修改updateTime为修改数据的时间。 不需要人为的手动赋值。 ```xml mysql> DESC emp; +-------------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------------+-------------+------+-----+---------+-------+ | id | bigint(20) | NO | PRI | NULL | | | name | varchar(30) | YES | | NULL | | | age | int(11) | YES | | NULL | | | deleted | tinyint(4) | NO | | 0 | | | create_time | datetime | YES | | NULL | | | update_time | datetime | YES | | NULL | | +-------------+-------------+------+-----+---------+-------+ 6 rows in set (0.00 sec) ``` * 在数据库层面需要先添加2个日期类型的字段create_time和update_time | create_time | datetime | YES | | NULL | | | update_time | datetime | YES | | NULL | | +-------------+-------------+------+-----+---------+-------+ * 使用@TableField注解标记实体类中的哪些字段需要填充 ```java @Data @EqualsAndHashCode(callSuper = true) public class Emp extends Model { private static final long serialVersionUID = 1L; private Long id; private String name; private Integer age; @TableLogic @TableField(select = false) private Integer deleted; // ============================================================ // 使用@TableField注解标记实体类中的哪些字段需要填充 @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; // ============================================================ } ``` ### FieldFill是一个枚举,指定在何种情况下自动填充 - DEFAULT:默认不处理 - INSERT: 插入时自动填充字段 - UPDATE:更形时自动填充字段 - INSERT_UPDATE: 插入和更新时自动填充字段 设置一个自动填充的配置类 ```java @Configuration public class MyMetaObjectHandler implements MetaObjectHandler { /* * 此种版本的写法适用于: 【version - 3.3.0后面的版本】 * * */ @Override public void insertFill(MetaObject metaObject) { this.fillStrategy(metaObject, "createTime", new Date()); this.fillStrategy(metaObject, "updateTime", new Date()); } @Override public void updateFill(MetaObject metaObject) { this.fillStrategy(metaObject, "updateTime", new Date()); } } ``` 测试: ```java @Test void insertTest(){ Emp emp = new Emp(); emp.setName("Developer: Daemon"); emp.setAge(26); int row = empMapper.insert(emp); System.out.println("影响行数: " + row); } ``` 结果: ```java | 1446127450947530753 | Developer: Daemon | 26 | 0 | 2021-10-07 22:57:07 | 2021-10-07 22:57:07 | ``` # MyBatis Plus 3.4版本之后分页插件的使用 > MyBatisPlusInterceptor是一系列的实现InnerInterceptor的拦截器链,可以理解为一个集合 - 自动分页: PaginationInnerInterceptor(最常用) - 多租户:TenantLineInnerInterceptor - 动态表名:DynamicTableNameInnerInterceptor - 乐观锁:OptimisticLockerInnerInterceptor - sql性能规范: IllegalSQLInnerInterceptor - 防止全表更新与删除: BlockAttackInnerInterceptor #### 旧版分页插件配置方法(Mybatis Plus 3.4.0版本之前) ``` @Configuration @MapperScan(basePackages = {"com.zimug.**.mapper"}) public class MybatisPlusConfig { @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false // paginationInterceptor.setOverflow(false); // 设置最大单页限制数量,默认 500 条,-1 不受限制 // paginationInterceptor.setLimit(500); // 开启 count 的 join 优化,只针对部分 left join paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true)); return paginationInterceptor; } } ``` #### 新的分页插件配置方法(MyBatis Plus 3.4.0版本及其之后的版本) 新的分页插件,一级缓存和二级缓存遵循MyBatis的规则需要设置MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题 ```java @Configuration @MapperScan(basePackages = {"com.zimug.**.mapper"}) public class MybatisPlusConfig { /** * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除) */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); //向Mybatis过滤器链中添加分页拦截器 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); //还可以添加i他的拦截器 return interceptor; } @Bean public ConfigurationCustomizer configurationCustomizer() { return configuration -> configuration.setUseDeprecatedExecutor(false); } } ``` 分页查询的使用方法: 没有变化,仍然和MyBatis之前的版本一致 ```java Page page = new Page<>(pageNum, pageSize); // 查询第pageNum页,每页pageSize条数据 //将分页参数page作为Mybatis或Mybatis Plus的第一个参数传入持久层函数,即可完成分页查询 return mySystemMapper.selectUser(page, 其他参数 ); ``` # MP通用字段自动填充的最佳实践 在进行持久层数据维护(新增或修改)的时候需要记录一些非业务字段 eg: create_time、update_time、update_by、create_by等用来维护数据记录的创建事件、修改时间、修改人、创建人等信息 对这些字段进行手动赋值。赋值的过程比较冗余,都是重复操作 * create_time: 系统当前时间 update_time:系统修改操作执行的当前时间 * create_by: 创建人 update_by: 修改人 eg: xxx.setUpdateBy("wangzz"); // 更新操作人 xxx.setUpdateTime(new Date()); // 更新操作的时间 ### 具体操作如下 #### 调整数据表结构 ```java 以mysql数据库环境下的xxx_yyy_zzz表为例,在原有的表字段的基础上,添加下面的四个通用数据维护字段。 ALTER TABLE `xxx_yyy_zzz` ADD COLUMN `create_by` VARCHAR(64) NOT NULL COMMENT '本条记录创建人'; ALTER TABLE `xxx_yyy_zzz` ADD COLUMN `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '本条记录创建时间'; ALTER TABLE `xxx_yyy_zzz` ADD COLUMN `update_by` VARCHAR(64) NOT NULL COMMENT '本条记录修改人'; ALTER TABLE `xxx_yyy_zzz` ADD COLUMN `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '本条记录的修改时间'; ``` ##### 维护信息父类 - 自动复制的字段 为了避免为每一个实体类都加上这四个成员变量,我们定义一个父类BaseColumns。 ```java @Data public class BaseColumns { /** * 本条记录创建人,insert操作的时候自动为该字段赋值 */ @TableField(fill = FieldFill.INSERT private String createBy; /** * 本条记录创建时间,insert操作的时候自动为该字段赋值 */ @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; /** * 本条记录更新人,insert或update操作的时候自动为该字段赋值,select = false */ @TableField(fill = FieldFill.INSERT_UPDATE,select = false) private String updateBy; /** * 本条记录更新时间,insert或update操作的时候自动为该字段赋值,select = false */ @TableField(fill = FieldFill.INSERT_UPDATE,select = false) private LocalDateTime updateTime; } ``` fill = FieldFill.INSERT表示insert操作的时候自动为该字段赋值 fill = FieldFill.INSERT_UPDATE表示nsert或update操作的时候自动为该字段赋值 select = false表示在使用Mybatis Wrapper条件构造器进行查询的时候,不查询这个属性对应的数据库字段。数据修改时间操作人通常对于运维更有意义,所以通常不需要展示在web页面上,所以通常select查询的时候不包含它。(这个内容与我们本机的字段自动填充没有太直接的联系,但是在实际应用中是有意义的) ##### 实体类的实现 下文实体类XxxYyyZzz对应数据库中的xxx_yyy_zzz表,除了以上四个通用字段,xxx_yyy_zzz表还包含其他的业务字段。 ```java @Data @EqualsAndHashCode(callSuper = true) public class XxxYyyZzz extends BaseColumns { //其他的属性字段 } ``` ##### 自动赋值的规则 ```java @Component public class MybastisColumnsHandler implements MetaObjectHandler { @Resource private JwtTokenUtil jwtTokenUtil; //我的工具类,用于从Token令牌中获取登陆人信息 //设置数据新增时候的,字段自动赋值规则 @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, "createBy", String.class, jwtTokenUtil.getUsernameFromToken()); this.strictUpdateFill(metaObject, "updateBy", String.class, jwtTokenUtil.getUsernameFromToken()); } //设置数据修改update时候的,字段自动赋值规则 @Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); this.strictUpdateFill(metaObject, "updateBy", String.class, jwtTokenUtil.getUsernameFromToken()); } } ``` 在数据新增的时候,自动为createTime、updateTime、createBy、updateBy赋值,即数据初始化。 在数据修改的时候,自动为updateTime、updateBy赋值。 JwtTokenUtil是我写的一个工具类,从当前的登录用户JWT Token中获取当前登录用户的用户名。(你的系统里面获取当前登录用户名方法和我的不一样,但总之可以获取到) ##### 实现效果 比如在进行数据更新的时候,下面的这两行代码就不需要写了,由updateFill(MetaObject metaObject) 自动完成 //xxxYyyZzz.setUpdateBy("zimug"); //数据记录更新操作人 //xxxYyyZzz.setUpdateTime(new Date()); //数据记录更新操作的时间 xxxYyyZzzMapper.updateById(xxxYyyZzz); 同理,在数据insert操作的时候,insertFill(MetaObject metaObject)将被自动执行