スプリングイニシエータ配置設計


スプリングイニシエータ配置設計


上記のスプリングガイド配置構成部品を使用して、コミュニティサイトに登録されているメンバーの1年後も状態が変わらないメンバーを人間のメンバーに変換するための展開例コードを作成しました.

技術規格は以下の通りである。

  • Java 8
  • Gradle 6.3
  • Spring Boot 2.3.0 RELEASE
  • IntelliJ IDEA 2020.02
  • H2
  • 依存ライブラリでは、Spring Data JPA、H 2、Lombok、Spring Batch Starterシリーズが選択されています.

    展開プロセス全体



    ハンドラ


    ItemReaderを使用して、
  • H 2 DBの1年以内にユーザーが更新されていないロジックを検索します.
  • ItemProcessorでは、
  • ターゲットユーザデータの状態値をヒトメンバに変換するプロセスが実施される.
  • のステータス値を変更したユーザメンバーが、実際にデータベースに格納されるItemWriterを実装します.
  • 1. build.grade依存性の設定

    buildscript {
        ext {
            springBootVersion = '2.3.0.RELEASE'
            gradle_node_version='2.2.4'
        }
    
        repositories {
            mavenCentral()
            maven {
                url "https://plugins.gradle.org/m2/"
            }
        }
    
        dependencies {
            classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        }
    }
    
    subprojects {
        apply plugin: 'java'
        apply plugin: 'eclipse'
        apply plugin: 'org.springframework.boot'
        apply plugin: 'io.spring.dependency-management'
    
        group = 'com.junyoung'
        version = '0.0.1-SNAPSHOT'
        sourceCompatibility = '1.8'
    
        repositories {
            mavenCentral()
        }
    
        configurations {
            compileOnly {
                extendsFrom annotationProcessor
            }
        }
    
        dependencies {
            compile('org.springframework.boot:spring-boot-starter-batch')
            runtime('com.h2database:h2')
            compileOnly 'org.projectlombok:lombok'
            annotationProcessor 'org.projectlombok:lombok'
            implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
            testImplementation('org.springframework.boot:spring-boot-starter-test') {
                exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
            }
            testCompile group: 'junit', name: 'junit', version: '4.12'
            testCompile('org.springframework.batch:spring-batch-test')
        }
    
        test {
            useJUnitPlatform()
        }
    }

    1.ドメインの作成


    まず、ヒトメンバーのバッチを処理するドメインを作成します.オブジェクト名はUserで、人間かどうかを識別するためにUserStatus Enumが追加されました.ACTIVEは活動メンバー、INACTIVEは人間関係メンバーです.

    UserStatus(会員アクティブ状態)

    public enum UserStatus {
        ACTIVE, INACTIVE;
    }

    Grade(会員レベル)

    public enum Grade {
        VIP, GOLD, FAMILY;
    }

    ユーザードメインオブジェクト

    @NoArgsConstructor
    @Entity
    @Table
    public class User implements Serializable {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long idx;
    
        @Column
        private String name;
    
        @Column
        private String password;
    
        @Column
        private String email;
    
        @Column
        private String principle;
    
        @Column
        @Enumerated(EnumType.STRING)
        private SocialType socialType;
    
        @Column
        @Enumerated(EnumType.STRING)
        private UserStatus status;
    
        @Column
        @Enumerated(EnumType.STRING)
        private Grade grade;
    
        @Column
        private LocalDateTime createdDate;
    
        @Column
        private LocalDateTime updatedDate;
    
        @Builder
        public User(String name, String password, String email, String principle,
                    SocialType socialType, UserStatus status, Grade grade, LocalDateTime createdDate,
                    LocalDateTime updatedDate) {
            this.name = name;
            this.password = password;
            this.email = email;
            this.principle = principle;
            this.socialType = socialType;
            this.status = status;
            this.grade = grade;
            this.createdDate = createdDate;
            this.updatedDate = updatedDate;
        }
    
        public User setInactive() {
            status = UserStatus.INACTIVE;
            return this;
        }
    }

    ユーザー・レポート・ライブラリ

    public interface UserRepository extends JpaRepository<User, Long> {
        List<User> findByUpdatedDateBeforeAndStatusEquals(LocalDateTime localDateTime, UserStatus status);
    }
    findByUpdatedDateBeforeAndStatusEquals()メソッドは、パラメータ値でLocalDateTime、すなわち1年前の日付値を受信し、現在の標準日付値としてユーザ状態タイプでクエリーを実行します.

    2.人的資源配置Jobの設置


    まず、以下に示すように、スプリングガイドを実行する「Batch Application」ファイルで@EnableBatchProcessingを有効にする必要があります.
    @EnableBatchProcessing
    @SpringBootApplication
    public class BatchApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(BatchApplication.class, args);
        }
    }
    バッチを事前に登録して使用するには、EnableBatchProcessingを適用する必要があります.
    以下の@Configurationアクションを使用する設定クラスで、展開情報を空に登録します.
    @Slf4j
    @RequiredArgsConstructor
    @Configuration
    public class InactiveUserJobConfig {
    
        private final UserRepository userRepository;
        private final JobBuilderFactory jobBuilderFactory;
        private final StepBuilderFactory stepBuilderFactory;
    
        @Bean
        public Job inactiveUserJob() {
            // (1) JobBuilderFactory 주입
            return jobBuilderFactory.get("inactiveUserJob3")
                    // (2) Job의 재실행 방지
                    .preventRestart()
                    .start(inactiveJobStep(null))
                    .build();
        }
    
    }
  • (1)は、Job Builder Factoryを注入しています.これはコンストラクタであり、Jobを直感的かつ容易に作成できます.
  • (2)preventRestart()のジョブの再実行を阻止します.
  • 上のジョブ設定が完了しましたので、Stepを設定します.
    @Slf4j
    @RequiredArgsConstructor
    @Configuration
    public class InactiveUserJobConfig {
    
        @Bean
        public Step inactiveJobStep(@Value("#{jobParameters[requestDate]}") final String requestDate) {
            log.info("requestDate: {}", requestDate);
            // (1)  StepBuilderFactory 주입
            return stepBuilderFactory.get("inactiveUserStep")
                    // (2) chunk 사이즈 입력
                    .<User, User>chunk(10)
                    // (3) reader, processor, writer를 각각 설정
                    .reader(inactiveUserReader())
                    .processor(inactiveUserProcessor())
                    .writer(inactiveUserWriter())
                    .build();
        }
    }
  • (1)Step Builder Factoryでは、「非アクティブユーザStep」がinactiveUserStepというStep Builderを作成します.
  • (2)JENICを使用して、ブロックの入出力タイプをユーザタイプに設定する.パラメータ値は10に設定し、書き込み時にブロック単位でwriter()メソッドを実行する単位を指定します.すなわち,コミットにはdanueが10個ある.
  • (3)にはreader、processor、writerがそれぞれ設定されている.
  • 3.Chungガイド処理


    ここで...Chungって何ですか?
    Chunkは、Spring Batchで各コミット間で処理されるローの数を表すブロックです.Chunk処理を使用しない場合、データベースは1000個のデータを読み出してバッチ処理を行う可能性があります.
    バッチ処理中に1つのデータが格納されている場合、残りの999個のデータをrollbackで処理する必要がある.これらの問題を回避するために、Chunk 지향 프로세스の方法でスプリングガイド上の展開をサポートする.실패하는 경우 해당 Chunk 만큼 롤백이 되고, 이전에 커밋된 트랜잭션 범위까지는 반영이 된다는 것입니다.は、Chung単位のトランザクションです.
    Chunkはスプリングガイド配置をより良く使用するために理解しなければならない概念である.
    次のサンプルコードは、Javaコードを使用して、jojoldu님がこのブログにインポートしたChung向けの処理を表す.
    for (int i = 0; i < totalSize; i += chunkSize) {
    
        List<Item> items = new ArrayList<> ();
    
        for (int j = 0; j < chunkSize; j++) {
            Object item = itemReader.read();
            Object processedItem = item.Processor.process(item);
            items.add(processedItem);    
        }
        itemWriter.write(items);
    }
    Chunk単位でreader、process、writerとみなされるため、chunkSizeが10の場合、プロセスまたはwriteに異常が発生すると、すべてロールバックされ、バッチがchunkSizeで処理されます.ブログ参照

    4.ItemReaderの実装


    インターフェースItemReader(데이터를 읽어오는 역할).ここでは,ItemReaderインタフェースを実装するQueueItemReader実装に戻る.
    @Slf4j
    @RequiredArgsConstructor
    @Configuration
    public class InactiveUserJobConfig {
    
        private final UserRepository userRepository;
        private final JobBuilderFactory jobBuilderFactory;
        private final StepBuilderFactory stepBuilderFactory;
    
        @Bean
        @StepScope // (1) Step의 주기에 따라 새로운 빈 생성
        public QueueItemReader<User> inactiveUserReader() {
            List<User> oldUsers =
                    userRepository.findByUpdatedDateBeforeAndStatusEquals(
                            LocalDateTime.now().minusYears(1), UserStatus.ACTIVE);
            return new QueueItemReader<>(oldUsers);
        }
    }
  • のデフォルトの作成は1つのループですが、(1)で@StepScopeを使用すると、このメソッドはステップサイクルに従って新しい空を作成します.これは、各ステップの実行によって新しい空が作成されることを意味し、지연 생성を使用することができる.@Step Scopeは、デフォルトのプロキシ・モードのクラス・タイプを参照して返すため、@StepScope를 사용하면 반드시 구현된 반환 타입을 명시해 반환해야 합니다.の例では、Step ScopeはそれをQueueItemReaderと命名する.
  • findByUpdatedDateBeforeAndStatusEquals()メソッドは、ユーザのステータス値がACTIVE(現在の日付の1年前の日付値に基づく)であるユーザリストを問合せ、QueItemReaderオブジェクトを作成する際にパラメータとしてQueueに格納する.
  • QueueItemReader

    public class QueueItemReader<T> implements ItemReader<T> {
    
        private Queue<T> queue;
    
        public QueueItemReader(List<T> data) {
            this.queue = new LinkedList<>(data);
        }
    
        @Override
        public T read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
            return this.queue.poll();
        }
    }
    上記のコードでは、QueItemReaderを使用してターゲットデータを一度にロードし、キューに入れます.
    次にread()メソッドを呼び出すと、キューのpoll()メソッドを使用してキューから1つ以上のデータが返されます.

    5.ItemProcessorの実装

    @Slf4j
    @RequiredArgsConstructor
    @Configuration
    public class InactiveUserJobConfig {
    
        ...
    
        public ItemProcessor<User, User> inactiveUserProcessor() {
            return new ItemProcessor<User, User>() {
                @Override
                public User process(User user) throws Exception {
                    return user.setInactive();
                }
            };
        }
    }
    ItemReaderから読み込んだユーザを人間の状態に変換するプロセッサメソッドの例を追加します.

    6.ItemWriterの実装

    @Slf4j
    @RequiredArgsConstructor
    @Configuration
    public class InactiveUserJobConfig {
    
        ...
    
         public ItemWriter<User> inactiveUserWriter() {
            return ((List<? extends User> users) -> userRepository.saveAll(users));
        }
    }

    ヒトメンバー変換デプロイメント処理最終コード

    @Slf4j
    @RequiredArgsConstructor
    @Configuration
    public class InactiveUserJobConfig {
    
        private final UserRepository userRepository;
        private final JobBuilderFactory jobBuilderFactory;
        private final StepBuilderFactory stepBuilderFactory;
    
        @Bean
        public Job inactiveUserJob() {
            return jobBuilderFactory.get("inactiveUserJob3")
                    .preventRestart()
                    .start(inactiveJobStep(null))
                    .build();
        }
    
        @Bean
        public Step inactiveJobStep(@Value("#{jobParameters[requestDate]}") final String requestDate) {
            log.info("requestDate: {}", requestDate);
            return stepBuilderFactory.get("inactiveUserStep")
                    .<User, User>chunk(10)
                    .reader(inactiveUserReader())
                    .processor(inactiveUserProcessor())
                    .writer(inactiveUserWriter())
                    .build();
        }
    
        @Bean
        @StepScope
        public QueueItemReader<User> inactiveUserReader() {
            List<User> oldUsers =
                    userRepository.findByUpdatedDateBeforeAndStatusEquals(
                            LocalDateTime.now().minusYears(1), UserStatus.ACTIVE);
            return new QueueItemReader<>(oldUsers);
        }
    
        public ItemProcessor<User, User> inactiveUserProcessor() {
            return new org.springframework.batch.item.ItemProcessor<User, User>() {
                @Override
                public User process(User user) throws Exception {
                    return user.setInactive();
                }
            };
        }
    
        public ItemWriter<User> inactiveUserWriter() {
            return ((List<? extends User> users) -> userRepository.saveAll(users));
        }
    }
    ItemWriterが受信したリストタイプはChunk 단위です.Chunk単位が10に設定されているため、ユーザは10人の人間のメンバーを持ち、saveAll()メソッドを使用して一度にDBに保存します.

    SQL文を使用したテストデータの注入


    SQLクエリーファイルを作成して実行し、実際の配置を実行する前にテストするデータを作成します.
    デフォルトでは、/resourcesサブパスをインポートします.sqlファイルを作成すると、スプリングブート(正確にはHypernet)が実行されると、ファイルのクエリーが自動的に実行されます.
    insert into user (idx, email, name, password, social_type, status, grade, created_date, updated_date) values (1001, '[email protected]', 'test1', 'test1', 'FACEBOOK', 'ACTIVE', 'VIP', '2016-03-01T00:00:00', '2018-03-01T00:00:00');
    insert into user (idx, email, name, password, social_type, status, grade, created_date, updated_date) values (1002, '[email protected]', 'test2', 'test2', 'FACEBOOK', 'ACTIVE', 'VIP', '2016-03-01T00:00:00', '2018-03-01T00:00:00');
    insert into user (idx, email, name, password, social_type, status, grade, created_date, updated_date) values (1003, '[email protected]', 'test3', 'test3', 'FACEBOOK', 'ACTIVE', 'VIP', '2016-03-01T00:00:00', '2016-03-01T00:00:00');
    insert into user (idx, email, name, password, social_type, status, grade, created_date, updated_date) values (1004, '[email protected]', 'test4', 'test4', 'FACEBOOK', 'ACTIVE', 'GOLD', '2016-03-01T00:00:00', '2016-03-01T00:00:00');
    insert into user (idx, email, name, password, social_type, status, grade, created_date, updated_date) values (1005, '[email protected]', 'test5', 'test5', 'FACEBOOK', 'ACTIVE', 'GOLD', '2016-03-01T00:00:00', '2016-03-01T00:00:00');
    insert into user (idx, email, name, password, social_type, status, grade, created_date, updated_date) values (1006, '[email protected]', 'test6', 'test6', 'FACEBOOK', 'ACTIVE', 'GOLD', '2016-03-01T00:00:00', '2016-03-01T00:00:00');
    insert into user (idx, email, name, password, social_type, status, grade, created_date, updated_date) values (1007, '[email protected]', 'test7', 'test7', 'FACEBOOK', 'ACTIVE', 'FAMILY', '2016-03-01T00:00:00', '2016-03-01T00:00:00');
    insert into user (idx, email, name, password, social_type, status, grade, created_date, updated_date) values (1008, '[email protected]', 'test8', 'test8', 'FACEBOOK', 'ACTIVE', 'FAMILY', '2016-03-01T00:00:00', '2016-03-01T00:00:00');
    insert into user (idx, email, name, password, social_type, status, grade, created_date, updated_date) values (1009, '[email protected]', 'test9', 'test9', 'FACEBOOK', 'ACTIVE', 'FAMILY', '2016-03-01T00:00:00', '2016-03-01T00:00:00');
    insert into user (idx, email, name, password, social_type, status, grade, created_date, updated_date) values (1010, '[email protected]', 'test10', 'test10', 'FACEBOOK', 'ACTIVE', 'FAMILY', '2016-03-01T00:00:00', '2016-03-01T00:00:00');
    insert into user (idx, email, name, password, social_type, status, grade, created_date, updated_date) values (1011, '[email protected]', 'test11', 'test11', 'FACEBOOK', 'ACTIVE', 'FAMILY', '2016-03-01T00:00:00', '2016-03-01T00:00:00');

    実行結果


    Before



    After



    「ヒトメンバー展開」を実行すると、UPDATE DATE列の値が現在のポイントから1年前に変更され、ステータス値がACTIVEのメンバーのステータス値がINAVTIVEに変更されたことがわかります.
    参照:https://jojoldu.tistory.com/331?category=902551https://github.com/young891221/Spring-Boot-Community-Batch