배치 선/후처리

배치 업무 전/후에 실행되는 선/후처리의 작성을 위한 Interface를 제공한다. 배치에서 선/후처리의 커스터마이징이 필요할 겨웅에는 해당 Interface를 이용하여 커스터 마이징을 진행하면 된다.

1. 배치 선/후처리 제공 Interface

배치 선/후처리를 할 수 있도록 제공하고 있는 Interface는 다음과 같다.

배치 선/후처리 제공 Interface
Interface 설명

BatchPreProcessor

배치 실행가능 여부 정의 및 배치 업무가 실행되기 전에
필수적으로 선처리 및 검증작업을 처리

BatchContextPreProcessor

Framework Context와 배치 Job Execution
Context가 생성 되고 난 후 호출

BatchPostProcessor

배치 수행이 완료(정상/오류 /중지 등)후에 호출이
되는 Interface로서 별도의 작업 로그를 남기는 등에 사용

2. 배치 선처리

BatchPreProcessor 는 배치작업정보 파라미터와 배치작업그룹정보를 체크한 결과에 대하여 실행가능 여부 정의 및 배치 업무가 실행되기 전에 필수적으로 선처리 및 검증작업을 처리하는 Interface 이다.

2.1. 구현 Method

BatchPreProcessor Interface의 구현 Method는 다음과 같다.

    public boolean checkIfRunnableBatch(String jobName, BasicBatchControlParameters basicBatchControlParameters, BatchType batchType) throws Exception;

    public boolean checkIfRunnableBatchGroup(String jobName, DataSource dataSource, BatchType batchType) throws Exception;

    public void preProcess(String jobName, DataSource dataSource, WrappedJobParameters wrapJobParameters
            , BatchType batchType, BasicBatchControlParameters basicBatchControlParameters, ContextHeader cHeader) throws Exception;

    public void initModuleTest(String jobId, DataSource dataSource, WrappedJobParameters wrapedJobParameters, BatchType batchType
            , BasicBatchControlParameters basicBatchControlParameters, ContextHeader cHeader) throws Exception;

2.1.1. checkIfRunnableBatch() Method

현재 실행되고 있는 배치가 입력 받은 배치작업 정보를 확인하여 수행 가능한 배치인지 판단한다.

checkIfRunnableBatch() Method 설명

속성

설명

파라미터

String jobId

배치작업ID (Spring Batch에서는 Job Name)

BasicBatchControlParameters

basicBatchControlParameters

배치작업정보 테이블(BXM_JOB_INFO)을 조회한 정보

BatchType batType

배치 실행 유형

반환값

boolean

배치 수행 가능 여부

배치 실행 유형은 다음과 같다.

배치 실행 유형
배치 유형(BatchType) 설명

GENERAL

일반 배치 (StandAlone 배치, Shell 배치)

ONDEMAND

온디맨드 배치

DAEMON

데몬 배치

2.1.2. checkIfRunnableBatchGroup Method

현재 실행되고 있는 배치가 포함되어 있는 배치작업그룹 정보를 조회하여 수행 가능한 배치인지 판단한다.

checkIfRunnableBatchGroup() Method 설명

속성

설명

파라미터

String jobId

배치 실행 시 입력한 배치작업ID(Spring에서는 Job Name).

DataSource dataSource

배치 그룹 정보를 Check하기 위한 DataSource

BatchType batchType

배치 실행 유형

반환값

boolean

배치 수행 가능 여부

2.1.3. preProcess() Method

필수 적인 배치 선처리 수행 및 기본적인 검증작업을 처리하는 Method이다.

Sample(DefaultBatchPreProcessor.java) 에는 중복 실행 가능여부에 따른 Job Parameter 수정 처리를 하고 있다.

preProcess() Method 설명

속성

설명

파라미터

String jobId

배치 실행 시 입력한 배치작업ID(Spring에서는 Job Name).

DataSource dataSource

DB 처리하기 위한 DataSource

WrapedJobParameters

wrapedJobParameters

Wrapping되어진 배치 Job Parameter

BatchType batchType

배치 실행 유형

BasicBatchControlParameters

basicBatchControlParameters

배치 Job Control 파라미터 정보

ContextHeader cHeader

시스템 헤더

리턴

void

2.1.4. initModuleTest() Method

Sample(DefaultBatchPreProcessor.java) 에는 중복 실행 가능여부에 따른 Job Parameter 수정 처리를 하고 있다.

initModuleTest() Method 설명

속성

설명

파라미터

String jobId

배치 실행 시 입력한 배치작업 ID(Spring에서는 Job Name).

DataSource dataSource

DB 처리하기 위한 DataSource

WrapedJobParameters

wrapedJobParameters

Wrapping되어진 배치 Job Parameter

BatchType batchType

배치 실행 유형

BasicBatchControlParameters

basicBatchControlParameters

배치 Job Control 파라미터 정보

ContextHeader

cHeader

시스템 헤더

리턴

void

2.2. Sample Source

기본으로 제공하는 Sample Source에서는 배치 수행 가능여부, 중복실행 방지등을 수행한다.

자세한 Source Sample은 bxm-batch-default-extension 프로젝트의 DefaultBatchPreProcessor.java 를 참고한다.

    package bxm.batch.dft.context;

    ......

    public class DefaultBatchPreProcessor implements BatchPreProcessor {

        @Override
        public boolean checkIfRunnableBatch(String jobId, BasicBatchControlParameters basicBatchControlParameters, BatchType batchType) throws BatchNotFoundException
        {
            boolean isCheck = false;

            // 일반배치 및 온디맨드 배치
            if(BatchType.GENERAL == batchType || BatchType.ONDEMAND == batchType)
            {
                isCheck = checkIfRunnableBatch(jobId, (BatchControlParameters)basicBatchControlParameters, batchType);
            }
            // 데몬 배치
            else if(BatchType.DAEMON == batchType)
            {
                isCheck = checkIfRunnableDaemon(jobId, (DaemonControlParameters)basicBatchControlParameters, batchType);
            }

            return isCheck;
        }

        /**
         * 일반, 온디맨드배치의  실행 가능여부 판단
         *
         * @param jobId 배치 Job ID
         * @param basicBatchControlParameters 배치 Job 정보 Load Instance
         * @param batchType 배치 유형
         * @return boolean 실행 가능여부
         * @throws BatchNotFoundException
         */
        private boolean checkIfRunnableBatch(String jobId, BatchControlParameters batchControlParameters, BatchType batchType) throws BatchNotFoundException
        {
            boolean isCheck = true;

            if(batchControlParameters != null)
            {
                // 배치 상태 구분 체크
                if(!batchControlParameters.isUseYn())
                {
                    isCheck = false;
                    logger.error("Batch USE_YN Column value : not use.......");
                    throw new RestrictedBatchExecutionException(jobId, BxmBatchErrorCode.RESTRICTED_BATCH_USE_YN, "Batch USE_YN Column value : not use.......");
                }

                if(!batchType.isEquals(batchControlParameters.getJobTypeCd()))
                {
                    isCheck = false;
                    logger.error("Batch job type check error. Job type : {}", batchControlParameters.getJobTypeCd());
                    throw new RestrictedBatchExecutionException(jobId, BxmBatchErrorCode.RESTRICTED_INVALID_JOB_TYPE, "Batch job type check error. Job type : " + batchControlParameters.getJobTypeCd());
                }

                if(batchType != BatchType.ONDEMAND)
                {
                    String applicationName = ApplicationContextTrace.getCurrentApplication().getApplicationName();
                    if(!applicationName.equals(batchControlParameters.getBxmAppId()))
                    {
                        isCheck = false;
                        logger.error("Parameter application ID [{}] and current application id [{}] are different. "
                                , new Object[]{batchControlParameters.getBxmAppId(), applicationName});
                        throw new RestrictedBatchExecutionException(jobId, BxmBatchErrorCode.RESTRICTED_INVALID_APP_ID, "Invalid applicaton name. Parameter application id : " + batchControlParameters.getBxmAppId());
                    }
                }

                // 일자 체크
                String currentDate = DateUtils.getCurrentDate(DateUtils.EMPTY_DATE_TYPE);
                if(StringUtils.hasText(batchControlParameters.getJobUseStartDt()))
                {
                    if(currentDate.compareTo(batchControlParameters.getJobUseStartDt()) < 0)
                    {
                        isCheck = false;
                        logger.error("Start date check error. Current Date : [{}], Start Date : [{}]"
                                , new Object[]{currentDate, batchControlParameters.getJobUseStartDt()});
                        throw new RestrictedBatchExecutionException(jobId, BxmBatchErrorCode.RESTRICTED_INVALID_START_DATE, "Invalid start date : " + batchControlParameters.getJobUseStartDt());
                    }
                }

                if(StringUtils.hasText(batchControlParameters.getJobUseEndDt()))
                {
                    if(currentDate.compareTo(batchControlParameters.getJobUseEndDt()) > 0)
                    {
                        isCheck = false;
                        logger.error("End date check error. Current Date : [{}], End Date : [{}]"
                                , new Object[]{currentDate, batchControlParameters.getJobUseEndDt()});
                        throw new RestrictedBatchExecutionException(jobId, BxmBatchErrorCode.RESTRICTED_INVALID_END_DATE, "Invalid end date : " + batchControlParameters.getJobUseEndDt());
                    }
                }

                // 프로세스 중복 실행 확인
                if(!batchControlParameters.isProcessDupExecUseYn())
                {
                    checkProcessDuplication(ApplicationContainerUtils.getApplicationContainer().getDataSoruce(), jobId);
                }
            }
            else
            {
                logger.error("Batch job control parameter is null.");
                throw new BatchJobNotFoundException(BatchApplicationContext.getDomainId(), jobId);
            }

            return isCheck;
        }

        /**
         * 데몬 배치의  실행 가능여부 판단
         *
         * @param jobId 배치 Job ID
         * @param basicBatchControlParameters 배치 Job 정보 Load Instance
         * @param batchType 배치 유형
         * @return boolean 실행 가능여부
         * @throws BatchNotFoundException
         */
        private boolean checkIfRunnableDaemon(String jobId, DaemonControlParameters daemonControlParameters, BatchType batchType) throws BatchNotFoundException
        {
            boolean isCheck = true;

            if(daemonControlParameters != null)
            {
                // 배치 상태 구분 체크
                if(!daemonControlParameters.isUseYn())
                {
                    isCheck = false;
                    logger.error("Batch USE_YN Column value : not use.......");
                    throw new RestrictedBatchExecutionException(jobId, BxmBatchErrorCode.RESTRICTED_BATCH_USE_YN, "Batch USE_YN Column value : not use.......");
                }

                String applicationName = ApplicationContextTrace.getCurrentApplication().getApplicationName();
                if(!applicationName.equals(daemonControlParameters.getBxmAppId()))
                {
                    isCheck = false;
                    logger.error("Parameter application ID [{}] and current application id [{}] are different. "
                            , new Object[]{daemonControlParameters.getBxmAppId(), applicationName});
                    throw new RestrictedBatchExecutionException(jobId, BxmBatchErrorCode.RESTRICTED_INVALID_APP_ID, "Invalid applicaton name. Parameter application id : " + daemonControlParameters.getBxmAppId());
                }
            }
            else
            {
                logger.error("Daemon Batch job control parameter is null.");
                throw new BatchJobNotFoundException(BatchApplicationContext.getDomainId(), jobId);
            }

            return isCheck;
        }


        @Override
        public boolean checkIfRunnableBatchGroup(String jobName, DataSource dataSource, BatchType batchType) throws Exception
        {
            // 일반 배치인 경우에만 배치 그룹정보를 체크한다.
            if(BatchType.GENERAL == batchType)
            {
                BatchGroupCheckProcessor batchGroupCheckProcessor = BatchApplicationContainer.getBatchGroupCheckProcessor();
                Map<String, BatchJobGroupInfo> batchGroupInfos = batchGroupCheckProcessor.loadBatchGroupInfo(jobName, dataSource);
                if(batchGroupInfos == null || batchGroupInfos.isEmpty())
                {
                    return true;
                }

                for(String jobGrpId : batchGroupInfos.keySet())
                {
                    if(!batchGroupCheckProcessor.batchGroupCheck(jobName, dataSource, jobGrpId, batchGroupInfos.get(jobGrpId)))
                    {
                        logger.error("Current batch job group count check is false. Job Group Id : [{}]", jobGrpId);
                        throw new RestrictedBatchExecutionException(jobName, BxmBatchErrorCode.RESTRICTED_GROUP_CONTROL
                                , "Current batch job group count check is false. Job Group Id : " + jobGrpId);
                    }
                }
            }

            return true;
        }

        @Override
        public void preProcess(String jobId, DataSource dataSource, WrapedJobParameters wrapedJobParameters, BatchType batchType
                , BasicBatchControlParameters basicBatchControlParameters)
        {
            // 기준일자 설정
            setStdDate(dataSource, cHeader, basicBatchControlParameters, wrappedJobParameters, batchType);

            // 일반배치일 경우
            if(BatchType.GENERAL == batchType)
            {
                // 시작로그 설정
                BatchLogUtils.startJobLog(jobId, true);
                /** 메모리 측정 스레드 수행 */
                /*Thread t = new Thread(new MonitorBatchMemory());
                  t.start();*/
            }
        }
    }

3. BatchContextPreProcessor Interface

BatchContextPreProcessor는 배치 실행 시, Framework Context와 배치 Job Execution이 생성 되고 난 후 호출되는 배치 선처리 Interface 이다.

3.1. 구현 Method

BatchContextPreProcessor Interface의 구현 Method는 다음과 같다.

        public void contextPreProcess(String jobId, DataSource dataSource, WrapedJobParameters wrapJobParameters, BatchType batchType, BasicBatchControlParameters basicBatchControlParameters, JobExecution jobExecution) throws Exception;

3.1.1. contextPreProcess Method

필수 적인 배치 선처리 수행 및 기본적인 검증작업을 처리하는 Method로서, 온디맨드 배치의 경우 Framework Context와 배치 Job Execution이 생성이 된 후 호출이 된다.

contextPreProcess() Method 설명

속성

설명

파라미터

String jobId

배치작업 ID(Spring Batch에서는 Job Name)

DataSource dataSource

DB 처리하기 위한 DataSource

WrapedJobParameters

wrapedJopParameters

Wrapping되어진 배치 Job Parameter

BatchType batchType

배치 실행 유형

BasicBatchControlParameters

basicBatchControlParameters

배치작업정보 테이블(BXM_JOB_INFO)을 조회한 정보

JobExecution

jobExecution

배치 Job의 Execution 정보

반환값

void

3.2. Sample Source

기본으로 제공하는 Sample Source에서는 시스템헤더 설정 및 로그레벨 및 Commit-Interval 관련 처리를 수행한다.

자세한 Source Sample은 bxm-batch-default-extension 프로젝트의 DefaultBatchContextPreProcessor.java 를 참고한다.

    package bxm.batch.dft.context;

    ......

    public class DefaultBatchContextPreProcessor implements BatchContextPreProcessor{

        private static final Logger logger = LoggerFactory.getLogger(DefaultBatchContextPreProcessor.class);

        @Override
        public void contextPreProcess(String jobId, DataSource dataSource,
                WrappedJobParameters wrapJobParameters, BatchType batchType,
                BasicBatchControlParameters basicBatchControlParameters, JobExecution jobExecution)
                throws Exception
        {
            logger.debug("batch context preProcess..........");

            BatchLogUtils.startJobExecLog(jobExecution.getJobId(), jobExecution.getId(), true);

            // 기동 실패한 JOB은 Context가 정상적으로 복구하여 처리하지 못했기 때문에.. 재시작 시 오류의 위험성이 있다.
            // 그래서 기동 실패한 경우에는 LOAD_FAIL 파라미터로 추가하여 해당 파라미터로 입력 하였을 경우.. 재실행 할 수 없도록 처리한다.
            if(wrapJobParameters.getJobParameters().getParameters().containsKey("LOAD_FAIL"))
            {
                throw new BxmBatchRuntimeException("Job failed to start cannot restart.");
            }

            // 기본 헤더 생성
            ServiceTraceContextImpl traceContext = (ServiceTraceContextImpl) ContextTrace.getCurrentContext();
            DefaultSystemHeader defaultSystemHeader = (DefaultSystemHeader) traceContext.getContextHeader();

            //String guid = DefaultGuidGenerator.generateInitialGuid();
            String guid = SystemUtils.generateGUID();
            defaultSystemHeader.setGuid(guid);  // Global Id
            defaultSystemHeader.setChlType("Z1");                                 // 채널유형
            defaultSystemHeader.setMsgReqDttm(ThreadSafeSimpleDateFormat.simpleFormatCurrentTime(Format.yyyyMMddHHmmssTT));
            defaultSystemHeader.setSysCd(DefaultBatchPropsConstants.PROPERTY_BAT_SYS_ID);   // 송신시스템구분
            defaultSystemHeader.setSystemId(DefaultBatchPropsConstants.PROPERTY_BAT_SYS_ID);
            defaultSystemHeader.setUserId(jobId);
            defaultSystemHeader.setLangCd(Locale.getDefault().getLanguage());

            if(BatchType.GENERAL == batchType || BatchType.ONDEMAND == batchType)
            {
                if(!(basicBatchControlParameters instanceof DefaultBatchControlParameter))
                {
                    logger.error("Invaild batch control parameter....");
                    throw new IllegalStateException("Invaild batch control parameter....");
                }

                DefaultBatchControlParameter defaultBatchControlParameter = (DefaultBatchControlParameter) basicBatchControlParameters;

                if(BatchType.ONDEMAND == batchType)
                {
                    logger.debug("system loglevel : {}", defaultBatchControlParameter.getSysLogLevel());
                    if(defaultBatchControlParameter.getSysLogLevel() != null)
                    {
                        // 온디맨드 배치 인 경우 로그레벨 설정
                        ((ServiceTraceContextImpl)ContextTrace.getCurrentContext()).setSysLoggingLevel(defaultBatchControlParameter.getSysLogLevel());
                    }

                    logger.debug("business loglevel : {}", defaultBatchControlParameter.getBizLogLevel());
                    if(defaultBatchControlParameter.getBizLogLevel() != null)
                    {
                        // 온디맨드 배치 인 경우 로그레벨 설정
                        ((ServiceTraceContextImpl)ContextTrace.getCurrentContext()).setBizLoggingLevel(defaultBatchControlParameter.getBizLogLevel());
                    }

                    logger.debug("database loglevel : {}", defaultBatchControlParameter.getDBLogLevel());
                    if(defaultBatchControlParameter.getDBLogLevel() != null)
                    {
                        // 온디맨드 배치 인 경우 로그레벨 설정
                        ((ServiceTraceContextImpl)ContextTrace.getCurrentContext()).setDBLoggingLevel(defaultBatchControlParameter.getDBLogLevel());
                    }
                }
            }

            // Set Step Commit Interval.
            if(!StringUtils.isEmpty(basicBatchControlParameters.getCommitCfgList()))
            {
                Map<String, Integer> stepCommitCounts = BxmDefaultStepListener.setCommitIntervalUtil(basicBatchControlParameters.getCommitCfgList());

                // 온디맨드 배치 선처리에서는 Context가 생성되어 있지 않기 때문에 설정값은 파라미터로 넘겨 Launcher 에서 설정 하도록 한다.
                if(LApplicationContext.getDataContainer() != null)
                {
                    LApplicationContext.getDataContainer().put(BatchContextConstans.CMDC_COMMIT_INTERVAL, stepCommitCounts);
                }
            }
            else
            {
                logger.info("commit interval value is null or empty");
            }

            // Default Commit Interval 설정
            if(LApplicationContext.getDataContainer() != null)
            {
                LApplicationContext.getDataContainer().put(BatchContextConstans.CMDC_JOB_DFT_COMMIT_INTERVAL, basicBatchControlParameters.getDefaultCommitInterval());
            }

            // Count, AMT Map CMDC에 설정
            // Count와 AMT가 복구 되었으면 존재, 복구 되지 않았으면.. 없음.
            if(LApplicationContext.getDataContainer().get(BatchContextConstans.CMDC_BATCH_COUNT_LOG) == null)
            {
                Map<Integer, Long> countMap = new ConcurrentHashMap<Integer, Long>();
                LApplicationContext.getDataContainer().put(BatchContextConstans.CMDC_BATCH_COUNT_LOG, countMap);
                Map<Integer, Long> lastCountMap = new ConcurrentHashMap<Integer, Long>();
                LApplicationContext.getDataContainer().put(BatchContextConstans.CMDC_BATCH_LAST_COUNT_LOG, lastCountMap);
            }
            if(LApplicationContext.getDataContainer().get(BatchContextConstans.CMDC_BATCH_AMT_LOG) == null)
            {
                Map<Integer, BigDecimal> amtMap = new ConcurrentHashMap<Integer, BigDecimal>();
                LApplicationContext.getDataContainer().put(BatchContextConstans.CMDC_BATCH_AMT_LOG, amtMap);
                Map<Integer, BigDecimal> lastAmtMap = new ConcurrentHashMap<Integer, BigDecimal>();
                LApplicationContext.getDataContainer().put(BatchContextConstans.CMDC_BATCH_LAST_AMT_LOG, lastAmtMap);
            }

            // 배치 데이타소스 허용 리스트 설정
            BatchDataSourceUtils.putDataSourceAllowList(basicBatchControlParameters.getDataSourceAllowList());
        }
    }

4. BatchPostProcessor Interface

BatchPostProcessor는 배치 수행이 완료(정상/오류 /중지 등)후에 호출이 되는 Interface로서 별도의 작업 로그를 남기는 등에 사용된다.

4.1. 구현 Method

BatchPostProcessor Interface의 구현 Method는 다음과 같다.

        public void postProcess(String jobId, DataSource dataSource, JobParameters jobParameters, JobExecution jobExecution, BatchType batchType, BasicBatchControlParameters basicBatchControlParameters) throws Exception;

4.1.1. postProcess() Method

배치 수행이 완료(정상/오류 /중지 등)후에 호출이 되어 배치의 후처리 작업을 수행한다.

postProcess() Method 설명

속성

설명

파라미터

String jobId

배치작업 ID(Spring Batch에서는 Job Name)

DataSource dataSource

DB 처리하기 위한 DataSource

JobParameters jobParameters

배치 Job Parameter

JobExecution jobExecution

배치 Job의 Execution 정보

BatchType batchType

배치 실행 유형

BasicBatchControlParameters

basicBatchControlParameters

배치작업정보 테이블(BXM_JOB_INFO)을 조회한 정보

반환값

void

4.2. Sample Source

현재 기본 Source Sample에는 배치 후처리 호출 로깅처리만 수행한다.

자세한 Source Sample은 bxm-batch-default-extension 프로젝트의 DefaultBatchContextPreProcessor.java 를 참고한다.

    public class DefaultBatchPostProcessor implements BatchPostProcessor {

        @Override
        public void postProcess(String jobId, DataSource dataSource,
                JobParameters jobParameters, JobExecution jobExecution,
                BatchType batchType, BasicBatchControlParameters basicBatchControlParameters)
        {
            logger.debug("batch postProcess..........");

            // Exception 정보 확인
            checkException(jobName, jobExecution);

            if(BatchType.GENERAL == batchType)
            {
                BatchLogUtils.endJobLog(jobExecution, true);

                if(jobExecution != null && jobExecution.getId() != null)
                {
                    // CPU 사용량 Update. 에러가 발생하더라도 계속 진행.
                    updateCpuUse(jobExecution.getId());

                    // 건수, 금액 Update
                    updateCountAmt(dataSource, jobExecution.getId());

                    // 배치 메모리 update
                    //updateBatchMemory(jobExecution.getId());
                }
            }

            // Job ExitStatus 처리
            applyJobExitStatus(jobExecution);

            BatchDataSourceUtils.removeDataSourceUseList();
            BatchDataSourceUtils.removeAllowDataSourceList();

            try {BxmLoadRemoteExecutor.destroyExecutors();} catch (Exception e) {}
        }
    }

5. 적용 방법

만일 배치 선/후처리와 관련하여 커스터마이징을 모듈을 개발하였다면 해당 모듈을 배치 Instance Configuration 파일(예:bxm-batch.xml)의 batch-context에 적용할 수 있다.

    ...
    <batch-context>
        <batch-preprocessor classname="bxm.batch.dft.context.DefaultBatchPreProcessor"/>
        <batch-context-preprocessor classname="bxm.batch.dft.context.DefaultBatchContextPreProcessor"/>
        <batch-postprocessor classname="bxm.batch.dft.context.DefaultBatchPostProcessor"/>
    </batch-context>
    ...

Copyright© Bankwareglobal All Rights Reserved.