基于 MybatisPlus 邏輯刪除開啟情況下兼容多數(shù)據(jù)庫實現(xiàn)批量插入更新通用流程實現(xiàn)
背景
- 項目上業(yè)務(wù)流程有大量使用
mysql
批量插入更新語法insert on duplicate update
- 由于現(xiàn)在公司業(yè)務(wù)需要匾效,同時兼容達(dá)夢數(shù)據(jù)庫使用盒卸,達(dá)夢數(shù)據(jù)庫雖然也有類似的語法菠隆,不過使用起來也比較麻煩镜硕,生成相應(yīng)的
SQL
可以看我這一篇文章
核心問題
-
MySQL
更新插入流程如何使用實現(xiàn)锚烦? - 如何實現(xiàn)多個數(shù)據(jù)庫兼容插入更新的流程?
- 如何編寫工具類優(yōu)雅實現(xiàn)插入通用流程封裝以及整合
MybatisPlus
lambda
表達(dá)式查詢,達(dá)到方便易用的效果? - 開啟
MybatisPlus
邏輯刪除功能连锯,怎么通過自定義SQL
查詢出所有數(shù)據(jù)(插入更新流程可能涉及到所有的數(shù)據(jù)归苍,并不是只是處理未邏輯刪除的數(shù)據(jù))? - 批量數(shù)據(jù)插入更新速度如何優(yōu)化运怖?
代碼實現(xiàn)
實現(xiàn)多數(shù)據(jù)庫兼容插入更新操作拼弃,只能根據(jù) MySQL
插入更新原理利用代碼抽象通用化流程,較好的通用化方式是使用 MybatisPlus
自帶的通用 CRUD
邏輯方法實現(xiàn)摇展,但是原有自帶方法開啟邏輯刪除功能以后吻氧,查詢方法都會自帶過濾邏輯刪除的數(shù)據(jù),需要實現(xiàn)自定義 SQL
注入器咏连,為了實現(xiàn)一套不帶邏輯刪除的通用方法
拓展自定義 SQL
注入器實現(xiàn)
新增自定義方法
SelectList
方法不帶邏輯刪除
public class SelectListWithoutLogicDelete extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
String sql = String.format(
SqlMethod.SELECT_LIST.getSql(),
sqlFirst(),
sqlSelectColumns(tableInfo, true),
tableInfo.getTableName(),
sqlWhereEntityWrapper(true, tableInfo),
sqlComment()
);
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
String mapperMethodName = StrUtil.lowerFirst(getClass().getSimpleName());
return addSelectMappedStatementForTable(mapperClass, mapperMethodName, sqlSource, tableInfo);
}
// 重寫 sqlWhereEntityWrapper 盯孙,去掉邏輯刪除相關(guān)代碼
@Override
protected String sqlWhereEntityWrapper(boolean newLine, TableInfo table) {
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;
}
}
UpdateById
方法不帶邏輯刪除
public class UpdateByIdWithoutLogicDelete extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
SqlMethod sqlMethod = SqlMethod.UPDATE_BY_ID;
final String additional = optlockVersion(tableInfo);
String sql = String.format(
sqlMethod.getSql(),
tableInfo.getTableName(),
// 搬運(yùn) UpdateById 代碼,第一個參數(shù)變化了
sqlSet(false, false, tableInfo, false, ENTITY, ENTITY_DOT),
tableInfo.getKeyColumn(),
ENTITY_DOT + tableInfo.getKeyProperty(),
additional
);
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
String mapperMethodName = StrUtil.lowerFirst(getClass().getSimpleName());
return addUpdateMappedStatement(mapperClass, modelClass, mapperMethodName, sqlSource);
}
}
注入不帶邏輯刪除自定義 mapper
方法
public class SqlInjectorExtension extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
List<AbstractMethod> methods = super.getMethodList(mapperClass);
// 原來基礎(chǔ)上注入兩個新方法
methods.add(new SelectListWithoutLogicDelete());
methods.add(new UpdateByIdWithoutLogicDelete());
return methods;
}
}
封裝自定義 mapper
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.UpdateChainWrapper;
import org.apache.ibatis.annotations.Param;
public interface BaseMapperExtension<T> extends BaseMapper<T> {
// === ChainWrappers 工具類轉(zhuǎn)發(fā) ===
/**
* 鏈?zhǔn)讲樵?普通
*
* @return QueryWrapper 的包裝類
*/
default QueryChainWrapper<T> queryChain() {
return new QueryChainWrapper<>(this);
}
/**
* 鏈?zhǔn)讲樵?lambda 式
* <p>注意:不支持 Kotlin </p>
*
* @return LambdaQueryWrapper 的包裝類
*/
default LambdaQueryChainWrapper<T> lambdaQueryChain() {
return new LambdaQueryChainWrapper<>(this);
}
/**
* 鏈?zhǔn)礁?普通
*
* @return UpdateWrapper 的包裝類
*/
default UpdateChainWrapper<T> updateChain() {
return new UpdateChainWrapper<>(this);
}
/**
* 鏈?zhǔn)礁?lambda 式
* <p>注意:不支持 Kotlin </p>
*
* @return LambdaUpdateWrapper 的包裝類
*/
default LambdaUpdateChainWrapper<T> lambdaUpdateChain() {
return new LambdaUpdateChainWrapper<>(this);
}
// === 自定義 sql ===
/**
* 忽略邏輯刪除功能的 selectList 方法
*
* @param queryWrapper
* 查詢條件
*
* @return {@code List<T>}
*/
List<T> selectListWithoutLogicDelete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 忽略邏輯刪除功能的 updateById 方法
*
* @param entity
* 實體
*
* @return int
*/
int updateByIdWithoutLogicDelete(@Param(Constants.ENTITY) T entity);
}
封裝自定義 IServiceExtension
和 ServiceImplExtension
模板
public interface IServiceExtension<T extends IBaseDO<T>> extends IService<T> {
@Override
BaseMapperExtension<T> getBaseMapper();
// === BaseMapperExtension 新默認(rèn)方法 ===
/**
* 通過 id 更新實體(忽略邏輯刪除功能)
*
* @param entity
* 實體數(shù)據(jù)
*
* @return boolean
*/
default boolean updateByIdWithoutLogicDelete(T entity) {
return SqlHelper.retBool(getBaseMapper().updateByIdWithoutLogicDelete(entity));
}
default List<T> listWithoutLogicDelete(Wrapper<T> queryWrapper) {
return getBaseMapper().selectListWithoutLogicDelete(queryWrapper);
}
default List<T> listWithoutLogicDelete() {
return listWithoutLogicDelete(Wrappers.emptyWrapper());
}
// === 增強(qiáng)型方法 ===
/**
* 批量根據(jù) ID 更新(忽略邏輯刪除)
*
* @param entityList
* 實體列表
* @param batchSize
* 批次大小
*
* @return boolean
*/
boolean updateBatchByIdWithoutLogicDelete(Collection<T> entityList, int batchSize);
}
public abstract class ServiceImplExtension<M extends BaseMapperExtension<T>, T extends IBaseDO<T>> extends ServiceImpl<M, T> implements IServiceExtension<T> {}
新增 MybatisPlusUtils
封裝
lambda
表達(dá)式通用插入更新流程
- 基礎(chǔ)實體類接口
IBaseDO
import java.util.Date;
public interface IBaseDO<E> {
Long getId();
E setId(Long id);
Date getCreateDate();
E setCreateDate(Date createDate);
Date getUpdateDate();
E setUpdateDate(Date updateDate);
}
- 抽象實體類
@EqualsAndHashCode
public abstract class BaseDO<E extends BaseDO<E>> implements IBaseDO<E> {
@ApiModelProperty(value = "主鍵")
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
@ApiModelProperty(value = "創(chuàng)建時間")
@TableField(value = "create_date", fill = FieldFill.INSERT)
private Date createDate;
@ApiModelProperty(value = "更新時間")
@TableField(value = "update_date", fill = FieldFill.INSERT_UPDATE)
private Date updateDate;
@Override
public Long getId() {
return id;
}
@Override
public E setId(Long id) {
this.id = id;
return self();
}
private E self() {
return (E) this;
}
@Override
public Date getCreateDate() {
return createDate;
}
@Override
public E setCreateDate(Date createDate) {
this.createDate = createDate;
return self();
}
@Override
public Date getUpdateDate() {
return updateDate;
}
@Override
public E setUpdateDate(Date updateDate) {
this.updateDate = updateDate;
return self();
}
public E fillLatestDate() {
Date date = new Date();
createDate = date;
updateDate = date;
return self();
}
public E fillDate(Date date) {
createDate = date;
updateDate = date;
return self();
}
}
-
MyBatisPlusUtils
工具類
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.tuple.Pair;
import java.util.*;
import java.util.function.BiFunction;
import static java.util.stream.Collectors.*;
@Slf4j
public abstract class MyBatisPlusUtils {
/**
* mysql insert on duplicate key update 替代方法
*
* @param service
* service 類
* @param dataCollect
* 新數(shù)據(jù)列表
* @param isFillId
* 無 ID 是否填充 ID
* @param uniqueCondition
* 唯一索引
* @param updateMapping
* 更新 mapping:函數(shù)入?yún)椴樵兊降呐f數(shù)據(jù) & 新數(shù)據(jù)祟滴,出參為更新后的舊數(shù)據(jù)(不能設(shè)置id和相關(guān)唯一索引)
* @param <T>
* 實體泛型
*/
public static <T extends IBaseDO<T>, S extends IServiceExtension<T>> List<T> insertOnUpdateBatch(
S service,
Collection<T> dataCollect,
boolean isFillId,
BiFunction<T, LambdaQueryWrapper<T>, LambdaQueryWrapper<T>> uniqueCondition,
BiFunction<T, T, T> updateMapping
) {
Objects.requireNonNull(service);
Objects.requireNonNull(dataCollect);
Objects.requireNonNull(updateMapping);
Objects.requireNonNull(uniqueCondition);
Class<T> entityClass = service.getEntityClass();
Objects.requireNonNull(entityClass);
if (CollectionUtils.isEmpty(dataCollect)) {
log.debug("[{}] insert on update data size: 0,ignore", service.getEntityClass().getSimpleName());
return new ArrayList<>();
}
Map<Boolean, List<T>> collect = dataCollect.stream()
.filter(Objects::nonNull)
.peek(newData -> execFillIdStrategy(isFillId, newData))
.flatMap(newData -> {
LambdaQueryWrapper<T> chainWrapper = uniqueCondition.apply(newData, Wrappers.lambdaQuery(service.getEntityClass()));
Objects.requireNonNull(chainWrapper);
// 構(gòu)造通過 ID 或者 多個唯一索引查詢條件振惰,進(jìn)行數(shù)據(jù)庫查詢,查詢結(jié)果可能查詢出來多條
List<Pair<Boolean, T>> updateData = service.listWithoutLogicDelete(chainWrapper.or(wrapper -> wrapper.eq(IBaseDO::getId, newData.getId())))
.stream()
.map(oldData -> {
// 查詢出來需要更新的數(shù)據(jù)
Date newUpdateDate = newData.getUpdateDate();
Date oldCreateDate = oldData.getCreateDate();
Date now = new Date();
// 更新操作回調(diào)
T update = updateMapping.apply(newData, oldData)
// 指定填充默認(rèn)更新字段
.setCreateDate(oldCreateDate == null ? now : oldCreateDate)
.setUpdateDate(newUpdateDate == null ? now : newUpdateDate);
// 轉(zhuǎn)換器返回不同 ID 數(shù)據(jù)拋出異常
if (!Objects.equals(update.getId(), oldData.getId())) {
throw new RuntimeException("mapping can't return new data");
}
return Pair.of(false, update);
})
.collect(toList());
if (CollectionUtils.isNotEmpty(updateData)) {
return updateData.stream();
}
// 如果上面不符合踱启,就是需要插入的新數(shù)據(jù)
List<Pair<Boolean, T>> insertData = Collections.singletonList(Pair.of(true, newData));
return insertData.stream();
})
.collect(groupingBy(Pair::getKey, mapping(Pair::getValue, toList())));
// 提取需要新插入的數(shù)據(jù)
List<T> insertData = collect.getOrDefault(true, new ArrayList<>());
if (CollectionUtils.isNotEmpty(insertData)) {
service.saveBatch(insertData);
}
// 提取需要更新的數(shù)據(jù)
List<T> updateData = collect.getOrDefault(false, new ArrayList<>());
if (CollectionUtils.isNotEmpty(updateData)) {
// 批量更新數(shù)據(jù)
service.updateBatchByIdWithoutLogicDelete(updateData);
}
log.debug(
"[{}] data size:[{}],insert data size:[{}],update data size:[{}]",
entityClass.getSimpleName(),
dataCollect.size(),
insertData.size(),
updateData.size()
);
insertData.addAll(updateData);
return insertData;
}
/**
* 執(zhí)行填充ID策略
*
* @param isFillId
* 是否填充 ID
* @param newData
* 數(shù)據(jù)
* @param <T>
* 類型
*/
private static <T extends IBaseDO<T>> void execFillIdStrategy(boolean isFillId, T newData) {
Long id = newData.getId();
if (id != null) {
return;
}
if (!isFillId) {
throw new RuntimeException("data id can't null,data:" + newData);
}
newData.setId(IdWorker.getId());
}
}
session
批量寫庫優(yōu)化
- 由于原來的實現(xiàn)只需要一條
SQL
進(jìn)行批量更新报账,現(xiàn)在插入更新邏輯是在業(yè)務(wù)代碼中實現(xiàn)研底,最少是需要 2 條SQL
埠偿,頻繁發(fā)送SQL
進(jìn)行讀寫操作耗時非常大,我們需要對插入更新操作的SQL
進(jìn)行批量輸入執(zhí)行榜晦,使用BATCH
批處理模式冠蒋,減少重復(fù)預(yù)編譯的次數(shù)。
工具類整合
為了更加方便使用乾胶,
MybatisPlusUtils
工具類整合進(jìn)去自定義IServiceExtension
和ServiceImplExtension
模板
- 整合后
IServiceExtension
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.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.List;
import java.util.function.BiFunction;
public interface IServiceExtension<T extends IBaseDO<T>> extends IService<T> {
@Override
BaseMapperExtension<T> getBaseMapper();
// === BaseMapperExtension 新默認(rèn)方法 ===
/**
* 通過 id 更新實體(忽略邏輯刪除功能)
*
* @param entity
* 實體數(shù)據(jù)
*
* @return boolean
*/
default boolean updateByIdWithoutLogicDelete(T entity) {
return SqlHelper.retBool(getBaseMapper().updateByIdWithoutLogicDelete(entity));
}
default List<T> listWithoutLogicDelete(Wrapper<T> queryWrapper) {
return getBaseMapper().selectListWithoutLogicDelete(queryWrapper);
}
default List<T> listWithoutLogicDelete() {
return listWithoutLogicDelete(Wrappers.emptyWrapper());
}
// === 增強(qiáng)型方法 ===
/**
* 批量根據(jù) ID 更新(忽略邏輯刪除)
*
* @param entityList
* 實體列表
* @param batchSize
* 批次大小
*
* @return boolean
*/
boolean updateBatchByIdWithoutLogicDelete(Collection<T> entityList, int batchSize);
/**
* 批量根據(jù) ID 更新(忽略邏輯刪除)
*
* @param entityList
* 實體列表
*
* @return boolean
*/
@Transactional(rollbackFor = Exception.class)
default boolean updateBatchByIdWithoutLogicDelete(Collection<T> entityList) {
return updateBatchByIdWithoutLogicDelete(entityList, DEFAULT_BATCH_SIZE);
}
/**
* mysql insert on duplicate key update 替代方法
*
* @param dataCollect
* 新數(shù)據(jù)列表
* @param isFillId
* 無 ID 是否填充 ID
* @param uniqueCondition
* 唯一索引
* @param updateMapping
* 更新函數(shù):函數(shù)入?yún)椴樵兊降呐f數(shù)據(jù) & 新數(shù)據(jù)抖剿,出參為更新后的舊數(shù)據(jù)(不能設(shè)置id和相關(guān)唯一索引)
*
* @return {@code List<T>}
*/
List<T> insertOnUpdateBatch(
Collection<T> dataCollect,
boolean isFillId,
BiFunction<T, LambdaQueryWrapper<T>, LambdaQueryWrapper<T>> uniqueCondition,
BiFunction<T, T, T> updateMapping
);
@Transactional(rollbackFor = Exception.class)
default List<T> insertOnUpdateBatch(
Collection<T> dataCollect,
BiFunction<T, LambdaQueryWrapper<T>, LambdaQueryWrapper<T>> uniqueCondition,
BiFunction<T, T, T> updateMapping
) {
return insertOnUpdateBatch(dataCollect, true, uniqueCondition, updateMapping);
}
}
- 整合后
ServiceImplExtension
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.ibatis.binding.MapperMethod;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.List;
import java.util.function.BiFunction;
public abstract class ServiceImplExtension<M extends BaseMapperExtension<T>, T extends IBaseDO<T>> extends ServiceImpl<M, T> implements IServiceExtension<T> {
@Transactional(rollbackFor = Exception.class)
@Override
public boolean updateBatchByIdWithoutLogicDelete(Collection<T> entityList, int batchSize) {
String sqlStatement = mapperClass.getName() + StringPool.DOT + "updateByIdWithoutLogicDelete";
// 批量更新優(yōu)化 使用 BATCH 批處理模式
return executeBatch(entityList, batchSize, (sqlSession, entity) -> {
MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
param.put(Constants.ENTITY, entity);
sqlSession.update(sqlStatement, param);
});
}
@Transactional(rollbackFor = Exception.class)
@Override
public List<T> insertOnUpdateBatch(
Collection<T> dataCollect,
boolean isFillId,
BiFunction<T, LambdaQueryWrapper<T>, LambdaQueryWrapper<T>> uniqueCondition,
BiFunction<T, T, T> updateMapping
) {
return MyBatisPlusUtils.insertOnUpdateBatch(this, dataCollect, isFillId, uniqueCondition, updateMapping);
}
}
使用案例
基礎(chǔ)實體
繼承
BaseDO
類
@ApiModel(value = "網(wǎng)絡(luò)拓?fù)鋱D-區(qū)域")
@Data
@TableName(value = "cmdb_topology_region")
public class CmdbTopologyRegion extends BaseDO<CmdbTopologyRegion> {
@TableField(value = "tenant_code")
@ApiModelProperty(value = "租戶編碼")
private Long tenantCode;
}
Mapper 接口
繼承
BaseMapperExtension
接口
public interface CmdbTopologyRegionMapper extends BaseMapperExtension<CmdbTopologyRegion> {
}
Service 接口
繼承
IServiceExtension
接口
@Validated
public interface CmdbTopologyRegionService extends IServiceExtension<CmdbTopologyRegion> {
@Validated
int insertOrUpdate(List<CmdbTopologyRegion> regions);
}
Service 實現(xiàn)
@Slf4j
@Service
public class CmdbTopologyRegionServiceImpl extends ServiceImplExtension<CmdbTopologyRegionMapper, CmdbTopologyRegion> implements CmdbTopologyRegionService {
@Override
public int insertOrUpdate(List<CmdbTopologyRegion> regions) {
// regions 為插入更新的數(shù)據(jù)列表
List<CmdbTopologyRegion> update = insertOnUpdateBatch(regions,
// 唯一索引查詢條件構(gòu)造,這個案例只是一個唯一索引 TenantCode识窿,如果沒有唯一索引斩郎,直接 return wrapper,如果存在多個唯一索引喻频,使用 wrapper.eq(唯一索引1 構(gòu)造).or(w->w.eq(唯一索引2 構(gòu)造))
(newData, wrapper) -> wrapper.eq(CmdbTopologyRegion::getTenantCode,"1002"),
(newData, oldData) -> {
// 存在數(shù)據(jù)時候缩宜,會調(diào)用這個更新回調(diào)方法,入?yún)?新數(shù)據(jù)甥温,數(shù)據(jù)庫查詢數(shù)據(jù)),這里需要把 newData 的更新字段值填充進(jìn)去 oldData 返回
BeanUtil.copyProperties(newData, oldData, "id","tenantCode");
return oldData;
}
);
return update.size();
}
}
問題回顧
- 詳細(xì)查看
MySQL
插入更新原理 - 不同數(shù)據(jù)庫的語法不一致锻煌,只能使用標(biāo)準(zhǔn)
SQL
,所以最好使用業(yè)務(wù)代碼邏輯實現(xiàn) - 詳細(xì)查看
MyBatisPlusUtils
工具類的實現(xiàn)方式 - 自定義
SQL
注入器實現(xiàn) - 使用
Mybatis BATCH
批處理模式
優(yōu)點缺點
優(yōu)點
- 簡單易用姻蚓,支持原有
MybatisPlus
的Lambda
表達(dá)式查詢方式宋梧,不需要自定義SQL
實現(xiàn) - 需要使用插入更新的時候,不需要考慮不同數(shù)據(jù)庫兼容性
- 有清晰的業(yè)務(wù)代碼邏輯實現(xiàn)狰挡,可以對每條需要更新數(shù)據(jù)的自定義屬性拷貝和轉(zhuǎn)換捂龄,同時具備對更新數(shù)據(jù)進(jìn)行回調(diào)
- 對原有代碼改造少:把原來繼承實現(xiàn)的基礎(chǔ)模板替換(
BaseMapper
,IService
,ServiceImpl
)
缺點
- 執(zhí)行速度慢释涛,
SQL
執(zhí)行數(shù)量變多,原來只需要一條SQL
就可以實現(xiàn)批量插入更新跺讯,現(xiàn)在需要多條SQL
(包括查詢枢贿、插入和更新語句)