在项目中经常会有如下场景:
往数据库中批量插入一批数据后,需要知道哪些插入成功,哪些插入失败了。
这时候往往会有两种思路,一个是在插入之前判断相同的记录是否存在,过滤掉重复的数据;另外一种就是边插入边判断,动态过滤。
第一种方式对于数据量过大的情况并不适用,为了采用第二种方法,我们使用了“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 (‘[email protected]‘, ‘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。
原文地址:https://www.cnblogs.com/haolnu/p/8290078.html