首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >批处理-使用Hibernate/Spring数据插入Postgres DB : 60K行需要2分钟,这是不可接受的

批处理-使用Hibernate/Spring数据插入Postgres DB : 60K行需要2分钟,这是不可接受的
EN

Stack Overflow用户
提问于 2020-02-20 15:07:58
回答 1查看 980关注 0票数 1

我需要使用Hibernate/Spring数据将60K行插入到Java/Spring应用程序中的Postgres中。

插入的数据是(1) USERS_T,(2)相关的新用户也必须在STUDY_PARTICIPANTS_T中。

下面的内容正在工作,但性能很差:60K需要2分钟。注意到,我正在填写Hibernate实体,然后根据1000大小的列表执行saveAll

代码语言:javascript
复制
        UsersT user = new UsersT();
        user.setUsername(study.getAbbreviation().toUpperCase()+subjectId);
        user.setRoleTypeId(new LookupT(150));
        user.setCreatedDate(new Date());
        //...
        List<StudyParticipantsT> participants = new ArrayList<StudyParticipantsT>();
        StudyParticipantsT sp = new StudyParticipantsT();
        sp.setStudyT(study);
        sp.setUsersT(user);
        sp.setSubjectId(subjectId);
        sp.setLocked("N");
        participants.add(sp);
        user.setStudyParticipantsTs(participants);

        // Add to Batch-Insert List; if list size ready for batch-insert, or if at the end of all subjectIds, do Batch-Insert saveAll() and clear the list
        batchInsertUsers.add(user);
        if (batchInsertUsers.size() == 1000 || i == subjectIds.size() - 1) {
            // Log this Batch-Insert
            if(log.isDebugEnabled()){
                log.debug("createParticipantsAccounts() Batch-Insert: Saving " + batchInsertUsers.size() + " records");
            }
            userDAO.saveAll(batchInsertUsers);
            // Reset list
            batchInsertUsers.clear();
        }          

我找到了一个线程,其中有人遇到了同样的问题,他们找到的唯一解决方案是为1000的每个块组合一个自定义的本机-SQL INSERT (..), (..), (..)字符串,然后手动运行,完全删除ORM/Hibernate层:Need to insert 100000 rows in mysql using hibernate in under 5 seconds

但我的插入涉及到一些连接的表。我可以自己花时间将所有这些实体语句重写为自定义SQL,但这并不简单。

还有其他解决办法吗?我正在使用-Spring5.0.2- Hibernate5.2.12

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-10-09 20:42:53

我们使用SpringJDBCjdbcTemplate.batchUpdate (无Hibernate)并预先为任何外键保留一个序列范围,从而提高了性能。

我们没有深入到实际的N个重复的INSERT语句的级别,上面提到的其他海报也这样做了;我们仍然在使用框架方法(JDBCTemplate),但至少我们不再使用Hibernate/ORM了。这个方法是快速的,但是它没有N个重复的INSERTs那么快,但是现在它是可以接受的。

实际的SpringJDBC批处理--插入是通过jdbcTemplate.batchUpdate(sqlInsert, new BatchPreparedStatementSetter() {..}进行的,我们实际上是自己分批-- BatchPreparedStatementSetter不会自动为我们拆分任何东西,它只会提交具有预定大小的特定批处理。

代码语言:javascript
复制
/**
 * The following method performs a Native-SQL Batch-Insert of Participant accounts (using JdbcTemplate) to improve performance.
 * Each Participant account requires 2 INSERTs: (1) USERS_T, (2) STUDY_PARTICIPANTS_T (with an FK reference to USERS_T.ID).
 * Since there is no easy way to track the Sequence IDs between the two Batch-Inserts, we reserve the ID range for both tables, and 
 * then manually calculate our own IDs for USERS_T and STUDY_PARTICIPANTS_T ourselves.
 * Initially, domain objects are filled out; then they are added to the Batch List that we submit and clear ourselves.
 * (Originally the Batch-Insert was implemented with Hibernate/HQL, but due to slow performance it was nativized with jdbcTemplate.)
 * 
 * NOTE: The entire method is @Transactional and all data will be rolled back in case of any exceptions in this method (rollbackFor=Exception.class).
 * The updated Sequence values (set during reservation) will not be rolled back in this case, but Sequence gaps are normal. 
 */
@Override
@Transactional(readOnly = false, rollbackFor = Exception.class)
public void createParticipantsAccounts(long studyId, List<String> subjectIds) throws Exception {

    int maxInsertParticipantsBatchSize = 1000; // Batch size is 1000
    
    /*
      We need to insert into 2 tables, USERS_T and STUDY_PARTICIPANTS_T. 
      The table STUDY_PARTICIPANTS_T has an FK dependency on USERS_T.ID.
      Since there is no easy way to track the Sequence IDs between the two Batch-Inserts, we reserve the ID range for both tables, 
      and then manually calculate our own IDs for USERS_T and STUDY_PARTICIPANTS_T ourselves.
      The Sequences are immediately updated to the calculated final values to reserve the range. 
     */
    // 1. Obtain current Sequence values
    Integer currUsersTSeqVal = userDAO.getCurrentUsersTSeqVal();
    Integer currStudyParticipantsTSeqVal = studyParticipantsDAO.getCurrentStudyParticipantsTSeqVal();
    // 2. Immediately update the Sequences to the calculated final value (this reserves the ID range immediately)
    // In Postgres, updating the Sequences is: SELECT setval('users_t_id_seq', :val)
    userDAO.setCurrentUsersTSeqVal(currUsersTSeqVal + subjectIds.size());
    studyParticipantsDAO.setCurrentStudyParticipantsTSeqVal(currStudyParticipantsTSeqVal + subjectIds.size());                          
    
    // List for Batch-Inserts, maintained and submitted by ourselves in accordance with our batch size
    List<UsersT> batchInsertUsers = new ArrayList<UsersT>();        
    
    for(int i = 0; i < subjectIds.size(); i++) {
        
        String subjectId = subjectIds.get(i);           
        
        // Prepare domain object (UsersT with associated StudyParticipantsT) to be used in the Native-SQL jdbcTemplate batchUpdate
        UsersT user = new UsersT();
        user.setId(currUsersTSeqVal + 1 + i); // Set ID to calculated value
        user.setUsername(study.getAbbreviation().toUpperCase()+subjectId);
        user.setActiveFlag(true);
        // etc., fill out object, then subobject:
        List<StudyParticipantsT> participants = new ArrayList<StudyParticipantsT>();
        StudyParticipantsT sp = new StudyParticipantsT();
        sp.setId(currStudyParticipantsTSeqVal + 1 + i); // Set ID to caculated value
        // ...etc.
        user.setStudyParticipantsTs(participants);
        
        // Add to Batch-Insert List of Users
        batchInsertUsers.add(user);
        
        // If list size ready for Batch-Insert, or if at the end of all subjectIds, perform Batch Insert (both tables) and clear list
        if (batchInsertUsers.size() == maxInsertParticipantsBatchSize || 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();
        }
    }
}

实际批处理的子方法-插入:

代码语言:javascript
复制
/**
 * Native-SQL Batch-Insert into USERS_T for Participant Upload.
 * NOTE: This method is part of its Parent's @Transactional. (Note also that we need "final" on the List param for Inner-Class access to this variable.)
 *  
 * @param jdbcTemplate
 * @param batchInsertUsers
 */
private void nativeBatchInsertUsers(JdbcTemplate jdbcTemplate, final List<UsersT> batchInsertUsers) {

    String sqlInsert =  "INSERT INTO PUBLIC.USERS_T (id, password, user_name, created_by, created_date, last_changed_by, last_changed_date, " + 
                                                    "first_name, last_name, organization, phone, lockout_date, lockout_counter, last_login_date, " + 
                                                    "password_last_changed_date, temporary_password, active_flag, uuid, " + 
                                                    "role_type_id, ws_account_researcher_id) " +
                        "VALUES (?, ?, ?, ?, ?, ?, ?, " +
                                "?, ?, ?, ?, ?, ?, ?, " + 
                                "?, ?, ?, ?, " + 
                                "?, ?" +
                                ") ";

    
    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)
            // etc., set PS for each i-th object

        }       
        
    });
    
}

/**
 * Native-SQL Batch-Insert into STUDY_PARTICIPANTS_T for Participant Upload.
 * NOTE: This method is part of its Parent's @Transactional. (Note also that we need "final" on the List param for Inner-Class access to this variable.)
 *  
 * @param jdbcTemplate
 * @param batchInsertUsers
 */ 
private void nativeBatchInsertStudyParticipants(JdbcTemplate jdbcTemplate, final List<UsersT> batchInsertUsers) {
    
    String sqlInsert =  "INSERT INTO PUBLIC.STUDY_PARTICIPANTS_T (id, study_id, subject_id, user_id, locked, " +                                                                     "created_by, created_date, last_changed_by, last_changed_date) " + 
                        "VALUES (?, ?, ?, ?, ?, " +
                                "?, ?, ?, ? " +
                                ") ";
            
    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).getStudyParticipantsTs().get(0).getId()); // ID (provided by ourselves)
            // etc. 
        }
        
    });
    
}       
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60322959

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档