文章目录
1 适用
- 适用于根据实体类字段的不同,动态的生成插入或更新的mapper,不用每个手写
- 只要表有主键,批量插入更新会自动判断是插入还是更新,业务代码无需关注
- 当更新动作时,不需要更新的实体字段只要为null即可,不需要考虑实体合并
2 背景
在使用mybatis或者mybatis-plus的时候,遇到这种场景:批量插入和更新。一般做法是,有数据就插入,没数据就更新,中间不免就有一个查询数据的环节,在mybatisplus源码里就是这样干的,相当于保存数据前根据查询结果将数据列表分成了两组,一组是插入组,一组是更新组。这时数据保存的性能就有待考虑了。
3 使用前提和注意事项
使用前提
- 数据库类型为mysql
- 数据表有主键
注意事项
在数据类型不为字符串时,就不要使用下面这用法。会出现比如date类型不能和string比较的错误
<if test="item.apiId != null and item.apiId != ''">#{item.apiId},</if>
可以使用以下方式
<if test="item.apiId != null>#{item.apiId},</if>
4 用法1 普通用法
在xml这样搞,属于常规的一套流程
<insert id="insertOrUpdateBatch" parameterType="java.util.List">
insert into tb_test
<trim prefix="( " suffixOverrides="," suffix=") ">
<if test="list[0].apiId != null and list[0].apiId != ''">api_id,</if>
<if test="list[0].orgId != null and list[0].orgId != ''">org_id,</if>
<if test="list[0].orgName != null and list[0].orgName != ''">org_name,</if>
</trim>
values
<foreach collection="list" item="item" index="index" separator=",">
<trim prefix="( " suffixOverrides="," suffix=") ">
<if test="item.apiId != null and item.apiId != ''">#{item.apiId},</if>
<if test="item.orgId != null and item.orgId != ''">#{item.orgId},</if>
<if test="item.orgName != null and item.orgName != ''">#{item.orgName},</if>
</trim>
</foreach>
ON DUPLICATE KEY UPDATE
<trim prefix=" " suffixOverrides="," suffix=" ">
<if test="list[0].apiId != null and list[0].apiId != ''">api_id=values(api_id),</if>
<if test="list[0].orgId != null and list[0].orgId != ''">org_id=values(org_id),</if>
<if test="list[0].orgName != null and list[0].orgName != ''">org_name=values(org_name),</if>
</trim>
</insert>
5 用法2 mybatis plus扩展mapper用法
此用法是基于mybatis plus源码,无缝贴合扩展,关键代码只需两步
5.1 定制mapper方法,绑定生成的sql片段
定制mapper,通俗讲就是动态生成上面xml的sql内容
import cn.自己的项目.bridge.mapper.extend.MysqlKeywords;
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.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.springframework.util.StringUtils;
import java.util.stream.Collectors;
/**
* 适用于mysql,按列字段名进行更新操作,有值的列进行插入或更新,没值的列不做处理,保留原样
* 其中,动态的列时动态变化
* 样例:
* INSERT INTO `test` (`date`, `time_str`, `xny_rq`)
* VALUES
* ('xxxx-01-01', '01:00', 111)
* ON DUPLICATE KEY UPDATE
* `date` = values(`date`),time_str=VALUES(`time_str`),xny_rq=VALUES(xny_rq);
*/
public class DynamicColumnInsertOrUpdateBath extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
String sqlTemplate = BridgeSqlMethod.DynamicColumnInsertOrUpdateBath.getSql();
String formatSql =
String.format(sqlTemplate, tableInfo.getTableName(), dynamicColumns(tableInfo), dynamicValues(tableInfo),
prepareDuplicateKeySql(tableInfo));
SqlSource sqlSource = languageDriver.createSqlSource(configuration, formatSql, modelClass);
return this.addInsertMappedStatement(mapperClass, modelClass,
BridgeSqlMethod.DynamicColumnInsertOrUpdateBath.getMethod(), sqlSource, new NoKeyGenerator(), null, null);
}
private String dynamicValues(TableInfo tableInfo) {
StringBuilder builder = new StringBuilder();
final String itemStrKey = "item.%s";
final String itemStrValue = "#{item.%s},";
String ifTest = tableInfo.getFieldList()
.stream()
.map(item -> convertIf(String.format(itemStrKey, item.getProperty()),
String.format(itemStrValue, item.getProperty()), item))
.collect(Collectors.joining(NEWLINE));
String trimSql = trimSql(LEFT_BRACKET, RIGHT_BRACKET, COMMA, ifTest);
builder.append("<foreach collection=\"list\" item=\"item\" index=\"index\" separator=\",\">")
.append(NEWLINE).append(trimSql).append(NEWLINE)
.append("</foreach>").append(NEWLINE);
return builder.toString();
}
/**
* 获取动态的列
*
* @param tableInfo
* @return
*/
private String dynamicColumns(TableInfo tableInfo) {
StringBuilder builder = new StringBuilder();
if(!StringUtils.isEmpty(tableInfo.getKeyColumn())) {
builder.append(tableInfo.getKeyColumn()).append(COMMA);
}
String collect = tableInfo.getFieldList().stream().map(item -> {
String column = MysqlKeywords.covertKeyword(item.getColumn());
return convertIf("list[0]." + item.getProperty(), column + ",", item);
}).collect(Collectors.joining(NEWLINE));
String trimSql = trimSql(LEFT_BRACKET, RIGHT_BRACKET, COMMA, collect);
builder.append(trimSql);
return builder.toString();
}
/**
* 准备ON DUPLICATE KEY UPDATE sql
* <if test="%s"> id=values(id) </if>
*
* @param tableInfo
* @return
*/
private String prepareDuplicateKeySql(TableInfo tableInfo) {
String ifKey = "list[0].%s";
String duplicateContext = "%s=values(%s),";
String ifTest = tableInfo.getFieldList()
.stream()
.map(tableFieldInfo -> convertIf(String.format(ifKey, tableFieldInfo.getProperty()),
String.format(duplicateContext, tableFieldInfo.getColumn(),
tableFieldInfo.getColumn()), tableFieldInfo))
.collect(Collectors.joining(NEWLINE));
String trimSql = trimSql(EMPTY, EMPTY, COMMA, ifTest);
return trimSql;
}
/**
* 转换成 if 标签的脚本片段
*
* @param testKey sql 脚本片段
* @param testValue java字段名,批量处理需要list[0].
* @return if 脚本片段
*/
private String convertIf(final String testKey, String testValue, TableFieldInfo tableFieldInfo) {
String testScript = String.format("%s != null", testKey);
if(tableFieldInfo.isCharSequence()) {
testScript = String.format("%s != null and %s != ''", testKey, testKey);
}
return String.format("<if test=\"%s\">%s</if>", testScript, testValue);
}
/**
* 截取拼接xml前置和后置内容
*
* @param prefix 前置内容
* @param suffix 后置内容
* @param suffixOverride 截取的最后字符
* @param sqlTemplate 中间的内容
* @return
*/
private String trimSql(String prefix, String suffix, String suffixOverride, String sqlTemplate) {
String scriptTemplate = "<trim prefix=\"%s \" suffixOverrides=\"%s\" suffix=\"%s \">\n %s \n</trim>\n";
return String.format(scriptTemplate, prefix, suffixOverride, suffix, sqlTemplate);
}
}
5.2 辅助枚举类
public enum BridgeSqlMethod {
/**
* 动态列批量插入或更新
*/
DynamicColumnInsertOrUpdateBath("dynamicColumnInsertOrUpdateBath", "动态列批量插入或更新(选择字段插入)",
"<script>insert into %s\n %s values\n %s ON DUPLICATE KEY UPDATE \n%s</script>");
private final String method;
private final String desc;
private final String sql;
BridgeSqlMethod(String method, String desc, String sql) {
this.method = method;
this.desc = desc;
this.sql = sql;
}
public String getMethod() {
return method;
}
public String getDesc() {
return desc;
}
public String getSql() {
return sql;
}
}
5.3 mapper方法注册,在启动的时候加载
import cn.自己的项目.mapper.extend.method.DynamicColumnInsertOrUpdateBath;
import cn.自己的项目.mapper.extend.method.MysqlInsertOrUpdateBath;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import com.baomidou.mybatisplus.extension.injector.methods.additional.InsertBatchSomeColumn;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 自定义mapper方法注入器。和mybatis_plus无缝集成
*/
@Component
public class BridgeMapperInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList() {
List<AbstractMethod> methodList = super.getMethodList();
methodList.add(new MysqlInsertOrUpdateBath());
methodList.add(new DynamicColumnInsertOrUpdateBath());
return methodList;
}
}
6、关联参考
可参考我上一篇博文
版权声明:本文为t18092838767原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。