MyBatis: 批量保存实例返回主键id

9 篇文章 0 订阅
2 篇文章 0 订阅

主键ID是设定AUTO_INCREMENT , 当插入对象数据成功后,会给该对象回写id值。

 <insert id="patchInsert" useGeneratedKeys="true"  keyProperty="id" parameterType="java.util.List">
        insert into invoice( invoice_code, invoice_no) values
        <foreach item="item" collection="list" separator=",">
          ( #{item.invoiceCode}, #{item.invoiceNo}  )
        </foreach>
    </insert>

批量插入对象集合数据时,当PreparedStatement#execute执行sql之后的过程Jdbc3KeyGenerator#processAfter里:

getLastInsertID()拿到的是Resultset#getUpdateID里保存数据库插入数据成功返回的第一条记录的id,该id值就是首条插入行auto_increment值。拿到受影响行数,然后依次 'id+= auto_increment' 即可得到所有的主键值了,按rows数组index匹配对应的数据实例对象进行回写。 (insert sql是按入参List内记录顺序组装)

源码分析

mybatis(8) - spring 事务下 mybatis 的执行过程https://my.oschina.net/u/3434392/blog/3010626

在上面文章里简要描述了一个sql方法在MyBatis的执行过程。

这个过程从SimpleExecutor开始。受到 Configuration.defaultExecutorType = ExecutorType.SIMPLE 的影响, 如果设定为 ExecutorType.BATCH 则 Configuration#newExecutor 创建的是BatchExecutor 。 默认开启cacheEnabled,外层用CachingExecutor裹着(这里真正使用二级缓存还需要其他配置配合)。 

xxxMapper#insert (xxxMapper是由MapperFactoryBean使用MapperProxyFactory创建的MapperProxy代理对象) -> MapperProxy#invoke -> MapperMethod#execute 
​​​​​​-> SqlSessionTemplate#insert(内部持有代理类sqlSessionProxy, 它的InvocationHandler是SqlSessionInterceptor) -> SqlSessionInterceptor#invoke  -> DefaultSqlSession#insert -> BaseExecutor#update -> SimpleExecutor#doUpdate (通过Configuration创建RoutingStatementHandler,内部持有PreparedStatementHandler; 再获取到Connection; 使用Connection创建PreparedStatement(绑定了sql); PreparedStatementHandler给PreparedStatement使用TypeHandler设置入参值) -> StatementHandler#update -> PreparedStatement#execute

 这里主要看PreparedStatementHandler#update 的逻辑

// PreparedStatementHandler 
 @Override
  public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute(); // 执行sql
    int rows = ps.getUpdateCount(); // 影响到的记录行数
    Object parameterObject = boundSql.getParameterObject(); // 入参实例对象
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); // Jdbc3KeyGenerator
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject); // 回写id
    return rows;
  }

插入后回写id

//  Jdbc3KeyGenerator#processAfter 实际的逻辑
public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
    final String[] keyProperties = ms.getKeyProperties();
    if (keyProperties == null || keyProperties.length == 0) {
      return;
    }
    try (ResultSet rs = stmt.getGeneratedKeys()) { // 先确定需要回写的id集合
      final ResultSetMetaData rsmd = rs.getMetaData();
      final Configuration configuration = ms.getConfiguration();
      if (rsmd.getColumnCount() < keyProperties.length) {
        // Error?
      } else {
        assignKeys(configuration, rs, rsmd, keyProperties, parameter); // 匹配数组index队形的对象,回写id属性
      }
    } catch (Exception e) {
      throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
    }
  }

 KeyGenerator 是在创建 MappedStatement 时 判定是 SqlCommandType.INSERT 指定为 Jdbc3KeyGenerator。

这里分析一下sql执行完毕后的过程Jdbc3KeyGenerator#processAfter。

  • JDBC的 com.mysql.jdbc.StatementImpl#getGeneratedKeys: 

批量插入对象集合数据时,#getLastInsertID()拿到的是Resultset#getUpdateID里保存数据库插入成功返回的第一个id。该id值是首条插入行的数据库表自增值。拿到受影响行数,然后依次 'id+= auto_increment' 即可得到所有的主键值了。

//第一个插入行的AUTO_INCREMENT值, 拿到受影响行数,然后依次获取id。 
public synchronized ResultSet getGeneratedKeys() throws SQLException {
  if (!this.retrieveGeneratedKeys) {
   throw SQLError.createSQLException(Messages.getString("Statement.GeneratedKeysNotRequested"), "S1009", this.getExceptionInterceptor());
  } else if (this.batchedGeneratedKeys == null) {
   // 批量走这边的逻辑
   return this.lastQueryIsOnDupKeyUpdate ? this.getGeneratedKeysInternal(1) : this.getGeneratedKeysInternal();
  } else {
   Field[] fields = new Field[]{new Field("", "GENERATED_KEY", -5, 17)};
   fields[0].setConnection(this.connection);
   return ResultSetImpl.getInstance(this.currentCatalog, fields, new RowDataStatic(this.batchedGeneratedKeys), this.connection, this, false);
  }
 }
 
 protected ResultSet getGeneratedKeysInternal() throws SQLException {
    // 获取影响的行数
    int numKeys = this.getUpdateCount();
    return this.getGeneratedKeysInternal(numKeys);
  }

   protected ResultSetInternalMethods getGeneratedKeysInternal(long numKeys) throws SQLException {
        try {
            synchronized(this.checkClosed().getConnectionMutex()) {
                String encoding = this.session.getServerSession().getCharacterSetMetadata();
                int collationIndex = this.session.getServerSession().getMetadataCollationIndex();
                Field[] fields = new Field[]{new Field("", "GENERATED_KEY", collationIndex, encoding, MysqlType.BIGINT_UNSIGNED, 20)};
                ArrayList<Row> rowSet = new ArrayList();
                long beginAt = this.getLastInsertID(); // //第一条insert数据的id开始值
                if (this.results != null) {
                    String serverInfo = this.results.getServerInfo();
                    if (numKeys > 0L && this.results.getFirstCharOfQuery() == 'R' && serverInfo != null && serverInfo.length() > 0) {
                        numKeys = this.getRecordCountFromInfo(serverInfo);
                    }

                    if (beginAt != 0L && numKeys > 0L) {
                        for(int i = 0; (long)i < numKeys; ++i) {
                            byte[][] row = new byte[1][];
                            if (beginAt > 0L) {
                                row[0] = StringUtils.getBytes(Long.toString(beginAt));
                            } else {
                                byte[] asBytes = new byte[]{(byte)((int)(beginAt >>> 56)), (byte)((int)(beginAt >>> 48)), (byte)((int)(beginAt >>> 40)), (byte)((int)(beginAt >>> 32)), (byte)((int)(beginAt >>> 24)), (byte)((int)(beginAt >>> 16)), (byte)((int)(beginAt >>> 8)), (byte)((int)(beginAt & 255L))};
                                BigInteger val = new BigInteger(1, asBytes);
                                row[0] = val.toString().getBytes();
                            }

                          // 递增+1 塞到rowSet里, 会按index匹配实例对象集合数据                            
                            rowSet.add(new ByteArrayRow(row, this.getExceptionInterceptor()));
                            beginAt += (long)this.connection.getAutoIncrementIncrement(); // 自动递增值为1 
                        }
                    }
                }

                ResultSetImpl gkRs = this.resultSetFactory.createFromResultsetRows(1007, 1004, new ResultsetRowsStatic(rowSet, new DefaultColumnDefinition(fields)));
                return gkRs;
            }
        } catch (CJException var18) {
            throw SQLExceptionsMapping.translateException(var18, this.getExceptionInterceptor());
        }
    }

 

  •  Jdbc3KeyGenerator#assignKeys -> #assignKeysToParamMap

将得到的id数组按index匹配rows,回写到实例对象中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: MyBatis可以使用批量插入来实现批量新增数据,而在Oracle数据库中,可以通过使用序列来自动生成主键,然后将主键值插入到相关的表中。 在MyBatis中,通常使用`insertBatch`语句来实现批量新增,可以通过定义一个`List`来作为参数,每个元素代表一个要新增的对象,然后使用#{属性名}来引用相应的属性值。在执行批量新增时,也可以设置`batch`元素的`size`属性来指定批处理的大小,以优化性能。 此外,为了在新增数据时返回主键值,可以使用`selectKey`语句来获取生成的主键值。在Oracle中,可以通过设置`selectKey`语句的`order`属性为`BEFORE`来让它在新增数据之前获取主键值,然后将主键值设置到相应的对象中。 例如,以下是一个使用MyBatis和Oracle批量新增并返回主键的示例: ``` <insert id="insertBatch" parameterType="java.util.List"> <selectKey keyProperty="id" order="BEFORE" resultType="java.lang.Long"> SELECT SEQ_ID.NEXTVAL FROM DUAL </selectKey> insert into USER (ID, NAME, AGE) values (#{id}, #{name}, #{age}) </insert> ``` 在上述示例中,`insertBatch`语句插入一个`List`中包含的多个`User`对象,通过`selectKey`语句获取自动生成的主键值并设置到相应的对象中。 总之,通过结合MyBatis和Oracle的相关特性,可以实现批量新增并返回主键的功能,以提高数据插入的性能和效率。 ### 回答2: MyBatis是一种JAVA持久层框架,使用MyBatis可以让程序员将数据库查询语句和JAVA代码相分离,很大程度上减少了编程量和维护成本。Oracle是一种常用的关系型数据库管理系统,是企业应用的首选。 在MyBatis Oracle批量新增数据时,我们需要使用Oracle的SEQUENCE(序列)来生成主键。SEQUENCE是Oracle的一种内部对象,能够生成一个唯一的数字序列,可以用于自动分配主键。我们需要在MyBatis XML配置文件中定义一个带有“selectKey”标签的insert语句,这个标签会让MyBatis在执行insert语句之前先查询SEQUENCE,然后将取得的结果作为主键插入到数据库中。 具体操作流程如下: 1. 创建一个Sequence。 CREATE SEQUENCE seq_test MINVALUE 1 MAXVALUE 999999999999999999999999999 START WITH 1 INCREMENT BY 1 CACHE 20; 2.在MyBatis XML配置文件中定义一个insert语句,并在该语句下面用“selectKey”标签来声明取得主键的方式。其中,“order”属性表示主键生成的顺序,可以选择“before”或“after”;“keyProperty”属性表示主键生成后存储在哪一个对象属性中,一般情况下是实体类的主键属性;“resultType”属性表示主键类型。 <insert id="batchInsert" parameterType="java.util.List"> <foreach collection="list" item="item" separator=";"> insert into table_test (id, name) values(seq_test.nextval, #{item.name}) </foreach> <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Long"> SELECT seq_test.currval FROM DUAL </selectKey> </insert> 3.在Java代码中调用该insert语句,并将数据存储在List中。 List<TestEntity> testList = new ArrayList<>(); for (int i = 0; i < 10; i++) { TestEntity test = new TestEntity(); test.setName("name_" + i); testList.add(test); } testMapper.batchInsert(testList); 4.执行以上代码后,MyBatis会根据insert语句将数据批量插入到数据库中,并通过SEQUENCE生成主键。在insert语句执行完之后,MyBatis会自动查询主键并将其存储在实体类的主键属性中。我们可以通过遍历List来获取新插入数据的主键值: for (TestEntity test : testList) { System.out.println(test.getId()); } 总之,MyBatis和Oracle的配合使用可以实现批量新增并返回主键的操作。使用Oracle的SEQUENCE可以保证主键值的唯一性,MyBatis的XML配置文件中使用selectKey标签可以获取新插入数据的主键值。 ### 回答3: 在 MyBatis 中,可以通过使用批量插入操作来实现在 Oracle 数据库批量新增并返回主键值。 实现步骤如下: 1. 首先需要在 mapper.xml 文件中定义一个 insertList 节点,用于批量插入数据: ``` <insert id="insertList" parameterType="java.util.List"> <selectKey keyProperty="id" order="BEFORE" resultType="java.lang.Long"> SELECT SEQ.nextval as id FROM dual </selectKey> insert into table_name (id, col1, col2, col3) values (#{id}, #{col1}, #{col2}, #{col3}) </insert> ``` 其中,keyProperty 属性指定主键的属性名称,order 属性指定主键生成的顺序,resultType 属性指定主键的数据类型(这里使用了 Long 类型)。 2. 在 Java 代码中,通过 sqlSession 的 insert 方法来执行批量新增操作: ``` List<Data> dataList = ...; // 构造待插入的数据列表 for (Data data : dataList) { sqlSession.insert("insertList", data); } sqlSession.commit(); // 提交事务 ``` 在执行插入操作时,MyBatis 会自动调用 selectKey 节点中指定的 SQL 语句来生成主键值,并将其赋值给对应的属性。最终,主键值会被映射回 Java 对象中,以便后续使用。 需要注意的是,在 Oracle 数据库中,使用批量插入操作时,需要将 autoCommit 属性设置为 false,以确保所有插入操作都在同一事务中执行。 总结:通过 MyBatis批量插入操作,我们可以很方便地实现在 Oracle 数据库批量新增并返回主键值的功能。在实现过程中,需要注意正确配置 mapper.xml 文件,并将 autoCommit 属性设置为 false,以确保操作在同一事务中执行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值