JPA Entity 는 왜 빈(empty) 생성자를 필요로 하는가?

jpa를 이용하여 엔티티를 작성하는 경우 간혹 비어있는 생성자를 만들어야 하는 경우가 있다.여기서 말하는 “간혹”이라는 경우는, 사용자가 자바에서 제공하는 기본 생성자를 사용하지 않고,생성자를 재정의 하는 경우인데, 이 때 재 정의하는 생성자가 파라미터를 필요로 하는 경우이다.


public Member(long memberId) {

this.memberId = memberId;
....
}

이렇게 생성자를 재정의 해 놓게 되면, 하이버네이트 등의 프레임워크에서 리플렉션을 이용해 해당 객체를 생성할 때 해당 객체(엔티티)의 기본 생성자를 호출하게 되는데 위와 같이 파라미터가 필요한 생성자로 재정의 한다면

Class<T>.newInstance

를 이용해서 해당 엔티티의 객체를 만들어 낼 수 없게 된다.

그래서, 사용자가 파라미터가 필요한 생성자를 재정의 하는 경우에는 반드시 파라미터가 없는 기본 생성자를 제공해 주어야 한다.

참고로 스프링 데이터를 사용하는 경우라면 위와 같은 파라미터가 필요한 생성자를 재정의하더라도 @PersistenceConstructor 어노테이션을 재정의한 생성자에 붙이게 되면 굳이 비어있는 생성자를 호출하지 않아도 된다.

@PersistenceConstructor
public Member(Long memberId) {
this.memberId = memberId;

….
}
참고로 jpa 2.0 스펙에 다음과 같이 명시되어 있다.

“엔티티는 반드시 파라미터가 없는 (no-arg) public 또는 protected 생성자가 있어야 한다.”

Single Responsebility Principle

오늘 DZONE에서 온 뉴스레터에 SRP(Single Responsibility Principle) 에 대한 언급이 있었는데 개인적으로는 조금 신선한 내용이었습니다.
아티클에서는 SRP 의 Responsibility 가 무엇이냐를 얘기하고 있는거 같은데 그 내용중, 내가 구현한 Class나 Method가 SRP를 준수하고 있는지 알 수 있는 방법을 소개하고 있습니다.
클래스나 매서드가 SRP를 준수하고 있는지 알려면 그 클래스나 매서드에 이름을 붙여보라고 얘기하고 있는데, 간결하게 이름을 붙이기 쉽다면 그 클래스나 매서드는 SRP를 준수하고 있을 가능성이 높고 그렇지 않다면 담당하고 있는 기능이 복잡해서 그럴것이라는 의견입니다.
어찌보면 당연한 얘기처럼 들리기도 하지만, 이렇게 작명행위를 통해 내가 지금 설계의 원칙을 잘 유지하고 있는지 여부를 쉽게 확인할 수 있겠다는 접근은 저에게는 신선하네요. 🙂

 

원본링크

JDK1.5에서 Apache CXF 돌리기

현재 프로젝트의 자바 컴파일환경 표준은 1.5, 런타임은 1.6이다.

웹서비스 관련 기능을 테스트 해보기 위해 apache의 cxf를 사용하려 하는데, Class Version 에러가 난다.

Apache CXF 공식 사이트에서는 jdk 1.5 환경에서 cxf를 사용하려면 2.6.X를 사용하라고 가이드를 하고 있다.

(하지만 왠만하면 1.7이상으로 업그레이드 하라고 “강하게”권고 하고 있다. 요즘 세상에 1.5라니…ㅜㅜ)

여튼 그래서 maven의 dependency 설정에 2.6.16 으로 버전을 수정했지만 ClassVersion에러가 계속 난다.

혹시나 해서 Apache CXF 홈페이지에서 CXF 런타임 2.6.16을 다운로드 받아서 사용했더니 제대로 된다.

(http://cxf.apache.org/download.html 에서 다운로드 받을 수 있다.)

예측건데, maven central repository에는 1.6으로 컴파일한 cxf 1.6.16이 올라가 있고, apache cxf 사이트에는 1.5로 컴파일한 2.6.16버전을 올려둔게 아닌가 싶다. 여튼 2.6.16 버전을 받아서 테스트 해 보니 jdk1.5에서 제대로 동작하는 것을 확인!

logBack을 사용해 보자

얼마전에 Spring의 기능을 참조할 일이 있어 Spring의 레퍼런스 프로젝트인 spring-petclinic 를 내려받게 되었다.
프로젝트 구조를 살펴보는 중 생소한 파일이 발견되었다.
스크린샷 2015-01-18 20.28.36

바로 ‘logback.xml’ 파일이었는데, 직감적으로 “아! 어느새 log4j를 대체하는 기술이 나온 모양이구나”하는 생각이 들었다. 한동안 기술 트랜드에서 너무 벗어나 있었다는 생각이 다시금 들면서 이놈이 뭔지 좀 살펴보기로 했다.

logback 홈페이지에 들어가 보면 logback을 이렇게 소개한다.

“log4j 프로젝트의 후계자로 만들어졌다”

log4j개발자가 거의 지난 10년동안 log4j가 사용되면서 불편(?)했던 내용들을 모두 개선해서 logback이라는 제품을 만들어 낸 듯한 느낌이다.

개발자가 소개하는 log4j 에서 logback으로 옮겨야 하는 여러 이유들 중 특히 내가 관심이 가는 몇가지 항목들은 다음과 같다.

  • Automatic Reloading Configuration file
    • 필자에게는 logback을 도입해야 하는 제일 중요한 이유라고 생가되는 기능이다.
    • log4j를 사용하던 시절(?)에는 로깅 레벨을 WARN -> INFO 등으로 변경하게 되면 Application(WAS)를 다시 시작했어야 했지만 logback 에서는 그럴 필요가 없다. 설정 파일을 변경하면 지정된 시간이 지나게 되면 파일의 변경을 감지하고 다시 읽어들인다.
  • Graceful Recovery from I/O Failures
    • 기존 log4j에서 FileAppender 를 사용하여 로그를 파일서버에 저장하는 경우, 파일서버에 문제가 있어 I/O fail이 나면 Application(was)를 재시작 했어야 했지만 logback에서는 파일 서버가 정상으로 돌아오면 에러 상황에서 빠르게 자동으로 복구가 된다.
  • Automatic Compress
    • 아마도 기존에 log4j를 사용해서 파일로 로그를 떨어뜨려 놓는 경우 별도의 배치 프로그램을 이용해서 로그파일을 압축하고 다른 곳으로 옮겨놓는 작업을 많이 해 보았을 것이라 생각된다. logback에서는 로그 파일의 자동 압축을 지원하고, 시간(파일의 갯수)이 지난 파일을 자동으로 삭제하는 기능도 제공을 한다.
    • 로그 파일을 거대해서 압축하는데 시간이 오래 걸린다 해도 비동기 방식으로 동작하므로 application의 성능에는 영향을 주지 않는다.
  • Prudnect Mode
    • 다수의 JVM 인스턴스에서 같은 로그 파일을 사용하여 로그를 “안전하게” 기록할 수 있는 기능을 제공한다고 한다. 어떤 제한이 있는것 같다고 하는데 내부적으로 비동기 큐를 사용하고 있는 것이 아닌가 추측이 된다.
    • 정확하게 확인해 보지는 않았지만 이런 모델들이 보통 문제를 많이 일으키는 관계로 꼭 필요하지 않다면 되도록 사용하지 않는 것이 좋을 것 같다는 생각이다.
  • Conditional Processing Configurations
    • 기존에는 개발환경과 운영환경에 별도의 설정파일을 적용-개발환경에서는 로깅레벨을 DEBUG, 운영환경에서는 WARN-시키기 위해 로깅 설정파일을 분리해 놓고 사용하던 경험들이 있을 것이라 생각된다.
    • logback에서는 설정 파일에 if-then-else 의 조건문을 사용할 수 있어 설정 파일 하나로만 개발-운영 환경을 모두 커버할 수 있도록 만드는 것이 가능한다.
  • Stack Traces with Packaging Data
    • Logback을 사용해서 Stack Trace를 남기게 되면 각 패키지의 아티팩트 정보와 버전정보를 출력해 준다. 배포가 문제가 있다면 바로 확인이 가능하게 해주는 유용한 기능이라 생각된다.
    • 스크린샷 2015-01-18 21.02.29

프로젝트에 적용해 보기

1. 디펜던시 설정

logbook 사용하기 위해서는 logback-core, logback-classic, slf4j-api jar들이 필요한다. 디펜던시는 아래와 같이 설정한다.

메이븐을 사용하고 있다면 – [ pom.xml ]

<dependency>
	<groupId>ch.qos.logback</groupId>
	<artifactId>logback-core</artifactId>
	<version>1.1.2</version>
</dependency>
<dependency>
	<groupId>ch.qos.logback</groupId>
	<artifactId>logback-classic</artifactId>
	<version>1.1.2</version>
</dependency>
<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-api</artifactId>
	<version>1.7.10</version>
</Dependency>
<!— 조건부 설정을 사용하려면 아래의 디펜던시를 추가해 주어야 한다. —>
<dependency>
	<groupId>org.codehaus.janino</groupId>
	<artifactId>janino</artifactId>
	<version>2.7.7</version>
</dependency>

그래들을 사용하고 있다면 – [build.gradle]

compile 'ch.qos.logback:logback-classic:1.1.2'

compile 'ch.qos.logback:logback-core:1.1.2'

compile 'org.slf4j:slf4j-api:1.7.10'
// 조건부 설정을 사용하려면 아래의 디팬던시를 추가해 주어야 한다
compile 'org.codehaus.janino:janino:2.7.7'

2. logback.xml 파일의 설정은 아래와 같이 한다. 주석으로 처리한 부분을 잘 읽어보자.

<?xml version="1.0" encoding="UTF-8"?>
<!-- 30초마다 설정 파일의 변경을 확인한다. 파일이 변경되면 다시 로딩한다 -->
<configuration scan="true" scanPeriod="30 seconds">

    <!-- 외부 설정파일을 사용할 수 있다. -->
    <property resource="resource.properties"/>

    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern> %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${APP_HOME}/sujemall-webapp.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 파일이 하루에 한개씩 생성된다 -->
            <fileNamePattern>sujemall-webapp.%d{yyyy-MM-dd}.log</fileNamePattern>

            <!-- maxHIstory 설정은 위 부분에 롤링 정책에 따라 적용되 된다고 보면된다.
             위 설정데로 라면 30일이 지난 파일은 삭제가 된다.-->
            <maxHistory>30</maxHistory>
        </rollingPolicy>

        <encoder>
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <logger name="org.springframework" level="info"/>
    <logger name="org.hibernate" level="debug"/>
    <logger name="com.sujemall.webapp" level="debug"/>
    <if condition='property("RUN_MODE").equals("SERVICE")'>
        <then>
            <!-- 설정파일에 RUN_MODE 가 SERVICE로 정의되어 있으면 로깅 레벨을 INFO로 지정 -->
            <root level="info">
                <appender-ref ref="console"/>
                <appender-ref ref="FILE"/>
            </root>
        </then>
        <!-- 설정파일에 RUN_MODE 가 SERVICE로 정의되어 있지 않으면  로깅 레벨을 DEBUG 지정 -->
        <else>
            <root level="debug">
                <appender-ref ref="console"/>
                <appender-ref ref="FILE"/>
            </root>
        </else>
    </if>
</configuration>

3. 로그 찍기는 slf4j api를 사용하는 기존의 방식과 크게 다르지 않다. 마커를 이용해서 로그를 찍어봤다.

package com.sujemall.webapp.service;

import com.sujemall.webapp.model.User;
import com.sujemall.webapp.repository.UserRepository;
import com.sujemall.webapp.utils.CommonUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * Created by yhlee on 15. 1. 12..
 */

@Service
public class UserServiceImpl implements UserService {

    static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);

    @Autowired
    private UserRepository userRepository;

    @Override
    @Transactional
    public User enrollUser(User user) {

        user.setPassword(CommonUtils.hashString(user.getPassword()));
        user = userRepository.save(user);
        LOGGER.info("#### Success Save User : UserName is  {}, Email is {} " ,user.getUserName(), user.getMainEmail());
        return user;
    }
}

위와같이 설정하고 application을 돌려보면 아래와 같이 로그가 잘 찍히는 것을 확인할 수 있다.

스크린샷 2015-01-18 22.08.02

Java8의 람다 표현식 ( Lambda Expression)

Java8 에 Lambda식이 새로 추가되었다. 람다 표현식에 대해서 잘 모른다면 그 표현식을 봤을 때 문맥을 이해하기가 쉽지 않다.

Java에서의 lambda expression을 어떻게 사용하고 왜 사용하는지 간단하게 정리를 해 보고자 한다.

람다식은 함수형 언어에서 선호하는 “정의”연산자 “->” 를 사용한다. 최근에 함수형 언어가 주목을 받고 있고, Java에서도 이를 지원하려는 움직임으로 보인다.

Scala, Erlang를 비롯한 함수형 언어라는 것의 의미를 먼저 살펴보자.

“함수형 언어”는 “명령형 언어”와 대조적인 관계에 있다. 명령형 언어에서는 상태를 바꾸는 것을 강조하는 것과 달리, 함수형 언어에서는 함수를 정의하는 것에 중점을 둔다.

명령형 언어에서 “a = 1” 이라고 선언을 하는 것은 “a라는 변수에 1을 담아라” 라는 의미이지만, 함수형 언어에서는 a 를 1로 정의한다는 의미로 사용한다. ( 그런 의미에서 R같은 언어에서는 대입 연산자를 A <- 1 이라고 표기하기도 한다)

대입과 정의가 비슷한 것 같아 보이지만 큰 차이가 있다. 대입은 프로그램의 수행중에 언제든지 값이 변경될 수 있고, 정의는 값이 변경될 수 없는 것을 의미한다.

즉 수학에서  f(x) = x + 1 이라고 정의된 함수가 있다면 입력값 x 에 대해 항상 x + 1 이라는 동일한 결과값을 기대할 수 있게 된다. ( 명령형 언어에서라면 변수의 값이 변경될 수 있으므로 같은 입력에 대해 결과값이 달라질 수 있음)

입력에 대해 결과값이 동일하다는 특징은 멀티 스레딩 환경에서 큰 장점이 있고(Thread safe), 이러한 특징은 손쉽게 병렬 프로그래밍을 할 수 있게 해 준다.

이런 특징이 멀티코어 프로세싱이 요구되고 있는 근래의 프로그래밍 환경에서 함수형 언어가 다시 주목을 받게 된 이유라고 볼 수 있다.

Java에서 Lambda 표현식을 사용하는 목적은, 예전처럼 변수를 직접 전달하여 그 값을 변경함으로서 흐름을 처리하지 않고, 행위 자체(Behavor Parameter)를 전달함으로서 함수형 프로그램이 지향하는 바를 얻기 위함이라고 생각된다.사실 이러한 목적으로는 기존에 이미 익명 클래스(Anonymous Class)를 통해 해결할수도 있었던 문제이지만, Lambda식을 사용함으로 해서 코드를 좀 더 깔끔하고 가독성 좋게 만들수 있게 된 것이다.

그리고 공식적으로 “자바는 함수형 프로그래밍을 지원한다!”라고 얘기하려고 한것이 아닐까 하는 생각도..

Lambda표션식이 생소한 이유중 하나가 바로 전에는 클래스가 없는 함수를 사용할 수 없었지만, 이 Lambda라는 놈은 클래스 없이 함수 구현체가 존재하기 때문에 자바에 친숙한 사용자일수록 처음에는 어색하게 느껴질 수 있을 것 같다.

“Java 에서 Lambda 표현식은 추상 메소드가 하나만 있는 인터페이스(Funtional Interface)를 익명 클래스 대신 구현할 수 있게 해 주는 방법” 이라고 할 수 있다.

추상 메소드가 하나만 있는 인터페이스들 중에 우리가 잘 알고 있는 것들이 있다. Java.util.Comparator , java.lang,Runnable 등이 그것인데 Comparator를 이용해서  lambda 표현식을 사용할때와 그렇지 않을 때 어떻게 코드가 달라지는지 살펴보자.

아래와 같이 User 클래스가 있다.


public class User implements Comparable{

     private Long userId;
     private String userName;
     private String emailAddress;
     private Date joinDate;

     // getter &amp;amp; setter 코드 생략

     @Override
     public int compareTo(Object o) {

         User otherUser = (User) o;
         if (this.userId &gt; otherUser.getUserId()) {
             return 1;
         } else if (this.userId &lt; otherUser.getUserId()) {
             return -1;
         } else {
             return 0;
         }
     }
 }

이 User 의 목록이 있을 때 userId를 기준으로 sorting을 하는 코드를 익명 클래스를 이용해서 만들면 아래와 같다.

userList.sort(new Comparator&lt;User&gt;() {
   
@Override
  
 public int compare(User user1, User user2) {
      
       return user1.compareTo(user2);
   
  }

});

익명 클래스를 사용하지 않고 “Lambda”표현식을 이용하여 아래와 같이 코딩할 수 있다.

userList.sort((User o1, User o2) -&gt; o1.compareTo(o2));

이렇게 Lambda 표현식은 함수의 argument로 전달이 될수도 있고, 아래와 같이 변수에 할당하는 것도 가능하다,

Comparator &lt;User&gt; userComparator = (User u1, User u2) -&gt; u1.compareTo(u2);


userList.sort(userComparator);

아래 그림을 통해 Lambda 표현식을 어떻게 작성하는지 살펴보자.
lamgda_exp

Lambda 표현식은 “->” 를 기준으로 왼쪽과 오른쪽으로 나누어 지는데,

왼쪽의 (User 01, User 02) 부분은 functional interface – 추상 메소드가 하나만 있는 인터페이스- 에 선언된 추상메소드의 argument 를 나타낸다.
오른쪽 부분은 추상메소드의 구현내용 – implementation-을 적어주는데 return type은 interface 의 추상메소드 signature를 따라 자동으로 결정된다. ( Runnable의 경우 void 임 )