가비지 컬렉션 용어 정리

GC (Garbage Collection)
- Java Application에서 사용하지 않는 메모리를 자동 수거하는 기능

STW (Stop-The-World)
- GC가 메모리 정리를 위해서 어플리케이션의 실행을 중지시키는 행위
- GC 튜닝을 한다면 이 시간을 줄여야 함

OOM (Out Of Memory)
- Heap 메모리 부족 시 발생되는 에러 상황 및 메시지

Mark
- Object가 현재 사용되고 있는지에 대한 검사
- Referrer가 존재하는지 확인하는 것으로 Minor GC단계에서 주로 수행됨

Sweep
- Mark 대상에 대해서 회수 작업 (메모리상에 미사용으로 처리)
- Minor GC단계에서 주로 수행됨

Compact
- 디스크 조각모음 하듯이 메모리 공간 확보 Major GC시에 수행됨 


'Development > Java' 카테고리의 다른 글

Garbage Collection 방식  (0) 2018.05.12
Garbage Collection 과정  (0) 2018.04.28
AES256 암호화 오류 해결  (4) 2017.10.06
반복문 성능 비교  (0) 2017.07.27
List 중복 제거  (0) 2017.07.13

형식 맞추기

  • 프로그래머라면 형식을 깔끔하게 맞춰 코드를 짜야 한다.
  • 팀이 합의하여 규칙을 정하고 모두가 그 규칙을 따라야 한다.

형식을 맞추는 목적

  • 코드 형식은 중요하다.
  • 의사소통의 일환이다. 의사소통은 전문 개발자의 일차적인 의무다.
  • 구현한 코드들이 변경되더라도 개발자의 스타일과 규율은 잘 사라지지 않는다.

적절한 행 길이를 유지하라

  • 자바에서 파일 크기는 클래스 크기와 밀접하다.
  • 일반적으로 작은 파일이 큰 파일보다 이해하기 쉽다.

신문 기사처럼 작성하라

  • 독자는 위에서 아래로 읽는다.
  • 메소드 전체 내용을 요약하는 이름을 작성한다.
  • 세세한 내용은 또 하나의 메소드로 분리한다.

개념은 빈 행으로 분리하라

  • 빈 행은 새로운 개념을 시작한다는 시각적 단서다.
  • 다른 개념 사이에 개행이 빠지면 가독성이 현저히 떨어진다.

세로 밀집도

  • 서로 밀접한 코드 행은 세로로 가까이 놓여야 한다.

수직 거리

  • 같은 파일에 속할 정도로 밀접한 두 개념은 세로 거리로 연관성을 표현한다.
    • 여기서 뜻하는 연관성이란 한 개념을 이해하는데 다른 개념이 중요한 정도다.
    • 연관성이 깊은 두 개념이 멀리 떨어져 있으면 코드를 읽는 사람이 함수 연관관계와 동작 방식을 이해하기 위해 여기저기 찾느라 시간과 노력을 소모한다.

[수직분리]

변수 선언
  • 처음으로 사용하기 직전에 선언하며 수직으로 가까운 곳에 위치해야 한다.
  • 비공개 함수는 처음으로 호출한 직후에 정의해야 쉽게 눈에 띄어야 한다.
인스턴스 변수
  • 클래스 맨 처음에 선언한다.
종속함수
  • 한 함수가 다른 함수를 호출한다면 두 함수는 세로로 가까이 배치한다.
  • 또한 가능하다면 호출하는 함수를 호출되는 함수보다 먼저 배치해야 자연스럽게 읽힌다.
개념적 유사성
  • 개념적인 친화도가 높을수록 코드를 가까이 배치한다.
  • 종속적인 관계가 없더라도 기능적으로 유사하다.
public boolean isAdmin() {  
  return StringUtils.equals(RoleCodes.ADMIN, role.getName());  
}

public boolean isManager() {  
  return StringUtils.equals(RoleCodes.MANAGER, role.getName());  
}

public boolean isUser() {  
  return StringUtils.equals(RoleCodes.USER, role.getName());  
}

세로 순서

  • 호출되는 순서대로 함수를 배치하면 소스 코드 모듈이 고차원에서 저차원으로 자연스럽게 내려간다.
  • 가장 중요한 개념을 표현하고나서 세세한 사항을 표현하면 함수 몇 개만 읽어도 개념을 파악하기 쉬워져서 세세한 사항까지 파고들 필요가 없다.

가로 형식 맞추기

  • 행 길이는 어느 정도가 적당할까?
    • 여러 논쟁이 있지만 120자 정도로 제한하는 것을 권고한다고 한다.

가로 공백과 밀집도

  • 가로로는 공백을 사용해 밀접한 개념과 느슨한 개념을 표현한다.
    • ex) 할당 연산자 명확, 메소드 파라미터 구분, 연산자 우선순위 강조

가로 정렬

  • 정렬을 통해 강조하려고한 진짜 의도가 가려질 수 있다.
  • 변수 유형은 무시하고 변수 이름부터 읽게 되버린다?
    • 정렬이 필요할 정도로 목록이 길다면 문제는 목록 길이지 정렬 부족이 아니다.
    • 만약 선언부가 길어진다면 클래스를 쪼개야 한다는 의미다.

들여쓰기

  • 들여쓰기가 없다면 인간이 코드를 읽기란 거의 불가능일 것이다.
  • 들여쓰기한 파일은 구조가 한눈에 들어온다.
    • ex) 변수, 메소드, 분기문 등..
  • 들여쓰기 무시하기

    • 간혹 간단하고 짧은 메소드에서 규칙을 무시하고픈 유혹이 생긴다.
    public String render() { return ""; }
    
    public String render() {   
      return "";   
    }

팀 규칙

  • 각자 선호하는 규칙이 있지만 팀에 속한다면 자신이 선호해야할 규칙은 바로 팀 규칙이다.
    • 그래야 스타일이 일관적이고 매끄럽다.
    • 팀 규칙을 정하여 IDE 코드 형식기를 설정하여 모두가 그 규칙을 따라야 한다.
  • 좋은 소프트웨어 시스템은 읽기 쉬운 문서로 이뤄진다.

마치며

팀에서 포괄적인 규칙을 정하여 IDE에 적용하여 사용하다가 의아스러운 부분이 있었다.
상세한 부분에 대해서도   규칙을 정하여  사람이 작성한듯한 가독성 좋은 코드를 작성해야 한다.

적용한 룰에 예외적으로 느껴진 항목

  • 테스트케이스 MockMvc 포맷
  • 포맷으로 지정한 가로 길이를 조금 초과하는 경우
  • 조건문 내 1줄 정지성 키워드(break, return, throw ..) 브레이스 여부

참고문헌 - CleanCode 애자일 소프트웨어 장인 정신

'Development > CleanCode' 카테고리의 다른 글

Meaningful Name  (0) 2017.12.31

의미있는 이름

  • 이름은 패키지, 클래스, 메소드, 변수 등 널리 사용된다.
  • 이름을 잘 지으면 여러모로 편하다.

의도를 분명히 밝혀라

  • 좋은 이름을 지으려면 시간이 걸리지만 좋은 이름으로 절약하는 시간이 훨씬 더 많다.
  • 코드를 보고 더 나은 이름이 떠오르면 즉시 개선하는 것이 좋다.
  • 이름에 주석이 필요하다면 의도를 분명히 드러내지 못한 것이다.
    • ex) int d; // 날짜

그릇된 정보를 피하라

  • 일반적으로 떠오르는 생각을 왜곡시키지 말라.
  • 중의적으로 해석될 수 있는 이름은 지양하라.
  • 코드의 일관성을 깨뜨리지 말라.

    ex)
    String codeList; 
    // 실제 데이터를 담는 컨테이너가 List가 아닌데 변수명으로 list를 사용하는 경우
    
    int a = l; 
    if (O == l)
     a = 01;
    else
     l = Ol;
    // 문자 l은 숫자 1처럼 보이고 문자 O는 숫자 0 처럼 보인다.

의미있게 구분하라

  • 불용어(noise word)를 피하라.
    • ex) int a1, a2, a3;
  • 읽는 사람이 차이를 알수 있도록 이름을 지어라.
  • 정확한 개념이 구분이 되지 않는다.
    • ex) ProductInfo? ProductData?

발음하기 쉬운 이름을 사용하라

  • 두뇌는 단어를 바탕으로 발음하려고 한다.
    • ex) genymdhms -> generateTimestamp

검색하기 쉬운 이름을 사용하라

  • 상수는 검색하기 쉽도록 변수로 정의해서 사용하라.
  • 긴 이름이 짧은 이름보다 검색하기 수월하다.
  • 이름 길이는 범위 크기에 비례해야 한다.
  • 간단한 메서드에서나 짧은 변수명을 사용하라.

인코딩을 피하라

  • 불필요한 정신적 부담일 뿐이다. (변수에 부가정보를 덧붙이는 표기. 해독, 발음, 오타)
  • 헝가리식 표기법
    • 변수명에 타입을 명시한다. 타입이나 이름이 바뀌더라도 반드시 같이 변하진 않는다.
  • 멤버변수 접두어
    • 코드를 읽을수록 접두어/접미어는 관심 밖으로 밀려난다. 구닥다리 코드의 징표일 뿐이다. ex) String m_title, strContent;
  • 인터페이스 클래스와 구현 클래스
    • 때론 구분하기 위해서 인코딩이 필요할 때도 있다.
    • ex) 인터페이스 보다는 구현 클래스를 인코딩 하라. ServerManagerImpl, CServerManager (O) / IServerManager (X)

자신의 기억력을 자랑하지 마라

  • 독자가 코드를 읽다가 변수명을 변환하게 해서는 안된다.
    • 이미 변수 a와 b가 있다고 c라는 이름을 선택하는 논리는 최악이다.
  • 똑똑한 프로그래머와 전문가 프로그래머의 차이점은 명료함이다. 다른 사람들이 잘 알아볼수 있도록 코딩해야 한다.

클래스 이름

  • 클래스/객체 이름은 명사/명사구가 적합하다.
    • Manager, Processor, Data, Info와 같은 단어는 피하라.
    • ex) Customer, AddressParser
  • 동사는 사용하지 않는다.

메서드 이름

  • 메서드 이름은 동사/동사구가 적합하다.
    • ex) deletePage, saveBoard
  • 변경자, 접근자, 조건자는 각각 set, get, is로 시작하라.
    • ex) getItem, setName, isStudent

기발한 이름은 피하라

  • 구어체/속어는 사용해서 안된다.
  • 특정 문화에서만 사용되는 사례도 금한다.
    • ex) kill() -> whack() / abort() -> eatMyShort()

한 개념에 한 단어를 사용하라

  • 똑같은 메서드를 클래스마다 get, fetch, retrieve 등 다른 단어로 혼용하면 혼란스럽다.
  • 일관성 있는 어휘가 독자에게 좋다.

말장난을 하지 마라

  • 한 단어를 두가지 목적으로 사용하지 마라.
  • 다른 개념에 같은 단어를 사용한다면 그것은 말장난에 불과하다.
    ex) 
    지금까지 구현한 모든 add 메소드가 기존 값 두개를 더하거나 이어서 새로운 값을 만든다고 가정하자. 
    새롭게 작성하는 메소드는 집합에 값을 하나만 추가한다. 
    일관성 있게 add라는 메소드명을 사용해야 하는가? 아니다. 
    insert나 append 등 다른 이름을 사용하자.
  • 최대한 이해하기 쉽게 작성해야 한다.
  • 집중적인 탐구가 필요한 코드가 아닌 대충 훑어봐도 이해할 수 있는 코드 작성이 목표다.

해법 영역에서 가져온 이름을 사용하라

  • 코드를 읽을 사람도 프로그래머라는 사실을 명심한다.
  • 전산용어, 알고리즘 이름, 패턴 이름, 수학용어 등을 사용해도 괜찮다.
  • 기술적인 개념에는 기술적인 이름이 가장 적합한 선택이다.
    • ex) queue, stack

문제 영역에서 가져온 이름을 사용하라

  • 적절한 용어가 없다면 문제 영역에서 이름을 가져온다.
    • 코드를 유지 보수하는 개발자가 분야 전문가에게 의미를 물어 파악할 수 있다.
  • 우수한 개발자와 설계자라면 해법 영역과 문제 영역을 구분할 줄 알아야 한다.

의미 있는 맥락을 추가하라

  • 스스로 의미가 분명한 이름은 있다. 하지만 대다수 이름은 그렇지 못하므로 클래스, 함수, 이름 공간에 넣어 맥락을 부여한다.
  • 모든 방법이 실패하면 마지막 수단으로 접두어를 붙인다.
    ex)
    firstName, lastName, street, houseNumber, city, state, zipcode 라는 변수가 있다. 
    변수를 훑어보면 주소라는 사실이 금방 알아챈다. 
    하지만 어느 메소드가 state라는 변수 하나만 사용한다면? 
    변수 state가 주소의 일부라는 사실을 금방 알아챌까? 
    addr 접두어를 추가해 addrFirstName, addrLastName, addrState라고 쓰면 맥락이 분명해진다.

불필요한 맥락을 없애라

ex)
 Gas Station Deluxe 라는 어플리케이션을 작성한다고 해서 클래스 이름 앞에 GSD를 붙이지는 말자. GSDAccount, GSDUser ..
 G를 입력하고 자동완성을 누를 경우 모든 클래스가 나타나는 등 효율적이지 못하다.
 개발자를 지원하는 IDE를 방해할 이유가 없다.

마치며

좋은 이름을 선택하려면 설명 능력이 뛰어나야 하고 의미가 뚜렷한 어휘인지 분별할 줄 알아야 한다.
다른 개발자가 반대할까 두려워 이름을 바꾸지 않아서는 안된다. 
나름대로 수정했다가 질책을 받더라도 개선하려는 노력을 중단해서는 안된다. 
그래야 자연스럽게 읽히는 코드를 짜는 데에 더 집중할 수 있다. 
단기적인 효과는 물론 장기적인 이익도 보장한다.

참고문헌 - CleanCode 애자일 소프트웨어 장인 정신

'Development > CleanCode' 카테고리의 다른 글

Formatting  (0) 2018.03.02

multiple 'X-Frame-Options' headers with conflicting 이슈 해결

동일 도메인에서 <iframe>을 통해 접근하는 경우 X-Frame-Options을 DENY로 설정하면 최신(?) 브라우저에서는 접근할 수 없는 문제가 발생할 수 있다. 이 경우 동일 도메인에서는 <iframe> 접근이 가능하도록 X-Frame-Options를 SAMEORIGIN으로 설정해줘야 한다.

<iframe>을 사용하는 부분이 있어 기존에 아래와 같이 SAMEORIGIN으로 설정해서 잘 사용하고 있었다.

http.headers().addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN));

그런데 스프링 시큐리티 버전(3.2.3 -> 4.0.4)을 올리면서 동작에 문제가 생겼다. 브라우저에서 아래와 같은 에러가 발생했다.

이슈

[Chrome]
in a frame because it set multiple 'X-Frame-Options' headers with conflicting values ('DENY, SAMEORIGIN'). Falling back to 'deny'


http 응답 헤더를 보니 DENY와 SAMEORIGIN 설정이 중첩되는 문제가 발생했다.
스프링 시큐리티 버전이 올라가면서 default 값인 DENY에 SAMEORIGIN이 추가되어 충돌난 듯하다.

해결방안

내용을 찾아보니 스프링 시큐리티 4.x 부터 설정하는 방식이 변경되었다.

http.headers().frameOptions().sameOrigin();

위와 같이 설정하니 문제없이 잘 동작하는 것을 확인했다.

'Development > Spring' 카테고리의 다른 글

DatabaseConfiguration 적용  (0) 2018.08.17
Configuration Condition  (0) 2018.07.16
비동기 메소드 HttpServletRequest 에러  (0) 2017.12.03
비동기 어노테이션  (0) 2017.11.25
캐시 어노테이션  (0) 2017.10.06

비동기 메소드 HttpServletRequest 이슈 해결

@Async로 선언된 비동기 메소드를 호출했다. 메소드는 해당 클래스에 @Autowired된 HttpServletRequest로 부터 정보를 가져오다가 SimpleAsyncUncaughtExceptionHandler.handleUncaughtException() 예외가 발생했다.

No thread-bound request found: Are you referring to request attributesoutside of an actual web request, or processing a request outside of theoriginally receiving thread.

원인은 비동기 처리시 다른 스레드에서 동작하기 때문이다.

해결방안

  • request로 부터 필요한 정보를 메소드의 파라미터로 전달한다.
  • 다음과 같이 RequestContextHolder에서 현재 HttpServletRequest를 가져온다.
// WebApplicationInitializer 설정에 리스너 추가
servletContext.addListener(new RequestContextListener());

ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = attr.getRequest();


'Development > Spring' 카테고리의 다른 글

DatabaseConfiguration 적용  (0) 2018.08.17
Configuration Condition  (0) 2018.07.16
multiple 'X-Frame-Options' headers with conflicting 에러  (0) 2017.12.15
비동기 어노테이션  (0) 2017.11.25
캐시 어노테이션  (0) 2017.10.06

Tomcat Cookie Domain 이슈 해결

기존 서비스를 Tomcat7에서 Tomcat8로 업그레이드 하는 과정에서 문제가 발생했다. 
원인을 찾아보니 서비스 내 쿠키 생성시 서브 도메인에서도 같이 사용 할 수 있도록 .xxx.com 을 도메인으로 생성하고 있었다.
그런데 Tomcat8에서 도메인이 .(dot)으로 시작하는 쿠키를 생성할 수 없도록 변경되었다. (RFC 6265 Cookie Processor 정책)

해결방안

  • 쿠키 도메인을 .(dot)으로 시작하지 않도록 변경한다.
  • 기존 LegacyCookieProcessor를 사용하도록 context.xml에 아래와 같이 코드를 추가해줘야 한다.
    <CookieProcessor className="org.apache.tomcat.util.http.LegacyCookieProcessor" />

참조 - https://stackoverflow.com/questions/29608550/tomcat-cookie-domain-validation

'Development > WAS' 카테고리의 다른 글

Web & WAS  (0) 2016.12.12
Tomcat 서비스 설치/삭제  (0) 2016.10.29

Spring @Async

  • 메소드를 비동기 처리하면 호출자는 호출한 메소드가 완료될 때 까지 기다릴 필요가 없다.
  • @Async를 사용하여 별도의 쓰레드에서 실행시킬 것이다.

Async Configuration

// Java Config
@Configuration
@EnableAsync
public class SpringAsyncConfig {
  ...
}

// XML Config
<task:executor id="myexecutor" pool-size="5"/>
<task:annotation-driven executor="myexecutor"/>

@Async

제약사항

  • @Async로 명시된 메소드는 반드시 public으로 선언되어야 한다. 메소드가 public 이어야 프록시가 될 수 있기 때문이다.
  • 같은 클래스 내에서 해당 메소드를 호출할 경우 비동기로 작동하지 않는다. 셀프호출은 프록시를 우회하고 해당 메소드를 직접 호출하기 때문이다.

void 반환형

@Async
public void asyncMethodWithVoidReturnType() {
    System.out.println("Execute method asynchronously. "
      + Thread.currentThread().getName());
}

Future 반환형

@Async
public Future<String> asyncMethodWithReturnType() {
    System.out.println("Execute method asynchronously - "
      + Thread.currentThread().getName());
    try {
        Thread.sleep(5000);
        return new AsyncResult<String>("hello world !!!!");
    } catch (InterruptedException e) {
        //
    }

    return null;
}

Executor

  • 스프링은 기본값으로 SimpleAsyncTaskExecutor를 사용하여 실제 메소드들을 비동기 실행한다. 기본 설정은 메소드/애플리케이션 레벨로 재정의하여 사용할 수 있다.
  • 다음과 같이 설정하면 @Async로 지정된 메소드를 실행하는 기본 실행자가 변경된다. 실행자의 상세 옵션들도 변경할 수 있다.

Method Level Override

  • 실행자 빈을 설정하고 @Async 속성에 실행자명을 명시해야 한다.
@Configuration
@EnableAsync
public class SpringAsyncConfig {

    @Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        return new ThreadPoolTaskExecutor();
    }
}

@Async("threadPoolTaskExecutor")
public void asyncMethodWithConfiguredExecutor() {
    System.out.println("Execute method with configured executor - "  + Thread.currentThread().getName());
}

Application Level Override

  • AsyncConfigurer 인터페이스를 구현하여 getAsyncExecutor() 메소드를 오버라이드해야 한다.
@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {

  @Override
  public Executor getAsyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(5);
    executor.setMaxPoolSize(100);
    executor.setQueueCapacity(100);
    executor.setThreadNamePrefix("TEST_");
    executor.initialize();
    return executor;
  }

}

Exception Handling

  • 반환형이 void일 때, 예외는 호출 스레드에 전달되지 않는다. 예외 처리를 위한 설정이 추가적으로 필요하다.
@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {

  @Override
  public Executor getAsyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(5);
    executor.setMaxPoolSize(100);
    executor.setQueueCapacity(100);
    executor.setThreadNamePrefix("TEST_");
    executor.initialize();
    return executor;
  }

  @Override
  public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    return new SimpleAsyncUncaughtExceptionHandler();
    // Custom 가능
    // return new CustomAsyncExceptionHandler();
  }

}
  • AsyncUncaughtExceptionHandler 인터페이스를 구현하여 커스텀 비동기 예외처리자를 만들 수도 있다. handleUncaughtException() 메소드는 잡히지 않은 비동기 예외가 발생할때 호출된다.
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

    @Override
    public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
        System.out.println("Exception message : " + throwable.getMessage());
        System.out.println("Method name : " + method.getName());
        for (Object param : obj) {
            System.out.println("Parameter value : " + param);
        }
    }

}

Etc

  • Spring Boot 기반의 웹 애플리케이션은 HTTP 요청이 들어왔을 때 내장된 서블릿 컨테이너가 관리하는 독립적인 1개의 Worker 쓰레드에 의해 동기 방식으로 실행된다. 하지만 요청 처리 중 @Async가 명시된 메소드를 호출하면 앞서 등록한ThreadPoolTaskExecutor 빈에 의해 관리되는 또 다른 독립적인 Worker 쓰레드로실행된다. 별도의 쓰레드로 동작하기 때에 원래의 요청 쓰레드는 그대로 다음 문장을 실행하여 HTTP 응답을 마칠 수 있다.

  • Async 어노테이션을 사용하는 순간 서로 다른 Thread에서 동작하기 때문에 앞에서 생성한 Transaction이 연장되지 않으므로 주의해야 한다.

레퍼런스 - http://www.baeldung.com/spring-async

'Development > Spring' 카테고리의 다른 글

DatabaseConfiguration 적용  (0) 2018.08.17
Configuration Condition  (0) 2018.07.16
multiple 'X-Frame-Options' headers with conflicting 에러  (0) 2017.12.15
비동기 메소드 HttpServletRequest 에러  (0) 2017.12.03
캐시 어노테이션  (0) 2017.10.06
엔티티 매핑

@Entity : JPA를 사용해 테이블과 매핑할 클래스로 지정하는 어노테이션.

  • 기본생성자가 반드시 필요하다.
  • final, enum, interface, inner 클래스에 사용할 수 없다.
  • 저장할 필드에 final을 사용할 수 없다.
  • name : 기본값으로 클래스명이 사용된다. 다른 패키지에서 중복 클래스명이 엔티티로 사용된다면 충돌을 막기위해 값 설정이 필요하다.

@Table : 엔티티와 매핑할 테이블을 지정하는 어노테이션.

  • name : 테이블명을 지정할 때 사용된다.(default 엔티티명)
  • uniqueConstraints : DDL 생성 시에 유니크 제약조건을 만든다. 복합 유니크 제약조건도 가능하다. 스키마 자동 생성 기능을 사용해서 DDL을 만들 때만 사용된다.
@Table(name = "t_user", indexes = { @Index(name = "idx_u_username", columnList = "username") }, 
uniqueConstraints = @UniqueConstraint(name="uk_user", columnNames = {"email", "username" }))

스키마 자동 생성

  • 애플리케이션 실행 시점에 데이터베이스 테이블을 자동으로 생성한다.
  • DDL은 운영환경에서 사용할 만큼 완벽하지는 않으므로 개발 환경에서 사용하거나 매핑을 어떻게 해야하는지 참고하는 정도로만 사용한다.
  • 자동 생성되는 DDL은 지정한 데이터베이스 방언에 따라 달라진다. ex) mysql : varchar, integer / oracle : varchar2, number

스키마 자동 생성 종류

create : 기존 테이블 삭제후 다시 생성. drop + create
create-drop : 위 과정에 추가로 앱 종료시 DDL 제거. drop + create + drop
update : DB 테이블과 엔티티 매핑정보를 비교해 변경사항만 수정.
validate : DB 테이블과 엔티티 매핑정보를 비교해 차이가 있으면 경고. 앱 실행X. DDL 변경X.
none : 자동생성기능 사용안함.

JPA 기본 환경설정

@Configuration
public class DataConfig {

    @Autowired
    private DatabaseProperties databaseProperties;

    @Bean
    public DataSource dataSoure(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(databaseProperties.getDriver());
        dataSource.setUrl(databaseProperties.getUrl());
        dataSource.setUsername(databaseProperties.getUsername());
        dataSource.setPassword(databaseProperties.getPassword());
        return dataSource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(){

        LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
        factoryBean.setDataSource(dataSoure());
        factoryBean.setPackagesToScan("com.example.model");
        factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());

        // JPA 상세설정
        Properties jpaProperties = new Properties();
        jpaProperties.put(AvailableSettings.SHOW_SQL, true); // SQL 보기
        jpaProperties.put(AvailableSettings.FORMAT_SQL, true); // SQL 포맷팅하기
        jpaProperties.put(AvailableSettings.USE_SQL_COMMENTS, true); // SQL 코멘트 보기
        jpaProperties.put(AvailableSettings.HBM2DDL_AUTO, databaseProperties.getHbm2ddlAuto()); // DDL 자동생성 종류
        jpaProperties.put(AvailableSettings.DIALECT, databaseProperties.getDialect()); // 방언 설정
        jpaProperties.put(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, true); // 키 생성 전략 사용시 설정
        jpaProperties.put("hibernate.ejb.naming_strategy", "org.hibernate.cfg.ImprovedNamingStrategy"); // 자바의 카멜 표기법을 테이블의 언더스코어 표기법으로 매핑
        factoryBean.setJpaProperties(jpaProperties);

        return factoryBean;
    }
}

기본키 매핑

  • 직접 할당 : @Id & Setter
    Board board = new board();
    board.setId("id1");
    em.persist(board); 
    // @Id로 지정한 id 필드에 Set메소드로 직접 할당하는 방법
  • 자동 생성 : 대리키 사용 방식. @GeneratedValue

영속성 컨텍스트는 엔티티를 영속 상태로 만드려면 식별자 값이 반드시 있어야 한다.
@Id, @GeneratedValue(strategy = GenerationType.매핑전략, generator = 식별자 생성기 이름)

GenerationType.IDENTITY

  • 기본키 생성을 DB에 위임하는 전략.
  • DB 종속적. Mysql, PostgreSQL, SQL SERVER
  • DB 저장시 @Id 컬럼을 비워두면 DB가 순서대로 값을 채워준다.
  • 전략 사용시 JPA는 기본키 값을 얻어오기 위해 DB를 추가로 조회한다.
  • statement.getGerenatedkey() 사용하면 데이터를 저장하면서 동시에 기본키 값을 얻어와서 DB를 한번만 조회한다.
  • 트렌잭션을 지원하는 쓰기 지연이 동작하지 않는다.

GenerationType.SEQUENCE

  • DB 시퀀스를 사용해 기본키를 할당한다.
  • DB 종속적. Oracle, H2, DB2
  • 엔티티나 컬럼에 @SequenceGenerator 표기
    • name : 식별자 생성기 이름
    • sequenceName : DB 시퀀스명
    • initialValue : 시퀀스 시작 값 (default 1)
    • allocationSize : 시퀀스 호출 시 증가 수 (default 50)
  • DB 시퀀스를 사용해 식별자를 조회해 엔티티에 할당 후, 엔티티를 영속성 컨텍스트에 저장하고 트랜잭션을 커밋하면 DB에 저장된다.

GenerationType.TABLE

  • 엔티티에 @TableGenerator 표기
    • name : 식별자 생성기 이름 (필수)
    • table : 키 생성 테이블명
    • pkColumnName : 시퀀스 컬럼명
    • valueColumnName : 시퀀스 값 컬럼명
  • 키 생성 전용 테이블 만들고 이름과 같은 값으로 사용할 컬럼을 생성한다.
  • 내부 동작은 SEQUENCE와 같다.
Sequence 테이블 생성 DDL

CREATE TABLE MY_SEQUENCE{
  SEQUENCE_NAME VARCHAR(255) NOT NULL,
  NEXT_VAL BIGINT,
  PRIMARY KEY (SEQUENCE_NAME)
}
TABLE 전략 매핑 코드

@Entity
@TableGenerator(
 name = "BOARD_SEQ_GENERATOR",
 table = "MY_SEQUENCE",
 pkColumnValue = "BOARD_SEQ",
 allocationSize = 1)
public class Board {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "BOARD_SEQ_GENERATOR")
    private Long id;
}

GenerationType.AUTO

  • DB 방언에 따라 위의 전략 중 하나를 자동으로 선택한다. ex) MySQL : IDENTITY
  • DB 변경시 코드를 수정할 필요가 없다.
  • SEQUENCE 전략이 사용되면 하이버네이트가 기본값을 사용해서 적절한 시퀀스를 만들어준다.

권장 식별자 선택 전략

  • 기본키 조건 : NOT NULL, 유일 값, 불변
  • 기본키 선택전략
    • 자연키 : 비즈니스에 의미가 있는 키. ex) email, 주민등록번호 등
    • 대리키(대체키) : 비즈니스에 무관한 임의의 키. ex) uuid, sequence, auto_increment 등

필드와 컬럼 매핑

@Column : 객체 필드를 테이블 컬럼과 매핑시킨다.

  • name(default 필드명) : 필드와 매핑할 컬럼명
  • nullable(default true) : null 허용 여부.
  • unique(default false) : 컬럼에 유일값 허용 여부.
  • length(default 255) : 문자길이 제약조건. String 필드에만 사용한다.
  • columnDefinition : 직접 정의 "varchar(10) default 'test'". 사용하는 DB 종류에 종속될 수 있으므로 지양한다.
  • insertable,updatable(default true) : 읽기전용시 false로 설정하여 DB에 저장/수정이 불가하도록 지정한다.자바 기본(primitive)타입에 @Column을 사용하면 nullable = false로 지정하는 것이 안전하다.

@Enumerated : 열거 타입 매핑

  • EnumType.ORDINAL(default) - enum 순서를 DB에 저장하기 때문에 순서가 변경될 경우 안전하지 못하다.
  • EnumType.STRING - enum 이름을 DB에 저장하기 때문에 안전하다.

@Temporal : 날짜 타입 매핑

  • TemporalType.DATE > 2013-10-11
  • TemporalType.TIME > 11:11:11
  • TemporalType.TIMESTAMP(default) > 2013-10-11 11:11:11

@Lob : CLOB,BLOB 타입 매핑

  • CLOB : 문자. String, char[], CLOB (mysql : longtext / oracle : clob)
  • BLOB : 나머지. byte[], BLOB (mysql : longblob / oracle : blob)

@Transient : 특정 필드 매핑 제외

  • 컬럼과 매핑하지 않으므로 DB에 저장하지 않는다.
  • 객체 필드에 임시로 데이터를 보관하는 용도. ex) 비밀번호/확인비밀번호 값을 객체 필드로 각각 입력받아 동일한 값인지 확인

@Access : JPA가 엔티티에 접근하는 방식 지정. @Id 대체

  • AccessType.FIELD : 필드에 직접 접근한다. 필드 접근 권한이 private 이어도 접근이 가능하다.
  • AccessType.PROPERTY : 접근자 Getter를 사용해 접근한다.
    @Id가 필드에 있냐 프로퍼티에 있냐에 따라 access 방식이 달라진다.

  • AccessType이 FIELD로 정의된 경우 영속화 과정에서 필드에 데이터를 설정하거나 읽어올 때, 
    메소드를 통하지 않고 직접 필드에 접근해서 읽어오기 때문에 Get/Set 메소드에 별도의 로직이 존재하는 경우 동작하지 않는다.

  • 반대로 필드명과 Get메소드의 이름이 다를 때, 별도의 로직을 통해 변환된 값을 DB에 저장하고 싶다면@Transient 어노테이션을 필드에 부여해 영속화에서 제외시키고 Get메소드에 @Access(AccessType.PROPERTY)를 설정하면 된다.
@Entity
public class Member {

    @Id
    private String id;

    @Transient
    private String firstName;

    @Transient
    private String lastName;

    @Access(AccessType.PROPERTY)
    public String getFullName(){
        return this.firstName + this.lastName;
    }
 }
// @Id가 필드에 있으므로 필드 접근 방식을 사용하고 getFullName만 프로퍼티를 사용한다.
// Member 엔티티에 fullName 컬럼이 생성되고 firstName + lastName의 결과가 저장된다.


참고서적 - 자바 ORM 표준 JPA 프로그래밍 (김영한)

'Development > JPA' 카테고리의 다른 글

영속성 관리  (0) 2017.01.21
JPA 시작  (0) 2016.11.20
JPA 소개  (0) 2016.05.04
캐시 추상화
추상화는 Java 메서드에 캐싱을 적용함으로써 캐시에 보관된 정보로 메서드의 실행 횟수를 줄여준다.
즉, 대상 메서드가 실행될 때마다 추상화가 해당 메서드가 같은 인자로 이미 실행되었는지 확인하는 캐싱 동작을 적용한다.
해당 데이터가 존재한다면 실제 메서드를 실행하지 않고 결과를 반환하고,
존재하지 않는다면 메서드를 실행하여 그 결과를 캐싱한 뒤에 사용자에게 반환해서 다음 호출 시에 사용할 수 있도록 한다.

@Cacheable
- 캐시할 수 있는 메서드를 지정할 때 사용한다.  즉, 캐시를 통해 메서드 실행을 건너뛸 수 있다.
- 캐시는 본질적으로 key-value 저장소이므로 캐시된 메서드를 호출할 때마다 해당 키로 변환되어야 한다.
- 캐시 추상화는 다음 알고리즘에 기반을 둔 KeyGenerator를 사용한다.
1) 파라미터가 없으면 0을 반환한다.
2) 파라미터가 하나만 있으면 해당 인스턴스를 반환한다.
3) 파라미터가 둘 이상이면 모든 파라미터의 해시를 계산한 키를 반환한다.

@CachePut
- 메서드 실행에 영향을 주지 않고 캐시를 갱신할 때 사용한다. 즉, 메서드를 항상 실행하고 어노테이션 옵션에 따라 그 결과를 캐시에 보관한다.

@CacheEvict
- 캐시를 제거하는 메서드를 구분할 때 사용한다. 즉, 캐시에서 데이터를 제거하는 트리거로 동작하는 메서드다.
- 한 지역의 전체 캐시를 모두 지워야 할 때 옵션(allEntries=true)을 편리하게 사용할 수 있다.
   비효율적으로 각 엔트리를 하나씩 지우는 것이 아니라 한 번에 모든 엔트리를 제거한다.
- @CacheEvict를 void 메서드에 사용할 수 있다는 것은 중요한 부분이다. 메서드가 트리거로 동작하므로 반환값을 무시한다.

@Caching
같은 계열의 어노테이션을 여러 개 지정해야 하는 경우가 있다. 
예를 들어, 조건이나 키 표현식이 캐시에 따라 다른 경우이다. 자바는 이러한 선언을 지원하지 않지만 우회할 수 있다.
@Caching에서 중첩된 @Cacheable, @CachePut, @CacheEvict를 같은 메서드에 다수 사용할 수 있다.

캐시 어노테이션 활성화
캐시 어노테이션을 선언하는 것만으로 자동으로 동작이 실행되지 않는다는 것이 중요하다. 스프링의 다른 기능처럼 선언적으로 기능을 활성화해야 한다.
이는 캐시에 문제가 있다고 의심된다면 코드의 모든 어노테이션을 지우는 대신 활성화 설정만 지워서 캐시를 비활성화 시킬 수 있다는 의미이기도 하다. 
실제로는 이 선언으로 스프링이 캐시 어노테이션을 처리하도록 한다.
- XML기반 : <cache:annotation-driven/>
- Java Config 기반 : @EnableCaching


AES256 암호화시 java.security.InvalidKeyException: Illegal key size 해결 방안

JAVA의 기본 정책으로 AES128 암호화 방식까지만 사용 가능하다. (미 통상법에 의해 자국내에서만 AES256 방식 허용)
그래서 AES256 방식으로 암호화하게 되면 아래와 같은 Exceptioin이 발생한다.
java.security.InvalidKeyException: Illegal key size

이를 해결하기 위해 $JAVA_HOME/jre/lib/security 경로에 아래의 unlimited strength crypto file을 덮어써야한다.
local_policy.jar, US_export_policy.jar

* 해당 파일 다운로드 경로
  - Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 7 (JDK7)
  - Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 8 (JDK8)

OpenJDK로 설치한 서버에서는 문제가 없어 확인해보니 정책이 아래와 같았다.
OracleJDK, OpenJDK v8 이전 - AES128 까지 허용 (모듈 교체 필요 O)
OpenJDK v8 이후 - AES256 까지 허용 (모듈 교체 필요 X)


JDK 1.8u151 버전? 부터 방법 변경

1) $JAVA_HOME/jre/lib/security 경로로 이동
2) java.security 파일 오픈.
3) crypto.policy 프로퍼티 수정. (limited -> unlimited)

$JAVA_HOME/jre/lib/security/policy 경로에 limited, unlimited 디렉토리로 구분되어져 있다.
crypto.policy 프로퍼티에 설정한 디렉토리명에 따라 전환되는듯 하다.


'Development > Java' 카테고리의 다른 글

Garbage Collection 과정  (0) 2018.04.28
Garbage Collection 용어 정리  (0) 2018.04.26
반복문 성능 비교  (0) 2017.07.27
List 중복 제거  (0) 2017.07.13
Reflection 클래스 정보  (0) 2017.07.12

+ Recent posts