溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

mybatisplus支持null字段全量更新的方法是什么

發(fā)布時間:2023-02-24 11:21:41 來源:億速云 閱讀:113 作者:iii 欄目:開發(fā)技術

本篇內容介紹了“mybatisplus支持null字段全量更新的方法是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

背景

如果僅僅只是標題所列的目標,那么mybatis-plus 中可以通過設置
mybatis-plus.global-config.db-config.field-strategy=ignored
來忽略null判斷,達到實體字段為null時也可以更新數據為null
但是一旦使用了這個策略,就相當于所有業(yè)務代碼都會按照這個策略執(zhí)行。
但是我們的業(yè)務往往需要如下支持
1、支持null字段全部更新
2、支持非null更新
3、支持指定null字段更新

所以單獨設置某一個策略是很難滿足實際的業(yè)務場景,因此我們需要在寫具體業(yè)務代碼的時候能夠根據需要選擇合適的方式。

mybatis-plus字段的四種策略

  • default 默認的,一般只用于注解里

    • 在全局里代表 NOT_NULL

    • 在注解里代表 跟隨全局

  • ignored 忽略判斷

  • not_empty 非空判斷

  • not_null 非NULL判斷

這四種策略既可以配置全局,也可以在實體的注解上配置,但是,配置之后就是死的玩意,無法動態(tài)。

一般默認情況下都是not_null,如果此時要更新為null,那么用lambdaUpdateWrapper手動設置哪些字段需要更新為null:
如將userName字段更新為null

userService.update(
                Wrappers.lambdaUpdate(user)
                        .set(User::getUserName, null)
                        .eq(User::getId,"0001"));

很顯然字段較少時這個方案還能說的過去,但是我們既有很少字段的情況,也有大批量字段的情況
所以此時使用這種方案很明顯的使用起來非常難受,那么有沒有方案既能支持有值更新,又能支持指定更新,還能
支持全量更新呢?
答案是有的,提供一個最低成本的適配方案如下

方案一

全局設置字段策略為not_null
因為本身LambdaUpdateWrapper 已經滿足了單個設置的需求,所以我們在寫個方法把全部字段組裝起來即可,
當然此處的全部字段肯定也不是真的全部字段比如:一些比較特別的字段就不能被更新為null公共的字段創(chuàng)建時間,更新時間,邏輯刪除字段等等。

public class WrappersFactory {

    private final static List<String> ignoreList = new ArrayList<>();

    static {
        ignoreList.add(CommonField.available);
        ignoreList.add(CommonField.create_time);
        ignoreList.add(CommonField.create_username);
        ignoreList.add(CommonField.update_time);
        ignoreList.add(CommonField.update_username);
        ignoreList.add(CommonField.create_user_code);
        ignoreList.add(CommonField.update_user_code);
        ignoreList.add(CommonField.deleted);
    }

    public static <T> LambdaUpdateWrapper<T> updateWithNullField(T entity) {
        UpdateWrapper<T> updateWrapper = new UpdateWrapper<>();
        List<Field> allFields = TableInfoHelper.getAllFields(entity.getClass());
        MetaObject metaObject = SystemMetaObject.forObject(entity);
        for (Field field : allFields) {
            if (!ignoreList.contains(field.getName())) {
                Object value = metaObject.getValue(field.getName());
                updateWrapper.set(StringUtils.camelToUnderline(field.getName()), value);
            }
        }
        return updateWrapper.lambda();
    }
}

使用

userService.update(WrappersFactory.updateWithNullField(user).eq(User::getId,"0001"));

方案二

此方案采用的是常規(guī)的mybatis-plus擴展
實際就是實現(xiàn) IsqlInjector

定義個方法

public class UpdateWithNull extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        //具體的字段邏輯在這里處理,實際上就是在這里構造一個新的statement
        return null;
    }
}

定義個CommonMapper繼承自BaseMapper,然后讓你的所有Mapper繼承自CommonMapper

public interface CommonMapper <T> extends BaseMapper<T> {

    /**
     * 根據 whereEntity 條件,更新記錄
     *
     * @param entity        實體對象 (set 條件值,可以為 null)
     * @param updateWrapper 實體對象封裝操作類(可以為 null,里面的 entity 用于生成 where 語句)
     */
    int updateWithNull(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);

}

聲明一個IsqlInjector,然后將其配置為spring的bean即可

public class CustomSqlInjector extends AbstractSqlInjector {
    @Override
    public List<AbstractMethod> getMethodList() {
        return Stream.of(
                new Insert(),
                new Delete(),
                new DeleteByMap(),
                new DeleteById(),
                new DeleteBatchByIds(),
                new Update(),
                new UpdateWithNull(),
                new UpdateById(),
                new SelectById(),
                new SelectBatchByIds(),
                new SelectByMap(),
                new SelectOne(),
                new SelectCount(),
                new SelectMaps(),
                new SelectMapsPage(),
                new SelectObjs(),
                new SelectList(),
                new SelectPage()
        ).collect(toList());
    }
}

 方案二個人認為沒有什么必要,這種擴展方式是為了增加一些mybatis-plus未支持的定式需求。而我們的目標相對簡單,所以使用方案一更高效。

方案二原理介紹

TableFieldInfo#getSqlSet

public String getSqlSet(final String prefix) {
        final String newPrefix = prefix == null ? EMPTY : prefix;
        // 默認: column=
        String sqlSet = column + EQUALS;
        if (StringUtils.isNotEmpty(update)) {
            sqlSet += String.format(update, column);
        } else {
            sqlSet += SqlScriptUtils.safeParam(newPrefix + el);
        }
        sqlSet += COMMA;
        if (fieldFill == FieldFill.UPDATE || fieldFill == FieldFill.INSERT_UPDATE) {
            // 不進行 if 包裹
            return sqlSet;
        }
        return convertIf(sqlSet, newPrefix + property);
    }

可以看到這段代碼的邏輯中有一行fieldFill判斷,為update或者insert_update時不進行if包裹。我們能可以利用這個特性。直接將需要的非公共字段全部設置為FieldFill.UPDATE即可。

final List<TableFieldInfo> fieldList = tableInfo.getFieldList();
        for (final TableFieldInfo tableFieldInfo : fieldList) {
            final Class<? extends TableFieldInfo> aClass = tableFieldInfo.getClass();
            try {
                final Field fieldFill = aClass.getDeclaredField("fieldFill");
                fieldFill.setAccessible(true);
                fieldFill.set(tableFieldInfo, FieldFill.UPDATE);
            } catch (final NoSuchFieldException e) {
                log.error("獲取fieldFill失敗", e);
            } catch (final IllegalAccessException e) {
                log.error("設置fieldFill失敗", e);
            }
        }

所以這里的具體邏輯為

@Slf4j
public class UpdateWithNull extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        SqlMethod sqlMethod = SqlMethod.UPDATE;

        final List<TableFieldInfo> fieldList = tableInfo.getFieldList();
        for (final TableFieldInfo tableFieldInfo : fieldList) {
            final Class<? extends TableFieldInfo> aClass = tableFieldInfo.getClass();
            try {
                final Field fieldFill = aClass.getDeclaredField("fieldFill");
                fieldFill.setAccessible(true);
                fieldFill.set(tableFieldInfo, FieldFill.UPDATE);
            } catch (final NoSuchFieldException e) {
                log.error("獲取fieldFill失敗", e);
            } catch (final IllegalAccessException e) {
                log.error("設置fieldFill失敗", e);
            }
        }

        String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(),
                sqlSet(true, true, tableInfo, true, ENTITY, ENTITY_DOT),
                sqlWhereEntityWrapper(true, tableInfo));
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return addUpdateMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource);
    }
}

“mybatisplus支持null字段全量更新的方法是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI