본문 바로가기

기타 보관함/개발자정보

Spring, iBATIS, MariaDB 환경에서 온라인 서비스와 배치 서비스 구성 가이드

반응형

1. 온라인 서비스 환경 구성

1.1. 기본 아키텍처

[클라이언트] ↔ [웹 서버] ↔ [애플리케이션 서버(Spring)] ↔ [MariaDB]

1.2. 개발 환경 설정

1.2.1. 의존성 설정 (pom.xml 또는 build.gradle)

<!-- Spring Web MVC -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.20</version>
</dependency>

<!-- iBATIS (MyBatis) -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.9</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.7</version>
</dependency>

<!-- MariaDB JDBC -->
<dependency>
    <groupId>org.mariadb.jdbc</groupId>
    <artifactId>mariadb-java-client</artifactId>
    <version>3.0.6</version>
</dependency>

<!-- Connection Pool -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-dbcp2</artifactId>
    <version>2.9.0</version>
</dependency>

1.2.2. Spring 설정 (applicationContext.xml)

<!-- DataSource 설정 -->
<bean id="onlineDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="org.mariadb.jdbc.Driver"/>
    <property name="url" value="jdbc:mariadb://online-db-server:3306/online_db?useSSL=false"/>
    <property name="username" value="online_user"/>
    <property name="password" value="password"/>
    <property name="initialSize" value="5"/>
    <property name="maxTotal" value="20"/>
    <property name="maxIdle" value="10"/>
    <property name="minIdle" value="5"/>
</bean>

<!-- iBATIS SqlSessionFactory 설정 -->
<bean id="onlineSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="onlineDataSource"/>
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <property name="mapperLocations" value="classpath*:mapper/online/**/*.xml"/>
</bean>

<!-- Mapper 인터페이스 스캔 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.example.online.mapper"/>
    <property name="sqlSessionFactoryBeanName" value="onlineSqlSessionFactory"/>
</bean>

<!-- 트랜잭션 관리자 -->
<bean id="onlineTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="onlineDataSource"/>
</bean>

<!-- 트랜잭션 어노테이션 활성화 -->
<tx:annotation-driven transaction-manager="onlineTransactionManager"/>

1.2.3. MyBatis 설정 (mybatis-config.xml)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="cacheEnabled" value="true"/>
        <setting name="lazyLoadingEnabled" value="false"/>
        <setting name="jdbcTypeForNull" value="NULL"/>
    </settings>
    <typeAliases>
        <package name="com.example.online.model"/>
    </typeAliases>
</configuration>

1.3. 온라인 서비스 개발 가이드

  1. 트랜잭션 관리
    • 서비스 레이어에서 @Transactional 어노테이션 사용
    • 읽기 전용 작업에는 @Transactional(readOnly = true) 적용
  2. 성능 고려사항
    • 데이터베이스 커넥션 풀 적절히 설정
    • 캐시 전략 수립 (Spring Cache, MyBatis 2nd level cache 등)
    • 쿼리 최적화 (MyBatis 쿼리 튜닝)
  3. 예외 처리
    • Spring의 @ControllerAdvice를 사용한 글로벌 예외 처리
    • MyBatis 예외를 Spring의 DataAccessException으로 변환
  4. 보안 고려사항
    • SQL 인젝션 방지를 위해 MyBatis의 #{} 문법 사용
    • 민감한 데이터는 암호화 저장

2. 배치 서비스 환경 구성

2.1. 기본 아키텍처

[배치 트리거] → [배치 애플리케이션(Spring Batch)] ↔ [MariaDB]

2.2. 개발 환경 설정

2.2.1. 의존성 추가

<!-- Spring Batch -->
<dependency>
    <groupId>org.springframework.batch</groupId>
    <artifactId>spring-batch-core</artifactId>
    <version>4.3.6</version>
</dependency>

<!-- 배치를 위한 추가 의존성 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>5.3.20</version>
</dependency>

2.2.2. Spring Batch 설정 (batch-context.xml)

<!-- 배치용 DataSource (온라인과 분리 권장) -->
<bean id="batchDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="org.mariadb.jdbc.Driver"/>
    <property name="url" value="jdbc:mariadb://batch-db-server:3306/batch_db?useSSL=false"/>
    <property name="username" value="batch_user"/>
    <property name="password" value="password"/>
    <property name="initialSize" value="3"/>
    <property name="maxTotal" value="10"/>
</bean>

<!-- 배치 메타데이터 테이블 설정 -->
<bean id="batchTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="batchDataSource"/>
</bean>

<bean id="jobRepository" class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
    <property name="dataSource" ref="batchDataSource"/>
    <property name="transactionManager" ref="batchTransactionManager"/>
    <property name="databaseType" value="mariadb"/>
</bean>

<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
    <property name="jobRepository" ref="jobRepository"/>
</bean>

<!-- iBATIS 설정 (배치용) -->
<bean id="batchSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="batchDataSource"/>
    <property name="configLocation" value="classpath:mybatis-config-batch.xml"/>
    <property name="mapperLocations" value="classpath*:mapper/batch/**/*.xml"/>
</bean>

2.3. 배치 서비스 개발 가이드

  1. 기본 배치 잡 구조
 
@Configuration
public class SampleBatchJob {
    @Autowired private JobBuilderFactory jobBuilderFactory;
    @Autowired private StepBuilderFactory stepBuilderFactory;
    
    @Bean
    public Job sampleJob() {
        return jobBuilderFactory.get("sampleJob")
                .incrementer(new RunIdIncrementer())
                .start(step1())
                .build();
    }
    
    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                .<Input, Output>chunk(100)
                .reader(reader())
                .processor(processor())
                .writer(writer())
                .build();
    }
    
    // reader, processor, writer 구현...
}
  1. 대용량 처리 전략
    • 청크 기반 처리 (적절한 chunk size 설정)
    • 페이징 처리 (MyBatis의 RowBounds 사용)
    • 병렬 처리 (partitioning, multi-threading)
  2. 실패 처리
    • SkipPolicy 구현을 통한 특정 예외 무시
    • RetryTemplate을 사용한 재시도 로직
    • 실패한 배치 재시작 전략 수립
  3. 성능 최적화
    • 배치 전용 DB 서버 사용
    • 적절한 인덱스 설정
    • 대량 삽입 시 bulk insert 사용

3. 공통 개발 가이드

3.1. 데이터베이스 설계

  • 온라인 DB와 배치 DB 분리 권장
  • 필요한 경우 복제(replication) 설정
  • 배치 작업 시 온라인 서비스에 영향 주지 않도록 설계

3.2. 모니터링

  • Spring Actuator 추가하여 건강 상태 확인
  • 배치 작업 히스토리 관리 (Spring Batch 메타데이터 활용)
  • 로깅 체계 수립 (Logback/SLF4J)

3.3. 배포 전략

  • 온라인 서비스: 무중단 배포 (Blue-Green, Canary)
  • 배치 서비스: 배치 작업 스케줄 고려하여 배포

3.4. 테스트 전략

  • 온라인 서비스: 통합 테스트, 부하 테스트
  • 배치 서비스: end-to-end 테스트, 대용량 데이터 테스트

4. 환경별 설정 예시

4.1. 개발 환경

  • 로컬 MariaDB 또는 Docker 컨테이너 사용
  • 최소한의 연결 풀 설정
  • 디버깅을 위한 상세 로깅

4.2. 스테이징 환경

  • 온라인/배치 DB 분리
  • 실제 운영 환경과 유사한 사양
  • 모니터링 도구 연동

4.3. 운영 환경

  • 고가용성 구성 (Master-Slave 복제)
  • 적절한 백업 전략 수립
  • 자동 확장(Auto Scaling) 구성 (클라우드 환경 시)

이 가이드는 Spring, iBATIS, MariaDB 환경에서 온라인 서비스와 배치 서비스를 구성하는 기본적인 방법을 설명합니다. 실제 적용 시에는 시스템의 규모와 요구사항에 따라 적절히 조정해야 합니다.

 

 

 

Oracle DB에서 조회하여 처리가 필요합니다.  추가하는 방법과 예제 코드 부탁 드립니다.

Oracle DB 연동 추가 가이드 (Spring + iBATIS/MyBatis 환경)

1. Oracle DB 연동 설정

1.1. 의존성 추가 (pom.xml 또는 build.gradle)

<!-- Oracle JDBC 드라이버 -->
<dependency>
    <groupId>com.oracle.database.jdbc</groupId>
    <artifactId>ojdbc8</artifactId>
    <version>21.5.0.0</version>
</dependency>

<!-- 또는 Oracle Thin 드라이버 -->
<dependency>
    <groupId>com.oracle.database.jdbc</groupId>
    <artifactId>ojdbc10</artifactId>
    <version>19.14.0.0</version>
</dependency>

1.2. Spring 설정 파일에 Oracle DataSource 추가

applicationContext-oracle.xml

<!-- Oracle DataSource 설정 -->
<bean id="oracleDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
    <property name="url" value="jdbc:oracle:thin:@//oracle-server:1521/ORCL"/>
    <property name="username" value="your_username"/>
    <property name="password" value="your_password"/>
    <property name="initialSize" value="3"/>
    <property name="maxTotal" value="15"/>
    <property name="validationQuery" value="SELECT 1 FROM DUAL"/>
    <property name="testOnBorrow" value="true"/>
</bean>

<!-- Oracle용 SqlSessionFactory -->
<bean id="oracleSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="oracleDataSource"/>
    <property name="configLocation" value="classpath:mybatis-config-oracle.xml"/>
    <property name="mapperLocations" value="classpath*:mapper/oracle/**/*.xml"/>
</bean>

<!-- Oracle Mapper 스캔 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.example.oracle.mapper"/>
    <property name="sqlSessionFactoryBeanName" value="oracleSqlSessionFactory"/>
</bean>

1.3. MyBatis Oracle 전용 설정

mybatis-config-oracle.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="jdbcTypeForNull" value="NULL"/>
        <!-- Oracle 페이징 처리를 위한 설정 -->
        <setting name="defaultFetchSize" value="100"/>
    </settings>
    <typeHandlers>
        <!-- Oracle 특화 타입 핸들러 필요시 추가 -->
    </typeHandlers>
</configuration>

2. Oracle 조회 및 처리 예제 코드

2.1. Mapper 인터페이스 예제

package com.example.oracle.mapper;

import java.util.List;
import java.util.Map;

public interface OracleDataMapper {
    // 단일 조회
    Map<String, Object> selectCustomerById(String customerId);
    
    // 리스트 조회
    List<Map<String, Object>> selectCustomersByCondition(Map<String, Object> params);
    
    // 페이징 조회 (Oracle 페이징)
    List<Map<String, Object>> selectCustomersWithPaging(Map<String, Object> params);
    
    // 프로시저 호출
    void callProcessCustomerData(Map<String, Object> params);
}

2.2. Mapper XML 예제

src/main/resources/mapper/oracle/CustomerMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.oracle.mapper.OracleDataMapper">

    <!-- 고객 정보 단건 조회 -->
    <select id="selectCustomerById" resultType="hashmap">
        SELECT 
            CUSTOMER_ID, 
            CUSTOMER_NAME,
            TO_CHAR(REG_DATE, 'YYYY-MM-DD') AS REG_DATE
        FROM 
            CUSTOMERS
        WHERE 
            CUSTOMER_ID = #{customerId}
    </select>

    <!-- 조건별 고객 조회 -->
    <select id="selectCustomersByCondition" parameterType="map" resultType="hashmap">
        SELECT 
            CUSTOMER_ID, 
            CUSTOMER_NAME,
            EMAIL,
            PHONE
        FROM 
            CUSTOMERS
        WHERE 
            REG_DATE >= TO_DATE(#{startDate}, 'YYYY-MM-DD')
            <if test="customerName != null and customerName != ''">
                AND CUSTOMER_NAME LIKE '%' || #{customerName} || '%'
            </if>
            <if test="status != null">
                AND STATUS = #{status}
            </if>
        ORDER BY CUSTOMER_NAME
    </select>

    <!-- 페이징 처리 (Oracle 방식) -->
    <select id="selectCustomersWithPaging" parameterType="map" resultType="hashmap">
        SELECT * FROM (
            SELECT a.*, ROWNUM rnum FROM (
                SELECT 
                    CUSTOMER_ID, 
                    CUSTOMER_NAME,
                    EMAIL,
                    PHONE
                FROM 
                    CUSTOMERS
                WHERE 
                    STATUS = 'ACTIVE'
                ORDER BY REG_DATE DESC
            ) a
            WHERE ROWNUM &lt;= #{endRow}
        )
        WHERE rnum &gt;= #{startRow}
    </select>

    <!-- 프로시저 호출 -->
    <select id="callProcessCustomerData" statementType="CALLABLE">
        { CALL PKG_CUSTOMER.PROCESS_CUSTOMER_DATA(
            #{customerId, mode=IN, jdbcType=VARCHAR},
            #{resultCode, mode=OUT, jdbcType=NUMERIC},
            #{resultMsg, mode=OUT, jdbcType=VARCHAR}
        )}
    </select>
</mapper>

2.3. 서비스 레이어 예제

@Service
@Transactional(transactionManager = "oracleTransactionManager")
public class OracleDataService {
    
    @Autowired
    private OracleDataMapper oracleDataMapper;
    
    /**
     * Oracle에서 고객 데이터 조회
     */
    public List<Map<String, Object>> getCustomerList(Map<String, Object> params) {
        // 페이징 파라미터 계산
        int page = params.get("page") != null ? Integer.parseInt(params.get("page").toString()) : 1;
        int size = params.get("size") != null ? Integer.parseInt(params.get("size").toString()) : 10;
        
        params.put("startRow", (page - 1) * size + 1);
        params.put("endRow", page * size);
        
        return oracleDataMapper.selectCustomersWithPaging(params);
    }
    
    /**
     * Oracle 프로시저 호출 예제
     */
    public Map<String, Object> processCustomerData(String customerId) {
        Map<String, Object> params = new HashMap<>();
        params.put("customerId", customerId);
        
        oracleDataMapper.callProcessCustomerData(params);
        
        Map<String, Object> result = new HashMap<>();
        result.put("code", params.get("resultCode"));
        result.put("message", params.get("resultMsg"));
        
        return result;
    }
    
    /**
     * MariaDB와 Oracle 데이터 연동 처리 예제
     */
    public void syncCustomerData() {
        // MariaDB에서 데이터 조회
        List<Map<String, Object>> mariaData = mariaDbMapper.selectTargetCustomers();
        
        // Oracle로 데이터 이관
        for(Map<String, Object> data : mariaData) {
            oracleDataMapper.insertCustomer(data);
            
            // 처리 로그 기록
            logService.writeSyncLog(data.get("customerId").toString(), "SYNC_COMPLETED");
        }
    }
}

2.4. 컨트롤러 예제

@RestController
@RequestMapping("/api/oracle")
public class OracleDataController {
    
    @Autowired
    private OracleDataService oracleDataService;
    
    @GetMapping("/customers")
    public ResponseEntity<?> getCustomers(
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(required = false) String name) {
        
        Map<String, Object> params = new HashMap<>();
        params.put("page", page);
        params.put("size", size);
        if(name != null) {
            params.put("customerName", name);
        }
        
        List<Map<String, Object>> customers = oracleDataService.getCustomerList(params);
        return ResponseEntity.ok(customers);
    }
    
    @PostMapping("/process/{customerId}")
    public ResponseEntity<?> processCustomer(@PathVariable String customerId) {
        Map<String, Object> result = oracleDataService.processCustomerData(customerId);
        return ResponseEntity.ok(result);
    }
}

3. 배치 작업에서 Oracle 조회 처리 예제

3.1. Spring Batch ItemReader 구현

public class OracleItemReader implements ItemReader<Customer> {
    
    private List<Customer> items;
    private final OracleDataMapper oracleDataMapper;
    private final Map<String, Object> params;
    
    public OracleItemReader(OracleDataMapper oracleDataMapper, Map<String, Object> params) {
        this.oracleDataMapper = oracleDataMapper;
        this.params = params;
        // 초기 데이터 로딩
        loadData();
    }
    
    private void loadData() {
        this.items = oracleDataMapper.selectCustomersByCondition(params)
                .stream()
                .map(this::convertToCustomer)
                .collect(Collectors.toList());
    }
    
    private Customer convertToCustomer(Map<String, Object> map) {
        Customer customer = new Customer();
        customer.setId(map.get("CUSTOMER_ID").toString());
        customer.setName(map.get("CUSTOMER_NAME").toString());
        // ... 다른 필드 매핑
        return customer;
    }
    
    @Override
    public Customer read() throws Exception {
        if(!items.isEmpty()) {
            return items.remove(0);
        }
        return null;
    }
}

3.2. 배치 잡 구성 예제

@Configuration
public class OracleBatchJobConfig {
    
    @Autowired
    private JobBuilderFactory jobBuilderFactory;
    
    @Autowired
    private StepBuilderFactory stepBuilderFactory;
    
    @Autowired
    private OracleDataMapper oracleDataMapper;
    
    @Bean
    public Job oracleDataProcessingJob() {
        return jobBuilderFactory.get("oracleDataProcessingJob")
                .incrementer(new RunIdIncrementer())
                .start(processOracleDataStep())
                .build();
    }
    
    @Bean
    public Step processOracleDataStep() {
        return stepBuilderFactory.get("processOracleDataStep")
                .<Customer, ProcessedData>chunk(100)
                .reader(oracleItemReader())
                .processor(customerProcessor())
                .writer(processedDataWriter())
                .build();
    }
    
    @Bean
    @StepScope
    public ItemReader<Customer> oracleItemReader() {
        Map<String, Object> params = new HashMap<>();
        params.put("status", "ACTIVE");
        params.put("startDate", LocalDate.now().minusMonths(1).toString());
        return new OracleItemReader(oracleDataMapper, params);
    }
    
    // ... processor, writer 구현
}

4. 주의사항 및 성능 고려사항

  1. 데이터 타입 처리
    • Oracle의 DATE, TIMESTAMP 타입은 Java의 java.sql.Date/Timestamp로 매핑
    • CLOB/BLOB 타입은 적절한 TypeHandler 구현 필요
  2. 페이징 처리
    • Oracle은 다른 DB와 페이징 방식이 다름 (ROWNUM 사용)
    • MyBatis에서 Oracle 페이징을 위한 전용 플러그인 고려
    • 대량 데이터 처리 시 fetchSize 조정 (100~1000 권장)성능 최적화
  3. <setting name="defaultFetchSize" value="500"/>
  4. 트랜잭션 관리
    • Oracle과 다른 DB 간 분산 트랜잭션 필요 시 JTA(Java Transaction API) 고려
    • Spring의 @Transactional에 명시적 트랜잭션 매니저 지정
    • Oracle 특화 오류 코드 처리오류 처리
    catch(SQLException e) {
        if(e.getErrorCode() == 1400) { // NOT NULL 위반
            // 특화 처리
        }
    }

이 예제 코드들은 Spring + iBATIS/MyBatis 환경에서 Oracle DB를 연동하는 기본적인 방법을 보여줍니다. 실제 프로젝트에서는 보안 설정(암호화된 자격 증명 사용), 연결 풀 최적화, 모니터링 등을 추가로 고려해야 합니다.

 

반응형