Java/Spring JDBC: Batch Insert into 2 Tables: Obtain FK ID from 1st Batch Insert Required for 2nd Table

谁说胖子不能爱 提交于 2021-02-05 10:48:21

问题


I'm using jdbcTemplate to Batch-Insert into 2 tables. The 1st table is easy, and has an ID. The 2nd table has an FK Reference USER_ID which I need to obtain from Table 1 before inserting.

Suppose I have this:

Main Java Code (here I split up into batches <= 1000)

for(int i = 0; i < totalEntries.size(); i++) {
    // Add to Batch-Insert List; if list size ready for batch-insert, or if at the end, batch-persist & clear list
    batchInsert.add(user);

    if (batchInsert.size() == 1000 || i == totalEntries.size() - 1) {
         // 1. Batch is ready, insert into Table 1
         nativeBatchInsertUsers(jdbcTemplate, batchInsert);
         // 2. Batch is ready, insert into Table 2
         nativeBatchInsertStudyParticipants(jdbcTemplate, batchInsert);
        // Reset list
        batchInsert.clear();
    }
}

Method to Batch-Insert into Table 1 (note I'm getting the Seq Val here for USERS_T)

    private void nativeBatchInsertUsers(JdbcTemplate jdbcTemplate, final List<UsersT> batchInsert) {

        String sqlInsert_USERS_T =  "INSERT INTO PUBLIC.USERS_T (id, password, user_name) " +
                                    "VALUES (nextval('users_t_id_seq'), ?, ? " +
                                            ")";        

        // Insert into USERS_T using overridden JdbcTemplate's Native-SQL batchUpdate() on the string "sqlInsert_USERS_T"
        jdbcTemplate.batchUpdate(sqlInsert_USERS_T, new BatchPreparedStatementSetter() {

            @Override
            public int getBatchSize() {
                return batchInsert.size();
            }

            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                ps.setString(1, null); 
                ps.setString(2, batchInsert.get(i).getUsername()); 
                // etc.
       });
}

Method to Batch-Insert into Table 2

private void nativeBatchInsertStudyParticipants(JdbcTemplate jdbcTemplate, final List<UsersT> batchInsertUsers) {

    String sqlInsert_STUDY_PARTICIPANTS_T = 
            "INSERT INTO PUBLIC.STUDY_PARTICIPANTS_T (id, study_id, subject_id, user_id) "  
                            "VALUES (nextval('study_participants_t_id_seq'), ?, ?, ?
                                            ")";        

    // Insert into STUDY_PARTICIPANTS_T using overridden JdbcTemplate's Native-SQL batchUpdate() on the string "sqlInsert_USERS_T"
    jdbcTemplate.batchUpdate(sqlInsert_STUDY_PARTICIPANTS_T, new BatchPreparedStatementSetter() {

        @Override
        public int getBatchSize() {
            return batchInsert.size();
        }

        @Override
        public void setValues(PreparedStatement ps, int i) throws SQLException {

             // PROBLEM: For Param #4, USER_ID, need to get the USERS_T.ID from Batch-Insert #1

        }       

    });

}   

When I come to the 2nd Batch-Insert, one of the columns is an FK back to USERS_T.ID which is called STUDY_PARTICIPANTS_T.USER_ID. Is it possible for me to obtain it by keeping the jdbcTemplate.batchUpdate() logic?


回答1:


Here's the answer.

1) One solution, if you're using jdbcTemplate (Spring JDBC), is to reserve your own ID range in advance. Then provide the manually-calculated IDs for each row yourself. E.g.

@Transactional(readOnly = false, rollbackFor = Exception.class)
public void doMultiTableInsert(List<String> entries) throws Exception {


    // 1. Obtain current Sequence values
    Integer currTable1SeqVal = table1DAO.getCurrentTable1SeqVal();
    Integer currTable2SeqVal = table2DAO.getCurrentTable2SeqVal();     
    // 2. Immediately update the Sequences to the calculated final value (this reserves the ID range immediately)
    table1DAO.setTable1SeqVal(currTable1SeqVal + entries.size());          
    table2DAO.setTable2SeqVal(currTable2SeqVal + entries.size());           

    for(int i = 0; i < entries.size(); i++) {
         // Prepare Domain object...
         UsersT user = new User();
         user.setID(currTable1SeqVal + 1 + i); // Set ID manually
         user.setCreatedDate(new Date());
         // etc.
         StudyParticipantsT sp = new StudyParticipantsT();
         sp.setID(currTable2SeqVal + 1 + i); // Set ID manually
         // etc.
         user.setStudyParticipant(sp);

         // Add to Batch-Insert List
         batchInsertUsers.add(user);

         // If list size ready for Batch-Insert (in this ex. 1000), or if at the end of all subjectIds, perform Batch Insert (both tables) and clear list
         if (batchInsertUsers.size() == 1000 || i == subjectIds.size() - 1) {
            // Part 1: Insert batch into USERS_T
            nativeBatchInsertUsers(jdbcTemplate, batchInsertUsers);             
            // Part 2: Insert batch into STUDY_PARTICIPANTS_T
            nativeBatchInsertStudyParticipants(jdbcTemplate, batchInsertUsers);                 
            // Reset list
            batchInsertUsers.clear();
         }
    }

}

then your Batch-Insert submethods referenced above:

1)

  private void nativeBatchInsertUsers(JdbcTemplate jdbcTemplate, final List<UsersT> batchInsertUsers) {
       String sqlInsert =   "INSERT INTO PUBLIC.USERS_T (id, password,  ... )"; // etc.
       jdbcTemplate.batchUpdate(sqlInsert, new BatchPreparedStatementSetter() {

           @Override
           public int getBatchSize() {
             return batchInsertUsers.size();
           } 

           @Override
           public void setValues(PreparedStatement ps, int i) throws SQLException {
              ps.setInt(1, batchInsertUsers.get(i).getId()); // ID (provided by ourselves)
              ps.setDate(2, batchInsertUsers.get(i).getCreatedDate());
              //etc.
           }            
       });
    }

2)

private void nativeBatchInsertStudyParticipants(JdbcTemplate jdbcTemplate, final List<UsersT> batchInsertUsers) {
   String sqlInsert =   "INSERT INTO PUBLIC.STUDY_PARTICIPANTS_T (id, ... )"; // etc.
   jdbcTemplate.batchUpdate(sqlInsert, new BatchPreparedStatementSetter() {

       @Override
       public int getBatchSize() {
         return batchInsertUsers.size();
       } 

       @Override
       public void setValues(PreparedStatement ps, int i) throws SQLException {
          ps.setInt(1, batchInsertUsers.get(i).getStudyParticipants().getId()); // ID (provided by ourselves)
          //etc.
       }            
   });
}

There are ways to get/set Sequence values, e.g. in Postgres it's

SELECT last_value FROM users_t_id_seq;   -- GET SEQ VAL
SELECT setval('users_t_id_seq', 621938); -- SET SEQ VAL

Note also that everything is under @Transactional. If there are any exceptions in the method all data gets rolled back (for all exceptions, rollbackFor = Exception.class). The only thing that doesn't get rolled back is the manual Sequence update. But that's OK, sequences can have gaps.

2) Another solution, if you're willing to drop down to the PreparedStatement level, is Statement.RETURN_GENERATED_KEYS:

PreparedStatement ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)

After you execute ps, the ResultSet will contain your IDs in the order they were created. You can iterate over the ResultSet and store the IDs in a separate list.

while (rs.next()) {
   generatedIDs.add(rs.getInt(1));
}

Remember that in this case you're responsible for your own Transaction Management. You need to conn.setAutoCommit(false); to have the batches pile up without real persistence, and then conn.commit(); / conn.rollback();.



来源:https://stackoverflow.com/questions/60356118/java-spring-jdbc-batch-insert-into-2-tables-obtain-fk-id-from-1st-batch-insert

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!