Skip to content

Mybatis Plus扩展方法

1、环境准备

  • JDK17

1、SQL脚本

mysql
create table grade
(
    id          varchar(32) comment '主键' primary key,
    grade_name  varchar(100)         null comment '班级名称',
    del_flag    tinyint(1) default 0 null comment '删除状态(0-正常,1-已删除)',
    create_by   varchar(255)         null comment '创建人',
    create_time datetime             null comment '创建时间',
    update_by   varchar(255)         null comment '修改人',
    update_time datetime             null comment '修改时间'
)comment '班级表';

INSERT INTO grade (id, grade_name, del_flag, create_by, create_time, update_by, update_time) VALUES ('1', '高三一班', 0, '1', now(), '1', now());

2、公共实体类

java
package com.xx.entity;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * @Author: xueqimiao
 * @Date: 2023/11/1 13:38
 */
@Data
public class BaseModel implements Serializable {

    @Schema(description = "创建人")
    private String createBy;

    @Schema(description = "创建时间")
    private Date createTime;

    @Schema(description = "更新人")
    private String updateBy;

    @Schema(description = "更新时间")
    private Date updateTime;

    @Schema(description = "0 正常,1已删除")
    private Integer delFlag;
}

3、实体类

java
package com.xx.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

/**
 * @Author: xueqimiao
 * @Date: 2023/11/1 13:42
 */
@Data
@TableName("grade")
public class GradeModel extends BaseModel{

    @Schema(description = "主键id")
    private String id;

    @Schema(description = "班级名称")
    private String gradeName;

}

4、Mapper层

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xx.mapper.GradeMapper">

</mapper>
java
package com.xx.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xx.entity.GradeModel;

/**
 * @Author: xueqimiao
 * @Date: 2023/11/1 13:46
 */
public interface GradeMapper extends BaseMapper<GradeModel> {
}

5、Service层

java
package com.xx.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.xx.entity.GradeModel;

/**
 * @Author: xueqimiao
 * @Date: 2023/11/1 13:45
 */
public interface GradeService extends IService<GradeModel> {
}
java
package com.xx.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xx.entity.GradeModel;
import com.xx.mapper.GradeMapper;
import com.xx.service.GradeService;
import org.springframework.stereotype.Service;

/**
 * @Author: xueqimiao
 * @Date: 2023/11/1 13:45
 */
@Service
public class GradeServiceImpl extends ServiceImpl<GradeMapper, GradeModel> implements GradeService {
}

6、Controller层

java
package com.xx.controller;

import com.xx.common.ResultData;
import com.xx.entity.GradeModel;
import com.xx.service.GradeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @Author: xueqimiao
 * @Date: 2023/11/1 13:47
 */
@RestController
@Slf4j
@Tag(name = "GradeController", description = "班级控制层")
public class GradeController {

    @Resource
    private GradeService gradeService;

    @GetMapping("/getList")
    @Operation(summary = "班级列表")
    public ResultData getList(){
        List<GradeModel> gradeList = gradeService.list();
        return new ResultData(gradeList);
    }

}

7、MybatisPlus相关配置

yaml
# mybatis-plus相关配置
mybatis-plus:
  # xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)
  mapper-locations: classpath:mapper/*Mapper.xml
  # type-enums-package: com.xx.enums
  configuration:
    # 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射
    map-underscore-to-camel-case: true
    # 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段
    call-setters-on-nulls: true
    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    cache-enabled: true

    db-config:
      logic-delete-field: del_flag  # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

#Mybatis输出sql日志
logging:
  level:
    com.xx.mapper: info

8、POM文件

xml
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3</version>
</dependency>

2、逻辑删除

1、逻辑删除和物理删除

物理删除:指文件存储所用到的磁存储区域被真正的擦除或清零,这样删除的文件是不可以恢复的,物理删除是计算机处理数据时的一个概念。如果在数据库中直接使用delete、drop删除了表数据,如果没有备份的话,数据就很难恢复了。

逻辑删除(软删除):逻辑删除就是对要被删除的数据打上一个删除标记,通常使用一个deleted字段标示行记录是不是被删除,比如该数据有一个字段deleted,当其值为0表示未删除,值为1表示删除。那么逻辑删除就是将0变成1。在逻辑上是数据是被删除的,但数据本身是依然存在的。

物理删除一定程度上删除了“无用”的数据,降低了表的数据量,对性能肯定是有好处的;但是如果没有备份的话,数据很难恢复。也无法对历史数据进行数据分析。

逻辑删除恢复的话只要修改deleted等类似的状态标示字段就可以了,但是表的数据量肯定会比物理删除增加了,并且查询时经常要考虑到deleted字段,对索引都会有影响。

所以一张表的数据是否采用逻辑删除,还要根据数据的重要性数据量查询性能以及业务需求等因素综合判断。

2、@TableLogic

java
@TableLogic
private Integer delFlag;

3、测试

mysql
==>  Preparing: SELECT id,grade_name,create_by,create_time,update_by,update_time,del_flag FROM grade WHERE del_flag=0
==> Parameters: 
<==    Columns: id, grade_name, create_by, create_time, update_by, update_time, del_flag
<==        Row: 1, 高三一班, 1, 2023-11-01 13:44:16, 1, 2023-11-01 13:44:20, 0
<==      Total: 1

3、主键生成策略

1、局部和全局设置主键生成策略

1、局部设置
java
@TableId(type = IdType.ASSIGN_ID)
private String id;
2、全局设置
yaml
mybatis-plus:
  global-config:
    db-config:
      id-type: auto

2、IdType取值

描述
AUTO数据库 ID 自增
NONE无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUTinsert 前自行 set 主键值
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 方法)

特别说明:

如果设置类型是AUTO自增策略,数据库字段一定设置自增

在没有进行设置主键生成算法的时候,默认算法是雪花算法

3、不配置主键策略

java
package com.xx;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.io.Serializable;

/**
 * @Author: xueqimiao
 * @Date: 2023/11/1 14:14
 */
@Data
public class GradeDTO implements Serializable {

    @Schema(description = "主键id")
    private String id;

    @Schema(description = "班级名称")
    private String gradeName;
}
java
@PostMapping("/saveGrade")
@Operation(summary = "添加班级")
public ResultData saveGrade(@RequestBody GradeDTO grade){
    if(ValidationUtil.isEmpty(grade)){
        return new ResultData(ResultCodeEnum.DATA_EMPTY_FAIL);
    }
    gradeService.save(ValueUtil.copyFieldValue(grade,GradeModel.class));
    return new ResultData();
}
json
{
  "gradeName": "高三二班"
}
java
==>  Preparing: INSERT INTO grade ( id, grade_name ) VALUES ( ?, ? )
==> Parameters: 1719599922669457410(String), 高三二班(String)

4、IdType.AUTO

如果设置类型是AUTO自增策略,数据库字段一定设置自增

5、IdType.NONE

采用默认的

6、IdType.INPUT

需要自己set主键值

java
### SQL: INSERT INTO grade  ( id, grade_name )  VALUES  ( ?, ? )
### Cause: java.sql.SQLIntegrityConstraintViolationException: Column 'id' cannot be null
; Column 'id' cannot be null] with root cause

7、IdType.ASSIGN_ID

雪花id

8、IdType.ASSIGN_UUID

UUID

java
==>  Preparing: INSERT INTO grade ( id, grade_name ) VALUES ( ?, ? )
==> Parameters: daf48291d4274a15e336424f98606a5e(String), 高三二班(String)

4、方法扩展

1、IService

更改为BaseService

java
package com.xx.mybatis;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xx.utils.ColumnUtil;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

/**
 * @Author: xueqimiao
 * @Date: 2022/4/7 14:26
 */
public interface BaseService<T> extends IService<T> {

    /**
     * 检查该字段集合组成的数据是否重复
     *
     * @param id     更新时所用的主键值,新增时传入空
     * @param fields 不能重复的字段集
     * @param values 需要检查重复的字段值集
     * @return 重复返回true, 不重复返回false
     */
    boolean checkDuplicate(String id, String[] fields, String[] values);

    /**
     * 检查该字段集合组成的数据是否重复
     *
     * @param id     更新时所用的主键值,新增时传入空
     * @param fn 不能重复的字段
     * @param value 需要检查重复的字段值
     * @return 重复返回true, 不重复返回false
     */
    boolean checkDuplicate(String id, ColumnUtil.SFunction<T, ?> fn, String value);

    /**
     * 根据多个主键查询
     * @param ids 主键几个
     * @return 对象对象集合
     */
    List<T> getByIds(Collection<? extends Serializable> ids);

    /**
     * 查询list返回 指定key value为T的 map
     * @param queryWrapper
     * @param key 指定key Function
     * @return
     */
    <E> Map<E,T> getListToMap(Wrapper<T> queryWrapper, Function<T, E> key);

    /**
     * 查询list返回 指定key value为T的 map
     * @param key 指定key Function
     * @return
     */
    <E> Map<E,T> getListToMap(Function<T, E> key);


    /**
     * 查询list返回 指定key value为List<T>的 map
     * @param queryWrapper
     * @param key 指定key Function
     * @return
     */
    <E> Map<E,List<T>> getListToMaps(Wrapper<T> queryWrapper, Function<T, E> key);

    /**
     * 查询list返回 指定key value为List<T>的 map
     * @param key 指定key Function
     * @return
     */
    <E> Map<E,List<T>> getListToMaps(Function<T, E> key);

    /**
     * 获取一条数据
     * @param lambdaQuery
     * @return
     */
    T getSingle(LambdaQueryWrapper<T> lambdaQuery);

    /**
     * 获取一条数据
     * @param fn
     * @param value
     * @return
     */
    T getSingle(ColumnUtil.SFunction<T, ?> fn, Object value);

    /**
     * 根据单个字段查询列表
     * @param fn
     * @param values
     * @return
     */
    List<T>  getList(ColumnUtil.SFunction<T, ?> fn, List values);

    /**
     * 根据单个字段查询列表
     * @param fn
     * @param value
     * @return
     */
    List<T>  getList(ColumnUtil.SFunction<T, ?> fn, Object value);

    /**
     * 获取列表
     * @param queryWrapper
     * @return
     */
    default List<T> getList(Wrapper<T> queryWrapper) {
        return this.getBaseMapper().selectList(queryWrapper);
    }

    /**
     * 获取列表
     * @return
     */
    default List<T> getList() {
        return this.list(Wrappers.emptyWrapper());
    }

    /**
     * 根据实体对象查询
     * @param entity 传入的实体对象要序列化
     * @return
     */
    List<T> getByEntity(Serializable entity);


    /**
     * 根据id进行物理删除,不会走逻辑删除逻辑
     * @param id
     * @return
     */
    int physicalDeleteById(Serializable id);

    /**
     * 根据id进行物理删除,不会走逻辑删除逻辑
     * @param ids
     * @return
     */
    int physicalDeleteByIds(Collection<? extends Serializable> ids);

    /**
     * 根据传入的对象进行更新,并在字段为 null 时将数据库中对应字段置空
     * @param entity
     * @return
     */
    boolean updateWithNullFields(T entity);



    ///**
    // * 根据实体对象查询
    // * @param source
    // * @param clazz
    // * @return
    // * @param <E>
    // */
    //<E> List<E> getByEntity(Serializable source,Class<E> clazz);
}

2、ServiceImpl

更改为BaseServiceImpl

java
package com.xx.mybatis;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xx.utils.ColumnUtil;
import com.xx.utils.FunctionUtils;
import com.xx.utils.ValidationUtil;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

/**
 * @Author: xueqimiao
 * @Date: 2022/4/7 15:05
 */
public class BaseServiceImpl<M extends BasePlusMapper<T>, T> extends ServiceImpl<M, T> implements BaseService<T> {

    /**
     * 检查该字段集合组成的数据是否重复
     *
     * @param id     更新时所用的主键值,新增时传入空
     * @param fields 不能重复的字段集
     * @param values 需要检查重复的字段值集
     * @return 重复返回true, 不重复返回false
     */
    @Override
    public boolean checkDuplicate(String id, String[] fields, String[] values) {
        if (ValidationUtil.isEmpty(fields) || ValidationUtil.isEmpty(values) || fields.length != values.length) {
            return true;
        }
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        if (!ValidationUtil.isEmpty(id)) {
            TableInfo tableInfo = TableInfoHelper.getTableInfo(this.entityClass);
            String keyProperty = tableInfo.getKeyProperty();
            wrapper.ne(keyProperty, id);
        }
        for (int pos = 0; pos < fields.length; pos++) {
            wrapper.eq(fields[pos], values[pos]);
        }
        wrapper.last("limit 1");
        Long count = count(wrapper);
        return count > 0;
    }

    /**
     * 检查该字段集合组成的数据是否重复
     *
     * @param id    更新时所用的主键值,新增时传入空
     * @param fn    不能重复的字段
     * @param value 需要检查重复的字段值
     * @return 重复返回true, 不重复返回false
     */
    @Override
    public boolean checkDuplicate(String id, ColumnUtil.SFunction<T, ?> fn, String value) {
        String field = ColumnUtil.getFieldName(fn, "_", 2);
        if (ValidationUtil.isEmpty(field) || ValidationUtil.isEmpty(value)) {
            return true;
        }
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        if (!ValidationUtil.isEmpty(id)) {
            TableInfo tableInfo = TableInfoHelper.getTableInfo(this.entityClass);
            String keyProperty = tableInfo.getKeyProperty();
            wrapper.ne(keyProperty, id);
        }
        wrapper.eq(field, value);
        Long count = count(wrapper);
        return count > 0;
    }

    /**
     * 根据多个主键查询
     *
     * @param ids 主键几个
     * @return 对象对象集合
     */
    @Override
    public List<T> getByIds(Collection<? extends Serializable> ids) {
        if (ValidationUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return listByIds(ids);
    }

    @Override
    public <E> Map<E, T> getListToMap(Wrapper<T> queryWrapper, Function<T, E> key) {
        return FunctionUtils.listToMap(list(queryWrapper), key);
    }

    @Override
    public <E> Map<E, T> getListToMap(Function<T, E> key) {
        return getListToMap(null, key);
    }

    @Override
    public <E> Map<E, List<T>> getListToMaps(Wrapper<T> queryWrapper, Function<T, E> key) {
        return FunctionUtils.group(list(queryWrapper), key);
    }

    @Override
    public <E> Map<E, List<T>> getListToMaps(Function<T, E> key) {
        return getListToMaps(null, key);
    }

    @Override
    public T getSingle(LambdaQueryWrapper<T> lambdaQuery) {
        if (ValidationUtil.isEmpty(lambdaQuery)) {
            return null;
        }
        lambdaQuery.last("limit 1");
        return getOne(lambdaQuery);
    }

    @Override
    public T getSingle(ColumnUtil.SFunction<T, ?> fn, Object value) {
        String field = ColumnUtil.getFieldName(fn, "_", 2);
        if (ValidationUtil.isEmpty(field) || ValidationUtil.isEmpty(value)) {
            return null;
        }
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        wrapper.eq(field, value);
        wrapper.last("limit 1");
        return getOne(wrapper);
    }

    @Override
    public List<T> getByEntity(Serializable source) {
        QueryWrapper<T> queryWrapper = new QueryWrapper<>((T) source);
        return baseMapper.selectList(queryWrapper);
    }

    @Override
    public List<T> getList(ColumnUtil.SFunction<T, ?> fn, List values) {
        String field = ColumnUtil.getFieldName(fn, "_", 2);
        if (ValidationUtil.isEmpty(field) || ValidationUtil.isEmpty(values)) {
            return null;
        }
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        wrapper.in(field, values);
        return list(wrapper);
    }

    @Override
    public List<T> getList(ColumnUtil.SFunction<T, ?> fn, Object value) {
        String field = ColumnUtil.getFieldName(fn, "_", 2);
        if (ValidationUtil.isEmpty(field) || ValidationUtil.isEmpty(value)) {
            return null;
        }
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        wrapper.eq(field, value);
        return list(wrapper);
    }

    @Override
    public int physicalDeleteById(Serializable id) {
        if (ValidationUtil.isEmpty(id)) {
            return 0;
        }
        TableInfo tableInfo = TableInfoHelper.getTableInfo(this.entityClass);
        return baseMapper.physicalDeleteById(tableInfo.getTableName(), tableInfo.getKeyProperty(), id);
    }

    @Override
    public int physicalDeleteByIds(Collection<? extends Serializable> ids) {
        if (ValidationUtil.isEmpty(ids)) {
            return 0;
        }
        TableInfo tableInfo = TableInfoHelper.getTableInfo(this.entityClass);
        return baseMapper.physicalDeleteByIds(tableInfo.getTableName(), tableInfo.getKeyProperty(), ids);
    }

    @Override
    public boolean updateWithNullFields(T entity) {
        if (entity == null) {
            return false;
        }
        // 使用 UpdateWrapper 指定更新条件,假设有一个叫做 "id" 的主键字段
        UpdateWrapper<T> updateWrapper = new UpdateWrapper<>();
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entity.getClass());
        String keyProperty = tableInfo.getKeyProperty();
        Field idField = null;
        try {
            idField = entity.getClass().getDeclaredField(keyProperty);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
        Object idValue = null;
        idField.setAccessible(true);
        TableId tableIdAnnotation = idField.getAnnotation(TableId.class);
        if (tableIdAnnotation != null) {
            try {
                idValue = idField.get(entity);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        updateWrapper.eq(keyProperty, idValue);
        // 遍历实体类的所有字段
        for (Field field : entity.getClass().getDeclaredFields()) {
            field.setAccessible(true);
            if (field.isAnnotationPresent(TableField.class) && field.getAnnotation(TableField.class).exist() == false) {
                continue;
            }
            if (field.getName().equals("updateBy") || field.getName().equals("updateTime")) {
                continue;
            }
            try {
                // 获取字段值
                Object value = field.get(entity);
                // 判断字段值是否为 null
                if (value == null) {
                    // 如果字段值为 null,则将数据库中对应字段置空
                    String columnName = camelToUnderline(field.getName());
                    updateWrapper.set(columnName, null);
                } else {
                    // 否则,将字段更新到数据库
                    String columnName = camelToUnderline(field.getName());
                    updateWrapper.set(columnName, value);
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        // 调用 update 方法执行更新操作
        return update(entity,updateWrapper);
    }


    public static String camelToUnderline(String camelCase) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < camelCase.length(); i++) {
            char c = camelCase.charAt(i);
            if (Character.isUpperCase(c)) {
                result.append("_").append(Character.toLowerCase(c));
            } else {
                result.append(c);
            }
        }
        return result.toString();
    }
}

3、BaseMapper

更改为BasePlusMapper

java
package com.xx.mybatis;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Param;

import java.io.Serializable;
import java.util.Collection;

/**
 * @Author: xueqimiao
 * @Date: 2023/3/31 11:15
 */
public interface BasePlusMapper<T> extends BaseMapper<T> {


    /**
     * 物理删除方法
     */
    @Delete("DELETE FROM ${tableName} WHERE ${tableId} = #{id}")
    int physicalDeleteById(@Param("tableName") String tableName, @Param("tableId") String tableId, @Param("id") Serializable id);

    @Delete("<script>" +
            "DELETE FROM ${tableName} WHERE ${tableId} IN " +
            "<foreach item='item' index='index' collection='ids' open='(' separator=',' close=')'>" +
            "#{item}" +
            "</foreach>" +
            "</script>")
    int physicalDeleteByIds(@Param("tableName") String tableName, @Param("tableId") String keyProperty, @Param("ids") Collection<? extends Serializable> ids);
}

5、自动注入扩展字段

  • 插入数据时自动填充:create_time

  • 更新数据时自动填充:update_time

1、注解填充字段

@TableField(fill = FieldFill.INSERT)

DEFAULT: 默认不处理

INSERT :插入时填充字段

UPDATE:更新时填充字段,当设置为这个的时候,即使在MetaObjectHandler.insertFil()的方法中执行了strictInsertFill()操作,执行SQL的时候,也是不会进行操作的。

INSERT_UPDATE:插入和更新时填充字段

我们到字段上加入了这个注解,可以观察到发的SQL语句,就加上了相应的字段,只是值是null,所以我们需要写拦截器赋值。

2、自定义实现类 MyMetaObjectHandler

java
package com.xx.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @Author: xueqimiao
 * @Date: 2023/11/8 14:08
 */
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        // 设置方式一
//        metaObject.setValue("createTime",new Date());
//        metaObject.setValue("updateTime",new Date());

        //设置方式二
        // 起始版本 3.3.3(推荐)
        this.strictInsertFill(metaObject, "createTime", Date.class,new Date());
        this.strictInsertFill(metaObject, "updateTime", Date.class,new Date());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", Date.class,new Date());
    }
}
java
package com.xx.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * @Author: xueqimiao
 * @Date: 2023/11/1 13:38
 */
@Data
public class BaseModel implements Serializable {

    @Schema(description = "创建人")
    private String createBy;

    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

    @Schema(description = "更新人")
    private String updateBy;

    @Schema(description = "更新时间")
    @TableField(fill = FieldFill.UPDATE)
    private Date updateTime;

    @Schema(description = "0 正常,1已删除")
    @TableLogic
    private Integer delFlag;

}
java
  @PostMapping("/saveGrade")
  @Operation(summary = "添加班级")
  public ResultData saveGrade(@RequestBody GradeDTO grade){
      if(ValidationUtil.isEmpty(grade)){
          return new ResultData(ResultCodeEnum.DATA_EMPTY_FAIL);
      }
      gradeService.save(ValueUtil.copyFieldValue(grade,GradeModel.class));
      return new ResultData();
  }

  @PostMapping("/updateGrade")
  @Operation(summary = "修改班级")
  public ResultData updateGrade(@RequestBody GradeDTO grade){
      if(ValidationUtil.isEmpty(grade)){
          return new ResultData(ResultCodeEnum.DATA_EMPTY_FAIL);
      }
      gradeService.updateById(ValueUtil.copyFieldValue(grade,GradeModel.class));
      return new ResultData();
  }

3、注意事项

  • 填充原理是直接给entity的属性设置值:也就是说底层会调用实体类如user的实例的setCreateTime()方法为字段createTime进行赋值。

  • 注解则是指定该属性在对应情况下必有值,如果无值则入库会是null。

  • MetaObjectHandler提供的默认方法的策略均为:如果属性有值则不覆盖,如果填充值为null则不填充。

  • 字段必须声明TableField注解,属性fill选择对应策略,该声明告知Mybatis-Plus需要预留注入SQL字段

  • 填充处理器MyMetaObjectHandler在 Spring Boot 中需要声明@Component或@Bean注入

  • 要想根据注解FieldFill.xxx和字段名以及字段类型来区分必须使用父类的strictInsertFill或者strictUpdateFill方法。

  • update(T t,Wrapper updateWrapper)时t不能为空,否则自动填充失效。

    java
    @PostMapping("/updateGrade2")
    @Operation(summary = "修改班级2")
    public ResultData updateGrade2(@RequestBody GradeDTO grade) {
        if (ValidationUtil.isEmpty(grade)) {
            return new ResultData(ResultCodeEnum.DATA_EMPTY_FAIL);
        }
        LambdaUpdateWrapper<GradeModel> lambdaUpdateWrapper = Wrappers.lambdaUpdate();
        lambdaUpdateWrapper.eq(GradeModel::getId, grade.getId());
        lambdaUpdateWrapper.set(GradeModel::getGradeName, grade.getGradeName());
        gradeService.update(lambdaUpdateWrapper);
        // gradeService.update(new GradeModel(), lambdaUpdateWrapper);
        return new ResultData();
    }
  • 当自定义mapper方法需要走填充时,建议按下列注解方式添加参数注解(如果使用编译参数保留的情况下,变量名字与注解名字保持一致也行):

数据类型注解示例
Collection@Param("collection") 或 @Param("coll")saveXxx(@Param("collection") Collection h2Users)
List@Param("list")saveXxx(@Param("list") List h2Users)
Array@Param("array")saveXxx(@Param("array") H2User[] h2Users)
实体@Param("et")saveXxx(@Param("et") H2User h2Users)

6、分页插件

java
@GetMapping("/listPage")
@Operation(summary = "分页查询")
@Parameters({
        @Parameter(name = "pageNo", description = "页数", required = true, in = ParameterIn.QUERY),
        @Parameter(name = "pageSize", description = "每页条数", required = true, in = ParameterIn.QUERY),
})
public ResultData<Page<GradeModel>> listPage(@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
                                             @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
    Page<GradeModel> page = new Page<>(pageNo, pageSize);
    Page<GradeModel> gradeModelPage = gradeService.page(page);
    return new ResultData(gradeModelPage);
}

发现没效果

java
package com.xx.config;

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;

/**
 * @Author: xueqimiao
 * @Date: 2023/11/8 14:51
 */
@Configuration
public class MybatisPlusConfig {

    /**
     * 添加分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        interceptor.addInnerInterceptor(paginationInnerInterceptor);//如果配置多个插件,切记分页最后添加
        //interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); 如果有多数据源可以不配具体类型 否则都建议配上具体的DbType
        return interceptor;
    }
}

属性介绍

属性名类型默认值描述
overflowbooleanfalse溢出总页数后是否进行处理(默认不处理,参见 插件#continuePage 方法)
maxLimitLong单页分页条数限制(默认无限制,参见 插件#handlerLimit 方法)
dbTypeDbType数据库类型(根据类型获取应使用的分页方言,参见 插件#findIDialect 方法)
dialectIDialect方言实现类(参见 插件#findIDialect 方法)

7、pagehelper

PageHelper是一个用于MyBatis的开源分页插件,它提供了非常方便的分页功能,使你能够轻松地在MyBatis查询中进行分页操作。PageHelper可以帮助你处理分页查询时的繁琐工作,包括计算总记录数、生成分页SQL语句等。以下是PageHelper的一些主要特点和用法:

  1. 简单的配置:PageHelper提供了一个简单的配置方式,你只需要在MyBatis的XML配置文件或者Java代码中配置分页参数,即可启用分页功能。
  2. 自动计算总记录数:PageHelper会自动在查询前执行一条COUNT查询,以便计算总记录数,这样你无需手动编写额外的SQL语句来获取总记录数。
  3. 支持多种数据库:PageHelper支持多种数据库,包括MySQL、Oracle、SQL Server等主流数据库,因此你可以在不同数据库上使用相同的分页插件。
  4. 多种分页方式:PageHelper支持多种分页方式,包括传统的物理分页和逻辑分页,你可以根据需要选择适合你的分页方式。
  5. 分页参数灵活配置:你可以通过PageHelper配置不同的分页参数,例如页码、每页显示的记录数等,以满足不同的分页需求。
  6. 多种排序方式:PageHelper支持多种排序方式,包括升序、降序等,可以在查询时指定排序字段。
java
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>2.0.0</version>
</dependency>
java
@GetMapping("/listPage2")
@Operation(summary = "分页查询2")
@Parameters({
        @Parameter(name = "pageNo", description = "页数", required = true, in = ParameterIn.QUERY),
        @Parameter(name = "pageSize", description = "每页条数", required = true, in = ParameterIn.QUERY),
})
public ResultData listPage2(@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
                                              @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
    Page startPage = PageHelper.startPage(pageNo, pageSize);
    gradeService.list();
    PageInfo pageInfo = new PageInfo<>(startPage);
    return new ResultData(pageInfo);
}

8、项目配置

1、pom.xml

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>dtx_test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.0</version>
        <relativePath/>
    </parent>

    <properties>
        <!--  依赖版本号 -->
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!--  组件版本号 -->
        <hutool.version>5.8.16</hutool.version>
        <druid.version>1.1.18</druid.version>
        <persistence-api.version>1.0.2</persistence-api.version>
        <mysql.version>8.0.27</mysql.version>
        <swagger3.version>3.0.0</swagger3.version>
        <mybatis-plus.spring.version>3.5.3</mybatis-plus.spring.version>
        <lombok.version>1.18.26</lombok.version>
        <boottest.version>3.0.5</boottest.version>
    </properties>

    <dependencies>
        <!--web + actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--swagger3-->
        <!--<dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>2.1.0</version>
        </dependency>-->

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
            <version>4.1.0</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.spring.version}</version>
        </dependency>

        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <!--persistence-->
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>persistence-api</artifactId>
            <version>${persistence-api.version}</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--boot-test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--hutool-all-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>

        <dependency>
            <groupId>com.xx</groupId>
            <artifactId>xx-common-core</artifactId>
            <version>1.2</version>
        </dependency>

        <dependency>
            <groupId>com.xuxueli</groupId>
            <artifactId>xxl-job-core</artifactId>
            <version>2.3.1</version>
        </dependency>

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2、application.yml

properties
server:
  port: 7777
  servlet:
    context-path: /dtx-test
spring:
  application:
    name: dtx_test
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/xx_db2023?connectTimeout=6000&socketTimeout=6000&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: mac_root
#  main:
#    allow-circular-references: true

# mybatis-plus相关配置
mybatis-plus:
#  global-config:
#    db-config:
#      id-type: auto
  # xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)
  mapper-locations: classpath:mapper/*Mapper.xml
  # type-enums-package: com.xx.enums
  configuration:
    # 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射
    map-underscore-to-camel-case: true
    # 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段
    call-setters-on-nulls: true
    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    cache-enabled: true

    db-config:
      logic-delete-field: del_flag  # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

#Mybatis输出sql日志
logging:
  level:
    com.xx.mapper: info

#xxl-job配置
xxl:
  job:
    admin:
      addresses: http://127.0.0.1:8080/xxl-job-admin
    accessToken: default_token
    executor:
      appname: xxl-job-executor-dtx
      address:
      ip:
      port: 9988
      logpath: ./xxl-job/jobhandler
      logretentiondays: 30