Mybatis-Plus如何自定義SQL隱碼攻擊器?

2023-04-13 06:00:49

有關Mybatis-Plus常用功能之前有做過一篇總結:

MyBatisPlus常用功能總結!(附專案範例)

一、什麼是SQL隱碼攻擊器

我們在使用Mybatis-Plus時,dao層都會去繼承BaseMapper介面,這樣就可以用BaseMapper介面所有的方法,

BaseMapper中每一個方法其實就是一個SQL隱碼攻擊器

在Mybatis-Plus的核心(core)包下,提供的預設可注入方法有這些:

那如果我們想自定義SQL隱碼攻擊器呢,我們該如何去做?

比如在Mybatis-Plus中呼叫updateById方法進行資料更新預設情況下是不能更新空值欄位的。

而在實際開發過程中,往往會遇到需要將欄位值更新為空值的情況。

那如何讓Mybatis-Plus支援空值更新呢?

如果僅是想實現支援更新空值欄位並不需要我們自定義SQL隱碼攻擊器,因為Mybatis-Plus提供了幾個擴充套件SQL隱碼攻擊器。


二、內建擴充套件SQL隱碼攻擊器有哪些?

1、自帶擴充套件SQL隱碼攻擊器

Mybatis-Plus 擴充套件SQL隱碼攻擊器在擴充套件包下,為我們提供了可延伸的可注入方法:

AlwaysUpdateSomeColumnById : 根據id更新欄位(全量更新不忽略null欄位),updateById預設會自動忽略實體中null值欄位。

InsertBatchSomeColumn : 真實批次插入,saveBatch其實是偽批次插入。

LogicDeleteBatchByIds : 邏輯刪除增加填充功能,比如刪除的時候填充更新時間、更新人。

Upsert : 插入一條資料(選擇欄位插入)。

2、SQL隱碼攻擊器全域性設定

@Component
public class MySqlInjector extends DefaultSqlInjector {
    
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
        /**
         * 把兩個擴充套件內建擴充套件SQL隱碼攻擊器注入
         */
        methodList.add(new InsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.UPDATE));
        methodList.add(new AlwaysUpdateSomeColumnById(i -> i.getFieldFill() != FieldFill.INSERT));
        return methodList;
    }
}

3、自定義Mapper

public interface MyBaseMapper<T> extends BaseMapper<T> {
    
    /**
     * 全欄位更新,不會忽略null值
     *
     * @param entity 實體物件
     */
    int alwaysUpdateSomeColumnById(T entity);

    /**
     * 全量插入,等價於insert
     * 
     * @param entityList 實體集合
     */
    int insertBatchSomeColumn(List<T> entityList);
}

三、擴充套件SQL隱碼攻擊器範例測試

1、使用者表

CREATE TABLE `user` (
  `id` int unsigned  AUTO_INCREMENT COMMENT '主鍵',
  `username` varchar(128)  COMMENT '使用者名稱',
  `phone` varchar(32)  COMMENT '手機號',
  `sex` char(1)  COMMENT '性別',
  `create_time` datetime  COMMENT '建立時間',
  `update_time` datetime  COMMENT '更新時間',
  `deleted` tinyint DEFAULT '0' COMMENT '1、刪除 0、未刪除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 

2、建立對應實體

@Data
@Accessors(chain = true)
@TableName("user")
public class UserDO implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 使用者名稱
     */
    @TableField("username")
    private String username;

    /**
     * 手機號
     */
    @TableField("phone")
    private String phone;

    /**
     * 性別
     */
    @TableField("sex")
    private String sex;

    /**
     * 建立時間
     */
    @TableField(value = "create_time",fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    /**
     * 更新時間
     */
    @TableField(value = "update_time",fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    /**
     * 1、刪除 0、未刪除
     */
    @TableField(value = "deleted",fill = FieldFill.INSERT)
    private Integer deleted;
}

其它有關程式碼這裡就不貼上了,具體看專案原始碼。

我們自定義的Mapper不再繼承BaseMapper而是繼承MyBaseMapper

 /**
  *  通用mapper介面,以後建立其他mapper介面時,不再繼承BaseMapper,而是繼承MyBaseMapper
  */
@Mapper
public interface UserMapper extends MyBaseMapper<UserDO> {

}

3、測試程式碼

@SpringBootTest
@RunWith(SpringRunner.class)
@ComponentScan("com.jincou.mybatisplus.dao")
public class SqlInjectorTest  {

   @Autowired
   private UserMapper mapper;
   
    @Test
    public void alwaysUpdateSomeColumnById() {
        UserDO user = new UserDO();
        user.setUsername("小小");
        user.setPhone(null);
        user.setSex("女");
        user.setId(1);
        mapper.alwaysUpdateSomeColumnById(user);
    }
    
    @Test
    public void insertBatchSomeColumn() {
        UserDO user = new UserDO();
        user.setUsername("zhangsan");
        user.setPhone("13811111111");
        user.setSex("女");

        UserDO user1 = new UserDO();
        user1.setUsername("lisi");
        user1.setPhone("13822222222");
        user1.setSex("男");

        ArrayList<UserDO> userDOS = Lists.newArrayList(user, user1);
        mapper.insertBatchSomeColumn(userDOS);
    }
}

執行結果

alwaysUpdateSomeColumnById方法

insertBatchSomeColumn方法

成功!


四、如何自定義SQL隱碼攻擊器?

在實際開發過程中,當Mybatis-Plus自帶的一些SQL隱碼攻擊器不滿足我們的條件時,我們就需要自定義SQL隱碼攻擊器,整個流程也非常簡單

這裡我們以一個很簡單的findAll方法為例進行學習。

在MyBaseMapper中新增findAll方法

public interface MyBaseMapper<T> extends BaseMapper<T> {
 
     /**
       *  查詢所有使用者
       */
      List<T> findAll();
}

2、編寫FindAll SQL隱碼攻擊器

public class FindAll extends AbstractMethod {

    public FindAll() {
        super("findAll");
    }


    public FindAll(String methodName) {
        super(methodName);
    }

    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        /* 執行 SQL ,動態 SQL 參考類 SqlMethod */
        String sql = "select *  from " + tableInfo.getTableName();
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addSelectMappedStatementForTable(mapperClass, sqlSource, tableInfo);
    }
}

3、註冊到Spring容器

@Component
public class MySqlInjector extends DefaultSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
        /**
         * 自定義SQL隱碼攻擊器注入
         */
        methodList.add(new FindAll());
        return methodList;
    }
}

4、測試

 @Test
    public void findAll() {
         List<UserDO> userDOS = mapper.findAll();
    }

成功!

補充

專案地址: https://github.com/yudiandemingzi/spring-boot-study

Mybatis-Plus官方SQL隱碼攻擊器範例地址:https://baomidou.com/pages/42ea4a/



宣告: 公眾號如需轉載該篇文章,發表文章的頭部一定要 告知是轉至公眾號: 後端元宇宙。同時也可以問本人要markdown原稿和原圖片。其它情況一律禁止轉載!