解讀tk.mybatis的通用批量更新方式
背景介紹
mybatis沒(méi)有提供批量更新的方法,通過(guò)代碼中循環(huán)調(diào)用單個(gè)更新方法太消耗資源影響性能,
在xml中寫(xiě)批量更新SQL又太繁瑣并且無(wú)法復(fù)用,并且項(xiàng)目中需要兼容多種類型數(shù)據(jù)庫(kù),
因此在tk.mybatis的基礎(chǔ)上擴(kuò)展一個(gè)通用批量更新Provider和Mapper;
實(shí)現(xiàn)原理
可選批量更新實(shí)現(xiàn)的方式:
on duplicate key update語(yǔ)法,存在則更新,不存在則插入,能同時(shí)實(shí)現(xiàn)插入和更新,但on duplicate key update是MySQL特有語(yǔ)法,切換成其他類型數(shù)據(jù)庫(kù)就無(wú)法使用了。- foreach成多條SQL去執(zhí)行,但Mybatis映射文件中的sql語(yǔ)句默認(rèn)是不支持以" ; " 結(jié)尾的,也就是不支持多條sql語(yǔ)句的執(zhí)行,為了支持這種方式,不同數(shù)據(jù)庫(kù)的處理方式也不同,MySQL數(shù)據(jù)庫(kù)需要在URL上設(shè)置
&allowMultiQueries=true,Oracle數(shù)據(jù)庫(kù)需要在語(yǔ)句的前后添加關(guān)鍵字BEGIN和END;: - 其他的實(shí)現(xiàn)方式都無(wú)法在多種數(shù)據(jù)庫(kù)中使用;
case when語(yǔ)法,該語(yǔ)法在常用的數(shù)據(jù)庫(kù)(MySQL、Oracle、DM、OB等)中都是支持的。
最終選定通過(guò)case when語(yǔ)法來(lái)實(shí)現(xiàn),并擴(kuò)展一個(gè)通用批量更新Provider和Mapper;
實(shí)現(xiàn)代碼
tk.mybatis的maven依賴: <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>2.2.4.4</version> </dependency>
UpdateListProvider類:
package com.demo.ibatis.provider;
import org.apache.ibatis.mapping.MappedStatement;
import tk.mybatis.mapper.entity.EntityColumn;
import tk.mybatis .mapper.mapperhelper.EntityHelper;
import tk.mybatis.mapper.mapperhelper.MapperHelper;
import tk.mybatis.mapper.mapperhelper.MapperTemplate;
import tk.mybatis.mapper.mapperhelper.SglHeTper;
import tk.mybatis.mapper.util.StringUtil;
import java.util.Set;
public class UpdateListProvider extends MapperTemplate {
public UpdatelistProvider(Class<?> mapperClass, MapperHelper mapperHelper) {
super(mapperClass, mapperHelper);
}
/**
* 根據(jù)主鍵批量更新實(shí)體所有屬性值,使用case when 方式,支持聯(lián)合主鍵
*
* @param ms MappedStatement
* @return sql
*/
public String updateListByPrimaryKey(MappedStatement ms) {
return this.sglHelper(ms, false);
}
/**
*根據(jù)主鍵批量更新實(shí)體中不是null的屬性值,使用case when方式,支持聯(lián)合主鍵
*
* @param ms MappedStatement
* @return sql
*/
public String updateListByPrimaryKeySelective(MappedStatement ms) {
return this.sglHelper(ms, true);
}
private String sqlHelper(MappedStatement ms, boolean notNull) {
final Class<?> entityclass = getEntityClass(ms);
// 開(kāi)始拼sql
StringBuilder sgl = new StringBuilder();
sql.append(SqlHelper.updateTable(entityclass,tableName(entityclass)));
sql.append("<trim prefix=\"set\" suffixOverrides= \",\">");
// 獲取全部列
Set<EntityColumn> allColumns = EntityHelper.getColumns(entityClass);
// 找到主鍵列
Set<EntityColumn> pkColumns = EntityHelper.getPKColumns(entityclass);
for (EntityColumn column : allColumns) {
if (!column.isId() && column.isUpdatable()) {
sql.append(" <trim prefix=\"").append(column.getColumn()).append(" = case\" suffix= \"end,\">");
sgl.append(" <foreach collection= \"list\" item= \"i\" index= \"index\">");
if (notNull) {
sql.append(this.getIfNotNull("i", column, isNotEmpty()));
}
sql.append(" when ");
int count = 0;
for (EntityColumn pk : pkColumns) {
if (count != 0) {
sql.append("and ");
}
sql.append(pk.getColumn()).append("=#{i.").append(pk.getProperty()).append("} ");
count++;
}
sql.append("then ").append(column.getColumnHolder("i"));
if (notNull) {
sql.append(" </if>");
}
sql.append(" </foreach>");
sql.append(" </trim>");
}
}
sql.append("</trim>");
sql.append("WHERE (");
int count = 0;
for (EntityColumn pk : pkColumns) {
sql.append(pk.getCotumn());
if (count < pkColumns.size() - 1) {
sql.append(", ");
}
count++;
}
sql.append(") IN");
sql.append("<trim prefix= \"(\" suffix= \")\">");
sql.append("<foreach collection=\"list\" separator=\"), (\" item=\"i\" index=\"index\" open=\"(\" close=\")\" >");
count = 0;
for (EntityColumn pk : pkColumns) {
sql.append("#{i.").append(pk.getProperty()).append("}");
if (count < pkColumns.size() - 1) {
sg.append(", ");
}
count++;
}
sql.append("</foreach>");
sql.append("</trim>");
return sql.toString();
}
private String getIfNotNull(String entityNameEntityColumn column, boolean empty) {
StringBuilder sql = new StringBuilder();
sql.append(" <if test=\"");
if (StringUtil.isNotEmpty(entityName)) {
sql.append(entityName).append(".");
}
sql.append(column.getProperty()).append(" != null");
if (empty && column.getJavaType().equals(String.class)) {
sql.append(" and ");
if (StringUtil.isNotEmpty(entityName)) {
sql.append(entityName).append(".");
}
sql.append(column.getProperty()).append(" != '' ");
}
sql.append("\">");
return sql.tostring();
}
}UpdateListByPrimaryKeyMapper類:
package com.demo.ibatis.mapper;
import com.demo.ibatis.provider.UpdateListProvider;
import org.apache.ibatis.annotations.UpdateProvider;
import tk.mybatis .mapper.annotation.RegisterMapper;
import java.util.List;
@RegisterMapper
public interface UpdateListByPrimaryKeyMapper<T> {
/**
* 根據(jù)主鍵批量更新實(shí)體中所有屬性值,支持聯(lián)合主鍵
*
* @param updateList 參數(shù)
* @return int
*/
@UpdateProvider(type = UpdateListProvider.class,method = "dynamicSQL")
int updateListByPrimaryKey(List<T> updateList);
}UpdateListByPrimaryKeySelectiveMapper類:
package com.demo.ibatis.mapper;
import com.demo.ibatis.provider.UpdateListProvider;
import org.apache.ibatis.annotations.UpdateProvider;
import tk.mybatis .mapper.annotation.RegisterMapper;
import java.util.List;
@RegisterMapper
public interface UpdateListByPrimaryKeySelectiveMapper<T> {
/** 根據(jù)主鍵批量更新實(shí)體中不是null的屬性值,支持聯(lián)合主鍵
*
* @param updateList 參數(shù)
* @return int
*/
@UpdateProvider(type = UpdateListProvider.class,method = "dynamicSQL")
int updateListByPrimaryKeySelective(List<T> updateList);
}總結(jié)
以上,根據(jù)主鍵批量更新數(shù)據(jù)的方法就實(shí)現(xiàn)了,只需要自己的Mapper繼承這兩個(gè)通用擴(kuò)展Mapper就可以調(diào)用updateListByPrimaryKeySelective()和updateListByPrimaryKey()方法進(jìn)行批量更新了,并且同時(shí)支持聯(lián)合主鍵和單一主鍵,兼容MySQL、Oracle、DM、OB等數(shù)據(jù)庫(kù)。
這些僅為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java Hibernate使用SessionFactory創(chuàng)建Session案例詳解
這篇文章主要介紹了Java Hibernate使用SessionFactory創(chuàng)建Session案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
Spring實(shí)戰(zhàn)之容器后處理器操作示例
這篇文章主要介紹了Spring實(shí)戰(zhàn)之容器后處理器操作,結(jié)合實(shí)例形式分析了spring容器后處理器配置、使用操作技巧與相關(guān)注意事項(xiàng),需要的朋友可以參考下2019-12-12
Springboot項(xiàng)目使用html5的video標(biāo)簽完成視頻播放功能
這篇文章主要介紹了Springboot項(xiàng)目使用html5的video標(biāo)簽完成視頻播放功能,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
SpringBoot項(xiàng)目中的視圖解析器問(wèn)題(兩種)
SpringBoot官網(wǎng)推薦使用HTML視圖解析器,但是根據(jù)個(gè)人的具體業(yè)務(wù)也有可能使用到JSP視圖解析器,所以本文介紹了兩種視圖解析器,感興趣的可以了解下2020-06-06
新聞列表的分頁(yè)查詢java代碼實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了新聞列表的分頁(yè)查詢java代碼實(shí)現(xiàn),感興趣的小伙伴們可以參考一下2016-08-08

