如何扩展MybatisPlus的BaseMapper(Sql注入器的使用、自动填充功能、逻辑删除功能)

工作中常用的MybatisPlus的扩展功能:Sql注入器的使用、自动填充功能、逻辑删除功能

文章底部有git地址

SQL注入器的使用

我们只用MybatisPlus时,MybatisPlus在BaseMapper中提供了很多可以直接调用的方法,这些方法主要是通过ISqlInjector注入器进行注入,然后并提供使用的,

如果我们也想提供一个公用的方法,就可以通过sql注入器来解决

创建mp_user表

CREATE TABLE `mp_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `create_user` varchar(255) DEFAULT NULL COMMENT '创建人',
  `create_date` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_user` varchar(255) DEFAULT NULL COMMENT '更新人',
  `update_date` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `delete_flag` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '删除标识:0-未删除,1-已删除',
  `username` varchar(50) DEFAULT NULL COMMENT '用户名',
  `password` varchar(50) DEFAULT NULL COMMENT '密码',
  `birthday` varchar(50) DEFAULT NULL COMMENT '生日',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1434793267970568194 DEFAULT CHARSET=utf8;

添加数据

INSERT INTO `test`.`mp_user`(`id`, `create_user`, `create_date`, `update_user`, `update_date`, `delete_flag`, `username`, `password`, `birthday`) VALUES (1, NULL, NULL, NULL, '2021-09-06 15:51:55', 0, 'lily', '123', '2019-12-12');
INSERT INTO `test`.`mp_user`(`id`, `create_user`, `create_date`, `update_user`, `update_date`, `delete_flag`, `username`, `password`, `birthday`) VALUES (2, NULL, NULL, NULL, '2021-09-06 15:51:56', 0, 'tom', '123', '2019-12-12');

创建User实体

package com.qjc.entity;


import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

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

@Data
@TableName("mp_user")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId
    private Long id;

    private String createUser;

    private Date createDate;

    private String updateUser;

    private Date updateDate;
    
    private Integer deleteFlag;

    private String username;

    private String password;

    private String birthday;

}

定义MyBaseMapper继承BaseMapper

并定义一个mySelectList方法

package com.qjc.mapper;

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

import java.util.List;

/**
 * @ClassName: MyBaseMapper
 * @Description:
 * @Author: qjc
 * @Date: 2021/9/6 12:59 下午
 */
public interface MyBaseMapper<T> extends BaseMapper {

    List<T> mySelectList(@Param("ew") Wrapper<T> queryWrapper);

}

然后创建注入器MySqlInjector,继承SqlInjector的实现类DefaultSqlInjector

package com.qjc.sqlInjector;

import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;

import java.util.List;

/**
 * @ClassName: MySqlInjector
 * @Description:
 * @Author: qjc
 * @Date: 2021/9/6 1:01 下午
 */
public class MySqlInjector extends DefaultSqlInjector {


    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);
        methodList.add(new MySelectList());
        return methodList;
    }
}

创建该注入器需要将自定义的方法添加到methodList中,所以需要创建一个方法类(可以参照源码中定义的类)

定义方法类MySelectList(和刚才MyBaseMapper中定义的方法名不一致也没关系,这不是重点)

package com.qjc.sqlInjector;

import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;

/**
 * @ClassName: FindAll
 * @Description:
 * @Author: qjc
 * @Date: 2021/9/6 1:02 下午
 */
public class MySelectList extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
//        String sqlMethod = "findAll";
//        String sql = "select * from " + tableInfo.getTableName();
//        SqlSource sqlSource = this.languageDriver.createSqlSource(this.configuration, sql, modelClass);
//        return this.addSelectMappedStatement(mapperClass, sqlMethod, sqlSource, modelClass, tableInfo);

        SqlMethod sqlMethod = SqlMethod.SELECT_LIST;
        String sql = String.format(sqlMethod.getSql(), sqlFirst(),sqlSelectColumns(tableInfo, true), tableInfo.getTableName(),
                sqlWhereEntityWrapper(true, tableInfo), sqlComment());
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addSelectMappedStatementForTable(mapperClass, "mySelectList", sqlSource, tableInfo);
    }

    @Override
    public String sqlWhereEntityWrapper(boolean newLine, TableInfo table) {
        if (table.isLogicDelete()) {
            String sqlScript = table.getAllSqlWhere(true, true, WRAPPER_ENTITY_DOT);
            sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", WRAPPER_ENTITY),
                    true);
            sqlScript += (NEWLINE + table.getLogicDeleteSql(true, true) + NEWLINE);
            String normalSqlScript = SqlScriptUtils.convertIf(String.format("AND ${%s}", WRAPPER_SQLSEGMENT),
                    String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT,
                            WRAPPER_NONEMPTYOFNORMAL), true);
            normalSqlScript += NEWLINE;
            normalSqlScript += SqlScriptUtils.convertIf(String.format(" ${%s}", WRAPPER_SQLSEGMENT),
                    String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT,
                            WRAPPER_EMPTYOFNORMAL), true);
            sqlScript += normalSqlScript;
            sqlScript = SqlScriptUtils.convertChoose(String.format("%s != null", WRAPPER), sqlScript,
                    table.getLogicDeleteSql(false, true));
            sqlScript = SqlScriptUtils.convertWhere(sqlScript);
            return newLine ? NEWLINE + sqlScript : sqlScript;
        } else {
            String sqlScript = table.getAllSqlWhere(false, true, WRAPPER_ENTITY_DOT);
            sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", WRAPPER_ENTITY), true);
            sqlScript += NEWLINE;
            sqlScript += SqlScriptUtils.convertIf(String.format(SqlScriptUtils.convertIf(" AND", String.format("%s and %s", WRAPPER_NONEMPTYOFENTITY, WRAPPER_NONEMPTYOFNORMAL), false) + " ${%s}", WRAPPER_SQLSEGMENT),
                    String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT,
                            WRAPPER_NONEMPTYOFWHERE), true);
            sqlScript = SqlScriptUtils.convertWhere(sqlScript) + NEWLINE;
            sqlScript += SqlScriptUtils.convertIf(String.format(" ${%s}", WRAPPER_SQLSEGMENT),
                    String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT,
                            WRAPPER_EMPTYOFWHERE), true);
            sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", WRAPPER), true);
            return newLine ? NEWLINE + sqlScript : sqlScript;
        }
    }
}

重写injectMappedStatement方法,来将方法注入到MappedStatement中,需要注意的是addSelectMappedStatement方法第二个参数,

是方法名,及MyBaseMapper中自定义的mySelectList

然后最重要的是sqlWhereEntityWrapper方法,该方法就是定义sql语句的,可在此修改为自己想要的查询语句

让UserMapper继承我们创建的MyBaseMapper

package com.qjc.mapper;

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

import java.util.List;

/**
 * @ClassName: MyBaseMapper
 * @Description:
 * @Author: qjc
 * @Date: 2021/9/6 12:59 下午
 */
public interface MyBaseMapper<T> extends BaseMapper {

    List<T> mySelectList(@Param("ew") Wrapper<T> queryWrapper);

}

最后一步将自定义的sql注入器注册到Spring容器中

@Bean
    public MySqlInjector mySqlInjector() {
        return new MySqlInjector();
    }

然后进行测试

 自动填充功能

我们创建的表中有创建用户,创建时间,删除标识字段,这些都是在创建用户时应该自动填充的,更新时间和更新用户字段是在更新用户信息的时候自动填充的

具体操作方法如下:在插入时需要自动填充的的字段上用注解@TableField(fill = FieldFill.INSERT),在更新时需要自动填充的字段上用注解@TableField(fill = FieldFill.UPDATE)

有其他需要填充的字段可参考FieldFill

User实体如下

package com.qjc.entity;


import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

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

@Data
@TableName("mp_user")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId
    private Long id;

    @TableField(fill = FieldFill.INSERT)
    private String createUser;

    @TableField(fill = FieldFill.INSERT)
    private Date createDate;

    @TableField(fill = FieldFill.UPDATE)
    private String updateUser;

    @TableField(fill = FieldFill.UPDATE)
    private Date updateDate;

    @TableField(fill = FieldFill.INSERT)
    private Integer deleteFlag;

    private String username;

    private String password;

    private String birthday;

}

然后定义MyMetaObjectHandler实现MetaObjectHandler接口,重写insertFill和updateFill方法

package com.qjc.handler;

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

import java.util.Date;

/**
 * @ClassName: MyMetaObjectHandler
 * @Description:
 * @Author: qjc
 * @Date: 2021/9/6 4:01 下午
 */
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createUser", String.class, "qjc");
        this.strictInsertFill(metaObject, "createDate", Date.class, new Date());
        this.strictInsertFill(metaObject, "deleteFlag", Integer.class, 0);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateUser", String.class, "qjc");
        this.strictUpdateFill(metaObject, "updateDate", Date.class, new Date());
    }
}

测试代码

@Test
    public void insert() {
        User user = new User();
        user.setUsername("lucy");
        user.setPassword("55555");
        user.setBirthday("2020-01-01");
        userMapper.insert(user);
        System.err.println(user.getId());
    }

结果如下

 然后进行更新操作

    @Test
    public void update() {
        User user = new User();
        user.setId(1435062640798867458L);
        user.setUsername("lucy");
        user.setPassword("66666");
        user.setBirthday("2020-01-01");
        userMapper.updateById(user);
    }

结果如下

逻辑删除

我们在实际工作中,一般数据是不被删除的,要留存下来,所以会在每张表中添加删除标识来避免数据真的被删除

我们可以在删除标识deleteFlag字段上添加注解@TableLogic(value = "0", delval = "1")

然后测试删除,在删除之前我们先插入一条数据

    @Test
    public void insert() {
        User user = new User();
        user.setUsername("cat");
        user.setPassword("777");
        user.setBirthday("2021-01-01");
        userMapper.insert(user);
        System.err.println(user.getId());
    }

结果如下

然后使用BaseMapper提供的删除方法测试删除

    @Test
    public void delete() {
        userMapper.deleteById(1435064337008955393L);
    }

结果如下

我们发现在删除的时候更新人字段还是空的,这是因为逻辑删除并没有用到自动填充,所以我们自定义一个可以自动填充的删除方法

在MyBaseMapper中添加自动填充属性的删除方法

    int myDeleteByIdWithFill(T t);

然后定义一个MyDeleteByIdWithFill方法类

package com.qjc.sqlInjector;

import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;

import java.util.List;
import java.util.stream.Collectors;

/**
 * @ClassName: MyDeleteById
 * @Description: 填充属性值的删除
 * @Author: qjc
 * @Date: 2021/9/6 4:53 下午
 */
public class MyDeleteByIdWithFill extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        String sql;
        SqlMethod sqlMethod;
        if (tableInfo.isLogicDelete()) {
            // 进行逻辑删除
            List<TableFieldInfo> fieldInfos = getWithUpdateFillFields(tableInfo);
            // 删除时自动填充需要填充的属性值
            String sqlLogicSet = "SET " + fieldInfos.stream().map(i -> i.getSqlSet(null)).collect(Collectors.joining(EMPTY))
                    + tableInfo.getLogicDeleteSql(false, false);
            sql = String.format(
                    "<script>\nUPDATE %s %s WHERE %s=#{%s}\n</script>",
                    tableInfo.getTableName(),
                    sqlLogicSet,
                    tableInfo.getKeyColumn(),
                    tableInfo.getKeyProperty());
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, Object.class);
            return addUpdateMappedStatement(mapperClass, modelClass, "myDeleteByIdWithFill", sqlSource);
        } else {
            sqlMethod = SqlMethod.DELETE_BY_ID;
            sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), tableInfo.getKeyColumn(),
                    tableInfo.getKeyProperty());
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, Object.class);
            return this.addDeleteMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource);
        }
    }

    /**
     * 过滤出更新时进行填充信息的字段列表
     *
     * @param
     * @return
     * @author qjc
     * @date 2021/9/6 5:09 下午
     */
    private List<TableFieldInfo> getWithUpdateFillFields(TableInfo tableInfo) {
        return tableInfo.getFieldList().stream()
                .filter(TableFieldInfo::isWithUpdateFill)
                .collect(Collectors.toList())
                ;
    }
}

将该方法添加到MethodList中

public class MySqlInjector extends DefaultSqlInjector {


    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);
        methodList.add(new MySelectList());
        methodList.add(new MyDeleteByIdWithFill());
        return methodList;
    }
}

然后再插入一条数据

    @Test
    public void insert() {
        User user = new User();
        user.setUsername("dog");
        user.setPassword("888");
        user.setBirthday("2021-02-01");
        userMapper.insert(user);
        System.err.println(user.getId());
    }

结果如下

测试自动填充属性的删除方法

    @Test
    public void myDeleteByIdWithFill() {
        User user = new User();
        user.setId(1435066199628091394L);
        userMapper.myDeleteByIdWithFill(user);
    }

结果如下

这时更新用户信息就填充进去了

demo地址:https://gitee.com/xiaorenwu_dashije/mybatis-plus-demo.git

posted @ 2021-09-06 15:27  劈天造陆  阅读(3395)  评论(1编辑  收藏  举报