728x90

 java stream에서 maptoobj 함수는 중간 연산이라 되어 있습니다. 길이 n짜리 배열을 obj object n개로 채우려고 합니다. obj 클래스에는 int 자료형만 하나 있고, 우리는 이 n개의 obj가 깊은 복사가 되어야 해요. 이걸 stream을 써서 할 때, Intstream의 range 메서드와, maptoObj를 사용하면 손쉽게 처리할 수 있습니다.

 


 

 

 mapToObj 메서드를 봅시다. Intstream 뿐만이 아니라, Longstream과 DoubleStream에도 있습니다. 설명을 보면, 스트림으로부터 해당 함수를 적용한 객체 값들의 stream을 반환한다고 되어 있어요. 즉 입력 스트림으로부터 무언가를 받아서, 새로운 결과 가지고 있는 스트림으로 변환합니다.

 

 n개의 크기를 가진 리스트에 깊은 복사한 Obj 오브젝트 n개를 채워야 한다고 해 볼게요. 그러면 단순하게 List를 새로 생성해서 Obj 객체를 추가해도 됩니다. 그런데, 조금 더 생각해 보면 아래와 같은 로직을 생각할 수 있습니다.

 

 

 

 1부터 5까지 있는 stream이 있어요. 이것을 아래와 같이 변환시키면 어떨까요?

 

 

 

 하나의 원소가 새로운 new Obj(3)으로 바뀌는 것입니다. mapToXXX류가 이러한 역할을 수행합니다. 이 경우 IntStream의 원소들을 다른 obj로 변환시키는 apply 작업을 수행했습니다.

 

 

 

 

 그림으로 그리면 이런 상황인 셈입니다. 즉, mapToXXX는 스트림의 요소들을 다른 요소로 바꾼 새로운 스트림을 돌려줍니다. 위 그림에서는 x를 new Obj(3)으로 바꾸었습니다. 따라서, 1 2 3 4 5로 이루어져 있던 것을 new Obj(3), ... , new Obj(3)으로 대체하게 됩니다.

 


 이제 문제 상황을 다시 봅시다. 저는 객체 Obj n개가 채워진 ArrayList를 원합니다. 객체 n개는 깊은 복사가 되어야 하고요. 객체 Obj는 int 필드 하나만 주어져 있습니다. n = 4라고 해 보겠습니다.

 

 

 

 당연하게도, setter가 있기 때문에, 불변 객체가 아닙니다. 어떻게 하면 될까요? 로직부터 설계해 봅시다. 일단, 이전에 배웠던 range는 1부터 n까지의 순서를 가지는 stream을 생성합니다. 고로, IntStream.range(1, 5)를 먼저 수행합니다.

 

 

 

 

 다음에, 해당 stream을 토대로 Obj로 이루어진 스트림을 새로 생성합니다. mapToObj로요.

 

 

 

 그러면, 1부터 5까지 순서로 이루어진 Stream이 new Obj(3)으로 이루어진 4개의 객체로 이루어 집니다. 참고로 각 원소별로 apply가 별개로 동작하므로 매번 새로운 객체가 생기게 됩니다.

 

 

 

 toList로 Stream을 List로 변환합니다.

 

 

 

 최종 코드는 20 ~ 21번째 줄에 나와 있습니다. 23번째 줄에 list의 0번째 원소의 x 값을 2로 바꾸어 봅시다. 만약에 저 객체들이 모두 얕은 복사가 되었다면, 모두 2가 출력되었을 겁니다.

 

 

 

 그런데 3이 출력되었습니다. 이유는 toList가 호출될 때 mapToObj에 있는 apply인 x -> new Obj(3)가 실행되기 때문입니다. 각각의 element에 대해 적용되기 때문에 별개의 새로운 오브젝트 Obj가 생성됩니다.

출처: https://codingdog.tistory.com/entry/java-stream-maptoobj-함수에-대해-알아봅시다 [강아지의 코딩공부:티스토리]

'Java' 카테고리의 다른 글

[Spring Batch] multiple nodes with MySQL 8.0.33  (0) 2024.05.03
자바 어플리케이션 inputstream 주의사항  (1) 2024.04.18
728x90

Improve replication friendliness of the MySQL schema

 

Status quo

The table BATCH_JOB_EXECUTION_PARAMS currently has no primary key.

For a single node MySQL deployment, this does not cause any issue. For a setup of multiple nodes with replication, this is however not ideal as it causes performance problems with row-based replication and leads to ambiguity when calculating check sums over tables to validate the integrity of the replication.

To prevent such issues, MySQL 8 offers the system variable sql_require_primary_key. If set to ON, it is currently not possible to deploy the Spring Batch schema.

Suggestion

As job parameters are handled by Spring Batch internally as maps, it already works with the assumption that job parameter keys are unique for each job execution. This allows to declare a primary key on BATCH_JOB_EXECUTION_PARAMS consisting of JOB_EXECUTION_ID and KEY_NAME.

It would be nice if this (or any other) primary key were defined on BATCH_JOB_EXECUTION_PARAMS in the MySQL schema.

The tables BATCH_JOB_SEQ, BATCH_JOB_EXECUTION_SEQ, BATCH_STEP_EXECUTION_SEQ also do not have a primary key. As they contain only a single row, respectively, this is not a problem in practice. But if the column UNIQUE_KEY were to be declared as primary key, this would allow a seamless deployment to a MySQL instance with sql_require_primary_key=ON.

 

 


BATCH_JOB_EXECUTION_PARAMS

CREATE TABLE BATCH_JOB_EXECUTION_PARAMS  (
	JOB_EXECUTION_ID BIGINT NOT NULL ,
	TYPE_CD VARCHAR(6) NOT NULL ,
	KEY_NAME VARCHAR(100) NOT NULL ,
	STRING_VAL VARCHAR(250) ,
	DATE_VAL DATETIME(6) DEFAULT NULL ,
	LONG_VAL BIGINT ,
	DOUBLE_VAL DOUBLE PRECISION ,
	IDENTIFYING CHAR(1) NOT NULL ,
    PRIMARY KEY (JOB_EXECUTION_ID, KEY_NAME)
	constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID)
	references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ENGINE=InnoDB;

 

Other meta tables

CREATE TABLE BATCH_STEP_EXECUTION_SEQ (
	ID BIGINT NOT NULL,
	UNIQUE_KEY CHAR(1) NOT NULL,
    PRIMARY KEY(UNIQUE_KEY),
	constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;

INSERT INTO BATCH_STEP_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_STEP_EXECUTION_SEQ);

CREATE TABLE BATCH_JOB_EXECUTION_SEQ (
	ID BIGINT NOT NULL,
	UNIQUE_KEY CHAR(1) NOT NULL,
    PRIMARY KEY(UNIQUE_KEY),
	constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;

INSERT INTO BATCH_JOB_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_EXECUTION_SEQ);

CREATE TABLE BATCH_JOB_SEQ (
	ID BIGINT NOT NULL,
	UNIQUE_KEY CHAR(1) NOT NULL,
    PRIMARY KEY(UNIQUE_KEY),
	constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;

INSERT INTO BATCH_JOB_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_SEQ);

 

[important]

Do not create generated key in PARAMS table like below;

https://docs.spring.io/spring-batch/reference/schema-appendix.html

 

Meta-Data Schema :: Spring Batch

The Spring Batch Metadata tables closely match the domain objects that represent them in Java. For example, JobInstance, JobExecution, JobParameters, and StepExecution map to BATCH_JOB_INSTANCE, BATCH_JOB_EXECUTION, BATCH_JOB_EXECUTION_PARAMS, and BATCH_ST

docs.spring.io

 

'Java' 카테고리의 다른 글

[method] java stream mapToObj  (0) 2024.05.07
자바 어플리케이션 inputstream 주의사항  (1) 2024.04.18
728x90
Spring Batch 에서는 meta table이라고 해서 Batch 의 작업을 도와주는 테이블이 있는데...

 

 

 

 

여하튼 서론은 막론하고 MySQL 8.0 버전을 올리면서 이후부터는 System Variable 로 sql_require_primary_key 가 true 가 걸려있게 되는 경우 application.yml 에서 설정을 해도 작동을 하지 않는다 이말이다

 

전통적으로는 spring.batch.jdbc.

initialize-schema: always

 

이렇게 때려박아 주고 있는데, 여하튼 이거랑 별도로 환경변수에서 저렇게 잡아놓으면 데이터베이스가 생성하고 싶어도 생성할 수가 없다.

 

 

그렇다면 Spring Batch 공식문서로 가보자


The BATCH_JOB_EXECUTION_PARAMS Table

The BATCH_JOB_EXECUTION_PARAMS table holds all information relevant to the JobParameters object. It contains 0 or more key/value pairs passed to a Job and serves as a record of the parameters with which a job was run. For each parameter that contributes to the generation of a job’s identity, the IDENTIFYING flag is set to true. Note that the table has been denormalized. Rather than creating a separate table for each type, there is one table with a column indicating the type, as the following listing shows:

CREATE TABLE BATCH_JOB_EXECUTION_PARAMS  (
	JOB_EXECUTION_ID BIGINT NOT NULL ,
	PARAMETER_NAME VARCHAR(100) NOT NULL ,
	PARAMETER_TYPE VARCHAR(100) NOT NULL ,
	PARAMETER_VALUE VARCHAR(2500) ,
	IDENTIFYING CHAR(1) NOT NULL ,
	constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID)
	references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
);
 

The following list describes each column:

  • JOB_EXECUTION_ID: Foreign key from the BATCH_JOB_EXECUTION table that indicates the job execution to which the parameter entry belongs. Note that multiple rows (that is, key/value pairs) may exist for each execution.
  • PARAMETER_NAME: The parameter name.
  • PARAMETER_TYPE: The fully qualified name of the type of the parameter.
  • PARAMETER_VALUE: Parameter value
  • IDENTIFYING: Flag indicating whether the parameter contributed to the identity of the related JobInstance.

Note that there is no primary key for this table. This is because the framework has no use for one and, thus, does not require it. If need be, you can add a primary key with a database generated key without causing any issues to the framework itself.


 

간단하게 요약하자면 원래 이 테이블에는 Primary Key 따위는 없는 것인데, 네놈이 꼴린다면 generated Key 를 생성해서 그 키를 Primary Key로 쓸 수 있다 이말씀이다.

 

참고로 MySQL에서 SEQUENCE 를 생성해서 사용하려고 해도 PRIMARY KEY 때문에 작동하지 않을 것이므로 

 

여하튼 데이터베이스를 알게된 이래로 generated key 라는 놈은 처음 들어봣는데

 

 

내용을 잘 살펴보아하자니 프로세스가 진행될 때마다 증가하면서 자동으로 생성되는 놈이라하니 꼭 AI 를 박아놓은 친구처럼 생겼다.

 

그래서 만들어보았다 generated key

CREATE TABLE BATCH_JOB_EXECUTION_PARAMS (
    PARAM_ID BIGINT AUTO_INCREMENT PRIMARY KEY,
    JOB_EXECUTION_ID BIGINT NOT NULL,
    TYPE_CD VARCHAR(6) NOT NULL,
    KEY_NAME VARCHAR(100) NOT NULL,
    STRING_VAL VARCHAR(250),
    DATE_VAL DATETIME(6) DEFAULT NULL,
    LONG_VAL BIGINT,
    DOUBLE_VAL DOUBLE PRECISION,
    IDENTIFYING CHAR(1) NOT NULL,
    CONSTRAINT JOB_EXEC_PARAMS_FK FOREIGN KEY (JOB_EXECUTION_ID)
        REFERENCES BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ENGINE=InnoDB;

INSERT INTO BATCH_STEP_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_STEP_EXECUTION_SEQ);
CREATE TABLE BATCH_STEP_EXECUTION_SEQ (
                                        SEQ_ID  BIGINT  AUTO_INCREMENT  PRIMARY KEY ,
                                          ID BIGINT NOT NULL,
                                          UNIQUE_KEY CHAR(1) NOT NULL,
                                          constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;


CREATE TABLE BATCH_JOB_EXECUTION_SEQ (
                                         SEQ_ID  BIGINT  AUTO_INCREMENT  PRIMARY KEY ,
                                         ID BIGINT NOT NULL,
                                         UNIQUE_KEY CHAR(1) NOT NULL,
                                         constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;

INSERT INTO BATCH_JOB_EXECUTION_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_EXECUTION_SEQ);

CREATE TABLE BATCH_JOB_SEQ (
                               SEQ_ID  BIGINT  AUTO_INCREMENT  PRIMARY KEY ,
                               ID BIGINT NOT NULL,
                               UNIQUE_KEY CHAR(1) NOT NULL,
                               constraint UNIQUE_KEY_UN unique (UNIQUE_KEY)
) ENGINE=InnoDB;

INSERT INTO BATCH_JOB_SEQ (ID, UNIQUE_KEY) select * from (select 0 as ID, '0' as UNIQUE_KEY) as tmp where not exists(select * from BATCH_JOB_SEQ);

 

이러면 쌈뽕하게 작동한다

728x90

첫번째 미션으로 유저 객체를 생성할 때 Builder 패턴을 이용해야 했다.

프로그래머스 스터디 뿐만 아니라 이전에 참고했던 여러 서적들(책 ‘처음 배우는 스프링 부트2’와 ‘스프링 부트와 AWS로 혼자 구현하는 웹서비스’)에서도 빌더 패턴을 사용해서 구현한다. 무엇보다도 찾아봤던 블로그, 책마다 빌더 패턴을 구현하는 모습이 서로 조금씩 달라서 빌더패턴을 코드로 이렇게 작성하는게 맞는건지 너무 헷갈렸다.

왜 빌더 패턴을 사용해야 하는 걸까? 빌더 패턴과 생성자의 차이는 무엇이고 어떤 상황에서 사용해야 하는 걸까? 코드로는 어떻게 구현할까? 이에 대해 한번 정리해 봐야겠다.


먼저 생성자란,

인스턴스가 생성될 때 호출되는 인스턴스 초기화 메소드이다. 연산자 new가 인스턴스를 생성하는 것이지 생성자가 인스턴스를 생성하는 것은 아니다.

User Sara = new User();

 

빌더패턴이란

생성자처럼 인스턴스를 초기화하고 new 대신 build() 메소드를 이용해 객체를 생성하는 방법이다. 기존 생성자의 문제점을 보완해서 객체 생성을 더 깔끔하고 유연하게 할 수 있다.

 

빌더 패턴은 왜 / 언제 사용해야할까?

일반 생성자 이용시, 인자 수가 늘어나면 코드 작성이 어려워지고 읽기 어려운 코드가 되고 만다

NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 3, 35, 27);

호출 코드만 보고서는 저 많은 인자가 도대체 무슨 값인지 알 수가 없고 특히 자료형이 같은 인자들이 많을 경우 컴파일 단계에서 걸러지지 않기 때문에 런타임시 문제 발생하기가 딱 좋다 - 실수하기 좋은 구조.

대안책으로 setter메소드를 이용한 자바빈 패턴이 있다.

// cocaCola을 영양 성분 정보를 setter메소드를 통해 초기화하는 자바빈 패턴
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohdydrate(27);

가독성은 좋아졌지만, 1회의 함수 호출로 객체 생성을 끝낼 수 없고 setter메서드로 인해 Immutable한 클래스를 만들 수가 없다. Setter 메소드 사용은 지양해야하며 변경 가능성은 최소화해야한다.

따라서 가독성도 좋고 immutable 객체로도 만들 수 있는 Builder 패턴을 사용하게 된다.

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
                                            .calories(100)
                                            .sodium(35)
                                            .carbohydrate(27)
                                            .build();

각 인자가 어떤 값을 의미하는지 알기가 쉽고 setter메소드가 없으므로 변경 불가능한 객체도 만들 수 있다. 한번에 객체를 생성하므로 객체 일관성이 깨지지 않으며, build() 함수로 잘못된 값이 입력되었는지 검증하게 할 수도 있다.

Builder패턴은

  • 생성자의 인자수가 많을 때 사용하며
  • 불변성과 네임드 파라미터(Named Parameter, 매개변수 이름을 통해서 인자 값을 전달)를 가져갈 수 있어
  • 안전하고 가독성이 좋다.

 

빌더패턴을 코드로 작성해보자

public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        // Required parameters(필수 인자)
        private final int servingSize;
        private final int servings;

        // Optional parameters - initialized to default values(선택적 인자는 기본값으로 초기화)
        private int calories      = 0;
        private int fat           = 0;
        private int carbohydrate  = 0;
        private int sodium        = 0;

        public Builder() {
        }

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings    = servings;
        }

        public Builder calories(int val) {
            calories = val;
            return this;    // 이렇게 하면 . 으로 체인을 이어갈 수 있다.
        }
        public Builder fat(int val) {
            fat = val;
            return this;
        }
        public Builder carbohydrate(int val) {
            carbohydrate = val;
            return this;
        }
        public Builder sodium(int val) {
            sodium = val;
            return this;
        }
        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servingSize  = builder.servingSize;
        servings     = builder.servings;
        calories     = builder.calories;
        fat          = builder.fat;
        sodium       = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

위와 같이 하면 다음처럼 객체를 생성해 사용하면 된다.

NutritionFacts cocaCola = new NutritionFacts
    .Builder(240, 8)    // 필수값 입력
    .calories(100)
    .sodium(35)
    .carbohydrate(27)
    .build();           // build() 가 객체를 생성해 돌려준다.

이렇게 정리해보니 궁금함이 생겼다. 생성자의 단점을 보완해서 나온게 빌더패턴이라면 생성자말고 빌더패턴만 사용하는게 좋지 않나? 그런데 왜 생성자와 빌더패턴을 섞어서 사용할까? => 스터디 리더분께 물어보니 명쾌하게 의견을 주셨다. 정답은 없다고. 생성자의 매개변수가 많아지면 네임드 파라미터의 빌더 패턴 사용을 고려해보는 것처럼 그 상황과 용도에 맞게 더 fit한 생성자를 이용하는게 더 좋다고. 결국 프로그래밍에 있어서 각 방법의 장단점을 확실하게 인지하고 상황에 맞는 방법을 유연하게 적용할 수 있는 능력이 정말 중요한 것 같다.

references

Effective Java 2/E
자바의 정석
https://johngrib.github.io/wiki/builder-pattern/
https://softwareengineering.stackexchange.com/questions/380397/why-do-we-need-a-builder-class-when-implementing-a-builder-pattern

 

Why do we need a Builder class when implementing a Builder pattern?

I have seen many implementations of the Builder pattern (mainly in Java). All of them have an entity class (let's say a Person class), and a builder class PersonBuilder. The builder "stacks" a vari...

softwareengineering.stackexchange.com

 

 

https://yjna2316.github.io/study/2020/11/06/%EC%83%9D%EC%84%B1%EC%9E%90%EC%99%80-%EB%B9%8C%EB%8D%94%ED%8C%A8%ED%84%B4/

 

 

+ Recent posts