IT/WEB

[JAVA] executeBatch 예외 처리 해결 (에러 무시하고 인서트)

오달달씨 2022. 5. 19. 19:39
728x90
반응형

일전에 executeBatch()를 사용해 대용량 Insert를 처리했다.

 

public static void insertDoneMsgBatchDB(String tablename, List<DoneMessageVO> doneList) {
	    Connection con = null;
	    PreparedStatement pstmt = null;
	    ResultSet rs = null;

	    String queryStr = String.format("REPLACE INTO %s %s", tablename, ADD_DONE_MSG);

	    DBConnectionMgr db = DBConnectionMgr.getInstance();

	    try{
		    //������������ ��������
		    con = db.getConnection();
		    con.setAutoCommit(false);
		    pstmt = con.prepareStatement(queryStr);
					    
			for(int i=0; i<doneList.size(); i++) {
				pstmt.setString(1, doneList.get(i).getSystem());
				pstmt.setString(2, doneList.get(i).getMsg_seq_id());
				pstmt.setString(3, doneList.get(i).getReal_user_submit_time());
				pstmt.setString(4, doneList.get(i).getUser_id());
				pstmt.setString(5, doneList.get(i).getCompany_name());
				pstmt.setString(6, doneList.get(i).getAgent_id());
				pstmt.setInt(7, doneList.get(i).getAgent_type());
				pstmt.setInt(8, doneList.get(i).getSvc_type());
				pstmt.setString(9, doneList.get(i).getCarrier_id());
				pstmt.setString(10, doneList.get(i).getCarrier_cid());
				pstmt.setString(11, doneList.get(i).getMachine_id());
				pstmt.setString(12, doneList.get(i).getReal_carrier_id());
				pstmt.setString(13, doneList.get(i).getRecv_mdn());
				pstmt.setString(14, doneList.get(i).getCall_mdn());
				pstmt.setString(15, doneList.get(i).getCarrier_tid());
				pstmt.setString(16, doneList.get(i).getAsp_id());
				pstmt.setInt(17, doneList.get(i).getIs_050());
				pstmt.setString(18,doneList.get(i).getCarrier_submit_time());
				pstmt.setString(19, doneList.get(i).getCarrier_deliver_time());
				pstmt.setString(20, doneList.get(i).getUser_deliver_time());
				pstmt.setString(21, doneList.get(i).getCarrier_code());
				pstmt.setString(22, doneList.get(i).getResult_code());
				pstmt.setString(23, doneList.get(i).getCharging_id());
				pstmt.setString(24, doneList.get(i).getCustomer_tid());
				pstmt.setString(25, doneList.get(i).getContent_info());
				pstmt.setString(26, doneList.get(i).getContent_size());
				pstmt.setString(27, doneList.get(i).getNat_cd());

				DoneCntVo.total_cnt++;
				
//				addBatch에 담기
				pstmt.addBatch();
//				파라미터 Clear
				pstmt.clearParameters();
				
				if( (i % 1000) == 0){ 
					// Batch 실행 
					int batchCnt[] = pstmt.executeBatch() ; 
					// insert 성공 카운트
					DoneCntVo.success_cnt += batchCnt.length;
					// Batch 초기화
					pstmt.clearBatch(); 
					// 커밋 
					con.commit() ; 
				}
			}
			// Batch 실행 
			int batchCnt[] = pstmt.executeBatch();
			// insert 성공 카운트
			DoneCntVo.success_cnt += batchCnt.length;
//			// Batch 초기화
			pstmt.clearBatch();
			// 커밋 
			con.commit() ; 

	    } 
	    catch (Exception e){
		    logger.error(e.getMessage(), e);
		    
	    } 
	    finally {
		    //������������ ������������
		    db.freeConnection(con, pstmt, rs);
		    db.removeConnection(con);
	    }
	}

 

executeBatch() 함수를 사용하면 대용량의 쿼리를 일괄 처리하여 단건 인서트보다 속도를 확 줄일 수 있으나,

 

여러개의 쿼리를 처리하는 도중에 Exception이 발생하며 처리했던 쿼리문도 모두 롤백한다.

 

이런 경우 로그를 남겨서 롤백한 파라미터를 확인해 따로 처리하는 방법도 있지만,

 

나는 처리해야할 데이터가 700만건이 되고, 1000건 단위로 인서트하고 있기 때문에 에러 하나의 발생으로 999건을 다시 처리하는 것은 좋은 방법으로 보이지 않았다.

 

어떻게든 executeBatch()에서 에러가 났을 경우 에러를 제외한 나머지 999건을 따로 인서트를 해야했다.

 

BatchUpdateException은 일괄 업데이트 작업 중에 오류가 발생할 때 throw되는 SQLException 의 하위 클래스이다. SQLException 에서 제공하는 정보 외에도 BatchUpdateException 은 일괄 업데이트 중에 성공적으로 실행 된 모든 명령, 즉 오류가 발생하기 전에 실행 된 모든 명령에 대한 업데이트 횟수를 제공하기도 한다. 업데이트 수 배열의 요소 순서는 명령이 배치에 추가 된 순서에 해당한다.

 

아무튼 해당 익셉션은 일괄처리 중 발생하는 오류에 대해 해당 쿼리만 무시하고 인서트하는 메소드는 따로 제공하고 있지 않다. 그래서 일괄처리 중 익셉션이 발생했을 경우 1000건 전체를 롤백하는 것이 아닌 1000건에 대해서 다시 인서트를 단건으로 진행하는 로직을 구현하였다.

 

public static void insertDoneMsgBatchDB(String tablename, List<DoneMessageVO> doneList) {
	    Connection con = null;
	    PreparedStatement pstmt = null;
	    ResultSet rs = null;
	    
	    List<DoneMessageVO> errorList = new ArrayList<DoneMessageVO>();
	    String queryStr = String.format("REPLACE INTO %s %s", tablename, ADD_DONE_MSG);

	    DBConnectionMgr db = DBConnectionMgr.getInstance();

	    try{
		    //������������ ��������
		    con = db.getConnection();
		    con.setAutoCommit(false);
		    pstmt = con.prepareStatement(queryStr);
					    
			for(int i=0; i<doneList.size(); i++) {
				pstmt.setString(1, doneList.get(i).getSystem());
				pstmt.setString(2, doneList.get(i).getMsg_seq_id());
				pstmt.setString(3, doneList.get(i).getReal_user_submit_time());
				pstmt.setString(4, doneList.get(i).getUser_id());
				pstmt.setString(5, doneList.get(i).getCompany_name());
				pstmt.setString(6, doneList.get(i).getAgent_id());
				pstmt.setInt(7, doneList.get(i).getAgent_type());
				pstmt.setInt(8, doneList.get(i).getSvc_type());
				pstmt.setString(9, doneList.get(i).getCarrier_id());
				pstmt.setString(10, doneList.get(i).getCarrier_cid());
				pstmt.setString(11, doneList.get(i).getMachine_id());
				pstmt.setString(12, doneList.get(i).getReal_carrier_id());
				pstmt.setString(13, doneList.get(i).getRecv_mdn());
				pstmt.setString(14, doneList.get(i).getCall_mdn());
				pstmt.setString(15, doneList.get(i).getCarrier_tid());
				pstmt.setString(16, doneList.get(i).getAsp_id());
				pstmt.setInt(17, doneList.get(i).getIs_050());
				pstmt.setString(18,doneList.get(i).getCarrier_submit_time());
				pstmt.setString(19, doneList.get(i).getCarrier_deliver_time());
				pstmt.setString(20, doneList.get(i).getUser_deliver_time());
				pstmt.setString(21, doneList.get(i).getCarrier_code());
				pstmt.setString(22, doneList.get(i).getResult_code());
				pstmt.setString(23, doneList.get(i).getCharging_id());
				pstmt.setString(24, doneList.get(i).getCustomer_tid());
				pstmt.setString(25, doneList.get(i).getContent_info());
				pstmt.setString(26, doneList.get(i).getContent_size());
				pstmt.setString(27, doneList.get(i).getNat_cd());
				
				errorList.add(doneList.get(i));
				DoneCntVo.total_cnt++;
				
//				addBatch에 담기
				pstmt.addBatch();
//				파라미터 Clear
				pstmt.clearParameters();
				
				if( (i % 1000) == 0){ 
					// Batch 실행 
					int batchCnt[] = pstmt.executeBatch() ; 
					// insert 성공 카운트
					DoneCntVo.success_cnt += batchCnt.length;
					// Batch 초기화
					pstmt.clearBatch(); 
					// error 초기화
					errorList.clear();
					// 커밋 
					con.commit() ; 
				}
			}
			// Batch 실행 
			int batchCnt[] = pstmt.executeBatch();
			// insert 성공 카운트
			DoneCntVo.success_cnt += batchCnt.length;
//			// Batch 초기화
			pstmt.clearBatch(); 
			// error 초기화
			errorList.clear();
			// 커밋 
			con.commit() ; 

	    } 
	    catch(BatchUpdateException b){
	    	
	    	String sql = "";
//	    	int failCount = 0;
	    	sql += "SQL 상태: " + b.getSQLState()+" \n";
	    	sql += "메시지내용: " + b.getMessage()+" \n";
	    	sql += "만든 곳: " + b.getErrorCode()+" \n";
//	    	sql += "업데이트 횟수: ";
//	    	int [] updateCounts = b.getUpdateCounts();
//	    	for (int i = 0; i < updateCounts.length; i++) {
//	    		if(updateCounts[i] == -3) {
//	    			sql += "\nFailCount : " + failCount;
//	    			break;
//	    		}
//	    		failCount++;
//	    	}
//	    	sql += "\nUpdateCounts : " + updateCounts.length;
	    	
	    	logger.info("BatchUpdateException" + sql);
	    	logger.info(">>>>>" + b.toString());
	    	
	    	try{
	    		
	    		for(DoneMessageVO doneVo : errorList) {
	    			tablename = String.format("%s_%s", SystemConfig.getTablePrefix(), doneVo.getUser_submit_time().substring(0, 8));
	    			insertDoneMsgDB(tablename, doneVo);
	    			DoneCntVo.success_cnt++;
	    		}
	    		
	    		errorList.clear();
	    		con.commit();
	    		
	    	}catch(SQLException e){
	    		logger.error(e.getMessage(), e);
			    DoneCntVo.fail_cnt++;
	    	}
	    } 
	    catch (Exception e){
		    logger.error(e.getMessage(), e);
		    
	    } 
	    finally {
		    //������������ ������������
		    db.freeConnection(con, pstmt, rs);
		    db.removeConnection(con);
	    }
	}

 

방법은 혹시 에러가 났을 경우를 대비해 List<DoneMessageVO> errorList = new ArrayList<DoneMessageVO>();리스트를 만들고 리스트에 add()한 다음 executeBatch() 정상 처리하면 clear()를 하고 익셉션이 발생하면 해당 리스트를 단건으로 인서트 하는 구조이다.

 

 

728x90
반응형