Mysql批量插入返回Id错乱(原因分析)
在项目中经常会有如下场景:
往数据库中批量插入一批数据后,需要知道哪些插入成功,哪些插入失败了。
这时候往往会有两种思路,一个是在插入之前判断相同的记录是否存在,过滤掉重复的数据;另外一种就是边插入边判断,动态过滤。
第一种方式对于数据量过大的情况并不适用,为了采用第二种方法,我们使用了“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错乱(原因分析)的更多相关文章
- MYSQL批量插入数据库实现语句性能分析
假定我们的表结构如下 代码如下 CREATE TABLE example ( example_id INT NOT NULL, name VARCHAR( 50 ) NOT NULL, value ...
- MYSQL批量插入数据库实现语句性能分析【转】 批量插入!程序里面对于数据库插入的功能尽量用【异步处理+批量插入+(事务)】
假定我们的表结构如下 代码如下 CREATE TABLE example (example_id INT NOT NULL,name VARCHAR( 50 ) NOT NULL,value VA ...
- Mybatis 插入一条或批量插入 返回带有自增长主键记录
首先讲一下, 插入一条记录返回主键的 Mybatis 版本要求低点,而批量插入返回带主键的 需要升级到3.3.1版本,3.3.0之前的都不行, <dependency> <grou ...
- IP访问频率限制不能用数组循环插入多个限制条件原因分析及解决方案
14.IP频率限制不能用数组循环插入多个限制条件原因分析及解决方案: define("RATE_LIMITING_ARR", array('3' => 3, '6' => ...
- mybatis oracle mysql 批量插入时的坑爹问题--需谨记
mybatis oracle mysql 批量插入一.oracle的批量插入方式insert into db(id, zgbh, shbzh) select '1', '2', '3' from du ...
- mysql批量插入简单测试数据
mysql批量插入简单测试数据 # 参考网址: https://www.2cto.com/database/201703/618280.html 1.mysql创建测试表 CREATE TABLE ` ...
- mybatis + mysql 批量插入、删除、更新
mybatis + mysql 批量插入.删除.更新 Student 表结构 批量插入 public int insertBatchStudent(List<Student> studen ...
- MyBatis之Oracle、Mysql批量插入
Mybatis中Dao层 public interface UsersMapper { public void insertEntitys(List<UserEntity> users); ...
- mybatis+mysql批量插入和批量更新、存在及更新
mybatis+mysql批量插入和批量更新 一.批量插入 批量插入数据使用的sql语句是: insert into table (字段一,字段二,字段三) values(xx,xx,xx),(oo, ...
随机推荐
- 使用谷歌chrome浏览器查看任何标签的固有属性
查看任何标签的固有属性property: 使用谷歌浏览器:Ctrl+Shift+I 开发者工具----点击Elements----点击a标签----点击Properties属性及其属性值. 自定义属性 ...
- learning express step(八)
To skip the rest of the middleware functions from a router middleware stack, call next('route') to p ...
- Spring Boot 如何部署到 Linux 中的服务
打包完成后的 Spring Boot 程序如何部署到 Linux 上的服务? 你可以参考官方的有关部署 Spring Boot 为 Linux 服务的文档. 文档链接如下: https://docs. ...
- 2018-2019 ACM-ICPC, Asia Jiaozuo Regional Contest
目录 Contest Info Solutions A. Xu Xiake in Henan Province D. Keiichi Tsuchiya the Drift King E. Resist ...
- LOJ6436. 「PKUSC2018」神仙的游戏 [NTT]
传送门 思路 首先通过各种手玩/找规律/严谨证明,发现当\(n-i\)为border当且仅当对于任意\(k\in[0,i)\),模\(i\)余\(k\)的位置没有同时出现0和1. 换句话说,拿出任意一 ...
- 使用ICEM绘制非结构网格时,如何提高网格质量?【转载】
作者:杨淑娟 链接:https://www.zhihu.com/question/20851390/answer/26152732 来源:知乎 著作权归作者所有.商业转载请联系作者获得授权,非商业转载 ...
- 第11组 Beta冲刺(4/5)
第11组 Beta冲刺(4/5) 队名 不知道叫什么团队 组长博客 https://www.cnblogs.com/xxylac/p/12018586.html 作业博客 https://edu. ...
- shell生成指定范围随即整数
#!/bin/bash function rand(){ min=$ max=$(($-$min+)) num=$( | cksum | awk -F ' ' '{print $1}') echo $ ...
- C++ STL partial_sort_copy iterator
vector<int>::iterator iter1 = partial_sort_copy(deq1.begin(), deq1.end(), vec1.begin(), vec1.e ...
- 阶段5 3.微服务项目【学成在线】_day05 消息中间件RabbitMQ_16.RabbitMQ研究-与springboot整合-生产者代码
springBoot给我们提供了 RarbbitTemplate发送消息 创建测试类,因为我们是基于SpringBoot的来写的测试类.所以要加上@SpringBootTest和@RunWith的注解 ...