MybatisPlus自定义insertBatchSomeColumn实现真正批量插入

一、批量插入数据SQL

  • MySQL批量插入数据SQL
INSERT INTO TABLE_NAME(COLUMN1,COLUMN2...,COLUMNN)VALUES(VALUE1,VALUE2...,VALUEN),(VALUE1,VALUE2...,VALUEN),(VALUE1,VALUE2...,VALUEN);
  • Oracle批量插入数据SQL
INSERT ALL
INTO TABLE_NAME(COLUMN1,COLUMN2...,COLUMNN)VALUES(VALUE1,VALUE2...,VALUEN)
INTO TABLE_NAME(COLUMN1,COLUMN2...,COLUMNN)VALUES(VALUE1,VALUE2...,VALUEN)
INTO TABLE_NAME(COLUMN1,COLUMN2...,COLUMNN)VALUES(VALUE1,VALUE2...,VALUEN)
SELECT * FROM DUAL

二、MybatisPlus批量插入实现方式

2.1 通过实现MybatisPlus IService接口,获取saveBatch,底层其实是单条插入

    @Transactional(
        rollbackFor = {Exception.class}
    )
    public boolean saveBatch(Collection<T> entityList, int batchSize) {
        String sqlStatement = this.getSqlStatement(SqlMethod.INSERT_ONE);
        return this.executeBatch(entityList, batchSize, (sqlSession, entity) -> {
            sqlSession.insert(sqlStatement, entity);
        });
    }

2.2 通过XML手动拼接SQL实现批量插入

缺点是每个表都要手动编写xml,优点是效率较高

  • MySQL
<insert id="batchInsert" parameterType="java.util.List">
    insert into user (id, name, age)values
    <foreach collection="list" item="user" separator=",">
         (#{user.id}, #{user.name}, #{user.age})
    </foreach>
</insert>
  • Oracle
// mapper.xml
<insert id="batchInsert" parameterType="java.util.List">
    insert all
    <foreach collection="list" item="user" separator=",">
        into user (id, name, age) values(#{user.id}, #{user.name}, #{user.age})
    </foreach>
    select * from dual
</insert>

2.3 通过使用InsertBatchSomeColumn方法批量插入

底层也是拼接sql,但无需手动编写sql语句,效率同第二种,本文重点介绍这种方式,使用步骤:

2.3.1. 自定义SQL注入器实现DefaultSqlInjector,添加InsertBatchSomeColumn方法

MySQL版

public class MySqlInjector extends DefaultSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
        List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
        methodList.add(new InsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.UPDATE));
        return methodList;
    }
}

Oracle版

import com.baomidou.mybatisplus.annotation.FieldFill;  
import com.baomidou.mybatisplus.core.injector.AbstractMethod;  
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;  
  
import java.util.List;  
  
public class OracleInjector extends DefaultSqlInjector {  
  
    @Override  
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {  
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);  
        //这里改成我们自己的实现MyInsertBatchSomeColumn  
        methodList.add(new OracleInsertBatchSomeColumn(i -> i.getFieldFill() != FieldFill.UPDATE));  
        return methodList;  
    }  
}

2.3.2 编写配置类,把自定义注入器放入spring容器

@Configuration  
public class MyBatisConfig {  
    @Bean  
    public OracleInjector sqlInjector() {  
        return new OracleInjector();  
    }
}

2.3.2 编写自定义BaseMapper,加入InsertBatchSomeColumn方法

public interface MyBaseMapper<T> extends BaseMapper<T> {
    /**
     * 以下定义的 4个 method 其中 3 个是内置的选装件
     */
    int insertBatchSomeColumn(List<T> entityList);
}

2.3.4 需要批量插入的Mapper继承自定义BaseMapper

@Mapper
public interface UserMapper extends MyBaseMapper<Student> {
    
}

2.3.5 修改适配Oracle

先了解下,Oracle批量插入数据SQL

INSERT ALL
INTO TABLE_NAME(COLUMN1,COLUMN2...,COLUMNN)VALUES(VALUE1,VALUE2...,VALUEN)
INTO TABLE_NAME(COLUMN1,COLUMN2...,COLUMNN)VALUES(VALUE1,VALUE2...,VALUEN)
INTO TABLE_NAME(COLUMN1,COLUMN2...,COLUMNN)VALUES(VALUE1,VALUE2...,VALUEN)
SELECT * FROM DUAL

因此我们需要把SQL组装成这种结构,查看InsertBatchSomeColumn类,可以发现SQL组装逻辑在injectMappedStatement方法,因此我们模仿InsertBatchSomeColumn类,编写SQL组装逻辑

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.extension.injector.methods.InsertBatchSomeColumn;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;

import java.util.List;
import java.util.Map;
import java.util.function.Predicate;

@NoArgsConstructor
@AllArgsConstructor
@SuppressWarnings("serial")
public class OracleInsertBatchSomeColumn extends InsertBatchSomeColumn {

    @Setter
    @Accessors(chain = true)
    private Predicate<TableFieldInfo> predicate;

    private final String INSERT_BATCH_SQL="<script>\nINSERT ALL \n  %s\n</script>";

    @SuppressWarnings("Duplicates")
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        //pojo类型为Map时禁用
        if (tableInfo.getEntityType().equals(Map.class)) {
            return null;
        }
        KeyGenerator keyGenerator = new NoKeyGenerator();
        SqlMethod sqlMethod = SqlMethod.INSERT_ONE;
        List<TableFieldInfo> fieldList = tableInfo.getFieldList();
        String insertSqlColumn = tableInfo.getKeyInsertSqlColumn(false) +
                this.filterTableFieldInfo(fieldList, predicate, TableFieldInfo::getInsertSqlColumn, EMPTY);
        String columns = insertSqlColumn.substring(0, insertSqlColumn.length() - 1) ;
        String insertSqlProperty = tableInfo.getKeyInsertSqlProperty(ENTITY_DOT, false) +
                this.filterTableFieldInfo(fieldList, predicate, i -> i.getInsertSqlProperty(ENTITY_DOT), EMPTY);
        insertSqlProperty = LEFT_BRACKET + insertSqlProperty.substring(0, insertSqlProperty.length() - 1) + RIGHT_BRACKET;
        String valuesScript = convertForeach(insertSqlProperty, "list", tableInfo.getTableName(),columns, ENTITY, NEWLINE);
        String keyProperty = null;
        String keyColumn = null;
        // 表包含主键处理逻辑,如果不包含主键当普通字段处理
        if (tableInfo.havePK()) {
            if (tableInfo.getIdType() == IdType.AUTO) {
                /* 自增主键 */
                keyGenerator = new Jdbc3KeyGenerator();
                keyProperty = tableInfo.getKeyProperty();
                keyColumn = tableInfo.getKeyColumn();
            } else {
                if (null != tableInfo.getKeySequence()) {
                    keyGenerator = TableInfoHelper.genKeyGenerator(getMethod(sqlMethod), tableInfo, builderAssistant);
                    keyProperty = tableInfo.getKeyProperty();
                    keyColumn = tableInfo.getKeyColumn();
                }
            }
        }
        String sql = String.format(INSERT_BATCH_SQL, valuesScript);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addInsertMappedStatement(mapperClass, modelClass, getMethod(sqlMethod), sqlSource, keyGenerator, keyProperty, keyColumn);
    }
    public static String convertForeach(final String sqlScript, final String collection, final String tableName,final String columns, final String item, final String separator) {
        StringBuilder sb = new StringBuilder("<foreach");

        if (StringUtils.isNotBlank(collection)) {
            sb.append(" collection=\"").append(collection).append("\"");
        }

        if (StringUtils.isNotBlank(item)) {
            sb.append(" item=\"").append(item).append("\"");
        }

        if (StringUtils.isNotBlank(separator)) {
            sb.append(" separator=\"").append(separator).append("\"");
        }

        sb.append(">").append("\n");

        if (StringUtils.isNotBlank(tableName)) {
            sb.append(" INTO ").append(tableName).append(" ");
        }

        if (StringUtils.isNotBlank(columns)) {
            sb.append(LEFT_BRACKET).append(columns).append(RIGHT_BRACKET).append(" VALUES ");
        }

        return sb.append(sqlScript).append("\n").append("</foreach>\n").append(" SELECT ").append("*").append(" FROM dual").toString();
    }
}

执行批量插入,会发现报错

 Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.type.TypeException: Could not set parameters for mapping: ParameterMapping{property='__frch_et_0.serialno', mode=IN, javaType=class java.lang.String, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}. Cause: org.apache.ibatis.type.TypeException: Error setting null for parameter #2 with JdbcType OTHER . Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. Cause: java.sql.SQLException: 无效的列类型: 1111] with root cause

这是因为字段值为NULL时无法确定jdbcType是什么类型,导致插入失败,有两种解决方法,第一种是指定实体所有属性的jdbcType类型,如

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data;
import org.apache.ibatis.type.JdbcType;

import java.io.Serializable;
import java.util.Date;


@SuppressWarnings("serial")
@Data
public class TzVerifyLog extends Model<TzVerifyLog> {
    private String id;
    @TableField(value = "serialno",jdbcType = JdbcType.VARCHAR)
    private String serialno;
    @TableField(value = "verify_msg",jdbcType = JdbcType.VARCHAR)
    private String verifyMsg;
    @TableField(value = "type",jdbcType = JdbcType.VARCHAR)
    private String type;
    @TableField(value = "row_num",jdbcType = JdbcType.INTEGER)
    private Integer rowNum;

    @TableField(value = "createtime",jdbcType = JdbcType.DATE)
    private Date createtime;

    /**
     * 获取主键值
     *
     * @return 主键值
     */
    @Override
    protected Serializable pkVal() {
        return this.id;
    }
}

第二种是设置mybatisplus的jdbc-type-for-null属性值

mybatis-plus:
  configuration:
    jdbc-type-for-null: varchar #空值时设置为varchar类型

2.4 service封装InsertBatchSomeColumn方法

service封装insertBatchSomeColumn方法,方便后面调用

  • 新建一个IMyService接口继承IServic

import com.baomidou.mybatisplus.extension.service.IService;

import java.util.List;

public interface IMyService <T> extends IService<T> {
    int insertBatchSomeColumn(List<T> entityList);
    int insertBatchSomeColumn(List<T> entityList,int batchSize);
}
  • 新建一个MyServiceImpl类继承ServiceImpl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;


import java.util.ArrayList;
import java.util.List;

public class MyServiceImpl<M extends MyBaseMapper<T>, T>extends ServiceImpl<M,T> implements IMyService<T> {
    @Override
    public int insertBatchSomeColumn(List<T> entityList) {
        return this.baseMapper.insertBatchSomeColumn(entityList);
    }

    @Override
    public int insertBatchSomeColumn(List<T> entityList, int batchSize) {
        int size=entityList.size();
        if(size<batchSize){
            return this.baseMapper.insertBatchSomeColumn(entityList);
        }
        int page=1;
        if(size % batchSize ==0){
            page=size/batchSize;
        }else {
            page=size/batchSize+1;
        }
        for (int i = 0; i < page; i++) {
            List<T> sub = new ArrayList<>();
            if(i==page-1){
                sub=entityList.subList(i*batchSize, entityList.size());
            }else {
                sub.subList(i*batchSize,(i+1)*batchSize);
            }
            if(sub.size()>0){
                baseMapper.insertBatchSomeColumn(sub);
            }

        }
        return size;
    }
}
  • 实体Service接口和接口实现类都分别继承IMyService和MyServiceImpl
public interface ITzVerifyLogService extends IMyService<TzVerifyLog> {  
  
}

import org.springframework.stereotype.Service;  

@Service 
public class TzVerifyLogServiceImpl extends MyServiceImpl<TzVerifyLogMapper, TzVerifyLog> implements ITzVerifyLogService {  
  
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容