기록은 기억의 연장선

더 많은 것을 기억하기 위해 기록합니다


  • Home

  • Tags

  • Categories

  • Archives

  • Search

[java] java list array 변환

Posted on 2019-03-09 | Edited on 2020-11-02 | In java | Comments: 0 Comments

list -> array

1
list.toArray(new String[0]);

toArray의 인자로 변환하고 싶은 array 타입의 변수를 성생해주면 된다.
java 1.6 이전에는 인자로 생성하고 싶은 array 개수만큼 사이즈를 주는게 좋았으나,
1.6 이후로는 0으로 주나 new String[list.size()] 하나 동일하다고 한다

stream -> array

1
Stream.toArray(String[]::new)
Read more »

intellij 기능 간단 정리

Posted on 2019-03-08 | Edited on 2020-11-02 | In etc | Comments: 0 Comments

maven, gradle
group id = 그룹 아이디, 예를 들면 스프링
artifact id = 모듈 이름, 예를 들면 스프링 시큐리티, 스프링 MVC

액션 검색 : meta + shift + a
새로 만들기 : meta + n
현재포커스 실행 : ctrl + shift + r
이전포커스 실행 : ctrl + r

라이브 템플릿
메인메서드 : psvm
System.out.println : sout

라인 복제하기 : meta + d
라인 삭제하기 : meta + delete
문자열 라인 합치기 : ctrl + shift + j
라인 단위로 옮기기 :

  • 문법 상관하면서 : meta + shift + up/down
  • 문법 상관없이 : option + shift + up/down
    element 단위로 옮기기(html, xml 속성, 메서드 매개변수 순서 등등) : meta + option + shift + left/right

파라미터 즉시보기 : meta + p
코드 구현부 즉시보기 : option + space
docs 보기 : f1

단어별 이동 : alt + 좌우(선택 : +shift)
라인 첫/끝 : fn + 좌우(선택 : +shift)
page up/down : fn + 위아래

포커스 범위(선택) 한 단계씩 늘리기 : alt + 위아래
포커스 앞/뒤 : meta + [/]
멀티포커스 : alt + alt + 위/아래(리눅스는 ctrl)
오류라인 자동 포커스 : f2

리팩토링

  • 변수추출
    똑같은 값들을 하나의 변수로 추출하는 과정
    추출하고자 하는 값을 선택한 뒤, command + option + v

  • 파라미터 추출
    command + option + p
    변수가 아니라 파라미터로 추출된다
    extract via overloading method를 사용하면 추출된 메서드를

  • 메서드 추출
    추출하고 싶은 만큼 코드를 선택한 다음 command + option + m

  • 이너클래스 추출
    이너클래스가 여러군데서 사용될 떄 외부클래스로 추출할 수 있다
    f6 번을 누르면 어떻게 이동시킬지 선택할 수 있는 부분이 나오고, 여기서 이동할 패키지를 지정해주면 깔끔하게 클래스가 이동된다

  • 이름 일괄 변경
    shift + f6
    변수 이름 외에, 메서드 이름, 클래스 이름 모두 적용 가능

  • 타입 일괄변경
    파라미터 리턴 타입에 마우스대고 cmd + shift + 6
    반환하는 값에 대해서는 자동 변환시키거나 직접 설정가능

  • 사용하지 않는 import 제거
    ctrl + option + o
    command shitf a + optimize import 부분을 off -> on으로 바꾸면 자동으로 사용하지 않는 import를 정리해준다
    파일을 열떄마다 자동으로 사라지게 해준다
    이 기능을 사용하면 import문을 * 으로 변경하는 경우가 많은데,
    action -> import with * 의 개수를 999로 설정하면 해결된다

  • 정렬되지 않은 코드 정렬
    command + option + l

Read more »

메서드 내용 직접 삽입

Posted on 2019-03-08 | Edited on 2020-11-02 | In refactoring | Comments: 0 Comments

메서드 기능이 너무 단순해서 메서드명만 봐도 너무 뻔할 땐,
그 메서드의 기능을 호출하는 메서드에 넣어버리고 그 메서드는 삭제하자

특징

  • 메서드명에 모든 기능이 반영될 정도로 메서드 기능이 지나치게 단순하면, 그 메서드는 없애야한다
    • 단순 위임 기능을 하는 메서드들 때문에 코드가 복잡해질 수 있다
    • 리팩토링의 핵심은 한눈에 파악할 수 있는 직관적인 메서드를 만드는 것과 메서드를 간결하게 만드는 것이긴 하지만!
  • 잘못 쪼개진 메서드에도 적용할 수 있다
    • 잘못 쪼개진 메서드의 내용을 다시 큰 메서드에 직접 삽입한 후, 다시 작은 메서드로 추출한다

방법

  1. 메서드가 재정의 되어있지 않는지 확인하자
    • 당연한 소리지만 하위클래스에서 재정의하고 있는 메서드를 삭제하면 안된다
  2. 그 메서드를 호출하는 부분을 모두 찾는다
  3. 각 호출 부분을 메서드 내용으로 교체한다
  4. 테스트를 실시하고, 메서드 정의를 삭제한다

참고 : 마틴 파울러, 『리팩토링』, 김지원 옮김, 한빛미디어(2012)

Read more »

메서드 추출

Posted on 2019-03-06 | Edited on 2020-11-02 | In refactoring | Comments: 0 Comments

어떤 코드를 그룹으로 묶어도 되겠다고 판단되면,
그 코드를 뺴내어 목적을 잘 나타내는 직관적 이름의 메서드로 만든다

특징

  • 매우 자주 사용됨
  • 메서드가 너무 길거나 코드에 주석을 달아야만 의도를 이해할 수 있을때 사용
  • 메서드가 적절히 잘게 쪼개져있으면 다른 메서드에서 사용하기 수월함
  • 효과를 보려면 메서드의 이름도 잘 지어야 한다
    • 그리고 쪼개는 것도 잘 해야겠지… 명확하게
  • 메서드명과 메서드 내용의 의미적 차이가 중요하다
    • 명료하기만 하면 메서드 길이가 메서드명보다 짧아도된다

방법

  1. 목적에 부합하는 새 이름의 메서드를 생성한다
    • 메서드명은 원리가 아니라 기능을 나타내야 한다.
  2. 기존 메서드에서 빼낸 코드를 새로 생성한 메서드로 복사한다
  3. 뺴낸 코드에서 기존 메서드의 모든 지역변수 참조를 찾는다
    • 새로 생성한 메서드의 지역변수나 매개변수로 활용할 것이다
  4. 추출한 메서드에서 사용되는 임시변수에 대해 처리한다
  5. 원본 메서드에서 뺴낸 코드 부분을 새로 생성한 메서드 호출로 수정한다

지역변수 처리

  • 추출한 메서드에서만 사용되는 값일 경우

    • 해당 메서드로 임시변수를 분리
  • 원본 메서드, 추출한 메서드 양쪽에서 다 사용되는 지역변수인데 추출한 메서드에서는 읽히기만 할 경우

    • 추출한 메서드에 매개변수로 전달
  • 원본 메서드, 추출한 메서드 양쪽에서 다 사용되는 지역변수이고 추출한 메서드에서 값이 변경될 경우

    • 변경되는 지역변수가 1개일 경우 리턴해주는 형식으로 작성 가능

      1
      2
      3
      4
      5
      int a = 0;

      // do something to `a`

      print(a);

      는 아래처럼 변경 가능

      1
      2
      int a = doSomething(0); // 필요한 값을 매개변수로 전달해줄 수 있음  
      print(a);
    • 2개 이상의 변수가 변경되는 경우, 메서드가 하나의 값을 반환하도록 더욱 분리해주는 것이 좋다.

      가능하면 하나의 값만 반환하는 것이 좋다고 한다.

  • 임시변수가 너무 많으면 임시 변수를 메서드 호출로 전환 같은 것을 사용해서 임시변수의 수를 줄이는 것이 좋다.

참고 : 마틴 파울러, 『리팩토링』, 김지원 옮김, 한빛미디어(2012)

Read more »

코드의 구린내와 해결법

Posted on 2019-03-06 | Edited on 2020-11-02 | In refactoring | Comments: 0 Comments

리팩토링의 기본

  • 한 클래스에서 다른 클래스로 상태와 기능을 옮기는 것은 리팩토링의 기본이다
  • 지금은 합리적이라고 판단되는 설계도 나중에는 그렇지 않을 수 있다
    • 문제는 이게 아니라, 이 상황에도 아무런 대처도 하지 않는 것이다

중복 코드(Duplicated Code)

최대의 구린내는 누가 뭐라해도 중복 코드이다
코드가 중복되면 한쪽만 수정하고, 다른 한쪽은 수정하지 않는 위험천만한 실수를 저지르기 쉽다

  • 한 클래스의 두 메서드안에 같은 코드가 들어있는 경우

    메서드 추출을 적용해서 겹치는 코드를 별도의 메서드로 분리하고, 그 메서드를 두 곳에서 호출하면 된다

  • 한 클래스의 두 하위클래스에 같은 코드가 들어있는 경우

    메서드 추출을 적용해서 중복을 없앤 후, 메서드 상향을 적용하면 된다.

    • 두 메서드의 코드가 똑같지 않고 비슷하다면?

      메서드 추출을 적용해서 같은 부분과 다른 부분을 분리해야 한다
      경우에 따라 템플릿 메서드 형성을 적용해야 할 수도 있다

    • 두 메서드의 기능이 같은데 알고리즘만 다르다면?

      두 알고리즘 중 더 간단한 것을 택해서 알고리즘 전환을 적용하면 된다

  • 중복코드가 메서드 가운데에 있는 경우

    주변 메서드 추출을 적용한다

  • 서로 상관없는 두 클래스안에 중복 코드가 있을 경우

    중복코드를 클래스 추출이나 모듈 추출을 적용해서 클래스나 모듈로 떼어낸 후, 그것을 호출한다
    두 클래스 중 한쪽에서 다른쪽을 호출할수도 있고, 제 3의 클래스로 추출하고 양쪽에서 호출할수도 있다

장황한 메서드

메서드가 길수록 이해하기 어렵기 때문에 메서드들은 작은 단위로 쪼개주는 것이 좋다
(최적의 상태로 장수하는 객체 프로그램들을 보면 공통적으로 메서드 길이가 짧다)

하지만 하나의 덩어리가 여러개로 나뉘어졌기 때문에, 읽으려면 화면간 전환이 생기기 되고, 이로 인해 사람의 머릿속에 오버헤드가 생기게 된다
그러므로 메서드의 기능을 한눈에 알 수 있는 메서드명을 사용하여 그 메서드 안의 코드를 분석하지 않아도 되게끔 해야한다

  • 메서드명은 기능 수행 방식이 아니라 목적(기능 그 자체)를 나타내는 이름으로 정해야한다

  • 메서드 호출이 원래 코드보다 길어지는 한이 있더라도, 메서드명은 그 코드의 의도를 잘 반영하는 것으로 정해야 한다

  • 메서드의 크기를 줄이려면 십중팔구는 메서드 추출 기법을 적용해야 한다

    • 메서드에서 하나로 묶으면 좋을 만한 부분들을 찾아서 메서드로 만든다
    • 주석을 참고하는 것도 방법이다
      • 주석으로 처리된 코드 구간을 메서드로 만든다
      • 주석에 설명된 기능을 참고해서 메서드명을 짓는다
  • 메서드에 매개변수와 임시변수가 많을 경우

    • 임시 변수를 메서드 호출로 전환이나 임시변수를 메서드 체인으로 전환을 사용하면 대부분의 임시변수는 제거된다
    • 길게 열거된 매개변수는 매개변수 세트를 객체로 전환과 객체를 통째로 전달을 적용하면 간결해진다
  • 위의 기법을 적용했음에도 불구하고 여전히 임시변수가 너무 많을 때는 메서드를 메서드 객체로 전환을 적용한다

  • 조건문과 루프도 메서드로 빼야 한다

    • 조건문을 추출하려면 조건문 쪼개기를 적용한다
    • 루프를 추출하려면 루프를 컬렉션 클로저 메서드로 전환을 적용한 후, 그 클로저 메서드 호출과 클로저 자체에 메서드 추출을 적용하면 된다

참고 : 마틴 파울러, 『리팩토링』, 김지원 옮김, 한빛미디어(2012)

Read more »

[java] java8 날짜/시간 api

Posted on 2019-03-06 | Edited on 2020-11-02 | In java | Comments: 0 Comments

UTC : https://ko.wikipedia.org/wiki/협정_세계시
ISO 8601, 표기법 : https://ohgyun.com/416

기존 자바 시간 API의 문제

java의 기존 Date 에 문제가 많았음

  • 불변 객체가 아니다
  • 상수를 남용한다(e.g. 월 파라미터에 1~12가 아닌 값이 들어가도 문제없음. 상수이기 때문이다)
  • 월이 상수 0부터 시작한다

그래서 Calendar가 나왔는데, 여전히 문제가 있음

  • 대부분의 날짜 유틸이 Date 위주임
  • Date로 변하기 위한 중간객체 수준밖에 안됨(생성 비용도 비쌈)
  • 0 부터 시작하는 월 은 변경되지 않음

좋은 API는 오용하기 어려워야 하고, 문서가 없어도 쉽게 사용할 수 있어야 한다
그러나 Java의 기본 API는 문서를 열심히 보기 전까지는 제대로 사용하기 어렵다

그래서 jodaTime을 대부분 많이 사용했는데, 자바 8 부터 jodaTime의 유용한 기능들을 java.time 패키지에 넣어서 새로 배포함

이와 관련된 자세한 내용은 아래의 글에 나와있다
https://d2.naver.com/helloworld/645609

java 8 시간 API

상세하게 설명되어 있는 블로그
https://perfectacle.github.io/2018/09/26/java8-date-time/

LocalDate, LocalTime, LocalDateTime

Timezone을 가지지 않는 시간

여기서 불변객체라 함은 setter 등으로 변경할 수 없는 객체를 말한다
LocalDate는 날짜를 표현하는 불변객체
LocalTime은 시간을 표현하는 불변객체
LocalDateTime은 둘을 합쳐서 표현

of static method로 연,월,일,시간 등을 받아서 생성할 수 있음
now 메서드로 현재 시간 생성 가능
parse로 문자열을 그대로 받아 사용 가능. 여기에는 DateTimeFormatter 인스턴스를 전달할 수도 있음

ZonedDateTime

http://www.daleseo.com/java8-zoned-date-time/
LocalDateTime에 타임존이나 시차가 추가되었다고 보면 된다
ZonedId나 ZonedOffset 값을 주면 타임존이나 시차를 적용할 수 있고, 값을 주지 않을 경우 로컬의 기본 타임존 값을 사용한다.

ZonedId 이 부모 클래스, ZonedOffset, ZonedRegion 이 하위 클래스임
시차를 쓰는 곳은 많지않고, ZonedOffset 같은 경우 Summer time 등을 처리하지 못하므로 ZonedId를 쓰는 것이 좋다
ZoneId seoulZone = ZoneId.of("Seould/Asia")

LocalDate, LocalDateTime, Instant를 ZonedDateTime으로 변환할 수 있다

1
2
3
4
5
6
7
8
LocalDate localDate = LocalDate.now();
ZonedDateTime zd1 = localDate.atStartOfDay(seoulZone);

LocalDateTime localDateTime = LocalDateTime.now();
ZonedDateTime zd2 = localDateTime.atZone(seoulZone);

Instant instant = Instant.now();
ZonedDateTime zd3 = instant.atZone(seoulZone);

이것보다는 OffsetDateTime이 더 선호된다고 함

Instant

컴퓨터가 알아보기 쉽게 표현하기 위한 형태이다
유닉스 에포크 시간(1970년 1월 1일 0시 0분 0초 UTC)를 기준으로 특정 지점까지를 초로 표현한 것이다.
나노초(10억분의 1)까지 표현 가능하다

ZonedId를 이용해서 LocalDateTime을 Instant로 바꿀 수 있다

1
Instant instant = LocalDateTime.now().toInstant(seoulZone);

Period, Duration

instant 자르기

https://stackoverflow.com/questions/27813691/how-to-compare-two-instant-based-on-the-date-not-time

Read more »

[spring] Spring Batch

Posted on 2019-02-23 | Edited on 2020-11-02 | In spring | Comments: 0 Comments

스프링 배치 설명

https://jojoldu.tistory.com/324?category=635883
https://jojoldu.tistory.com/325?category=635883
https://jojoldu.tistory.com/326?category=635883

배치서비스의 특징

  • 대용량 데이터
    • 배치 어플리케이션은 대량의 데이터를 가져오거나, 전달하거나, 계산하는 등의 처리를 할 수 ​​있어야 합니다.
  • 자동화
    • 배치 어플리케이션은 심각한 문제 해결을 제외하고는 사용자 개입 없이 실행되어야 합니다.
  • 견고성
    • 배치 어플리케이션은 잘못된 데이터를 충돌/중단 없이 처리할 수 있어야 합니다.
  • 신뢰성
    • 배치 어플리케이션은 무엇이 잘못되었는지를 추적할 수 있어야 합니다. (로깅, 알림)
  • 성능
    • 배치 어플리케이션은 지정한 시간 안에 처리를 완료하거나 동시에 실행되는 다른 어플리케이션을 방해하지 않도록 수행되어야합니다.

Spring Batch는 Acceuture의 배치 프레임워크를 추상화 한것이다.
Spring의 3대 요소인 DI, AOP, 서비스 추상화를 다 사용가능하다.

Spring Quartz는 스케줄러이고, Spring Batch는 대용량 처리 배치이다.
둘은 완전히 다르다.
보통 Quartz + Batch를 조합해서 사용한다. Quartz가 Batch를 실행시키는 구조이다.

  • Spring Batch 를 사용하고 싶으면 스프링 부트 어플리케이션 루트에 @EnableBatchProcessing 어노테이션을 써줘야함

  • Job은 하나의 배치 작업 단위를 뜻함

  • JOB은 @Configuration에 등록하고, 서비스가 뜨면 실행된다

    1
    2
    3
    4
    5
    6
    7
    @Bean
    public Job simpleJob(){
    return jobBuilderFactory.get("simple-job")
    .start(simpleStep1(null))
    .next(simpleStep2(null))
    .build();
    }
  • 등록한 Batch Job들을 관리하려면 Spring Batch에서 정의한 메타 테이블들을 사용해야 한다

    • 배치와 관련된 전반적인 데이터를 관리한다
    • spring batch와 같이 다운되는 schema-XXX.sql 파일에 스키마가 들어있다
  • Job 안에 여러 Step이 존재함

    • Step이 실제 배치작업(비즈니스 로직)이 들어있는 곳이다
    • 말 그대로 진짜 스텝이다. job이 실행해야 할 step을 작성하는 것이다.
      • job 을 큰 작업, step 을 세부 작업이라고 생각하면 안된다
      • ‘job은 일괄 등록 요청, 요청 개수 만큼 step 생성’ 은 잘못된 생각이다
  • Step 안에 Tasklet 또는 Reader & Processor & Writer 묶음이 존재함

    • 진짜 비즈니스 로직을 작성하는 곳
    • 둘은 같은 레벨이므로 하나만 실행가능
    • Reader & Processing 후에 Tasklet 불가능
  • Job, Step, Tasklet(ItemReader, ItemWriter 등) 은 전부 스프링 빈이다

지정한 JOB만 실행되도록 하는 방법
application.yml에 spring.batch.job.names: ${job.name:NONE} 를 추가하고,
외부 파라미터로 넘어오는 job.name의 값에 맞춰 배치를 실행한다. 전달되지 않으면 NONE에 의해 아무 배치도 실행하지 않는다.
parameter에 --job.name=stepNextJob 의 형태로 입력하면 된다

Spring Batch Metadata Table

JOB은 파라미터에 따라 BATCH_JOB_INSTACNE에 저장됨
JOB들의 실행을 관리하는 BATCH_JOB_EXECUTION
JOB들의 실제 로직들인 STEP이 저장된 BATCH_STEP_EXECUTION

  1. BATCH_JOB_INSTANCE
    • 등록한 JOB들에 대한 인스턴스들인데, 외부에서 전달한 파라미터에 따라 생성된다
    • 파라미터가 같으면 중복해서 생성되지 않으며, 파라미터가 다를 때만 생성된다
      • job이나 step에서 파라미터를 꼭 받아야 하는것은 아니다
    • 이 파라미터들은 BATCH_JON_PARAMETERS에 저장된다. 근데 왜 BATCH_JOB_INSTANCE와 관계를 가지지 않고 BATCH_JOB_EXECUTION과 관계를 가질까?
  2. BATCH_JOB_EXECUTION
    • JOB_INSTACNE의 실행 내역을 가짐(성공, 실패)
    • 그러므로 JOB_INSTANCE와 1:N 관계임
    • 똑같은 파라미터에 대해서는 2번 이상 실행하면 에러가 발생한다
      • 정확히 말해 COMPLETED가 이미 있으면 에러가 발생하는 것이다
      • FAILD가 있으면 다시 실행해도 에러가 발생하지 않는다
  3. BATCH_JOB_PARAMETERS
    • EXECUTION에 사용한 파라미터들을 저장
    • VALUE OBJECT COLLECTION Table 형태로 사용함

BatchStatus, ExitStatus
JOB_EXECUTION, STEP_EXECUTION 테이블을 보면 status, exit_code라는 것이 있다.
status는 job이나 step의 실행 결과를 기록할 때 사용하는 Enum이고,
exit_code는 job이나 step의 실행 후 상태를 나타내는 일반 string이다.
기본적으로 exit_code는 status와 같도록 설정되어 있으나, 커스텀한 exit_code가 있으면 추가할 수 있는 구조이다.

스프링 배치 사용

step flow 제어

Job 내에 Step 등록 시 호출 Flow를 제어 가능하다(순서, 조건별 분기 등등)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Bean
public Job stepNextConditionalJob() {
return jobBuilderFactory.get("stepNextConditionalJob")
.start(conditionalJobStep1())
.on("FAILED") // FAILED 일 경우
.to(conditionalJobStep3()) // step3으로 이동한다.
.on("*") // step3의 결과 관계 없이
.end() // step3으로 이동하면 Flow가 종료한다.
.from(conditionalJobStep1()) // step1로부터
.on("*") // FAILED 외에 모든 경우
.to(conditionalJobStep2()) // step2로 이동한다.
.next(conditionalJobStep3()) // step2가 정상 종료되면 step3으로 이동한다.
.on("*") // step3의 결과 관계 없이
.end() // step3으로 이동하면 Flow가 종료한다.
.end() // Job 종료
.build();
}
  • 처음 시작할떄는 무조건 start
  • ExitStatus를 catch하고 싶다면 on 사용하고, 뒤에 연결할 step에 to 사용
  • from은 이벤트 리스너. 인자로 받는 step의 이벤트를 listen함
    • 위의 상황에선 step1의 이벤트를 이미 캐치하고 있으므로, 추가로 이벤트를 캐치하려면 from을 사용해야했음
    • on + end 뒤에만 붙일 수 있는데, 왜 그런건지…
    • step을 종료시키기 위해서 붙여야하는 패턴인건지?
  • step에서 contribution.setExitStatus 를 세팅해줘야만 job에서 catch 가능
  • custom ExitStatus를 세팅하려면 StepExecutionListenerSupport 를 상속받은 클래스를 추가로 등록해야 한다

decide

step들의 flow 속에서 분기만 담당하는 애들
ExitStatus 세팅하는 부분을 분리할 수 있다

Scope, jobParameter

@JobScope, @StepScope 를 선언하면 빈 생성시점이 해당 scope가 실행되는 시점까지 지연된다
Scope를 이렇게 뒤로 미루면서 얻는 장점이 여러가지가 있다

  • 비즈니스 로직 처리단계에서 Job Parameter 할당받을 수 있음
  • 병렬처리 가능(step당 각자 tasklet을 가지므로)

jobParameter

@JobScope, @StepScope 에서 파라미터(외부/내부)를 받을 수 있다
step에 @JobScope를 선언하고 parameter를 매개변수로 받는 방법과,
tasklet, itemReader 등에 @StepScope를 선언하고 parameter를 매개변수로 받는 방법이 있다.

jobParameter가 필요한 곳에서 사용하도록 해야할듯
예를들어 step 단위에서 parameter가 필요하다면 @JobScope에서 파라미터를 받아야 할 것이고,
tasklet 단위에서 parameter가 필요하다면 @StepScope에서 파라미터를 받아야 할 것이다

jobParameter는 @JobScope(step), @StepScope(chunk) 빈을 생성할때만 사용할수있으며,
step과 chunk의 최상위에는 job이 있다.
즉, job을 생성할때 던진 파라미터를 이용해서 scope bean에서 job parameter로 사용하는 것이다(아마도)

이 scope 빈을 @XXXScope로 생성하지 않고 일반 싱글톤으로 생성하면 job Parameter를 찾을 수 없다
jobParameter는 job으로 부터 오는것이니까
job을 생성(실행)할 때 던진 parameter를 가지고 @JobScope, @StepScope 빈을 생성하며 parameter를 주는 것이다
그러므로 job 코드에는 parameter로 null이 들어가게 된다(… 아마도?)

1
2
3
4
5
JobParameters jobParameters = new JobParametersBuilder()
.addString("input.file.name", fileName)
.addLong("time", System.currentTimeMillis())
.toJobParameters();
jobLauncher.run(job, jobParameters);

jobLauncher.run을 따라가보면(SimpleJobLauncher 기준)
jobExecution이 있으면 오류가 발생하고, jobExecution이 없으면 jobExecution을 생성한다
그리고 해당 jobExecution은 async로 실행시킨다(아마도)

chunk

chunk 지향 처리란 itemReader, itemProcerssor, itemWriter로 이어지는 형태를 말함
tasklet은 내부에 모든 로직이 다 있다
reader에서 job을 읽어오고, process에서 처리하고 writer로 쓴다
reader, process는 1건씩 처리되고, chunkSize만큼 processing이 완료되면 writer로 한방에 쓴다?

chunk는 배치에서 한번에 트랜잭션으로 처리할 단위이다
pageSize와는 다르다. pageSize는 한번에 조회하는 단위이다.
pageSize와 chunkSize를 같게해야 성능상 좋다

reader

reader로 읽을 수 있는 데이터는 데이터베이스만이 아니라 입력 데이터, 파일, jms 등등 여러가지이다
cursor, paging


장점

  1. spring batch에 사용할 단위들을 bean으로 생성할 수 있고, scope가 Job, Step scope 단위라는 점
    그러므로 파라미터를 원하는 타이밍에 자유자재로 받을 수 있고, 병렬처리에도 좋다

  2. chunk

[tdd] 테알못 신입은 어떻게 테스트를 시작했을까? 리뷰

Posted on 2019-02-18 | Edited on 2020-11-02 | In tdd | Comments: 0 Comments

https://www.youtube.com/watch?v=1bTIMHsUeIk

방법에 대한 이야기

RED에서 시작할 수도 있고, GREEN 에서 시작할 수 있다.
여기서 RED는 버그가 있어서 RED인 것이고, TDD의 RED는 아직 코드가 없기 떄문에 RED이다

테스트 라스트 방식

이미 구현되어 있는 코드에 테스트를 붙이는 방식

테스트하기 쉬운 코드로 시작한다

  • 순수함수로 만들어져 있다
  • 외부 의존성이 전혀 없다
  • 인풋과 아웃풋이 명확한 단순한 기능들(유틸리티 같은)
  1. 프로덕션 코드는 아무것도 수정하지 않고, 함수에 제대로된 인풋에 제대로된 아웃풋이 나오는지 테스트하는 것을 먼저 만든다
  2. 리팩토링을 한다
  3. 테스트 코드는 수정하지 않고 프로덕션 코드를 수정한다

이제 테스트하기 어려운 코드를 리팩토링 한다

  • 중요도가 높은 비즈니스 로직
  • 버그가 발견된 부분
  • 결합이 낮고 논리는 복잡한 부분

여기서 쉽게 테스트할 수 있는 부분을 분리해서 테스트한다

  1. 테스트하기 어려운 코드에서 테스트 가능한 코드를 찾는다
  2. 이를 분리한다
  3. 테스트코드를 추가하고 GREEN이 되도록 만든다
  4. 리팩토링 한다
  5. 1~4를 계속 반복한다

예시

전달받은 정보를 validate 한 뒤
api를 요청하고, 요청이 끝나면 모달창을 닫는다
api 요청은 테스트하기 너무 어렵지만, 위의 validate는 테스트하기 쉽다
그 부분을 함수로 빼고 테스트를 만든다.
여기서 테스트가 실패하는데, 이는 외부 의존성이 남아있었기 떄문이다.
이를 파라미터로 받는 형태로 바꿔서 독립적인 함수로 변형시킬 수 있다
이제 스팩을 추가하고(나와야 하는 결과들) 해당 테스트가 성공하도록 하고,
리팩토링 하면 된다

TDD

Test LAST와 달리 이미 구현되어 있는 코드가 없다
신규 요구사항에 대한 개발을 말한다

  1. 요구사항에 맞춘 테스트를 먼저 작성한다
    • 이미 구현되어 있는 코드가 없으므로 무조건 RED 부터 시작한다
  2. 테스트에 맞춰 기능을 개발한다
  3. GREEN 이 뜨면 테스트는 손대지 않고 프로덕션 코드를 리팩토링한다
  4. 이를 반복한다

경험에 대한 이야기

좋은점

  • 불안감소

  • 스펙문서기능

    • 본인이 만든 함수의 세부사항을 다 기억하는 사람은 없다
    • 함수를 다시 읽는것보단 테스트를 읽는것이 훨씬 빠르다(스펙의 역할을 한다)
  • 디자인 개선 효과

    • 하나의 함수가 여러가지 역할을 수행하고 있는지 체크할 수 있다
  • 학습 동기부여

    • 디자인 개선을 하다보면 내가 얼마나 디자인을 못하게 되는지 알게 된다
    • 그래서 설계에 계속 관심을 가지게 된다
    • 이게 학습이다!
  • 개발 생산성 향상

    • 통합테스트는 손해보는 시간
    • 테스트 안해서 아낀 시간(테스트 작성 시간) < 테스트 안해서 나온 버그 고치는 시간
    • 비즈니스 로직의 허점을 미리 발견할 수 있다
      • 스펙에 대해서 많이 생각하게 되기 떄문이다
      • 코딩하기 전에 발견할수 있어서 아끼는 비용이 많다
  • 집중력 향상

    • TDD는 죽었다에 대한 켄트백의 말
      • 나는 내가 필요하게 될 거라고 알고 있는 기능을 추가하려는 성향이 있다.
      • 하지만 하나의 빨간 테스트를 녹색으로 만드는 것을 딱 충분한 만큼 구현하도록 도와준다.
      • 집중을 유지할 새로운 방법을 찾아야한다.
    • 실제로도 테스트(스펙)을 정해놓으면 내가 구현해야 할 기능에 집중할 수 있다

실수

테스트 자체가 목적이 되어서(커버리지를 높이자!) 테스트를 막 찍어내는 상황

  • 불필요한 테스트

    • 비즈니스와 관련된 버그를 낼 가능성이 낮거나 없음
    • 테스트를 유지함으로써 얻는 이익 < 테스트 유지와 관리에 드는 비용
    • 테스트가 단언하고 있는 내용이 사용자에게 중요한 가치를 주는것이 아닐 때
      • 이때 div가 몇개 나오게 되고, div 안에는 p가 몇개고… 같은 테스트
        • 이런것은 사용자의 관심이 아니다
        • 이런것까지 굳이 테스트 할 필요는 없다
  • 필요하지만 검증방식이 잘못된 테스트

    • DOM 구조에 의존한 테스트
      • div의 첫번째 자식에 이러이러한 테스트가 있다 등
    • 사용자에게 주는 가치가 낮고, 테스트를 유지하는 비용이 더 큰 경우
    • 특정 위치를 찝어서 검사하는 형태보단, 의존관계를 줄이는 방식으로 테스트를 개선한다(DOM구조 의존 제거)
  • 검증력이 떨어지는 테스트

    • 테스트가 원하는 결과가 나올수 있는 상황이 여러가지 일때
      • 우리가 원하지도 않는 형태에서 우리가 원하는 결과 발생
      • e.g. expect 값이 undefined
  • 테스트 제목과 검증의 불일치

    • e.g. 전체요소에 대해 테스트하는 척해놓고 엘리먼트 1번만 테스트 하기
  • 테스트를 앞서가는 프로덕션 코드

    • 프로덕션 코드에서 테스트에서 테스트하는 대상보다 이상의 행위를 할 때
    • 프로덕션에 테스트되지 않은 코드가 추가되는 것이 허용되는 것이다
    • 첫번째 요소에 대해 테스트했다면, 프로덕션에서는 첫번째 요소만 렌더링 했어야 했다

고민

  • 픽스쳐(테스트를 위해 만들어진 데이터) 생성을 어떻게 해야할까?
  • 함수만 계속해서 만들어지게 된다
    • 무조건 함수들을 분리 하는게 맞는건가?
    • 이게 가독성을 더 해치는건 아닌가?
      • 추상화 수준이 낮아서 그렇다
      • 높은 응집, 낮은 결합 을 충족해야 한다
        • 함수로 분리해서 낮은 결합은 달성했지만, 높은 응집은 달성하지 못함
      • 그냥 니 리팩토링 수준이 낮아서다 라고 얘기하는 중

[jpa] 한 컬럼에 여러 엔티티 저장하기(feat.@Any)

Posted on 2019-02-17 | Edited on 2020-11-02 | In jpa | Comments: 0 Comments

여러 테이블에 공용으로 저장하는 테이블을 만들어야 한다고 가정해보자.

여러 엔티티라고 해서 리스트를 얘기하는 것이 아니라, 다형성을 얘기하는 것이다.

http://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#mapping-column-any

https://www.concretepage.com/hibernate/hibernate-any-manytoany-and-anymetadef-annotation-example

Read more »

[jpa] JPA 성능 최적화

Posted on 2019-02-15 | Edited on 2020-11-02 | In jpa | Comments: 0 Comments

N+1 문제

성능상 가장 주의해야 하는것이 이 N+1 문제이다.
아래와 같은 엔티티가 있다고 가정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Entity
class Member{
@Id @GeneratedValue
private Long id;

@OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
private List<Order> orders = new ArrayList<>();
}

@Entity
class Order{
@Id @GeneratedValue
private Long id;

@ManyToOne
private Member member;
}

(참고로 Order에서 Member를 EAGER로 설정해도 동일하게 N+1은 발생한다)

즉시 로딩

1
2
3
List<Member> members = 
em.createQuery("SELECT m FROM Member", Member.class)
.getResultList();

예전에도 언급했지만, JPA는 fetchType을 전혀 신경쓰지 않고 충실하게 JPQL에 맞춰 SQL을 생성한다.

따라서 Member를 전체 조회하는 쿼리가 먼저 실행되고,
Member의 개수만큼 Order를 조회하게 될 것이다(…)

1
2
3
4
5
6
SELECT * FROM Member; -- if result is 5
SELECT * FROM Order_ WHERE MEMBER_ID = 1;
SELECT * FROM Order_ WHERE MEMBER_ID = 2;
SELECT * FROM Order_ WHERE MEMBER_ID = 3;
SELECT * FROM Order_ WHERE MEMBER_ID = 4;
SELECT * FROM Order_ WHERE MEMBER_ID = 5;

이처럼 처음 실행한 SQL의 결과 수만큼 추가로 SQL을 실행하는 것을 N+1 문제라고 한다.

지연 로딩

즉시로딩을 지연로딩으로 바꿔도 N+1에서 자유로울수는 없다.
즉시로딩이 아니라서 Member 조회와 동시에 Member 건수만큼 Order를 조회해오진 않겠지만,
Member에서 Order를 사용하는 시점에는 똑같이 불러오게 된다.

1
2
3
for(Member member : membres){
System.out.println(member.getOrders().size());
}

members 개수만큼 order 조회 SQL이 실행될 것이다.
즉, 이것도 결국 N+1 문제다.

해결법

페치 조인

가장 일반적인 방법이다. SQL 조인을 이용해서 연관된 엔티티를 함께 조회하므로 N+1 문제가 발생하지 않는다.
JPQL은 아래와 같다.

1
SELECT m FROM Member m JOIN FETCH m.orders

JOIN으로 같이 조회해서 Member 엔티티의 orders 속성에 초기화 하였기 때문에 더이상 N+1이 발생하지 않는다.
참고로 위 예제는 일대다 조인이므로 결과가 늘어날 수 있다. DISTINCT를 써줘야한다.

하이버네이트 @BatchSize

하이버네이트가 제공하는 org.hibernate.annotations.BatchSize 어노테이션을 이용하면 연관된 엔티티를 조회할 때 지정된 size 만큼 SQL의 IN절을 사용해서 조회한다.

1
2
3
4
5
6
7
8
9
@Entity
class Member{
@Id @GeneratedValue
private Long id;

@org.hibernate.annotations.BatchSize(size = 5)
@OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
private List<Order> orders = new ArrayList<>();
}

즉시로딩이므로 Member를 조회하는 시점에 Order를 같이 조회한다.
@BatchSize가 있으므로 Member의 건수만큼 추가 SQL을 날리지 않고, 조회한 Member 의 id들을 모아서 SQL IN 절을 날린다.

1
2
3
4
SELECT * FROM
ORDER_ WHERE MEMBER_ID IN(
?, ?, ?, ?, ?
)

size는 IN절에 올수있는 최대 인자 개수를 말한다. 만약 Member의 개수가 10개라면 위의 IN절이 2번 실행될것이다.

그리고 만약 지연로딩이라면 지연로딩된 엔티티 최초 사용시점에 5건을 미리 로딩해두고, 6번째 엔티티 사용 시점에 다음 SQL을 추가로 실행한다.

hibernate.default_batch_fetch_size 속성을 사용하면 애플리케이션 전체에 기본으로 @BatchSize를 적용할 수 있다.

1
2
> <property name="hibernate.default_batch_fetch_size" value="5" />
>

하이버네이트 @Fetch(FetchMode.SUBSELECT)

연관된 데이터를 조회할 때 서브쿼리를 사용해서 N+1 문제를 해결한다

1
2
3
4
5
6
7
8
9
@Entity
class Member{
@Id @GeneratedValue
private Long id;

@org.hibernate.annotations.Fetch(FetchMode.SUBSELECT)
@OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
private List<Order> orders = new ArrayList<>();
}

아래와 같이 실행된다.

1
2
3
4
5
6
SELECT * FROM Member;
SELECT * FROM Order_
WHERE MEMBER_ID IN(
SELECT ID
FROM Member
)

즉시로딩으로 설정하면 조회시점에, 지연로딩으로 설정하면 지연로딩된 엔티티를 사용하는 시점에 위의 쿼리가 실행된다.

모두 지연로딩으로 설정하고 성능 최적화가 필요한 곳에는 JPQL 페치 조인을 사용하는 것이 추천되는 전략이다.


읽기 전용 쿼리의 성능 최적화

JPA의 영속성 컨텍스트는 변경 감지를 위해 스냅샷 인스턴스를 보관하는 특징이 있다.
하지만 단순 조회 화면에서는 조회한 엔티티를 다시 조회할 필요도 없고, 수정할 필요도 없어서 이때는 스냅샷 인스턴스를 위한 메모리가 낭비된다.
이럴 경우 아래의 방법으로 메모리 사용량을 최적화할 수 있다.

스칼라 타입으로 조회

엔티티가 아닌 스칼라 타입으로 모든 필드를 조회하는 것이다.
알다시피 스칼라 타입은 영속성 컨텍스트가 관리하지 않는다.

1
SELECT m.id, m.name, m.age FROM Member m

읽기 전용 쿼리 힌트 사용

하이버네이트 전용 힌트인 org.hibernate.readOnly를 사용하면 엔티티를 읽기 전용으로 조회할 수 있다.
읽기 전용이므로 영속성 컨텍스트가 스냅샷을 저장하지 않으므로 메모리 사용량을 최적화 할 수 있다.

1
2
3
Member member = em.createQuery("SELECT m FROM Member m", Member.class)
.setHint("org.hibernate.readOnly", true)
.getSingleResult();

스냅샷이 없으므로 member의 값을 수정해도 update 쿼리가 발생하지 않는다.

스냅샷만 저장하지 않는 것이지, 1차 캐시에는 그대로 저장한다
똑같은 식별자로 2번 조회했을 경우 반환되는 엔티티의 주소가 같다

읽기 전용 트랜잭션 사용

스프링 프레임워크를 사용하면 트랜잭션을 읽기 전용 모드로 설정할 수 있다.

1
@Transactional(readOnly = true)

트랜잭션을 읽기 전용으로 설정하면 스프링 프레임워크가 하이버네이트 세션의 플러시 모드를 MANUAL로 설정한다.
이렇게하면 강제로 플러시 호출을 하지 않는 한 플러시가 일어나지 않는다.

엔티티의 플러시 모드는 AUTO, COMMIT 모드만 있다.
MANUAL 모드는 하이버네이트 세션에 있는 플러시모드이다. 이는 강제로 플러시를 호출하지 않으면 절대 플러시가 일어나지 않는 특징을 가지고 있다.
하이버네이트 세션은 JPA 엔티티 매니저를 하이버네이트로 구현한 구현체이다.

플러시를 수행하지 않으므로 플러시할 떄 일어나는 스냅샷 비교와 같은 무거운 로직들이 실행되지 않으므로 성능이 향상된다.
(그래도 스냅샷은 그대로 저장하는 듯. 단지 플러시만 일어나지 않는 것 같다.)
물론 트랜잭션을 시작했으므로 트랜잭션 시작, 수행, 커밋의 과정은 이루어진다.

트랜잭션 밖에서 읽기

트랜잭션 없이 영속성 컨텍스트만 가지고 엔티티를 조회하는 것을 의미한다.
JPA에서 엔티티를 변경하려면 트랜잭션이 필수이므로, 조회가 목적일 때만 사용해야 한다.

JPA는 기본적으로 아래의 2가지 특성이 있다

  • 트랜잭션이 커밋될 때 영속성 컨텍스트를 플러시한다
  • 영속성 컨텍스트만 있으면 트랜잭션 없이 읽기가 가능하다

스프링을 사용하지 않을 때

1
2
3
4
5
6
7
8
9
EntityManager em = emf.createEntityManger();
// EntityTransaction tx = em.getTransaction();

try {
// tx.begin();

Some some = em.find(Some.class, 1L);
SomeChild someChild = some.getSomeChildren().get(0);
}

트랜잭션을 시작하는 부분 없이 엔티티매니저만 가져와서 조회를 수행해도 정상 동작하고, 보다시피 lazy 로딩까지도 동작한다.

참고로 여기서 스프링이 @PersistenceContext를 통해 가져온 EntityManager를 사용할 경우 예외가 발생한다(org.hibernate.LazyInitializationException)
위처럼 메서드내에서 생성된 entityManager에만 유효하다(@PersistenceContext는 공유되는 애라서 그런가…)
공유되는 애인게 왜 문제가 되지?

스프링을 사용할 때

스프링을 사용할떄는 OSIV를 사용하거나, 직접 EntityManager를 생성시켜주는 방법을 사용해야 한다
기본적으로 @Transactional 을 통해 트랜잭션이 생성될 때 영속성 컨텍스트를 생성하는 구조이기 떄문이다
트랜잭션을 시작하지 않을거라면 @Transactional 어노테이션을 제거해줘야하는데, 그러면 스프링 입장에서 언제 영속성 컨텍스트를 생성해줘야 할지 알지 못하게 된다
그러므로 OSIV를 사용하지 않을거면 EntityManagerFactory를 가져온 뒤 직접 엔티티 매니저를 생성해줘야만 트랜잭션 없이 읽기가 가능하다
이 과정 없이 트랜잭션 없이 읽기를 사용하려면(@Transactional 어노테이션을 제거해서) OSIV를 켜줘야한다

1…678…19

JunYoung Park

182 posts
18 categories
344 tags
RSS
© 2020 JunYoung Park
Powered by Hexo v3.6.0
|
Theme – NexT.Muse v7.1.0