mybatis-plus支持null字段全量更新的兩種方法
背景
如果僅僅只是標(biāo)題所列的目標(biāo),那么mybatis-plus 中可以通過設(shè)置
mybatis-plus.global-config.db-config.field-strategy=ignored
來忽略null判斷,達(dá)到實(shí)體字段為null時(shí)也可以更新數(shù)據(jù)為null
但是一旦使用了這個(gè)策略,就相當(dāng)于所有業(yè)務(wù)代碼都會(huì)按照這個(gè)策略執(zhí)行。
但是我們的業(yè)務(wù)往往需要如下支持
1、支持null字段全部更新
2、支持非null更新
3、支持指定null字段更新
所以單獨(dú)設(shè)置某一個(gè)策略是很難滿足實(shí)際的業(yè)務(wù)場景,因此我們需要在寫具體業(yè)務(wù)代碼的時(shí)候能夠根據(jù)需要選擇合適的方式。
mybatis-plus字段的四種策略
- default 默認(rèn)的,一般只用于注解里
- 在全局里代表 NOT_NULL
- 在注解里代表 跟隨全局
- ignored 忽略判斷
- not_empty 非空判斷
- not_null 非NULL判斷
這四種策略既可以配置全局,也可以在實(shí)體的注解上配置,但是,配置之后就是死的玩意,無法動(dòng)態(tài)。
一般默認(rèn)情況下都是not_null,如果此時(shí)要更新為null,那么用lambdaUpdateWrapper手動(dòng)設(shè)置哪些字段需要更新為null:
如將userName字段更新為null
userService.update( Wrappers.lambdaUpdate(user) .set(User::getUserName, null) .eq(User::getId,"0001"));
很顯然字段較少時(shí)這個(gè)方案還能說的過去,但是我們既有很少字段的情況,也有大批量字段的情況
所以此時(shí)使用這種方案很明顯的使用起來非常難受,那么有沒有方案既能支持有值更新,又能支持指定更新,還能
支持全量更新呢?
答案是有的,提供一個(gè)最低成本的適配方案如下
方案一
全局設(shè)置字段策略為not_null
因?yàn)楸旧鞮ambdaUpdateWrapper 已經(jīng)滿足了單個(gè)設(shè)置的需求,所以我們在寫個(gè)方法把全部字段組裝起來即可,
當(dāng)然此處的全部字段肯定也不是真的全部字段比如:一些比較特別的字段就不能被更新為null公共的字段創(chuàng)建時(shí)間,更新時(shí)間,邏輯刪除字段等等。
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擴(kuò)展
實(shí)際就是實(shí)現(xiàn) IsqlInjector
定義個(gè)方法
public class UpdateWithNull extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { //具體的字段邏輯在這里處理,實(shí)際上就是在這里構(gòu)造一個(gè)新的statement return null; } }
定義個(gè)CommonMapper繼承自BaseMapper,然后讓你的所有Mapper繼承自CommonMapper
public interface CommonMapper <T> extends BaseMapper<T> { ? ? /** ? ? ?* 根據(jù) whereEntity 條件,更新記錄 ? ? ?* ? ? ?* @param entity ? ? ? ?實(shí)體對象 (set 條件值,可以為 null) ? ? ?* @param updateWrapper 實(shí)體對象封裝操作類(可以為 null,里面的 entity 用于生成 where 語句) ? ? ?*/ ? ? int updateWithNull(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper); }
聲明一個(gè)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()); } }
方案二個(gè)人認(rèn)為沒有什么必要,這種擴(kuò)展方式是為了增加一些mybatis-plus未支持的定式需求。而我們的目標(biāo)相對簡單,所以使用方案一更高效。
方案二原理介紹
TableFieldInfo#getSqlSet
public String getSqlSet(final String prefix) { final String newPrefix = prefix == null ? EMPTY : prefix; // 默認(rèn): 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) { // 不進(jìn)行 if 包裹 return sqlSet; } return convertIf(sqlSet, newPrefix + property); }
可以看到這段代碼的邏輯中有一行fieldFill判斷,為update或者insert_update時(shí)不進(jìn)行if包裹。我們能可以利用這個(gè)特性。直接將需要的非公共字段全部設(shè)置為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("設(shè)置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("設(shè)置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); ? ? } }
到此這篇關(guān)于mybatis-plus支持null字段全量更新的兩種方法的文章就介紹到這了,更多相關(guān)mybatis-plus null全量更新內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一篇文章學(xué)會(huì)java死鎖與CPU 100%的排查
這篇文章主要介紹了一篇文章學(xué)會(huì)java死鎖與CPU 100%的排查,文中主要介紹了Java死鎖以及服務(wù)器CPU占用率達(dá)到100%時(shí)的排查和解決方法,感興趣的朋友一起來看一看吧2021-08-08springboot項(xiàng)目(jar包)指定配置文件啟動(dòng)圖文教程
這篇文章主要給大家介紹了關(guān)于springboot項(xiàng)目(jar包)指定配置文件啟動(dòng)的相關(guān)資料,在多環(huán)境部署過程中、及線上運(yùn)維中可能會(huì)遇到臨時(shí)指定配置文件的情況,需要的朋友可以參考下2023-07-07基于Java實(shí)現(xiàn)無向環(huán)和有向環(huán)的檢測
這篇文章主要介紹了如何在?Java?中實(shí)現(xiàn)無向環(huán)和有向環(huán)的檢測,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Java有一定的幫助,需要的可以參考一下2022-04-04