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

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

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

第一种方式对于数据量过大的情况并不适用,为了采用第二种方法,我们使用了“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. vue开发记录

    vue开发过程中遇到的一些小问题.小技巧等,会不断更新~ 记录不详细处,欢迎留言

  2. call()与构造函数的运用

    一.简介 call()和apply()方法是所有函数体的固有属性,可以在指定作用域下调用函数.这里面有两层意思:1.可以在另外的作用域下调用函数:2.当函数体是是构造函数时,call()方法能达到类之 ...

  3. Notepad++编辑.sh文件

    使用记事本创建创建test.txt文件,修改后缀名为sh后,再文件里写以下内容: #!/bin/bash echo "hello world" 这样的文件再linux里是无法执行的 ...

  4. vue后台_实战篇

    一.一些常用组件效果的实现 1)面包屑导航 主要是使用$route.mathed:一个数组,包含当前路由的所有嵌套路径片段的路由记录 .路由记录就是 routes 配置数组中的对象副本 (还有在 ch ...

  5. 记录下我用Jenkins打包碰到的坑

    使用Andorid Studio 打包都是正常的,但是使用Jenkins自动打包一直报错,尝试过网上的各种方案,依然都不行,报错如下. FAILURE: Build failed with an ex ...

  6. 通过xshell上传和下载文件

    参考:http://www.cnblogs.com/mingaixin/p/5588699.html

  7. 启动maven项目时报错Failed to start component [StandardEngine[Tomcat]]: A child container failed during start

    详细错误信息:Failed to execute goal org.apache.tomcat.maven:tomcat7-maven-plugin:2.2:run (default-cli) on ...

  8. WIN7+QT5.2.0 连接oracle11g问题及解决方法

    用下面的代码建立连接之后,出现了几个问题 //连接数据库 QSqlDatabase db = QSqlDatabase::addDatabase("QOCI"); /**连接Ora ...

  9. PHP多进程开发与Redis结合实践

    原文:https://blog.51cto.com/laok8/2107892?source=drh 业务逻辑介绍: 用户在 APP 上发帖子,然后存储到 Redis 的 List 列表中 利用 Li ...

  10. mongoose 实现 增、删、改、查

    mongoose常用的API 增 save是一个实例方法,使用时需要先 new Model() 来实例化 //保存一个用户信息,userobj为你创建的文档对象模型里的字段,需正确对应传入 const ...