在项目中经常会有如下场景:

往数据库中批量插入一批数据后,需要知道哪些插入成功,哪些插入失败了。

这时候往往会有两种思路,一个是在插入之前判断相同的记录是否存在,过滤掉重复的数据;另外一种就是边插入边判断,动态过滤。

第一种方式对于数据量过大的情况并不适用,为了采用第二种方法,我们使用了“Mybatis批量插入返回自增主键”的方式进行处理。

mysql插入操作后返回主键是jdbc的功能,用到的方法是getGeneratedKeys()方法,使用此方法获取自增数据,性能良好,只需要一次交互。

        String sql = "insert IGNORE into user(user_name,password,nick_name,mail) VALUES (?,?,?,?)";
List<User> userList = Lists.newArrayList();
userList.add(new User("2","2","2","2"));
userList.add(new User("3","3","3","3"));
userList.add(new User("4","4","4","4")); try {
conn = DatabaseUtil.getConnectDB();
ps = conn.prepareStatement(sql,PreparedStatement.RETURN_GENERATED_KEYS);
for(User user : userList){
ps.setString(1, user.getUserName());
ps.setString(2, user.getPassword());
ps.setString(3, user.getNickName());
ps.setString(4, user.getMail());
ps.addBatch();
}
ps.executeBatch(); ResultSet generatedKeys = ps.getGeneratedKeys();
ArrayList<Integer> list = Lists.newArrayList();
while (generatedKeys.next()){
list.add(generatedKeys.getInt(1));
}
} catch (SQLException e) {
LOGGER.error("error:{}", e.getMessage(), e);
} finally {
DatabaseUtil.close(conn, ps, null);
}

getGeneratedKeys()返回的就是刚刚生成的id。

相应的如果在mybatis中使用的话,只需要在mybatis的mapper文件中设置参数“keyProperty="id" useGeneratedKeys="true"”即可。例如:

   <insert id="insertListSelective" keyColumn="id" keyProperty="id"
parameterType="Bill" useGeneratedKeys="true"> </insert>

为了满足我们的需求,我们需要对上述sql进行改造,思路就是在批量插入的时候,如果遇到重复的数据,就忽略,继续插入下一个记录,这时我们采用的是ignore:

MySQL 提供了Ignore 用来避免数据的重复插入.

IGNORE :
若有导致unique key 冲突的记录,则该条记录不会被插入到数据库中.
示例:
INSERT IGNORE INTO `table_name` (`email`, `phone`, `user_id`) VALUES ('test9@163.com', '99999', '9999');
这样当有重复记录就会忽略,执行后返回数字0

但是经过多次测试发现,对象返回的id错乱。

对于上述情况,如果没有重复数据就不会出现问题,于是就猜测是因为ignore的原因,经过查看源码,验证了自己的想法:

public void processBatch(MappedStatement ms, Statement stmt, Collection<Object> parameters) {
ResultSet rs = null;
try {
rs = stmt.getGeneratedKeys();
final Configuration configuration = ms.getConfiguration();
final TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
//指的是keyProperty="id" 这种参数
final String[] keyProperties = ms.getKeyProperties();
//ResultSet的元数据,指的是有关 ResultSet 中列的名称和类型的信息。
final ResultSetMetaData rsmd = rs.getMetaData();
TypeHandler<?>[] typeHandlers = null;
if (keyProperties != null && rsmd.getColumnCount() >= keyProperties.length) {
for (Object parameter : parameters) {
// there should be one row for each statement (also one for each parameter)
if (!rs.next()) {
break;
}
final MetaObject metaParam = configuration.newMetaObject(parameter);
if (typeHandlers == null) {
typeHandlers = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties, rsmd);
}
//设置返回的keyProperty(反射)
populateKeys(rs, metaParam, keyProperties, typeHandlers);
}
}
} catch (Exception e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
} finally {
if (rs != null) {
try {
rs.close();
} catch (Exception e) {
// ignore
}
}
}
}
private void populateKeys(ResultSet rs, MetaObject metaParam, String[] keyProperties, TypeHandler<?>[] typeHandlers) throws SQLException {
for (int i = 0; i < keyProperties.length; i++) {
String property = keyProperties[i];
TypeHandler<?> th = typeHandlers[i];
if (th != null) {
Object value = th.getResult(rs, i + 1);
metaParam.setValue(property, value);
}
}
}

注意代码中的这一句注释: // there should be one row for each statement (also one for each parameter)    ,翻译过来就是每一个元素对应一个ResultSet

分析这段循环代码:


for (Object parameter : parameters) {
// there should be one row for each statement (also one for each parameter)
if (!rs.next()) {
break;
}
final MetaObject metaParam = configuration.newMetaObject(parameter);
if (typeHandlers == null) {
typeHandlers = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties, rsmd);
}
//设置返回的keyProperty(反射)
populateKeys(rs, metaParam, keyProperties, typeHandlers);
}

循环遍历要插入的元素,然后通过反射方式设置主键的值,但是注意每次遍历插入元素的时候,ResultSet也在往下遍历,这时候就有问题了:
stmt.getGeneratedKeys()永远返回的都是插入成功的记录的id,如果插入的集合中有几个重复的元素,这时候插入的集合元素与返回的ResultSet就对应不上了,所以才会造成之前的那个问题。

为了避免上述的问题,现在我们采用的方式是单条插入,挨个返回id。

Mysql批量插入返回Id错乱(原因分析)的更多相关文章

  1. MYSQL批量插入数据库实现语句性能分析

    假定我们的表结构如下 代码如下   CREATE TABLE example ( example_id INT NOT NULL, name VARCHAR( 50 ) NOT NULL, value ...

  2. MYSQL批量插入数据库实现语句性能分析【转】 批量插入!程序里面对于数据库插入的功能尽量用【异步处理+批量插入+(事务)】

    假定我们的表结构如下 代码如下   CREATE TABLE example (example_id INT NOT NULL,name VARCHAR( 50 ) NOT NULL,value VA ...

  3. Mybatis 插入一条或批量插入 返回带有自增长主键记录

    首先讲一下,  插入一条记录返回主键的 Mybatis 版本要求低点,而批量插入返回带主键的 需要升级到3.3.1版本,3.3.0之前的都不行, <dependency> <grou ...

  4. IP访问频率限制不能用数组循环插入多个限制条件原因分析及解决方案

    14.IP频率限制不能用数组循环插入多个限制条件原因分析及解决方案: define("RATE_LIMITING_ARR", array('3' => 3, '6' => ...

  5. mybatis oracle mysql 批量插入时的坑爹问题--需谨记

    mybatis oracle mysql 批量插入一.oracle的批量插入方式insert into db(id, zgbh, shbzh) select '1', '2', '3' from du ...

  6. mysql批量插入简单测试数据

    mysql批量插入简单测试数据 # 参考网址: https://www.2cto.com/database/201703/618280.html 1.mysql创建测试表 CREATE TABLE ` ...

  7. mybatis + mysql 批量插入、删除、更新

    mybatis + mysql 批量插入.删除.更新 Student 表结构 批量插入 public int insertBatchStudent(List<Student> studen ...

  8. MyBatis之Oracle、Mysql批量插入

    Mybatis中Dao层 public interface UsersMapper { public void insertEntitys(List<UserEntity> users); ...

  9. mybatis+mysql批量插入和批量更新、存在及更新

    mybatis+mysql批量插入和批量更新 一.批量插入 批量插入数据使用的sql语句是: insert into table (字段一,字段二,字段三) values(xx,xx,xx),(oo, ...

随机推荐

  1. windows版idea 2018.3.5版 永久激活教程

    1.下载idea并安装:https://download.jetbrains.com/idea/ideaIU-2018.3.5.exe?_ga=2.179947812.1869744014.15658 ...

  2. P4160 [SCOI2009]生日快乐 搜索

    思路:无脑搜索 提交:1次 题解: 大力搜索,枚举每个状态\((x,y,l)\)(\(x\)指分配到的长(可能比\(y\)要短),\(y\)指分配到的宽(可能比\(x\)要长),\(l\)指剩余切的次 ...

  3. 022_STM32中断优先级分组解析

    (0)STM32有十六个优先级 (一)STM32分组为:组0-4 (二)分组配置在寄存器SCB->AIRCR中: (三)解析第二点 1. 组0就是4位都用来设置成响应优先级,2^4=16位都是响 ...

  4. Oracle 审计 部署监控 user DML操作

    1.移动audit表及索引到dbadmin表空间 alter table aud$ move tablespace DBADMIN;alter table AUDIT$ move tablespace ...

  5. Codeforces.520B.Two Buttons(正难则反)

    题目链接 \(Description\) 给定两个数\(n,m\),每次可以使\(n\)减一或使\(n\)乘2.求最少需要多少次可以使\(n\)等于\(m\). \(Solution\) 暴力连边BF ...

  6. Luogu5369 [PKUSC2018]最大前缀和

    题目链接:洛谷 题目大意:给定一个长为$n$的整数序列,求全排列的最大前缀和(必须包含第一个数)之和. 数据范围:$1\leq n\leq 20,1\leq \sum_{i=1}^n|a_i|\leq ...

  7. Js 之复制到剪贴板 clipboard.js

    一.下载 https://github.com/zenorocha/clipboard.js/archive/master.zip 二.Demo示例 <!DOCTYPE html> < ...

  8. Ryu控制器编程开发——packet_in和packet_out简易交换机实现

    Ryu控制器二次开发,实现一个简单的只能够简单地广播数据包的交换机. from ryu.base import app_manager from ryu.controller import ofp_e ...

  9. linux系统安装硬盘分区建议

      一.常见挂载点的情况说明一般来说,在linux系统中都有最少两个挂载点,分别是/ (根目录)及 swap(交换分区),其中,/ 是必须的: 详细内容见下文: 安装系统时选择creat custom ...

  10. [GIT]比较不同分支的差异

        比如我们有 2 个分支:master, dev,现在想查看这两个 branch 的区别,有以下几种方式: undefined 1.查看 dev 有,而 master 中没有的: 1.查看 de ...