데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      [SpringBatch 연재 07] MyBatisPagingItemReader로 DB내용을 읽고, MyBatisItemWriter로 DB에 쓰기

      KIDO 24.11.14
      3,439 7 4
      DEVOTEE 요약
      MyBatis는 여전히 Spring Java 프로젝트에서 인기 있는 ORM 도구로, 직관적인 사용법과 DB Tool을 통한 쿼리 실행이 장점입니다. MyBatis를 Spring Batch에서 사용하는 방법으로 MyBatisPagingItemReader를 통해 데이터를 읽고, MyBatisBatchItemWriter를 통해 데이터를 저장합니다. MyBatisPagingItemReader는 설정이 간편하며 쿼리 최적화와 동적 쿼리 생성 장점을 갖고 있지만, MyBatis 의존성과 커스터마이징의 복잡성이 단점입니다.
      DEVOTEE 추천 블로그

      여전히 Spring Java 기반의 프로젝트에서 많이 사용하고 있는 ORM(?) 툴중의 하나는 MyBatis이다.

      직관적인 사용법과, 쿼리를 mapper 파일에 직접 기술하고, 이를 DB Tool을 통해 수행할 수 있는 장점이 있는 도구라서 여전히 인기가 있는것 같다.


      MyBatis를 Spring Batch에서 사용하는 방법을 알아보자.

      MyBatisItemReader

      MyBatisPagingItemReader Spring Mybatis에서 제공하는 ItemReader 인터페이스를 구현하는 클래스이다.

      MyBatis의 Object Relation Mapper를 이용하며 다음과 같은 특징을 가지고 있다.

      장점:

      • 간편한 설정: MyBatis 쿼리 매퍼를 직접 활용하여 데이터를 읽을 수 있어 설정이 간편하다.

      • 쿼리 최적화: MyBatis의 다양한 기능을 활용하여 최적화된 쿼리를 작성할 수 있다.

      • 동적 쿼리 지원: 런타임 시 조건에 따라 동적으로 쿼리를 생성할 수 있다.

      단점:

      • MyBatis 의존성: MyBatis 라이브러리에 의존해야 한다.

      • 커스터마이징 복잡: Chunk-oriented Processing 방식과 비교했을 때 커스터마이징이 더 복잡할 수 있다.


      주요 구성 요소

      • SqlSessionFactory: MyBatis 설정 정보 및 SQL 쿼리 매퍼 정보를 담고 있는 객체이다.

      • QueryId: 데이터를 읽을 MyBatis 쿼리 ID이다.

      • PageSize: 페이징 쿼리를 위한 페이지 크기를 지정한다.

      1. SqlSessionFactory:

      • MyBatisPagingItemReader SqlSessionFactory 객체를 통해 MyBatis와 연동된다.

      • SqlSessionFactory는 다음과 같은 방법으로 설정할 수 있다.

      • @Bean 어노테이션을 사용하여 직접 생성

      • Spring Batch XML 설정 파일에서 설정

      • Java 코드에서 직접 설정

      2. QueryId:

      • MyBatisPagingItemReader setQueryId() 메소드를 통해 데이터를 읽을 MyBatis 쿼리 ID를 설정한다.

      • 쿼리 ID는 com.example.mapper.CustomerMapper.selectCustomers 와 같은 형식으로 지정된다.

      3. PageSize:

      • MyBatisItemReader는 pageSize를 이용하여 offset, limit 을 이용하는 기준을 설정할 수 있다.

      4. 추가 구성 요소:

      • SkippableItemReader: 오류 발생 시 해당 Item을 건너뛸 수 있도록 한다.

      • ReadListener: 읽기 시작, 종료, 오류 발생 등의 이벤트를 처리할 수 있도록 한다.

      • SaveStateCallback: 잡의 중단 시 현재 상태를 저장하여 재시작 시 이어서 처리할 수 있도록 한다.


      샘플 코드

      Customer 작성

      package com.schooldevops.springbatch.batchsample.jobs.models;
      
      import lombok.Data;
      
      @Data
      public class Customer {
          private Long id;
          private String name;
          private int age;
          private String gender;
      }
      • Customer 객체를 지정한다.

      MyBatisPagingItemReader 작성하기

          @Bean
          public MyBatisPagingItemReader<Customer> myBatisItemReader() throws Exception {
      
              return new MyBatisPagingItemReaderBuilder<Customer>()
                      .sqlSessionFactory(sqlSessionFactory)
                      .pageSize(CHUNK_SIZE)
                      .queryId("com.schooldevops.springbatch.batchsample.jobs.selectCustomers")
                      .build();
          }
      • MyBatisPagingItemReader 를 이용하여 DB 쿼리 결과를 읽을 수 있도록 ItemReader를 반환한다.

      • sqlSessionFactory: 세션 팩토리를 지정한다.

      • pageSize: 페이징 단위를 지정한다.

      • parameterValues: 파라미터를 전달할 수 있다.

      query 작성하기

      • customer.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.schooldevops.springbatch.batchsample.jobs">
      
      	<resultMap id="customerResult" type="com.schooldevops.springbatch.batchsample.jobs.models.Customer">
      		<result property="id" column="id"/>
      		<result property="name" column="name"/>
      		<result property="age" column="age"/>
      		<result property="gender" column="gender"/>
      	</resultMap>
      
      	<select id="selectCustomers" resultMap="customerResult">
      		SELECT id, name, age, gender
      		FROM customer
      		LIMIT #{_skiprows}, #{_pagesize}
      	</select>
      </mapper>
      • namespace: 쿼리들을 그룹화해서 모아놓은 이름 공간이다.

      • resultMap: 결과로 반환할 결과맵을 지정한다. 이는 db 칼럼과, java 필드 이름을 매핑한다.

      • select: 쿼리를 지정한다.

      • #{_skiprows}: 오프셋을 이야기하며, 쿼리 결과에서 얼마나 스킵할지 지정한다. pageSize를 지정했다면 자동으로 계산된다.

      • #{_pagesize}: 한번에 가져올 페이지를 지정한다.


      전체 코드 보기

      package com.schooldevops.springbatch.batchsample.jobs.mybatis;
      
      import com.schooldevops.springbatch.batchsample.jobs.models.Customer;
      import lombok.extern.slf4j.Slf4j;
      import org.apache.ibatis.session.SqlSessionFactory;
      import org.mybatis.spring.batch.MyBatisPagingItemReader;
      import org.mybatis.spring.batch.builder.MyBatisPagingItemReaderBuilder;
      import org.springframework.batch.core.Job;
      import org.springframework.batch.core.Step;
      import org.springframework.batch.core.job.builder.JobBuilder;
      import org.springframework.batch.core.launch.support.RunIdIncrementer;
      import org.springframework.batch.core.repository.JobRepository;
      import org.springframework.batch.core.step.builder.StepBuilder;
      import org.springframework.batch.item.file.FlatFileItemWriter;
      import org.springframework.batch.item.file.builder.FlatFileItemWriterBuilder;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.core.io.FileSystemResource;
      import org.springframework.transaction.PlatformTransactionManager;
      
      import javax.sql.DataSource;
      
      @Slf4j
      @Configuration
      public class MyBatisReaderJobConfig {
      
          /**
           * CHUNK 크기를 지정한다.
           */
          public static final int CHUNK_SIZE = 2;
          public static final String ENCODING = "UTF-8";
          public static final String MYBATIS_CHUNK_JOB = "MYBATIS_CHUNK_JOB";
      
          @Autowired
          DataSource dataSource;
      
          @Autowired
          SqlSessionFactory sqlSessionFactory;
      
          @Bean
          public MyBatisPagingItemReader<Customer> myBatisItemReader() throws Exception {
      
              return new MyBatisPagingItemReaderBuilder<Customer>()
                      .sqlSessionFactory(sqlSessionFactory)
                      .pageSize(CHUNK_SIZE)
                      .queryId("com.schooldevops.springbatch.batchsample.jobs.selectCustomers")
                      .build();
          }
      
      
          @Bean
          public FlatFileItemWriter<Customer> customerCursorFlatFileItemWriter() {
              return new FlatFileItemWriterBuilder<Customer>()
                      .name("customerCursorFlatFileItemWriter")
                      .resource(new FileSystemResource("./output/customer_new_v4.csv"))
                      .encoding(ENCODING)
                      .delimited().delimiter("\t")
                      .names("Name", "Age", "Gender")
                      .build();
          }
      
      
          @Bean
          public Step customerJdbcCursorStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) throws Exception {
              log.info("------------------ Init customerJdbcCursorStep -----------------");
      
              return new StepBuilder("customerJdbcCursorStep", jobRepository)
                      .<Customer, Customer>chunk(CHUNK_SIZE, transactionManager)
                      .reader(myBatisItemReader())
                      .processor(new CustomerItemProcessor())
                      .writer(customerCursorFlatFileItemWriter())
                      .build();
          }
      
          @Bean
          public Job customerJdbcCursorPagingJob(Step customerJdbcCursorStep, JobRepository jobRepository) {
              log.info("------------------ Init customerJdbcCursorPagingJob -----------------");
              return new JobBuilder(MYBATIS_CHUNK_JOB, jobRepository)
                      .incrementer(new RunIdIncrementer())
                      .start(customerJdbcCursorStep)
                      .build();
          }
      }

      WrapUp

      • 여기서는 MyBatis를 이용하여, 페이징을 이용한 청크단위 처리를 수행했다.

      • Mybatis는 쿼리를 직접 작성하고, 쿼리 내용직접 최적화 할수 있는 장점이 있다.

      • pageSize를 이용하면 자동으로 offset과 pagesize를 자동으로 계산하여 페이징 처리를 수행해준다.

      • ItemWriter은 단순하게 파일로 작성한 예제를 확인했다.




      MyBatisItemWriter

      • MyBatisBatchItemWriter Spring Batch에서 제공하는 ItemWriter 인터페이스를 구현하는 클래스이다.

      • 데이터를 MyBatis를 통해 데이터베이스에 저장하는 데 사용된다.

      구성 요소

      • SqlSessionTemplate: MyBatis SqlSession 생성 및 관리를 위한 템플릿 객체이다.

      • SqlSessionFactory: SqlSessionTemplate 생성을 위한 팩토리 객체이다.

      • StatementId: 실행할 MyBatis SQL 맵퍼의 스테이tement ID이다.

      • ItemToParameterConverter: 객체를 ParameterMap으로 변경할수 있다.

      장점

      • ORM 연동: MyBatis를 통해 다양한 데이터베이스에 데이터를 저장할 수 있다.

      • SQL 쿼리 분리: SQL 쿼리를 Java 코드로부터 분리하여 관리 및 유지 보수가 용이하다.

      • 유연성: 다양한 설정을 통해 원하는 방식으로 데이터를 저장할 수 있다.

      단점

      • 설정 복잡성: MyBatis 설정 및 SQL 맵퍼 작성이 복잡할 수 있다.

      • 데이터베이스 종속: 특정 데이터베이스에 종속적이다.

      • 오류 가능성: 설정 오류 시 데이터 손상 가능성이 있다.


      샘플

      Mybatis xml작성하기

      • customer.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.schooldevops.springbatch.batchsample.jobs">
      
          <resultMap id="customerResult" type="com.schooldevops.springbatch.batchsample.jobs.models.Customer">
              <result property="id" column="id"/>
              <result property="name" column="name"/>
              <result property="age" column="age"/>
              <result property="gender" column="gender"/>
          </resultMap>
      
          <insert id="insertCustomers" parameterType="com.schooldevops.springbatch.batchsample.jobs.models.Customer">
              INSERT INTO customer2(name, age, gender) VALUES (#{name}, #{age}, #{gender});
          </insert>
      </mapper>

      MyBatisBatchItemWriter 작성하기

          @Bean
          public MyBatisBatchItemWriter<Customer> mybatisItemWriter() {
              return new MyBatisBatchItemWriterBuilder<Customer>()
                      .sqlSessionFactory(sqlSessionFactory)
                      .statementId("com.schooldevops.springbatch.batchsample.jobs.insertCustomers")
                      .build();
          }
      • Map으로 파라미터를 전달한다면 다음과 같이 작성할수도 있다.

          @Bean
          public MyBatisBatchItemWriter<Customer> mybatisItemWriter() {
              return new MyBatisBatchItemWriterBuilder<Customer>()
                      .sqlSessionFactory(sqlSessionFactory)
                      .statementId("com.schooldevops.springbatch.batchsample.jobs.insertCustomers")
                     .itemToParameterConverter(item -> {
                         Map<String, Object> parameter = new HashMap<>();
                         parameter.put("name", item.getName());
                         parameter.put("age", item.getAge());
                         parameter.put("gender", item.getGender());
                         return parameter;
                     })
                      .build();
          }

      결과보기

      ID,Name,Age,Female
      1,unclebae,40,Male
      2,superman,45,Male
      3,WonderWoman,30,Female


      WrapUp

      • 지금까지 MyBatisBatchItemWriter를 작성해 보았다.

      • mybatis를 이용하여 ORM을 이용하고자 한다면 MyBatisBatchItemWriter로 청크단위 업데이트를 수행할 수 있다.

      • 쿼리를 xml로 작성하고, 쿼리를 statementId를 통해서 조회하여 해당 쿼리를 수행하여 데이터를 DB에 밀어 넣었다.

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      KIDO 님의 최신 블로그

      더보기

      DEVOTEE 추천 블로그

      동영상 기고하기