완벽한 JPA 엔터티 만들기 [닫기]


422

나는 JPA (implementation Hibernate)와 얼마 동안 일해 왔으며 엔티티를 만들어야 할 때마다 AccessType, 불변 속성, equals / hashCode 등의 문제로 어려움을 겪고있는 것으로 나타났습니다.
그래서 각 문제에 대한 일반적인 모범 사례를 찾아서 개인적인 용도로 작성하기로 결정했습니다.
그러나 나는 누군가 그것에 대해 의견을 말하거나 내가 틀렸다는 것을 말하지 않아도됩니다.

엔터티 클래스

  • 직렬화 가능 구현

    이유 : 사양에 따라야하지만 일부 JPA 제공자는이를 강제하지 않습니다. JPA 공급자로서의 최대 절전 모드는 이것을 강제하지 않지만 Serializable이 구현되지 않은 경우 ClassCastException으로 위장 어딘가에서 실패 할 수 있습니다.

생성자

  • 엔터티의 모든 필수 필드로 생성자를 만듭니다.

    이유 : 생성자는 항상 인스턴스를 정상 상태로 두어야합니다.

  • 이 생성자 외에 : 패키지 개인 기본 생성자를 가짐

    이유 : 기본 생성자는 최대 절전 모드에서 엔티티를 초기화해야합니다. private은 허용되지만 런타임 프록시 생성 및 바이트 코드 계측없이 효율적인 데이터 검색을 위해서는 패키지 private (또는 public) 가시성이 필요합니다.

분야 / 재산

  • 필요한 경우 일반적으로 현장 접근 및 재산 접근

    이유 : 하나 또는 다른 하나에 대한 분명하고 설득력있는 주장이 없기 때문에 이것은 아마도 가장 논쟁의 여지가있는 문제 일 것입니다 (속성 액세스 대 필드 액세스). 그러나 더 명확한 코드, 더 나은 캡슐화 및 불변 필드에 대한 세터를 만들 필요가 없기 때문에 필드 액세스가 일반적으로 선호되는 것 같습니다.

  • 변경 불가능한 필드에 대한 세터 생략 (액세스 유형 필드에는 필요하지 않음)

  • 속성은 개인이 될 수 있습니다
    한번 보호가 더 나은 (최대 절전 모드) 성능이라고 들었하지만 웹에서 찾을 수있는 모든은 다음과 같습니다 : 이유 공공, 민간 및 보호 분야에 직접뿐만 아니라, 최대 절전 모드가 액세스 할 수 공개, 개인 및 방법 접근 보호 . 선택은 당신에게 달려 있으며 당신은 그것을 당신의 어플리케이션 디자인에 맞출 수 있습니다.

같음 / 해시 코드

  • 이 ID가 엔티티를 유지할 때만 설정된 경우 생성 된 ID를 사용하지 마십시오
  • 기본 설정 : 변경 불가능한 값을 사용하여 고유 한 비즈니스 키를 형성하고이를 사용하여 동등성 테스트
  • 고유 한 비즈니스 키를 사용할 수없는 경우 엔터티가 초기화 될 때 생성되는 비 과도 UUID 를 사용하십시오 . 자세한 내용은 이 훌륭한 기사 를 참조하십시오.
  • 관련 엔티티 (ManyToOne)를 참조 하지 마십시오 . 이 엔티티 (부모 엔티티와 같은)가 비즈니스 키의 일부 여야하는 경우 ID 만 비교하십시오. 속성 액세스 유형을 사용하는 한 프록시에서 getId ()를 호출하면 엔티티로드가 트리거되지 않습니다 .

엔터티 예

@Entity
@Table(name = "ROOM")
public class Room implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue
    @Column(name = "room_id")
    private Integer id;

    @Column(name = "number") 
    private String number; //immutable

    @Column(name = "capacity")
    private Integer capacity;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "building_id")
    private Building building; //immutable

    Room() {
        // default constructor
    }

    public Room(Building building, String number) {
        // constructor with required field
        notNull(building, "Method called with null parameter (application)");
        notNull(number, "Method called with null parameter (name)");

        this.building = building;
        this.number = number;
    }

    @Override
    public boolean equals(final Object otherObj) {
        if ((otherObj == null) || !(otherObj instanceof Room)) {
            return false;
        }
        // a room can be uniquely identified by it's number and the building it belongs to; normally I would use a UUID in any case but this is just to illustrate the usage of getId()
        final Room other = (Room) otherObj;
        return new EqualsBuilder().append(getNumber(), other.getNumber())
                .append(getBuilding().getId(), other.getBuilding().getId())
                .isEquals();
        //this assumes that Building.id is annotated with @Access(value = AccessType.PROPERTY) 
    }

    public Building getBuilding() {
        return building;
    }


    public Integer getId() {
        return id;
    }

    public String getNumber() {
        return number;
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder().append(getNumber()).append(getBuilding().getId()).toHashCode();
    }

    public void setCapacity(Integer capacity) {
        this.capacity = capacity;
    }

    //no setters for number, building nor id

}

이 목록에 추가 할 다른 제안은 환영 이상입니다 ...

최신 정보

이 기사를 읽은 후 eq / hC 구현 방법을 조정했습니다.

  • 불변의 간단한 비즈니스 키를 사용할 수있는 경우 :
  • 다른 모든 경우 : uuid 사용

6
이것은 질문이 아니며 목록 요청과 함께 검토 요청입니다. 게다가, 그것은 매우 개방적이고 모호하거나 다르게 달리고 있습니다 : JPA 엔티티가 완벽한 지 여부는 그것이 사용될 용도에 달려 있습니다. 엔티티의 가능한 모든 용도에서 엔티티가 필요로하는 모든 것을 나열해야합니까?
meriton

내가 사과 한 분명한 질문이 아니라는 것을 알고 있습니다. 다른 제안은 환영하지만 실제로 목록 요청이 아니라 의견 / 발언 요청입니다. JPA 엔티티의 가능한 용도에 대해 자세히 설명하십시오.
Stijn Geukens 님이

나는 또한 필드를 원할 것 final입니다 (세터의 생략으로 판단하면 당신도 그렇게 생각합니다).
Sridhar Sarnobat

시도해야하지만 Hibernate가 여전히 해당 속성의 값을 설정할 수 있어야하기 때문에 final이 작동하지 않을 것이라고 생각합니다.
Stijn Geukens

어디 notNull에서 왔습니까?
bruno

답변:


73

몇 가지 핵심 사항에 대답하려고합니다. 여러 주요 응용 프로그램을 포함한 오랜 최대 절전 모드 / 지속성 경험입니다.

엔티티 클래스 : Serializable?

는 Serializable을 구현해야합니다. HttpSession에 들어가거나 RPC / Java EE를 통해 유선으로 전송되는 항목은 Serializable을 구현해야합니다. 다른 것들 : 그렇게 많지 않습니다. 중요한 일에 시간을 보내십시오.

생성자 : 엔티티의 모든 필수 필드로 생성자를 작성 하시겠습니까?

응용 프로그램 논리의 생성자에는 엔터티를 만들 때 항상 알려진 몇 가지 중요한 "외부 키"또는 "유형 / 종류"필드 만 있어야합니다. 나머지는 setter 메소드를 호출하여 설정해야합니다.

생성자에 너무 많은 필드를 넣지 마십시오. 생성자는 편리해야하며 객체에 기본 정신을 부여해야합니다. 이름, 유형 및 / 또는 부모는 모두 일반적으로 유용합니다.

OTOH 신청 규칙 (오늘)에 고객이 주소를 요구할 경우, 세터에게 맡기십시오. 이것이 "약한 규칙"의 예입니다. 다음 주에 세부 사항 입력 화면으로 이동하기 전에 고객 오브젝트를 작성 하시겠습니까? 자신을 트립하지 말고 알 수 없거나 불완전하거나 "부분적으로 입력 된"데이터를 남겨 두십시오.

생성자 : 또한 패키지 개인 기본 생성자?

예. 그러나 개인 패키지보다는 '보호'를 사용하십시오. 서브 클래 싱은 필요한 내부 요소가 보이지 않을 때 큰 고통입니다.

분야 / 재산

최대 절전 모드에 대해 인스턴스 외부에서 '속성'필드 액세스를 사용하십시오. 인스턴스 내에서 필드를 직접 사용하십시오. 이유 : 최대 절전 모드에서 가장 단순하고 가장 기본적인 방법 인 표준 반사가 작동하도록합니다.

애플리케이션에 '불변'필드는 최대 절전 모드에서 여전히로드 할 수 있어야합니다. 이러한 메소드를 '비공개'로 만들거나 애플리케이션 코드에 원치 않는 액세스를 방지하기 위해 주석을 달 수 있습니다.

참고 : equals () 함수를 작성할 때 'other'인스턴스의 값에 getter를 사용하십시오! 그렇지 않으면 프록시 인스턴스에서 초기화되지 않은 / 빈 필드가 발생합니다.

(최대 절전) 성능이 더 우수합니까?

있을 것 같지 않게.

같음 / 해시 코드?

이것은 저장되기 전에 엔티티와 작업하는 것과 관련이 있습니다. 이는 중대한 문제입니다. 불변 값에 대한 해싱 / 비교? 대부분의 비즈니스 응용 프로그램에는 없습니다.

고객은 주소 변경, 업체 이름 변경 등을 할 수 있지만 일반적이지 않습니다. 데이터가 올바르게 입력되지 않은 경우에도 정정 할 수 있어야합니다.

일반적으로 불변으로 유지되는 몇 가지 사항은 육아 및 아마도 유형 / 종류입니다. 일반적으로 사용자는 레코드를 변경하지 않고 레코드를 다시 만듭니다. 그러나 이것들은 실체를 고유하게 식별하지 못한다!

따라서 길고 짧게 주장 된 "불변의"데이터는 실제로는 아닙니다. 기본 키 / ID 필드는 안정성과 불변성을 보장하기 위해 정확한 목적으로 생성됩니다.

A) "자주 변경되지 않은 필드"를 비교 / 해시하는 경우 A) UI에서 "변경된 / 바운드 데이터"로 작업 할 때, 비교 및 ​​해싱 및 요청 처리 작업 단계를 계획하고 고려해야합니다. 저장하지 않은 데이터 '(ID를 비교 / 해시하는 경우)

Equals / HashCode-고유 한 비즈니스 키를 사용할 수없는 경우 엔터티가 초기화 될 때 생성되는 비 과도 UUID를 사용하십시오.

예, 이것은 필요할 때 좋은 전략입니다. UUID는 성능면에서 자유롭지 않으며 클러스터링은 복잡합니다.

Equals / HashCode-관련 엔터티를 참조하지 않음

"부모 엔티티와 같은 관련 엔티티가 비즈니스 키의 일부 여야하는 경우 삽입 할 수없고 업데이트 할 수없는 필드를 추가하여 상위 ID (ManyToOne JoinColumn과 동일한 이름으로)를 저장하고이 ID를 동등성 검사에 사용하십시오. "

좋은 조언처럼 들립니다.

도움이 되었기를 바랍니다!


2
Re : 생성자, 나는 종종 제로 arg 만 보며 (즉 없음) 호출 코드에는 엄청나게 긴 setter 목록이있어 조금 어지럽습니다. 필요에 맞는 두 개의 생성자가있어 호출 코드를 간결하게 만드는 데 실제로 문제가 있습니까?
허리케인

특히 ctor에 대해 완전히 의견이 있습니다. 더 아름다운 코드는 무엇입니까? obj의 제정신 상태를 생성하는 데 필요한 값 (조합) 또는 알 수없는 ctor를 설정하여 어떤 설정을해야하고 어떤 순서로 사용자 실수에 취약하게하는지 알 수있는 무응답 ctor ?
mohamnag

1
@mohamnag에 따라 다릅니다. 내부 시스템 생성 데이터의 경우, 유효한 Bean은 훌륭합니다. 그러나 최신 비즈니스 응용 프로그램은 많은 수의 사용자 데이터 입력 CRUD 또는 마법사 화면으로 구성됩니다. 사용자가 입력 한 데이터는 적어도 편집하는 동안 부분적으로 또는 형식이 잘못된 경우가 많습니다. 보험 응용 프로그램 캡처, 고객 가입 등을 생각하면 나중에 완료 할 수없는 불완전한 상태를 기록 할 수있는 비즈니스 가치가있는 경우가 종종 있습니다. 사업 상황.
Thomas W

1
@ThomasW 먼저 말하면, 나는 도메인 기반 디자인과 클래스 이름의 이름을 사용하고 메소드에 대한 완전한 동사를 의미하는 것에 대해 강력하게 의견을 모았습니다. 이 패러다임에서 말하는 것은 실제로 DTO이며 임시 데이터 저장에 사용해야하는 도메인 엔터티가 아닙니다. 또는 방금 도메인을 오해했거나 구조화했습니다.
mohamnag

@ThomasW 내가 초보자라고 말하려는 모든 문장을 걸러 내면 사용자 입력을 제외하고 의견에 남은 정보가 없습니다. 앞에서 말했듯이, 그 부분은 DTO에서 이루어지며 실체에서는 직접 수행되지 않습니다. Fowler와 같은 DDD 뒤의 큰 마음의 5 %가 될 수 있다고 50 년 후에 다시 이야기 해보십시오! 건배 : D
mohamnag

144

JPA 2.0 사양 한다고 :

  • 엔티티 클래스에는 인수가없는 생성자가 있어야합니다. 다른 생성자도있을 수 있습니다. 인수없는 생성자는 공개 또는 보호되어야합니다.
  • 엔티티 클래스는 최상위 클래스 여야합니다. 열거 형 또는 인터페이스를 엔티티로 지정해서는 안됩니다.
  • 엔터티 클래스는 최종 클래스가 아니어야합니다. 엔터티 클래스의 메서드 나 영구 인스턴스 변수는 최종적 일 수 없습니다.
  • 엔티티 인스턴스가 분리 된 객체로서 (예를 들어, 원격 인터페이스를 통해) 값에 의해 전달되는 경우 , 엔티티 클래스는 직렬화 가능 인터페이스를 구현해야합니다.
  • 추상 클래스와 구체적인 클래스는 모두 엔티티가 될 수 있습니다. 엔터티는 엔터티 클래스뿐만 아니라 비 엔티티 클래스를 확장 할 수 있으며 비 엔티티 클래스는 엔터티 클래스를 확장 할 수 있습니다.

사양에는 엔티티의 equals 및 hashCode 메소드 구현에 대한 요구 사항이 없으며 기본 키 클래스 및 맵 키에 대해서만 알고 있습니다.


13
True, equals, hashcode, ...는 JPA 요구 사항은 아니지만 물론 권장되고 모범 사례로 간주됩니다.
Stijn Geukens 님이

6
@TheStijn 글쎄, 분리 된 엔티티를 동등하게 비교하지 않는다면, 이것은 아마도 불필요 할 것입니다. 엔터티 관리자는 요청할 때마다 지정된 엔터티의 동일한 인스턴스를 반환합니다. 따라서 내가 이해하는 한 관리 대상 엔터티에 대한 ID 비교만으로도 잘 수행 할 수 있습니다. 이를 모범 사례로 생각할 수있는 시나리오에 대해 좀 더 자세히 설명해 주시겠습니까?
Edwin Dalorzo

2
나는 항상 equals / hashCode의 올바른 구현을 위해 노력하고 있습니다. JPA에는 필요하지 않지만 엔티티를 설정하거나 세트에 추가 할 때 좋은 방법이라고 생각합니다. 엔티티가 세트에 추가 될 때만 같음을 구현하도록 결정할 수 있지만 항상 미리 알고 있습니까?
Stijn Geukens가

10
@TheStijn JPA 공급자는 주어진 컨텍스트에서 주어진 엔터티의 인스턴스가 하나만 있도록 보장하므로 관리되는 엔터티 만 사용하는 경우 등호 / 하스 코드를 구현하지 않고도 세트가 안전합니다. 예를 들어, 엔티티에 대해 이러한 방법을 구현하는 데 어려움이없는 것은 아닙니다. 예를 들어 주제에 대한 이 최대 절전 모드 기사를 살펴보십시오 . 내 요점은 관리 대상 엔터티와 만 작업하는 경우 엔터티 없이도 더 나은 방법을 사용하고 그렇지 않으면 매우 신중하게 구현하는 것입니다.
Edwin Dalorzo

2
@TheStijn 이것은 좋은 혼합 시나리오입니다. 일단 엔티티가 지속성 계층의 안전을 포기하면 더 이상 JPA 표준에 의해 시행되는 규칙을 신뢰할 수 없기 때문에 처음 제안한대로 eq / hC를 구현할 필요가 있습니다. 우리의 경우, DTO 패턴은 처음부터 건축 적으로 시행되었습니다. 지속성 API는 비즈니스 오브젝트와 상호 작용하는 공용 방법을 제공하지 않으며 DTO를 사용하여 지속성 계층과 상호 작용하는 API 만 제공합니다.
Edwin Dalorzo

13

여기에 대한 답변에 2 센트가 추가되었습니다.

  1. 필드 또는 속성 액세스 (성능 고려 사항 제외)와 관련하여 getter 및 setter를 통해 두 가지 모두 합법적으로 액세스하므로 내 모델 논리는 동일한 방식으로 설정하거나 가져올 수 있습니다. 지속성 런타임 제공자 (Hibernate, EclipseLink 또는 기타)가 테이블 B의 일부 열을 참조하는 외래 키가있는 테이블 A의 일부 레코드를 지속 / 설정해야 할 때 차이가 발생합니다. 속성 액세스 유형의 경우 지속성 런타임 시스템은 코딩 된 setter 방법을 사용하여 표 B 열의 셀에 새로운 값을 할당합니다. 필드 액세스 유형의 경우, 지속성 런타임 시스템은 표 B 열의 셀을 직접 설정합니다. 이 차이점은 단방향 관계에서 중요하지 않습니다. 그러나 setter 메소드가 일관성을 설명하도록 잘 설계된 경우 양방향 관계에 대해 내 코딩 된 setter 메소드 (속성 액세스 유형)를 사용해야합니다. 일관성은 양방향 관계에서 중요한 문제입니다.잘 설계된 세터에 대한 간단한 예제 링크 .

  2. Equals / hashCode와 관련하여 : 양방향 관계에 참여하는 엔티티에 대해 Eclipse 자동 생성 Equals / hashCode 메소드를 사용할 수 없습니다. 그렇지 않으면 순환 참조가 발생하여 스택 오버 플로우 예외가 발생합니다. 양방향 관계 (예 : OneToOne)를 시도하고 Equals () 또는 hashCode () 또는 toString ()을 자동 생성하면이 스택 오버플로 예외가 발생합니다.


9

엔터티 인터페이스

public interface Entity<I> extends Serializable {

/**
 * @return entity identity
 */
I getId();

/**
 * @return HashCode of entity identity
 */
int identityHashCode();

/**
 * @param other
 *            Other entity
 * @return true if identities of entities are equal
 */
boolean identityEquals(Entity<?> other);
}

모든 엔티티에 대한 기본 구현은 Equals / Hashcode 구현을 단순화합니다.

public abstract class AbstractEntity<I> implements Entity<I> {

@Override
public final boolean identityEquals(Entity<?> other) {
    if (getId() == null) {
        return false;
    }
    return getId().equals(other.getId());
}

@Override
public final int identityHashCode() {
    return new HashCodeBuilder().append(this.getId()).toHashCode();
}

@Override
public final int hashCode() {
    return identityHashCode();
}

@Override
public final boolean equals(final Object o) {
    if (this == o) {
        return true;
    }
    if ((o == null) || (getClass() != o.getClass())) {
        return false;
    }

    return identityEquals((Entity<?>) o);
}

@Override
public String toString() {
    return getClass().getSimpleName() + ": " + identity();
    // OR 
    // return ReflectionToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
}

실체 impl :

@Entity
@Table(name = "ROOM")
public class Room extends AbstractEntity<Integer> {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "room_id")
private Integer id;

@Column(name = "number") 
private String number; //immutable

@Column(name = "capacity")
private Integer capacity;

@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "building_id")
private Building building; //immutable

Room() {
    // default constructor
}

public Room(Building building, String number) {
    // constructor with required field
    notNull(building, "Method called with null parameter (application)");
    notNull(number, "Method called with null parameter (name)");

    this.building = building;
    this.number = number;
}

public Integer getId(){
    return id;
}

public Building getBuilding() {
    return building;
}

public String getNumber() {
    return number;
}


public void setCapacity(Integer capacity) {
    this.capacity = capacity;
}

//no setters for number, building nor id
}

JPA 엔티티의 모든 경우에 비즈니스 필드를 기반으로 엔티티의 동등성을 비교할 필요가 없습니다. 이러한 JPA 엔터티가 도메인 기반 엔터티 (이 코드 예제의 경우) 대신 도메인 기반 ValueObject로 생각되는 경우 더 많은 경우가 있습니다.


4
보일러 플레이트 코드를 취하기 위해 부모 엔티티 클래스를 사용하는 것이 좋은 접근 방법이지만 equals 메소드에서 DB 정의 ID를 사용하는 것은 좋지 않습니다. 귀하의 경우 2 개의 새로운 엔티티를 비교하면 NPE가 발생할 수도 있습니다. 널 (null) 안전으로 설정하더라도 지속될 때까지 두 개의 새 엔티티는 항상 동일합니다. Eq / hC는 불변이어야합니다.
Stijn Geukens

2
Equals ()는 DB id가 null인지 여부를 확인하기 때문에 NPE를 throw하지 않으며 DB id가 null 인 경우 동등성이 false입니다.
ahaaman

3
실제로 코드가 null 안전하다는 것을 놓친 방법을 알 수 없습니다. 그러나 ID를 사용하는 IMO는 여전히 나쁜 습관입니다. 인수 : onjava.com/pub/a/onjava/2006/09/13/…
Stijn Geukens

본 버논 (Vaughn Vernon)의 'IDDementing DDD'책에서, "이른 PK 생성"(ID를 먼저 생성하여 데이터베이스 생성을 허용하지 않고 엔티티의 생성자에 전달하면 ID를 동일하게 사용할 수 있다고 주장함) 엔티티를 유지할 때 id.)
Wim Deblauwe

또는 동일 비 영구 엔티티 비교 계획이없는 경우? 왜 당신이해야
합니까
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.