Java书签 #解锁MyBatis的4种批量插入方式及ID返回姿势

news2024/11/24 1:20:06

1. 今日书签

项目开发中,我们经常会用到单条插入和批量插入。但是实际情况可能是,项目初期由于种种原因,在业务各处直接使用单条插入SQL进行开发(未开启批处理),在后面的迭代中,系统性能问题渐渐凸显,然后再通过技术优化,大面积的对单条插入SQL、单条更新SQL进行批量插入、批量更新优化。这不可取,但确实存在。

那数据的批量 insert/update 有几种方式实现呢?
哪些批量操作能直接获取到数据入库后的自增ID呢?
它们的优点、缺点是分别是什么呢?
在优化的过程中可能会出现哪些异常呢?我们要注意什么呢?…

这里我们列举4种数据批量保存方式,并对不同方式进行性能测试和对比分析。

 

2. 解锁方案

2.1. 前置任务

2.1.1. 标签

foreach 标签: myBatis-3-mapper.dtd 中 foreach 元素的属性主要有 item,index,collection,open,separator,close 这6种。

<foreach collection="" close="" index="" item="" open="" separator="">
属性含义
collection表示需要进行批量操作的对象集合
item表示集合中每一个元素进行迭代时的别名
index用于表示在迭代过程中,每次迭代到的位置
open表示该语句以什么开始
separator表示在每次进行迭代之间以什么符号作为分隔符
close表示以什么结束

trim 标签:

<trim prefix="" suffix="" suffixOverrides="" prefixOverrides=""></trim>
属性含义
prefix在trim标签内SQL语句加上前缀
suffix在trim标签内SQL语句加上后缀
suffixOverrides去除trim标签内SQL语句多余的后缀内容
prefixOverrides去除trim标签内SQL语句多余的前缀内容

案例:
MaBatis 生成批量插入SQL语句中:insert into t_item (id, product_id, product_name) values (1, ‘999’ , ), (2, ‘葡萄糖’ , ), …
MaBatis 生成批量插入SQL语句后:insert into t_item (id, product_id, product_name) values (1, ‘999’), (2, ‘葡萄糖’)

2.1.2. 配置

需要添加 JDBC 配置 allowMultiQueries=true,开启批处理。比如修改 mysql jdbc 的连接参数为:

spring.datasource.druid.wei.url = jdbc:mysql://192.168.1.1:3306/wei?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&allowMultiQueries=true

 

2.2. 四种数据批量插入方式

方式1:循环所有对象,生成固定字段的 VALUES

<insert id="batchSaveItemLogDo1" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO t_item_operate_log (
        product_id, node_type, node_scene, node_data, `operator`
    ) VALUES
    <foreach collection="list" item="item" index="index" separator=",">
        (
            #{item.productId,jdbcType=BIGINT}, #{item.nodeType,jdbcType=INTEGER}, #{item.nodeScene,jdbcType=INTEGER},
            #{item.nodeData,jdbcType=VARCHAR}, #{item.operator,jdbcType=VARCHAR}
        )
    </foreach>
</insert>

这种批量插入的SQL使用了 MyBatis 的 foreach 标签来实现批量插入功能。与下面的SQL相比,它使用了更加简洁的语法来构建批量插入的SQL语句。比较常用。

优点:

  • 语法简洁:使用 foreach 标签可以使SQL语句更加简洁,直观地表示批量插入的值。
  • 性能优化:通过将多个插入值合并为一条INSERT语句,将所有数据一次性插入,可以减少与数据库的交互次数,从而提高性能。
  • 自动生成主键:通过设置 useGeneratedKeys="true"keyProperty="id" ,可以在插入数据时自动生成主键,并且将自动生成的主键值回写到Java对象中(通过 list 对象可拿到全部数据入库后的自增ID)。

缺点:

  • 数据量限制:批量插入的数据量可能受到数据库配置和性能限制。对于非常大的数据量,需要考虑进一步优化或使用其他导入数据的方式。
  • SQL注入风险:虽然这种方式使用了 MyBatis 的参数绑定,但仍需谨慎对待输入值,以防止SQL注入攻击。
  • 不支持条件判断:这种方式将所有数据一次性插入,不支持对某些字段进行条件判断是否插入。如果需要在插入时根据条件判断是否插入某些字段,可参考下面的批量插入方式。

注意事项:

  1. 此方案 insert 的字段是固定的,不能动态适配
  2. 此批量插入方案须要保证传给持久层 Dao 接口的参数 list 对象(需要批量插入的对象)中每个对象的属性字段个数与SQL中指定的 insert 字段个数一致,且属性名一致,顺序不能错

异常案例: 如果 Dao 接口的参数 list 对象(需要批量插入的对象)中每个对象的属性字段个数与SQL中指定的 insert 字段个数不一致,或者,表中指定不允许为 NULL 的字段,在入参对象中其值为 NULL,则会出现异常:

### Error updating database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'node_scene' cannot be null
### The error may exist in file [D:\Ct_ iSpace\tan\wei-saas\saas-persistence\target\classes\com\meiwei\tan\saas\persistence\item\ItemOperateLogDao.xml]
### The error may involve com.meiwei.tan.saas.persistence.item.mapper.ItemOperateLogDao.batchSaveItemLogDo1-Inline
### The error occurred while setting parameters
### SQL: INSERT INTO t_item_operate_log (         product_id, node_type, node_scene, node_data, `operator`         ) VALUES                        (             ?, ?, ?,             ?, ?             )          ,              (             ?, ?, ?,             ?, ?             )
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'node_scene' cannot be null
; Column 'node_scene' cannot be null; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'node_scene' cannot be null

此异常原因是,我去掉了对象中 nodeScene 属性的设值,但插入SQL中需要插入 node_scene 该字段,而且表中该字段为非NULL无默认值。

 

方式2:循环所有对象,根据每个对象的不同字段值选择性生成 VALUES

<insert id="batchSaveItemLogDo2" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO t_item_operate_log (
        product_id, node_type, node_scene, node_data, `operator`
    ) VALUES
    <foreach collection="list" item="item" separator=",">
        <if test="item != null">
            <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="item.productId != null">
                    #{item.productId,jdbcType=BIGINT},
                </if>
                <if test="item.nodeType != null">
                    #{item.nodeType,jdbcType=INTEGER},
                </if>
                <if test="item.nodeScene != null">
                    #{item.nodeScene,jdbcType=INTEGER},
                </if>
                <if test="item.nodeData != null">
                    #{item.nodeData,jdbcType=VARCHAR},
                </if>
                <if test="item.operator != null">
                    #{item.operator,jdbcType=VARCHAR},
                </if>
            </trim>
        </if>
    </foreach>
</insert>

这种批量插入方式与上面的SQL非常相似,同样也是使用了MyBatis的foreach标签来实现批量插入功能。主要区别在于对空值的处理和SQL语句的拼接方式。比较常用。

优点:

  • 空值处理:这种方式对空值的处理更加细致,在插入时能够将空字段插入为空字符串,避免插入NULL值。
  • 性能优化:通过直接将每个字段值拼接在一起,可以减少拼接SQL语句的时间和内存开销,可能略微提高性能。
  • 代码可读性:通过使用 trim标签if条件,能够更清晰地看到插入值的拼接逻辑,SQL的拼接方式更灵活,使得代码更易读懂和维护。
  • 自动生成主键:通过设置 useGeneratedKeys="true"keyProperty="id",可以在插入数据时自动生成主键,并且将自动生成的主键值回写到Java对象中(通过 list 对象可拿到全部数据入库后的自增ID)。

缺点:

  • 长SQL语句:这种方式生成的SQL语句字段越多SQL越长,尤其当数据量较大时,多个插入值的拼接逻辑可能导致SQL语句较长。
  • 数据量限制:批量插入的数据量可能受到数据库配置和性能限制。对于非常大的数据量,需要考虑进一步优化或使用其他导入数据的方式。

注意事项:

  • 此方案 insert 的字段是固定的,不能动态适配

备注: 此批量插入方式在 方式1 上有所优化,可以有效规避方案1中 Dao 接口的参数 list 对象(需要批量插入的对象)中每个对象的属性字段个数与SQL中指定的 insert 字段个数不一致,或者,表中指定不允许为 NULL 的字段,但在入参对象中其值为 NULL 的场景。

 

方式3:循环所有对象,为每个对象生成一个 INSERT 语句,一次执行多条SQL

<insert id="batchSaveItemLogDo3" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
    <foreach collection="list" item="item" separator=";">
        INSERT INTO t_item_operate_log (
            product_id, node_type, node_scene, node_data, `operator`
        ) VALUES (
            #{item.productId,jdbcType=BIGINT}, #{item.nodeType,jdbcType=INTEGER}, #{item.nodeScene,jdbcType=INTEGER},
            #{item.nodeData,jdbcType=VARCHAR}, #{item.operator,jdbcType=VARCHAR}
        )
    </foreach>
</insert>

这种批量插入的SQL使用了 MyBatis 框架的 动态SQL特性foreach标签,将多条数据一次性插入到数据库中。

优点:

  • 性能优化:使用批量插入可以减少与数据库的交互次数,从而提高性能。相较于逐条插入,批量插入能够减少网络开销和数据库连接/关闭的开销。
  • 代码简洁:通过使用MyBatis的foreach标签,可以在SQL中直接处理Java集合(java.util.List)并且将多条数据一次性插入到数据库中,使得代码更加简洁、易读。
  • 自动生成主键:通过设置 useGeneratedKeys="true"keyProperty="id",可以在插入数据时自动生成主键,并且将自动生成的主键值回写到Java对象中。
  • 可维护性:由于批量插入的SQL代码较为简洁,维护起来更加容易。

缺点:

  • 一次性插入的数据量限制:虽然批量插入可以减少与数据库的交互次数,但一次性插入的数据量可能受到数据库配置和性能限制。对于非常大的数据量,可能需要考虑进一步优化或使用其他导入数据的方式。
  • 数据库兼容性:不是所有的数据库都对批量插入提供了良好的支持。不同的数据库可能对批量插入的语法有所不同,这需要根据实际情况进行适配。
  • SQL注入风险:使用MyBatis的foreach标签时,要确保在生成SQL语句时,正确处理输入值,以防止SQL注入攻击。

注意事项:

  • 此方案 insert 的字段是固定的,不能动态适配
  • 此方案批量插入后,只能获取到第1个对象的自增ID,拿不到全部数据入库后的自增ID
  • 此方案需要对 DruidDataSource 的 WallConfig 属性 setMultiStatementAllow 设置为 true(见下)

备注: 此方案比较少见。基本思路是组装好所有需要批量插入的对象后一次执行多条SQL。但这里的看似一次提交,实际是一条一条提交,所以效率比较慢。

@Configuration
public class MyBatisConfiguration {

    @Configuration
    @MapperScan(basePackages = {"com.meiwei.tan.saas.persistence.*"}, sqlSessionFactoryRef = "sqlSessionFactory4Wei")
    protected static class MyBatisDataSourceConfiguration4Wei {
        @Bean
        @Primary
        @ConfigurationProperties("spring.datasource.druid.wei")
        public DataSource dataSource4Wei() {
            DruidDataSource druidDataSource = new DruidDataSource();
            List<Filter> filterList = new ArrayList<>();
            filterList.add(wallFilter());
            filterList.add(statFilter());
            druidDataSource.setProxyFilters(filterList);
            return druidDataSource;
        }

        public WallFilter wallFilter() {
            WallFilter wallFilter = new WallFilter();
            wallFilter.setConfig(wallConfig());
            return wallFilter;
        }

        public StatFilter statFilter() {
            return new StatFilter();
        }

        public WallConfig wallConfig() {
            WallConfig wallConfig = new WallConfig();
            wallConfig.setMultiStatementAllow(true);
            return wallConfig;
        }
    }

	// ......
}

 

方式4:循环所有对象,根据 Mapper 接口或 Dao 接口传入的字段参数,动态可选生成 VALUES

<insert id="batchSaveItemLogDo4Selective" parameterType="map" useGeneratedKeys="true" keyColumn="id" keyProperty="list.id">
    INSERT INTO t_item_operate_log (
    <foreach collection="selective" item="column" separator=",">
        ${column.escapedColumnName}
    </foreach>
    )
    VALUES
    <foreach collection="list" item="item" separator=",">
        (
        <foreach collection="selective" item="column" separator=",">
            <if test="'product_id'.toString() == column.value">
                <if test="item.productId != null">
                    #{item.productId,jdbcType=BIGINT}
                </if>
            </if>
            <if test="'node_type'.toString() == column.value">
                <if test="item.nodeType != null">
                    #{item.nodeType,jdbcType=INTEGER}
                </if>
            </if>
            <if test="'node_scene'.toString() == column.value">
                #{item.nodeScene,jdbcType=INTEGER}
            </if>
            <if test="'node_data'.toString() == column.value">
                <if test="item.nodeData != null">
                    #{item.nodeData,jdbcType=VARCHAR}
                </if>
                <if test="item.nodeData == null">
                    ''
                </if>
            </if>
            <if test="'operator'.toString() == column.value">
                <if test="item.operator != null">
                    #{item.operator,jdbcType=VARCHAR}
                </if>
                <if test="item.operator == null">
                    ''
                </if>
            </if>
        </foreach>
        )
    </foreach>
</insert>

这种批量插入方式与之前给出的SQL有相似之处,也是使用了 MyBatis 的 foreach标签 来实现批量插入功能。主要区别在于对字段列的选择和对空值的处理。

区别:

  • 对列的选择:在这种方式中,通过使用 <foreach>标签selective参数,可以动态选择要插入的列。${column.escapedColumnName} 会根据传入的 selective参数,动态生成要插入的列名。
  • 对空值的处理:对于插入的每个字段,通过 <if> 条件判断,对空值进行了特殊处理。如果字段值为空,则插入为空字符串’',避免插入NULL值。

优势:

  • 列的选择:通过selective参数,可以动态选择要插入的列,使得插入操作更灵活和可配置。
  • 对空值的处理:与之前方式相似,对空值的处理更为细致,将空字段插入为空字符串,避免插入NULL值。
  • 性能优化:通过将多个插入值合并为一条INSERT语句,可以减少与数据库的交互次数,从而提高性能。
  • 自动生成主键:通过设置 useGeneratedKeys="true"keyColumn="id" keyProperty="list.id",可以在插入数据时自动生成主键,并且将自动生成的主键值回写到Java对象中(通过 list 对象可拿到全部数据入库后的自增ID)。

劣势:

  • 数据量限制:批量插入的数据量可能受到数据库配置和性能限制。对于非常大的数据量,可能需要考虑进一步优化或使用其他导入数据的方式。
  • SQL注入风险:虽然这种方式使用了MyBatis的参数绑定,但仍需谨慎对待输入值,以防止SQL注入攻击。

注意事项:

  • 与其它方案不同的是,此方案中 foreach 的每个 if SQL注入中,不要习惯性加逗号(,)哦,加了会报错
  • 此方案需要对插入对象进行字段枚举配置,支持 Dao 接口接收需要插入的可选入参字段,见下文

备注: 该批量插入方案可解决其它方案中不能动态按入参字段进行动态组装生成SQL语句的问题,也可有效规避方案1中 Dao 接口的参数 list 对象(需要批量插入的对象)中每个对象的属性字段个数与SQL中指定的 insert 字段个数不一致问题,或者,表中指定不允许为 NULL 的字段,但在入参对象中其值为 NULL 的场景。

import lombok.Builder;
import lombok.Data;

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

@Data
@Builder
public class ItemOperateLogDO implements Serializable {
    private Long id;
    private Long productId;

    /**
     * 节点操作类型(1-新增;2-更新;3-删除)
     */
    private Integer nodeType;

    private Integer nodeScene;
    private String nodeData;
    private String operator;
    private Date operatorTime;

    private static final long serialVersionUID = 1L;

    /**
     * This enum was generated by MyBatis Generator.
     * This enum corresponds to the database table t_operate_detail
     *
     * @mbg.generated Tue Mar 02 10:29:28 CST 2021
     */
    public enum Column {
        id("id", "id", "BIGINT", false),
        productId("product_id", "productId", "BIGINT", false),
        nodeType("node_type", "nodeType", "INTEGER", false),
        nodeScene("node_scene", "nodeScene", "INTEGER", false),
        nodeData("node_data", "nodeData", "VARCHAR", false),
        operator("operator", "operator", "VARCHAR", false);

        private static final String BEGINNING_DELIMITER = "\"";
        private static final String ENDING_DELIMITER = "\"";
        private final String column;
        private final boolean isColumnNameDelimited;
        private final String javaProperty;
        private final String jdbcType;

        public String value() {
            return this.column;
        }

        public String getValue() {
            return this.column;
        }

        public String getJavaProperty() {
            return this.javaProperty;
        }

        public String getJdbcType() {
            return this.jdbcType;
        }

        Column(String column, String javaProperty, String jdbcType, boolean isColumnNameDelimited) {
            this.column = column;
            this.javaProperty = javaProperty;
            this.jdbcType = jdbcType;
            this.isColumnNameDelimited = isColumnNameDelimited;
        }

        public String desc() {
            return this.getEscapedColumnName() + " DESC";
        }

        public String asc() {
            return this.getEscapedColumnName() + " ASC";
        }

        public static ItemOperateLogDO.Column[] excludes(ItemOperateLogDO.Column... excludes) {
            ArrayList<ItemOperateLogDO.Column> columns = new ArrayList<>(Arrays.asList(ItemOperateLogDO.Column.values()));
            if (excludes != null && excludes.length > 0) {
                columns.removeAll(new ArrayList<>(Arrays.asList(excludes)));
            }
            return columns.toArray(new ItemOperateLogDO.Column[]{});
        }

        public static ItemOperateLogDO.Column[] all() {
            return ItemOperateLogDO.Column.values();
        }

        public String getEscapedColumnName() {
            if (this.isColumnNameDelimited) {
                return new StringBuilder().append(BEGINNING_DELIMITER).append(this.column).append(ENDING_DELIMITER).toString();
            } else {
                return this.column;
            }
        }

        public String getAliasedEscapedColumnName() {
            return this.getEscapedColumnName();
        }
    }
}

 

2.3. Dao接口(Mapper接口)

如上各个批量SQL对应的 Mapper 接口(Dao 接口):

public interface ItemOperateLogDao {
    int batchSaveItemLogDo0(ItemOperateLogDO record);

    int batchSaveItemLogDo1(@Param("list") List<ItemOperateLogDO> list);

    int batchSaveItemLogDo2(@Param("list") List<ItemOperateLogDO> list);

    int batchSaveItemLogDo3(@Param("list") List<ItemOperateLogDO> list);

    int batchSaveItemLogDo4Selective(@Param("list") List<ItemOperateLogDO> list, @Param("selective") ItemOperateLogDO.Column ... selective);
}

 

2.4. Service接口

public interface ItemOperateLogService {
    /** 循环所有对象,普通单条插入(未开启批处理) */
    Map<String, String>  batchSaveItemLogDo0(List<ItemOperateLogDO> itemOperateLogDOList);

    /** 方案1:循环所有对象,生成固定字段的 VALUES */
    Map<String, String>  batchSaveItemLogDo1(List<ItemOperateLogDO> itemOperateLogDOList);

    /** 方案2:循环所有对象,根据每个对象的不同字段值选择性生成 VALUES */
    Map<String, String>  batchSaveItemLogDo2(List<ItemOperateLogDO> itemOperateLogDOList);

    /** 方案3:循环所有对象,为每个对象生成一个 INSERT 语句,一次执行多条SQL */
    Map<String, String>  batchSaveItemLogDo3(List<ItemOperateLogDO> itemOperateLogDOList);

    /** 方案4:循环所有对象,根据 Mapper 接口或 Dao 接口传入的字段参数,动态可选生成 VALUES */
    Map<String, String>  batchSaveItemLogDo4Selective(List<ItemOperateLogDO> itemOperateLogDOList);
}

 

2.5. 测试结果

批量保存方法(batchSaveItemLogDo0)插入数据1000条,耗时15666毫秒,批量保存后获取自增ID抽样结果:{"第1个对象的自增ID":"1","中间对象的自增ID":"500","最后1个对象的自增ID":"1000"}
批量保存方法(batchSaveItemLogDo1)插入数据1000条,耗时360毫秒,批量保存后获取自增ID抽样结果:{"第1个对象的自增ID":"1001","中间对象的自增ID":"1500","最后1个对象的自增ID":"2000"}
批量保存方法(batchSaveItemLogDo2)插入数据1000条,耗时490毫秒,批量保存后获取自增ID抽样结果:{"第1个对象的自增ID":"2001","中间对象的自增ID":"2500","最后1个对象的自增ID":"3000"}
批量保存方法(batchSaveItemLogDo3)插入数据1000条,耗时10583毫秒,批量保存后获取自增ID抽样结果:{"第1个对象的自增ID":"3001","中间对象的自增ID":"null","最后1个对象的自增ID":"null"}
批量保存方法(batchSaveItemLogDo4)插入数据1000条,耗时365毫秒,批量保存后获取自增ID抽样结果:{"第1个对象的自增ID":"4001","中间对象的自增ID":"4500","最后1个对象的自增ID":"5000"}

 

2.6. 性能对比

1)横向性能对比分析
性能对比1
通过4种批量插入方案横向对比可以发现:

  • 方案3效率最低,即一条数据一条SQL的批量插入方式是一种伪批处理,它有多少条数据有会有多少条插入SQL,就需要执行提交多少次,所以效率相对较低。
  • 方案4会稍慢点,对于少量数据的批量插入操作,且想动态组装插入字段时可以考虑这个方案来实现。但是,可以看到,随着数据量的增多,其性能会急剧下降。
  • 方案2与方案1各有特点:方案1适合无差别的全字段批量插入,方案2适合字段判空批量插入。

所以,使用时需要根据实际应用场景进行方案选择,以及注意其方案的优劣和注意事项。

2)纵向性能对比分析
性能对比2
通过两种数据量切片批量插入效果来看:通过调整切片列表大小,在一定范围内,可以有效提高批量插入效率。如上案例,从200一批,调到500一批,数据量超过10000时,500一批的批量插入要比200一批的速度快。

但要注意的是,在 MySQL 5.7 及更早版本中,数据包大小默认设置是 4M。如果不注意实际场景,一味调大切片大小,很可能会造成 SQL 语句执行失败。

异常如:“Could not execute JDBC batch update”。
虽然可以通过修改 mysql 的配置文件(my.cnf 或 my.ini)中的 max_allowed_packet = 8M 来解决,但是大的SQL同样可能会异变成慢SQL,得不偿失。

注意: 修改 max_allowed_packet 的值时,应该根据实际需求和数据库负载进行合理的调整,避免设置过大导致资源浪费或设置过小无法满足插入或更新大数据量的需求。比如:批量插入比较简单的字段和信息时,切片大小可以调到500;但如果,批量记录的是复杂的信息,一个字段可能就是 3000 Varchar 时,那500大小的切片显然不合适。所以,实际中需要先评估数据量与字段多少,再看合适与否。

 

3. 共性异常

附带几种批量SQL中常见的异常:

3.1. 如果 SQL 中 foreach 标签下,collection="list" 配置的 list 与 Dao 接口入参对象使用的 @Param("list") 名称不一致,如 collection="productList",则会出现异常:

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.binding.BindingException: Parameter 'lists' not found. Available parameters are [list, param1]

 

3.2. 如果 SQL 中 foreach 标签下,SQL注入时未使用 item. 规则循环每个对象,不管是一个属性未使用 item.,还是多个属性未使用 item.(此处 foreach 中的 item="item" 只是列举,item 也可以自定义名称),则会出现异常:

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.binding.BindingException: Parameter 'nodeType' not found. Available parameters are [list, param1]

 

3.3. 如果 SQL 中 foreach 标签下,编写的属性名称与接口入参对象中的属性名称不一致,则会出现异常:

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'nodeTypes' in 'class com.meiwei.tan.saas.persistence.item.model.ItemOperateLogDO'

 

4. 其它批量插入操作

4.1) 如果需要批量插入数据,并且希望在遇到重复键值或主键冲突时避免报错,可以使用
INSERT IGNORE 或 INSERT … ON DUPLICATE KEY UPDATE
这两种方法在插入时能够处理重复键的情况,前者忽略重复键,后者在遇到重复键时更新记录。

语法如下:
使用 INSERT IGNORE:

INSERT IGNORE INTO your_table (column1, column2, column3)
VALUES (value1, value2, value3),
       (value4, value5, value6),
       ...;

使用 INSERT … ON DUPLICATE KEY UPDATE:

INSERT INTO your_table (column1, column2, column3)
VALUES (value1, value2, value3),
       (value4, value5, value6),
       ...
ON DUPLICATE KEY UPDATE column1 = VALUES(column1), column2 = VALUES(column2), ...;
  • INSERT IGNORE: 如果插入数据时发生了主键或唯一索引冲突,即要插入的记录已存在,那么数据库会忽略该条插入操作,而不会报错。如果插入的数据与已有记录冲突,该插入操作会被静默丢弃,不会触发错误,也不会返回自动生成的主键。
  • INSERT … ON DUPLICATE KEY UPDATE: 如果插入数据时发生了主键或唯一索引冲突,即要插入的记录已存在,那么数据库会执行更新操作,而不是插入新记录。同时,该语句也可以用于返回自动生成的主键。

4.2) 如果需要导入大量数据的情况,比如从CSV文件导入数据
LOAD DATA INFILE 语句可以通过读取本地文件将大量数据一次性导入数据库表中,这样可以实现高效的批量插入(仅适用于MySQL等数据库)。

注意: 在使用 LOAD DATA INFILE 语句时,数据直接从文件导入到数据库表中,绕过了MySQL的INSERT语句,因此不会触发INSERT操作,也就不会返回自动生成的主键。

语法如下:

LOAD DATA INFILE 'file_path'
INTO TABLE your_table
FIELDS TERMINATED BY ',' -- 或其他分隔符
LINES TERMINATED BY '\n' -- 或其他行终止符
(column1, column2, column3);

 

5.总结

无论使用哪种方法,都应根据具体的需求和数据量选择最适合的方式。对于大量数据的批量插入,使用 LOAD DATA INFILE 通常会比INSERT 语句更高效。而对于少量数据的批量插入,使用 INSERT 语句可能更简便。此外,如果遇到可能会导致主键冲突的情况,可以选择合适的方法来处理重复键值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/795366.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Boot 集成 Redis 三种模式实践汇总

背景 项目的某个模块集成了 SpringBoot Redis 包&#xff0c;客户端使用 Lettuce&#xff0c;Redis 测试环境单机模式。但是现场反馈的 Redis 环境是集群&#xff0c;如果简单的修改 spring.redis 配置为集群的配置信息&#xff0c;程序能否能无缝衔接呢&#xff1f; 本文记录…

Android Studio 的版本控制Git

Android Studio 的版本控制Git。 Git 是最流行的版本控制工具&#xff0c;本文介绍其在安卓开发环境Android Studio下的使用。 本文参考链接是&#xff1a;https://learntodroid.com/how-to-use-git-and-github-in-android-studio/ 一&#xff1a;Android Studio 中设置Git …

智安网络|实现数据安全:探索数据动态脱敏的落地策略

在当今数字化时代&#xff0c;数据安全成为企业和组织管理中的头等大事。然而&#xff0c;数据共享和数据大规模处理的需求也日益增长&#xff0c;这就需要在数据传输和存储过程中采取措施来保护用户的隐私。数据动态脱敏技术应运而生&#xff0c;为解决数据隐私和保护的问题提…

【雕爷学编程】Arduino动手做(93)--- 0.96寸OLED液晶屏模块3

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

Doris(二) -通过外部表同步数据

前言 参考网址 1.官网 2.ODBC External Table Of Doris 3.Apache doris ODBC外表使用方式 第一步 创建 RESOURCE DROP RESOURCE IF EXISTS mysql_test_odbc; CREATE EXTERNAL RESOURCE mysql_test_odbc PROPERTIES ( "type" "odbc_catalog", "…

《JeecgBoot系列》 表单实现指定列导出Excel“合并单元格“

表单"合计"列导出Excel合并单元格 注解&#xff1a; Excel 参数&#xff1a;mergeVertical&#xff0c;参数设置为Boolean类型&#xff0c;默认为false。当设置为true时&#xff0c;可以纵向合并内容相同的单元格 1.需求 介绍&#xff1a;每个类别下有多个型号&…

linux免交互

免交互 指不需要人为控制就可以完成的自动化操作。 shell脚本和免交互是一个概念&#xff0c;但是有两种写法。shell脚本基于bash。 here Document 免交互 它是一种标准输入&#xff0c;只能接收正确的命令&#xff0c;它主要是使用i/o重定向的方式将命令的列表提供给交互式…

BFS()

目录 多源BFS 矩阵距离 最小步数模型 魔板 八数码 双端队列广搜 电路维修 双向广搜 字串变换 A* 第K短路 多源BFS 单源BFS是求一个点到起点的最短距离 多源BFS是求有很多个起点&#xff0c;某一点到离它最近一个起点的距离 矩阵距离 给定一个 N 行M 列的 01矩阵…

【每日一题】—— C. Tiles Comeback (Codeforces Round 888 (Div. 3))

&#x1f30f;博客主页&#xff1a;PH_modest的博客主页 &#x1f6a9;当前专栏&#xff1a;每日一题 &#x1f48c;其他专栏&#xff1a; &#x1f534; 每日反刍 &#x1f7e1; C跬步积累 &#x1f7e2; C语言跬步积累 &#x1f308;座右铭&#xff1a;广积粮&#xff0c;缓称…

FTP客户端登录报错:Login failed

FTP客户端登录报错&#xff1a;Login failed 是selinux的问题&#xff0c;一般用户无法登录用户主目录 [rootchenshuyi ~]# setsebool -P tftp_home_dir 1

Filebeat学习笔记

Filebeat基本概念 简介 Filebeat是一种轻量级日志采集器&#xff0c;内置有多种模块&#xff08;auditd、Apache、Nginx、System、MySQL等&#xff09;&#xff0c;针对常见格式的日志大大简化收集、解析和可视化过程&#xff0c;只需一条命令即可。之所以能实现这一点&#…

【Go语言】Golang保姆级入门教程 Go初学者介绍chapter1

Golang 开山篇 Golang的学习方向 区块链研发工程师&#xff1a; 去中心化 虚拟货币 金融 Go服务器端、游戏软件工程师 &#xff1a; C C 处理日志 数据打包 文件系统 数据处理 很厉害 处理大并发 Golang分布式、云计算软件工程师&#xff1a;盛大云 cdn 京东 消息推送 分布式文…

集睿致远推出CS5466多功能拓展坞方案:支持DP1.4、HDMI2.1视频8K输出

ASL新推出的 CS5466是一款Type-C/DP1.4转HDMI2.1的显示协议转换芯片,&#xff0c;它通过类型C/显示端口链路接收视频和音 频流&#xff0c;并转换为支持TMDS或FRL输出信令。DP接收器支持81.Gbp s链路速率。HDMI输出端口可以作为TMDS或FRL发射机工作。FRL发射机符合HDMI 2.1规范…

08综合评价作业

某核心企业需要在6个待选的零部件供应商中选择一个合作伙伴&#xff0c;各待选供应商有关数据如表1所列&#xff0c;试从中选择一个最优供应商(理想解法) 评价指标产品质量产品价格/元地理位置/km售后服务/h技术水平经济效益供应能力/件市场影响度交货情况10.83326213.20.20.1…

Emacs之实现鼠标/键盘选中即拷贝外界内容(一百二十)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

linux操作历史history定制

history记录 Linux中历史操作记录history是一个很有用的功能&#xff0c;有时忘记了&#xff0c;翻翻以前的命令&#xff0c;十分方便。 # 展示所有历史记录 history # 筛选历史记录 history | grep nginx # 清除全部记录 -c history -c # 指定删除某一行,15是行号 history -…

5-Ngnix配置基于用户访问控制和IP的虚拟主机

目录 5.1.Ngnix配置基于用户访问控制的多虚拟主机 5.1.1.前提条件 5.1.2.Ngnix配置基于用户访问控制的多虚拟主机 5.2.Ngnix配置基于IP的虚拟主机 5.3.Ngnix配置基于IP的多虚拟主机 Nginx配置文件在/usr/local/nginx/conf下&#xff0c;文件名为nginx.conf 5.1.Ngnix配置…

如何部署MySQL读写分离

目录 第一步 部署MySQL主从复制 第二步 导入依赖环境文件 第三步 赋权并执行 第四步 优化文件名称 第五步 修改配置文件 第六步 刷新文件并查看详细信息 第七步 安装Amoeba 第八步 配置开放权限 第九步 修改配置文件 第十步 修改配置文件内容 第十一步 修改另一个配…

排队理论简介

排队理论简介 1. 理论背景2. 研究的数学方法3. 拒绝型排队系统与等候型排队系统4. 拒绝型排队系统 本文参考文献为Вентцель Е. С.的《Исследование операций》。 1. 理论背景 排队理论又称大众服务理论&#xff0c;顾名思义指的是在有限的服务条…

防风固沙功能重要性评价

1.3 防风固沙功能重要性评价 1.3.1 评估模型 以生态系统防风固沙服务能力指数作为评估指标&#xff0c;计算公式为&#xff1a; 式中&#xff1a;Sws为防风固沙服务能力指数&#xff0c;NPPmean为多年植被净初级生产力平均值&#xff0c;K 为土壤可蚀性因子&#xff0c;Fq为多…