JPA hashCode () / equals () 딜레마


311

가 있었다 몇 가지 논의 JPA 엔티티와 여기에 대해 hashCode()/ equals()JPA의 엔티티 클래스에 사용되어야 구현입니다. 그들 중 대부분은 Hibernate에 의존하지만 JPA 구현 중립적으로 논의하고 싶습니다 (그런데 EclipseLink를 사용하고 있습니다).

가능한 모든 구현에는 다음과 관련하여 자체 장점단점 이 있습니다.

  • hashCode()/equals() 계약 적합성 에 대한 (불변) List/ Set운영
  • 이든 동일한 개체 (예를 들면 서로 다른 세션에서, 느리게로드 된 데이터 구조로부터 동적 프록시)가 검출 될 수있다
  • 엔티티가 분리 (또는 비 지속) 상태 에서 올바르게 작동하는지 여부

내가 알 수 있듯이 세 가지 옵션이 있습니다 .

  1. 재정의하지 마십시오. 에 의존 Object.equals()하고Object.hashCode()
    • hashCode()/ equals()
    • 동일한 객체를 식별 할 수 없음, 동적 프록시 문제
    • 분리 된 엔티티에 문제가 없음
  2. 기본 키를 기준으로 재정의
    • hashCode()/ equals()깨졌다
    • 올바른 ID (모든 관리 대상 엔터티)
    • 분리 된 엔터티 문제
  3. Business-Id (기본이 아닌 키 필드, 외래 키는 어떻습니까)를 기준으로이를 재정의하십시오 .
    • hashCode()/ equals()깨졌다
    • 올바른 ID (모든 관리 대상 엔터티)
    • 분리 된 엔티티에 문제가 없음

내 질문은 :

  1. 옵션 및 / 또는 프로 / 콘택 포인트가 누락 되었습니까?
  2. 어떤 옵션을 선택했으며 그 이유는 무엇입니까?



업데이트 1 :

" hashCode()/ equals()깨진"이라는 말은 연속적인 hashCode()호출이 다른 값을 리턴 할 수 있음을 의미하며 , 이는 (정확하게 구현 된 경우) ObjectAPI 문서 의 의미에서 손상되지 않지만 Map, Set또는 해시 기반 Collection. 따라서 JPA 구현 (적어도 EclipseLink)은 경우에 따라 올바르게 작동하지 않습니다.

업데이트 2 :

답변 주셔서 감사합니다. 대부분 뛰어난 품질을 가지고 있습니다.
불행히도 실제 응용 프로그램에 가장 적합한 방법이나 응용 프로그램에 가장 적합한 방법을 결정하는 방법은 여전히 ​​확실하지 않습니다. 따라서 질문을 계속 열어두고 더 많은 토론이나 의견이 있기를 바랍니다.


4
"hashCode () / equals () broken"
nanda

4
옵션 2와 3에서 같은 전략을 사용하여 equals ()와 hashCode ()를 모두 구현하는 것처럼, 그런 의미에서 "파손"되지 않습니다.
matt b

11
이것은 옵션 3에 해당되지 않습니다. hashCode ()와 equals ()는 동일한 기준을 사용해야합니다. 따라서 필드 중 하나가 변경되면 예, hashcode () 메소드는 이전과 동일한 인스턴스에 대해 다른 값을 리턴합니다. 그러나 equals ()도 마찬가지입니다. hashcode () javadoc에서 문장의 두 번째 부분을 생략 했습니다. Java 응용 프로그램을 실행하는 동안 동일한 객체에서 두 번 이상 호출 될 때마다 hashCode 메소드는 정보가 없으면 동일한 정수를 일관되게 반환해야합니다. 등호에 사용 된 개체에 대한 비교가 수정 됩니다.
matt b

1
실제로 문장의 해당 부분은 반대를 의미합니다 . 구현에 hashcode()사용 된 필드가 equals()변경 되지 않는 한 동일한 객체 인스턴스를 호출 하면 동일한 값을 반환해야합니다 . 즉, 클래스에 세 개의 필드가 있고 equals()메소드가 두 필드 만 사용하여 인스턴스의 동등성을 결정하는 hashcode()경우 해당 필드 값 중 하나를 변경하면 리턴 값이 변경 될 것으로 예상 할 수 있습니다. 이 객체 인스턴스는 더 이상 이전 인스턴스가 나타내는 값과 "같지 않습니다".
matt b

2
"Map, Set 또는 기타 해시 기반 컬렉션에서 변경된 엔터티를 검색 할 때 문제 발생"... "HashMap, HashSet 또는 기타 해시 기반 컬렉션에서 변경된 엔터티를 검색 할 때 문제 발생"
nanda

답변:


122

주제에이 아주 좋은 기사를 읽기 : 음주 최대 절전 모드가 ID를 도용시키지 .

기사의 결론은 다음과 같습니다.

객체가 데이터베이스에 유지 될 때 객체 아이덴티티는 기만적으로 올바르게 구현하기가 어렵습니다. 그러나 문제는 개체를 저장하기 전에 ID없이 개체를 존재하게하는 것에서 비롯됩니다. 우리는 Hibernate와 같은 객체 관계형 매핑 프레임 워크로부터 객체 ID를 할당하는 책임을지게함으로써 이러한 문제를 해결할 수있다. 대신, 객체가 인스턴스화되는 즉시 객체 ID를 할당 할 수 있습니다. 따라서 개체 ID를 간단하고 오류없이 만들 수 있으며 도메인 모델에 필요한 코드 양이 줄어 듭니다.


21
아니요, 좋은 기사가 아닙니다. 그것은 주제에 관한 놀라운 기사이며 모든 JPA 프로그래머에게 읽어야합니다! +1!
Tom Anderson

2
예, 같은 솔루션을 사용하고 있습니다. DB가 ID를 생성하지 못하게하면 객체를 생성하고이를 유지하기 전에 이미 객체를 참조하는 다른 객체를 생성하는 것과 같은 다른 이점도 있습니다. 이를 통해 클라이언트-서버 앱에서 대기 시간 및 여러 요청 / 응답주기를 제거 할 수 있습니다. 그러한 솔루션에 대한 영감이 필요하면 내 프로젝트 suid.jssuid-server-java를 확인하십시오 . 기본적으로 클라이언트 측을 가져 와서 사용할 수있는 suid.jsID 블록을 가져옵니다 suid-server-java.
Stijn de Witt

2
이것은 단순히 미쳤다. 나는 후드 아래에서 작업을 최대 절전 모드로 시작하고 단위 테스트를 작성 중이며 객체를 수정 한 후 세트에서 객체를 삭제할 수 없으며 해시 코드 변경 때문이라고 결론 지었지만 방법을 이해할 수 없다는 것을 알았습니다. 해결하다. 기사는 간단하고 화려합니다!
XMight

좋은 기사입니다. 그러나 처음으로 링크를 보는 사람들에게는 대부분의 응용 프로그램에 과도한 영향을 줄 수 있다고 제안합니다. 이 페이지에 나열된 다른 3 가지 옵션은 여러 가지 방법으로 문제를 어느 정도 해결해야합니다.
HopeKing

1
Hibernate / JPA는 엔티티의 equals 및 hashcode 메소드를 사용하여 레코드가 데이터베이스에 이미 존재하는지 확인합니까?
Tushar Banne

64

항상 equals / hashcode를 재정의하고 비즈니스 ID를 기반으로 구현합니다. 나에게 가장 합리적인 해결책 인 것 같습니다. 다음 링크를 참조하십시오 .

이 모든 것을 요약하면 equals / hashCode를 처리하는 다른 방법으로 작동하거나 작동하지 않는 항목이 있습니다. 여기에 이미지 설명을 입력하십시오

편집 :

이것이 왜 효과가 있는지 설명하려면 :

  1. 일반적으로 JPA 응용 프로그램에서 해시 기반 컬렉션 (HashMap / HashSet)을 사용하지 않습니다. 필요한 경우 UniqueList 솔루션을 만드는 것을 선호합니다.
  2. 런타임에 비즈니스 ID를 변경하는 것이 모든 데이터베이스 응용 프로그램에 대한 모범 사례가 아니라고 생각합니다. 드물게 다른 솔루션이없는 경우 요소를 제거하고 해시 기반 컬렉션에 다시 넣는 것과 같은 특별한 처리를 수행합니다.
  3. 내 모델의 경우 생성자에 비즈니스 ID를 설정하고 해당 설정자를 제공하지 않습니다. JPA 구현 이 속성 대신 필드 를 변경 하도록했습니다.
  4. UUID 솔루션은 과도하게 보입니다. 자연적인 사업체 ID가있는 경우 왜 UUID입니까? 결국 데이터베이스에서 비즈니스 ID의 고유성을 설정했습니다. 그러면 데이터베이스의 각 테이블에 대해 세 개의 인덱스가있는 이유는 무엇 입니까?

1
그러나이 테이블에는 다섯 번째 줄 "List / Sets와 함께 작동"이 없습니다 (OneToMany 매핑에서 Set의 일부인 엔터티를 제거하려는 경우). ) 계약을 위반하는 변경.
MRalwasser

질문에 대한 의견을 참조하십시오. equals / hashcode contract을 오해하는 것 같습니다
nanda

1
@ MRalwasser : 당신이 옳은 것을 의미한다고 생각합니다. 그것은 위반 된 equals / hashCode () 계약 자체가 아닙니다. 그러나 변경 가능한 equals / hashCode는 세트 계약에 문제를 발생시킵니다 .
Chris Lercher

3
@MRalwasser : 비즈니스 ID가 변경되는 경우에만 해시 코드가 변경 될 수 있으며 요점은 비즈니스 ID 변경 되지 않는 것입니다. 따라서 해시 코드는 변경되지 않으며 해시 컬렉션과 완벽하게 작동합니다.
Tom Anderson

1
자연스러운 비즈니스 키가 없으면 어떻게합니까? 예를 들어, 그래프 그리기 응용 프로그램에서 2 차원 점 Point (X, Y)의 경우? 그 지점을 엔티티로 어떻게 저장 하시겠습니까?
jhegedus

35

엔티티에는 일반적으로 두 개의 ID가 있습니다.

  1. 지속성 계층에만 해당되므로 지속성 공급자와 데이터베이스가 개체 간의 관계를 파악할 수 있습니다.
  2. 우리의 애플리케이션 요구 사항에 대한인가 ( equals()그리고 hashCode()특히)

구경하다:

@Entity
public class User {

    @Id
    private int id;  // Persistence ID
    private UUID uuid; // Business ID

    // assuming all fields are subject to change
    // If we forbid users change their email or screenName we can use these
    // fields for business ID instead, but generally that's not the case
    private String screenName;
    private String email;

    // I don't put UUID generation in constructor for performance reasons. 
    // I call setUuid() when I create a new entity
    public User() {
    }

    // This method is only called when a brand new entity is added to 
    // persistence context - I add it as a safety net only but it might work 
    // for you. In some cases (say, when I add this entity to some set before 
    // calling em.persist()) setting a UUID might be too late. If I get a log 
    // output it means that I forgot to call setUuid() somewhere.
    @PrePersist
    public void ensureUuid() {
        if (getUuid() == null) {
            log.warn(format("User's UUID wasn't set on time. " 
                + "uuid: %s, name: %s, email: %s",
                getUuid(), getScreenName(), getEmail()));
            setUuid(UUID.randomUUID());
        }
    }

    // equals() and hashCode() rely on non-changing data only. Thus we 
    // guarantee that no matter how field values are changed we won't 
    // lose our entity in hash-based Sets.
    @Override
    public int hashCode() {
        return getUuid().hashCode();
    }

    // Note that I don't use direct field access inside my entity classes and
    // call getters instead. That's because Persistence provider (PP) might
    // want to load entity data lazily. And I don't use 
    //    this.getClass() == other.getClass() 
    // for the same reason. In order to support laziness PP might need to wrap
    // my entity object in some kind of proxy, i.e. subclassing it.
    @Override
    public boolean equals(final Object obj) {
        if (this == obj)
            return true;
        if (!(obj instanceof User))
            return false;
        return getUuid().equals(((User) obj).getUuid());
    }

    // Getters and setters follow
}

편집 :setUuid() 메소드 호출과 관련된 요점을 명확히 합니다. 일반적인 시나리오는 다음과 같습니다.

User user = new User();
// user.setUuid(UUID.randomUUID()); // I should have called it here
user.setName("Master Yoda");
user.setEmail("yoda@jedicouncil.org");

jediSet.add(user); // here's bug - we forgot to set UUID and 
                   //we won't find Yoda in Jedi set

em.persist(user); // ensureUuid() was called and printed the log for me.

jediCouncilSet.add(user); // Ok, we got a UUID now

테스트를 실행하고 로그 출력을 볼 때 문제를 해결합니다.

User user = new User();
user.setUuid(UUID.randomUUID());

또는 별도의 생성자를 제공 할 수 있습니다.

@Entity
public class User {

    @Id
    private int id;  // Persistence ID
    private UUID uuid; // Business ID

    ... // fields

    // Constructor for Persistence provider to use
    public User() {
    }

    // Constructor I use when creating new entities
    public User(UUID uuid) {
        setUuid(uuid);
    }

    ... // rest of the entity.
}

내 예는 다음과 같습니다.

User user = new User(UUID.randomUUID());
...
jediSet.add(user); // no bug this time

em.persist(user); // and no log output

기본 생성자와 세터를 사용하지만 두 생성자가 더 적합한 방법을 찾을 수 있습니다.


2
나는 이것이 정확하고 좋은 해결책이라고 믿습니다. 정수는 일반적으로 데이터베이스 인덱스에서 uuid보다 성능이 우수하기 때문에 약간의 성능 이점이 있습니다. 그러나 그 외에도 현재 정수 id 속성을 제거하고 (응용 프로그램 할당) uuid?
Chris Lercher

4
JVM 평등과 지속성 평등에 기본 hashCode/ equals메소드 를 사용하는 것과 어떻게 다른 id가요? 이것은 전혀 이해가되지 않습니다.
Behrang Saeedzadeh

2
데이터베이스에서 동일한 행을 가리키는 여러 엔티티 오브젝트가있는 경우 작동합니다. Object의는 equals()반환 false이 경우. UUID 기반 equals()반환합니다 true.
앤드류 Андрей Листочкин

4
-1-ID가 두 개인 신분이있는 이유는 없습니다. 이것은 완전히 무의미하며 잠재적으로 해로울 수 있습니다.
Tom Anderson

1
내가 선호하는 솔루션을 가리 키지 않고 솔루션을 비난 해 죄송합니다. 요컨대, 객체에 단일 ID 필드를 제공하고이를 기반으로 equals 및 hashCode를 구현하고 데이터베이스에 저장할 때가 아니라 객체 생성시 값을 생성합니다. 이렇게하면 모든 형태의 객체가 비 영구적, 지속성 및 분리 된 방식으로 작동합니다. 최대 절전 모드 프록시 (또는 유사한)도 올바르게 작동해야하며 equals 및 hashCode 호출을 처리하기 위해 수화 할 필요조차 없다고 생각합니다.
Tom Anderson

31

저는 개인적으로이 세 가지 상태를 모두 다른 프로젝트에서 이미 사용했습니다. 그리고 옵션 1은 실제 응용 프로그램에서 가장 실용적이라고 생각해야합니다. 내 경험상 hashCode () / equals () 적합성을 깨는 것은 엔티티가 컬렉션에 추가 된 후 평등의 결과가 변경되는 상황에서 매번 끝날 것이므로 많은 미친 버그로 이어집니다.

그러나 추가 옵션이 있습니다 (장단점도 있습니다).


a) 해시 코드 / 세트에 기초하여 일치 한 불변 , null이 , 할당 생성자 필드

(+) 세 가지 기준 모두 보장

새 인스턴스를 만들려면 (-) 필드 값을 사용할 수 있어야합니다

(-) 다음 중 하나를 변경해야하는 경우 처리가 복잡해집니다.


b) hashCode / 같음 (JPA 대신 애플리케이션에서 생성자에 의해 지정된 기본 키를 기준으로 함)

(+) 세 가지 기준 모두 보장

(-) DB 시퀀스와 같은 단순하고 안정적인 ID 생성 상태를 활용할 수 없습니다.

(-) 분산 된 환경 (클라이언트 / 서버) 또는 앱 서버 클러스터에서 새로운 엔티티가 생성되면 복잡


c) 엔티티의 생성자가 할당 한 UUID를 기반으로하는 hashCode / 같음

(+) 세 가지 기준 모두 보장

(-) UUID 생성 오버 헤드

(-)는 사용 된 알고리즘에 따라 동일한 UUID의 두 배가 사용될 위험이 적을 수 있습니다 (DB의 고유 색인으로 감지 될 수 있음)


나는 옵션 1접근법 C의 팬 이기도합니다. 더 민첩한 접근 방식이 필요할 때까지 아무것도하지 마십시오.
Adam Gent

2
옵션 (b)의 경우 +1 IMHO, 엔터티에 자연적인 비즈니스 ID가있는 경우 데이터베이스 기본 키이기도합니다. 간단하고 간단하며 훌륭한 데이터베이스 디자인입니다. 그러한 ID가 없으면 대리 키가 필요합니다. 객체 생성시 설정하면 다른 모든 것이 간단합니다. 사람들이 자연 키를 사용하지 않는 때입니다 조기 그들이 곤경에 얻을 대리 키를 생성하지 않습니다. 구현의 복잡성에 관해서는 그렇습니다. 그러나 실제로는 많지 않으며 모든 엔티티에 대해 한 번 해결하는 매우 일반적인 방법으로 수행 할 수 있습니다.
Tom Anderson

나는 또한 옵션 1을 선호하지만, equals 메소드를 구현해야하기 때문에 전체 동등성을 주장하기 위해 단위 테스트를 작성하는 방법은 큰 문제입니다 Collection.
OOD 워터볼

하지 마십시오. 참조 않는 최대 절전 함께 엉망
alonana

29

동일한 엔티티 가 한 번만 존재할 수 equals()/hashCode()있다는 의미에서 세트 에 사용하려는 경우 옵션 2 만 있습니다. 옵션 2 이는 정의에 따라 엔티티 의 기본 키 가 변경되지 않기 때문입니다 (누군가가 실제로 업데이트하는 경우) 그것은 더 이상 같은 실체가 아니다)

문자 그대로 사용해야 equals()/hashCode()합니다. 기본 키를 기반으로하므로 기본 키가 설정 될 때까지 이러한 방법을 사용해서는 안됩니다. 따라서 기본 키가 할당 될 때까지 집합에 엔터티를 넣지 않아야합니다. (예, UUID 및 이와 유사한 개념은 기본 키를 조기에 할당하는 데 도움이 될 수 있습니다.)

이제는 소위 "비즈니스 키"가 변경할 수있는 단점이 있지만 옵션 3을 사용하여 이론적으로 달성 할 수도 있습니다. "세트에서 이미 삽입 된 엔티티를 삭제하기 만하면됩니다 ( ) 다시 삽입하십시오. " 그것은 사실이지만 분산 시스템에서는 데이터가 삽입 된 모든 곳에서 수행되어야하며 업데이트가 수행되었는지 확인해야합니다. 다른 일이 발생하기 전에). 특히 일부 원격 시스템에 연결할 수없는 경우 정교한 업데이트 메커니즘이 필요합니다.

옵션 1은 세트의 모든 오브젝트가 동일한 최대 절전 모드 세션에있는 경우에만 사용할 수 있습니다. Hibernate 문서는 13.1.3 장에서 이것을 명확하게한다 . 객체 아이덴티티 고려 :

세션 내에서 응용 프로그램은 ==를 사용하여 객체를 안전하게 비교할 수 있습니다.

그러나 세션 외부에서 ==를 사용하는 응용 프로그램은 예기치 않은 결과를 생성 할 수 있습니다. 예기치 않은 장소에서도 발생할 수 있습니다. 예를 들어, 분리 된 두 인스턴스를 동일한 세트에 배치하면 둘 다 동일한 데이터베이스 ID를 가질 수 있습니다 (즉, 동일한 행을 나타냄). 그러나 분리 된 상태의 인스턴스에 대해서는 정의에 따라 JVM ID가 보장되지 않습니다. 개발자는 영속 클래스에서 equals () 및 hashCode () 메소드를 재정의하고 자체 객체 평등 개념을 구현해야합니다.

옵션 3에 찬성하여 계속 주장하고 있습니다.

한 가지주의 사항이 있습니다. 데이터베이스 식별자를 사용하여 동등성을 구현하지 마십시오. 고유하고 일반적으로 불변 인 속성의 조합 인 비즈니스 키를 사용하십시오. 임시 오브젝트가 지속되면 데이터베이스 ID가 변경됩니다. 임시 인스턴스 (일반적으로 분리 된 인스턴스와 함께)가 세트에 유지되는 경우 해시 코드를 변경하면 세트의 계약이 중단됩니다.

이것은 사실입니다 만약 당신이

  • ID를 조기에 할당 할 수 없습니다 (예 : UUID 사용)
  • 그러나 객체는 일시적인 상태에서 객체를 세트에 넣기를 원합니다.

그렇지 않으면 옵션 2를 자유롭게 선택할 수 있습니다.

그런 다음 상대적인 안정성이 필요하다고 언급합니다.

비즈니스 키의 속성은 데이터베이스 기본 키만큼 안정적 일 필요는 없습니다. 객체가 동일한 세트에있는 한 안정성 만 보장하면됩니다.

맞습니다. 내가 실제로 볼 수있는 문제는 다음과 같습니다. 절대 안정성을 보장 할 수없는 경우 "객체가 같은 세트에있는 한"안정성을 어떻게 보장 할 수 있습니까? 대화를 위해서만 세트를 사용하고 버리는 것과 같은 특별한 경우를 상상할 수는 있지만 이것의 일반적인 실행 가능성에 의문을 제기합니다.


짧은 버전 :

  • 옵션 1은 단일 세션 내의 개체에만 사용할 수 있습니다.
  • 가능하면 옵션 2를 사용하십시오. PK가 할당 될 때까지 개체를 세트로 사용할 수 없으므로 가능한 빨리 PK를 할당하십시오.
  • 상대적 안정성을 보장 할 수있는 경우 옵션 3을 사용할 수 있습니다. 그러나이 점에주의하십시오.

기본 키가 절대 변경되지 않는다는 가정은 거짓입니다. 예를 들어, 최대 절전 모드는 세션이 저장 될 때 기본 키만 할당합니다. 따라서 기본 키를 hashCode로 사용하면 객체를 처음 저장하기 전과 처음 저장 한 후 hashCode ()의 결과가 달라집니다. 게다가 세션을 저장하기 전에 새로 생성 된 두 개의 객체는 동일한 hashCode를 가지며 컬렉션에 추가 될 때 서로 덮어 쓸 수 있습니다. 객체 생성시 즉시 해당 접근 방식을 사용하도록 저장 / 세척을 수행해야 할 수도 있습니다.
William Billingsley

2
@ 윌리엄 : 엔터티 의 기본 키는 변경되지 않습니다. 매핑 된 객체 의 id 속성 이 변경 될 수 있습니다. 이것은 특히 과도 객체가 영속적 일 때 설명 된대로 발생합니다 . equals / hashCode 메소드에 대해 언급 한 답변의 일부를주의 깊게 읽으십시오. "기본 키가 설정 될 때까지이 메소드를 사용해서는 안됩니다."
Chris Lercher

전적으로 동의합니다. 옵션 2를 사용하면 수퍼 클래스에서 equals / hashcode를 제외하고 모든 엔티티에서 재사용 할 수 있습니다.
Theo

+1 저는 JPA를 처음 사용하지만 여기에있는 일부 의견과 답변은 사람들이 "기본 키"라는 용어의 의미를 이해하지 못했음을 암시합니다.
Raedwald

16
  1. 비즈니스 키 가 있으면 equals/에 해당 를 사용해야합니다 hashCode.
  2. 비즈니스 키가없는 경우 기본 키 Object와 hashCode 구현을 그대로두면 안됩니다.이 키 는 사용자 merge와 엔터티 이후에는 작동하지 않기 때문입니다 .
  3. 이 게시물에서 제안한 엔터티 식별자를 사용할 수 있습니다 . 유일한 문제점은 다음 hashCode과 같이 항상 동일한 값을 리턴 하는 구현 을 사용해야한다는 것입니다.

    @Entity
    public class Book implements Identifiable<Long> {
    
        @Id
        @GeneratedValue
        private Long id;
    
        private String title;
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Book)) return false;
            Book book = (Book) o;
            return getId() != null && Objects.equals(getId(), book.getId());
        }
    
        @Override
        public int hashCode() {
            return 31;
        }
    
        //Getters and setters omitted for brevity
    }

어느 것이 더 낫습니다 : (1) onjava.com/pub/a/onjava/2006/09/13/… 또는 (2) vladmihalcea.com/… ? 솔루션 (2)가 (1)보다 쉽습니다. 그래서 왜 (1)을 사용해야합니까? 둘의 효과가 동일합니까? 둘 다 동일한 솔루션을 보장합니까?
nimo23

그리고 솔루션을 사용하면 동일한 인스턴스간에 "hashCode 값이 변경되지 않습니다". 이것은 (솔루션 (1)에서) "동일한"uuid 인 것과 같은 동작을합니다. 내가 맞아?
nimo23

1
그리고 UUID를 데이터베이스에 저장하고 레코드 및 버퍼 풀의 설치 공간을 늘리십시오. 이것이 고유 한 hashCode보다 장기적으로 더 많은 성능 문제를 일으킬 수 있다고 생각합니다. 다른 솔루션의 경우 모든 엔티티 상태 전환에서 일관성을 제공하는지 확인할 수 있습니다. GitHub에서이를 확인하는 테스트를 찾을 수 있습니다 .
Vlad Mihalcea

1
변경 불가능한 비즈니스 키가있는 경우 hashCode가이를 사용할 수 있으며 여러 버킷에서 이점을 얻을 수 있으므로 하나의 버킷이 있으면 유용하게 사용할 수 있습니다. 그렇지 않으면 기사에서 설명한대로 엔티티 식별자를 사용하십시오.
Vlad Mihalcea

1
나는 당신이 그것을 좋아해서 기쁘다. 나는이 수백 더욱 JPA와 최대 절전 모드에 대한 기사를.
Vlad Mihalcea

10

비즈니스 키 (옵션 3)를 사용하는 것이 가장 일반적으로 권장되는 방법 이지만 ( Hibernate 커뮤니티 Wiki , "Hibernate를 사용한 Java Persistence", 398 페이지) 가장 많이 사용하는 방식이지만 Hibernate 버그가있어 열성적으로 가져 왔습니다. 세트 : HHH-3799 . 이 경우 Hibernate는 필드가 초기화되기 전에 엔티티를 세트에 추가 할 수 있습니다. 권장되는 비즈니스 키 접근 방식이 실제로 문제가되기 때문에이 버그가 왜 더주의를 기울이지 않았는지 잘 모르겠습니다.

문제의 핵심은 equals와 hashCode가 불변 상태 ( Odersky et al. 참조)를 기반으로해야하며 Hibernate 관리 기본 키를 가진 Hibernate 엔티티 에는 그러한 불변 상태 가 없다는 것 입니다. 기본 키는 임시 객체가 지속될 때 Hibernate에 의해 수정됩니다. 비즈니스 키는 초기화 과정에서 개체를 수화시킬 때 Hibernate에 의해 수정됩니다.

옵션 1 만 남겨두고, 객체 아이덴티티를 기반으로하는 java.lang.Object 구현을 상속하거나, "Do n't Let Hibernate Steal Your Identity" (Stijn Geukens의 답변에서 이미 참조) 에서 James Brundege가 제안한 애플리케이션 관리 기본 키를 사용합니다. ) 및 Lance Arlaus의 "객체 생성 : 최대 절전 모드 통합 방법" .

옵션 1의 가장 큰 문제는 분리 된 인스턴스를 .equals ()를 사용하는 영구 인스턴스와 비교할 수 없다는 것입니다. 그러나 괜찮습니다. equals와 hashCode의 계약은 개발자가 각 클래스에 대한 평등의 의미를 결정하도록합니다. 따라서 equals와 hashCode가 Object에서 상속되도록하십시오. 분리 된 인스턴스를 영구 인스턴스와 비교해야하는 경우 해당 목적에 따라 boolean sameEntity또는 boolean dbEquivalent또는 으로 명시 적으로 새 메소드를 작성할 수 있습니다 boolean businessEquals.


5

앤드류의 대답에 동의합니다. 우리는 응용 프로그램에서 동일한 작업을 수행하지만 UARD를 VARCHAR / CHAR로 저장하는 대신 두 개의 긴 값으로 나눕니다. UUID.getLeastSignificantBits () 및 UUID.getMostSignificantBits ()를 참조하십시오.

고려해야 할 또 다른 사항은 UUID.randomUUID () 호출이 매우 느리므로 지속성 또는 equals () / hashCode () 호출과 같이 필요할 때만 느리게 UUID를 생성하는 것이 좋습니다.

@MappedSuperclass
public abstract class AbstractJpaEntity extends AbstractMutable implements Identifiable, Modifiable {

    private static final long   serialVersionUID    = 1L;

    @Version
    @Column(name = "version", nullable = false)
    private int                 version             = 0;

    @Column(name = "uuid_least_sig_bits")
    private long                uuidLeastSigBits    = 0;

    @Column(name = "uuid_most_sig_bits")
    private long                uuidMostSigBits     = 0;

    private transient int       hashCode            = 0;

    public AbstractJpaEntity() {
        //
    }

    public abstract Integer getId();

    public abstract void setId(final Integer id);

    public boolean isPersisted() {
        return getId() != null;
    }

    public int getVersion() {
        return version;
    }

    //calling UUID.randomUUID() is pretty expensive, 
    //so this is to lazily initialize uuid bits.
    private void initUUID() {
        final UUID uuid = UUID.randomUUID();
        uuidLeastSigBits = uuid.getLeastSignificantBits();
        uuidMostSigBits = uuid.getMostSignificantBits();
    }

    public long getUuidLeastSigBits() {
        //its safe to assume uuidMostSigBits of a valid UUID is never zero
        if (uuidMostSigBits == 0) {
            initUUID();
        }
        return uuidLeastSigBits;
    }

    public long getUuidMostSigBits() {
        //its safe to assume uuidMostSigBits of a valid UUID is never zero
        if (uuidMostSigBits == 0) {
            initUUID();
        }
        return uuidMostSigBits;
    }

    public UUID getUuid() {
        return new UUID(getUuidMostSigBits(), getUuidLeastSigBits());
    }

    @Override
    public int hashCode() {
        if (hashCode == 0) {
            hashCode = (int) (getUuidMostSigBits() >> 32 ^ getUuidMostSigBits() ^ getUuidLeastSigBits() >> 32 ^ getUuidLeastSigBits());
        }
        return hashCode;
    }

    @Override
    public boolean equals(final Object obj) {
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof AbstractJpaEntity)) {
            return false;
        }
        //UUID guarantees a pretty good uniqueness factor across distributed systems, so we can safely
        //dismiss getClass().equals(obj.getClass()) here since the chance of two different objects (even 
        //if they have different types) having the same UUID is astronomical
        final AbstractJpaEntity entity = (AbstractJpaEntity) obj;
        return getUuidMostSigBits() == entity.getUuidMostSigBits() && getUuidLeastSigBits() == entity.getUuidLeastSigBits();
    }

    @PrePersist
    public void prePersist() {
        // make sure the uuid is set before persisting
        getUuidLeastSigBits();
    }

}

실제로 equals () / hashCode ()를 재정의하면 어쨌든 모든 엔터티에 대해 UUID를 생성해야합니다 (코드에서 만든 모든 엔터티를 유지하려는 것으로 가정합니다). 데이터베이스에 처음 저장하기 전에 한 번만 수행하십시오. 그 후에 UUID는 Persistence Provider에 의해로드됩니다. 따라서 나는 게으르게 행동하는 요점을 보지 못합니다.
Andrew Андрей Листочкин

UUID를 데이터베이스에 숫자 쌍으로 저장하고 equals () 메소드 내부의 특정 유형으로 캐스팅하지 않는 다른 아이디어를 좋아하기 때문에 귀하의 답변을 투표했습니다. 정말 깔끔합니다! 앞으로이 두 가지 요령을 확실히 사용할 것입니다.
Andrew Андрей Листочкин

1
투표 해 주셔서 감사합니다. UUID를 느리게 초기화하는 이유는 앱에서 HashMap에 넣거나 지속되지 않는 많은 엔티티를 생성했기 때문입니다. 따라서 객체를 만들 때 성능이 100 배 떨어졌습니다 (100,000 개). 따라서 필요한 경우에만 UUID를 초기화합니다. 128 비트 숫자에 대한 MySql의 지원이 좋았으므로 id에도 UUID를 사용할 수 있고 auto_increment에 신경 쓰지 않아도됩니다.
Drew

아, 알겠습니다 필자의 경우 해당 엔티티를 컬렉션에 넣지 않으면 UUID 필드를 선언조차하지 않습니다. 단점은 때때로 우리가 실제로 컬렉션에 넣어야하기 때문에 추가해야한다는 것입니다. 이것은 때때로 개발 과정에서 발생하지만 고객에게 처음 배포 한 후에는 결코 발생하지 않았으므로 큰 문제가되지 않았습니다. 시스템이 가동 된 후에 이런 상황이 발생하면 DB 마이그레이션이 필요합니다. 게으른 UUID는 이러한 상황에서 매우 유용합니다.
Andrew Андрей Листочкин

어쩌면 상황에서 성능이 중요한 문제인 경우 Adam이 자신의 답변에서 제안한 더 빠른 UUID 생성기를 사용해보십시오.
Andrew Андрей Листочкин

3

다른 사람들이 이미 지적한 것처럼 이미 지적했듯이 수많은 전략이 있습니다. 적용되는 디자인 패턴의 대부분이 성공을 향해 해킹을 시도하는 경우가있는 것 같습니다. 특수 생성자와 팩토리 메소드로 생성자 호출을 완전히 방해하지 않으면 생성자 액세스를 제한합니다. 실제로 명확한 컷 API로 항상 유쾌합니다. 그러나 유일한 이유는 equals 및 hashcode 재정의가 응용 프로그램과 호환되도록하는 것이라면 해당 전략이 KISS (Keep It Simple Stupid)를 준수하는지 궁금합니다.

나를 위해 id를 검사하여 equals와 hashcode를 재정의하고 싶습니다. 이 방법에서는 id가 null이 아니어야 하며이 동작을 잘 문서화해야합니다. 따라서 개발자를 다른 곳에 보관하기 전에 새로운 엔티티를 유지하는 계약이 될 것입니다. 이 계약을 준수하지 않는 응용 프로그램은 1 분 안에 실패 할 것입니다.

주의 사항 : 엔터티가 다른 테이블에 저장되어 있고 공급자가 기본 키에 대해 자동 생성 전략을 사용하는 경우 엔터티 유형간에 중복 된 기본 키를 얻게됩니다. 이 경우 런타임 유형을 Object # getClass () 호출과 비교 하면 물론 두 가지 유형이 동일하다고 간주 할 수 없습니다. 그것은 대부분 나에게 적합합니다.


Mysql과 같은 DB 누락 시퀀스가 ​​있어도이를 시뮬레이션 할 수 있습니다 (예 : table hibernate_sequence). 따라서 항상 테이블에서 고유 한 ID를 얻을 수 있습니다. +++ 그러나 필요하지 않습니다. Object#getClass() H. 프록시 때문에 전화 가 잘못되었습니다. 부름이 Hibernate.getClass(o)도움이되지만 다른 종류의 개체 평등 문제는 여전히 남아 있습니다. 약간 복잡하지만 사용 가능한 canEqual 을 사용하는 솔루션이 있습니다 . 일반적으로 필요하지 않다는 데 동의했습니다. +++ null ID로 eq / hc를 던지면 계약에 위배되지만 매우 실용적입니다.
maaartinus

2

여기에 이미 매우 유익한 답변이 있지만 우리가하는 일을 알려 드리겠습니다.

우리는 아무것도하지 않습니다 (즉, 무시하지 않습니다).

컬렉션에서 작동하기 위해 equals / hashcode가 필요한 경우 UUID를 사용합니다. 생성자에서 UUID를 작성하기 만하면됩니다. 우리는 UUID에 http://wiki.fasterxml.com/JugHome 을 사용 합니다. UUID는 약간 현명한 CPU이지만 직렬화 및 db 액세스에 비해 저렴합니다.


1

나는 이러한 토론을 알고 있었고 올바른 일을 알기 전까지는 아무것도하지 않는 것이 좋기 때문에 과거에는 항상 옵션 1을 사용했습니다. 이러한 시스템은 모두 여전히 성공적으로 실행되고 있습니다.

그러나 다음에 데이터베이스 생성 ID를 사용하여 옵션 2를 시도 할 수 있습니다.

ID가 설정되지 않은 경우 해시 코드와 같음은 IllegalStateException을 발생시킵니다.

이렇게하면 저장되지 않은 엔터티와 관련된 미묘한 오류가 예기치 않게 표시되지 않습니다.

사람들은이 접근법에 대해 어떻게 생각합니까?


1

비즈니스 키 접근 방식은 우리에게 적합하지 않습니다. 딜레마를 해결하기 위해 DB 생성 ID , 임시 임시 tempId를 사용 하고 equal () / hashcode ()를 재정의 합니다. 모든 엔터티는 엔터티의 자손입니다. 장점 :

  1. DB에 추가 필드가 없습니다
  2. 자손 엔터티에 추가 코딩이 없으며 모든 사람을위한 하나의 접근법
  3. UUID와 같은 성능 문제 없음, DB Id 생성
  4. 해시 맵에는 문제가 없습니다 (등등의 사용을 명심할 필요는 없습니다)
  5. 새로운 엔티티의 해시 코드는 지속 후에도 시간이 바뀌지 않습니다.

단점 :

  1. 지속되지 않는 엔터티를 직렬화 및 역 직렬화하는 데 문제가있을 수 있습니다.
  2. 저장된 엔터티의 해시 코드는 DB에서 다시로드 한 후 변경 될 수 있습니다
  3. 지속되는 객체가 항상 다른 것으로 간주되지는 않습니다 (아마도 맞습니까?)
  4. 또 뭐요?

우리의 코드를보십시오 :

@MappedSuperclass
abstract public class Entity implements Serializable {

    @Id
    @GeneratedValue
    @Column(nullable = false, updatable = false)
    protected Long id;

    @Transient
    private Long tempId;

    public void setId(Long id) {
        this.id = id;
    }

    public Long getId() {
        return id;
    }

    private void setTempId(Long tempId) {
        this.tempId = tempId;
    }

    // Fix Id on first call from equal() or hashCode()
    private Long getTempId() {
        if (tempId == null)
            // if we have id already, use it, else use 0
            setTempId(getId() == null ? 0 : getId());
        return tempId;
    }

    @Override
    public boolean equals(Object obj) {
        if (super.equals(obj))
            return true;
        // take proxied object into account
        if (obj == null || !Hibernate.getClass(obj).equals(this.getClass()))
            return false;
        Entity o = (Entity) obj;
        return getTempId() != 0 && o.getTempId() != 0 && getTempId().equals(o.getTempId());
    }

    // hash doesn't change in time
    @Override
    public int hashCode() {
        return getTempId() == 0 ? super.hashCode() : getTempId().hashCode();
    }
}

1

사전 정의 된 유형 식별자 및 ID를 기반으로 다음 접근 방식을 고려하십시오.

JPA에 대한 특정 가정 :

  • 동일한 "유형"및 동일한 널이 아닌 ID의 엔티티는 동일한 것으로 간주됩니다.
  • 비 지속적 엔티티 (ID가 없다고 가정)는 다른 엔티티와 동일하지 않습니다.

추상 실체 :

@MappedSuperclass
public abstract class AbstractPersistable<K extends Serializable> {

  @Id @GeneratedValue
  private K id;

  @Transient
  private final String kind;

  public AbstractPersistable(final String kind) {
    this.kind = requireNonNull(kind, "Entity kind cannot be null");
  }

  @Override
  public final boolean equals(final Object obj) {
    if (this == obj) return true;
    if (!(obj instanceof AbstractPersistable)) return false;
    final AbstractPersistable<?> that = (AbstractPersistable<?>) obj;
    return null != this.id
        && Objects.equals(this.id, that.id)
        && Objects.equals(this.kind, that.kind);
  }

  @Override
  public final int hashCode() {
    return Objects.hash(kind, id);
  }

  public K getId() {
    return id;
  }

  protected void setId(final K id) {
    this.id = id;
  }
}

구체적인 실체 예제 :

static class Foo extends AbstractPersistable<Long> {
  public Foo() {
    super("Foo");
  }
}

테스트 예 :

@Test
public void test_EqualsAndHashcode_GivenSubclass() {
  // Check contract
  EqualsVerifier.forClass(Foo.class)
    .suppress(Warning.NONFINAL_FIELDS, Warning.TRANSIENT_FIELDS)
    .withOnlyTheseFields("id", "kind")
    .withNonnullFields("id", "kind")
    .verify();
  // Ensure new objects are not equal
  assertNotEquals(new Foo(), new Foo());
}

주요 장점 :

  • 간단
  • 서브 클래스가 타입 아이덴티티를 제공하도록 보장
  • 프록시 클래스의 예상 동작

단점 :

  • 각 엔티티가 전화해야 함 super()

노트:

  • 상속을 사용할 때주의가 필요합니다. 의 예 : 인스턴스 평등 class Aclass B extends A응용 프로그램의 구체적인 세부 사항에 따라 달라질 수 있습니다.
  • 비즈니스 키를 ID로 사용하는 것이 이상적입니다.

귀하의 의견을 기대합니다.


0

이것은 Java 및 JPA를 사용하는 모든 IT 시스템에서 공통적 인 문제입니다. 문제점은 equals () 및 hashCode () 구현을 넘어 조직이 엔티티를 참조하는 방법과 클라이언트가 동일한 엔티티를 참조하는 방법에 영향을줍니다. 나는 내 견해를 표현하기 위해 내 자신의 블로그 를 썼을 때 비즈니스 키가 없다는 고통을 충분히 보았다 .

요컨대, RAM 이외의 스토리지에 의존하지 않고 생성되는 비즈니스 키로 의미있는 접두어가있는 짧고 사람이 읽을 수있는 순차 ID를 사용하십시오. 트위터의 눈송이 는 아주 좋은 예입니다.


0

IMO에는 equals / hashCode를 구현하기위한 3 가지 옵션이 있습니다

  • 응용 프로그램 생성 ID (예 : UUID)를 사용하십시오.
  • 비즈니스 키를 기반으로 구현
  • 기본 키를 기반으로 구현

응용 프로그램에서 생성 한 ID를 사용하는 것이 가장 쉬운 방법이지만 몇 가지 단점이 있습니다.

  • 128 비트가 단순히 32 또는 64 비트보다 크기 때문에 PK로 사용할 때 결합 속도가 느려집니다.
  • "디버깅은 더 어렵다"자신의 눈으로 확인하기 때문에 어떤 데이터가 정확한지 확인하는 것은 매우 어렵다

이러한 단점을 해결 하려면이 방법을 사용하십시오.

조인 문제를 극복하기 위해 UUID를 자연 키로 사용하고 시퀀스 값을 기본 키로 사용할 수 있지만 조인 기반으로하기를 원하기 때문에 ID가 포함 된 컴포지션 자식 엔터티에서 equals / hashCode 구현 문제가 계속 발생할 수 있습니다 기본 키에. 자식 엔터티 ID에서 자연 키를 사용하고 부모를 참조하기 위해 기본 키를 사용하는 것이 좋습니다.

@Entity class Parent {
  @Id @GeneratedValue Long id;
  @NaturalId UUID uuid;
  @OneToMany(mappedBy = "parent") Set<Child> children;
  // equals/hashCode based on uuid
}

@Entity class Child {
  @EmbeddedId ChildId id;
  @ManyToOne Parent parent;

  @Embeddable class ChildId {
    UUID parentUuid;
    UUID childUuid;
    // equals/hashCode based on parentUuid and childUuid
  }
  // equals/hashCode based on id
}

IMO는 모든 단점을 피하고 동시에 시스템 내부를 노출시키지 않고 외부 시스템과 공유 할 수있는 가치 (UUID)를 제공하므로 가장 깨끗한 접근 방식입니다.

사용자가 좋은 아이디어라고 생각할 수 있지만 몇 가지 단점도 있습니다. 비즈니스 키를 기반으로 구현하십시오.

대부분의 경우이 비즈니스 키는 사용자가 제공하는 일종의 코드 이며 여러 속성의 합성 횟수는 적습니다.

  • 가변 길이 텍스트를 기반으로하는 결합이 느리기 때문에 결합이 느립니다. 키가 특정 길이를 초과하면 일부 DBMS에서 색인 작성에 문제가있을 수도 있습니다.
  • 내 경험상 비즈니스 키는 변경되는 경향이 있으며이를 참조하는 개체에 대한 계단식 업데이트가 필요합니다. 외부 시스템이이를 참조하는 경우 불가능합니다

IMO는 비즈니스 키를 독점적으로 구현하거나 사용해서는 안됩니다. 사용자가 해당 비즈니스 키로 신속하게 검색 할 수있는 훌륭한 추가 기능이지만 시스템은 운영에 의존해서는 안됩니다.

기본 키를 기반으로 구현하면 문제가 있지만 그렇게 큰 문제는 아닙니다.

외부 시스템에 ID를 노출해야하는 경우 제안한 UUID 방식을 사용하십시오. 그렇지 않은 경우 여전히 UUID 접근 방식을 사용할 수 있지만 반드시 그럴 필요는 없습니다. equals / hashCode에서 DBMS 생성 ID를 사용하는 문제는 ID를 지정하기 전에 오브젝트가 해시 기반 콜렉션에 추가되었을 수 있다는 사실에서 비롯됩니다.

이 문제를 해결하는 확실한 방법은 id를 할당하기 전에 해시 기반 컬렉션에 객체를 추가하지 않는 것입니다. ID를 이미 할당하기 전에 중복 제거를 원할 수 있기 때문에 이것이 항상 가능한 것은 아니라는 것을 알고 있습니다. 해시 기반 컬렉션을 계속 사용하려면 id를 할당 한 후 컬렉션을 다시 작성하면됩니다.

다음과 같이 할 수 있습니다.

@Entity class Parent {
  @Id @GeneratedValue Long id;
  @OneToMany(mappedBy = "parent") Set<Child> children;
  // equals/hashCode based on id
}

@Entity class Child {
  @EmbeddedId ChildId id;
  @ManyToOne Parent parent;

  @PrePersist void postPersist() {
    parent.children.remove(this);
  }
  @PostPersist void postPersist() {
    parent.children.add(this);
  }

  @Embeddable class ChildId {
    Long parentId;
    @GeneratedValue Long childId;
    // equals/hashCode based on parentId and childId
  }
  // equals/hashCode based on id
}

나는 정확한 접근법을 직접 테스트하지 않았기 때문에 지속 전후 이벤트에서 콜렉션 변경이 어떻게 작동하는지 잘 모르겠지만 아이디어는 다음과 같습니다.

  • 해시 기반 컬렉션에서 개체를 일시적으로 제거
  • 그것을 유지
  • 해시 기반 컬렉션에 개체를 다시 추가

이 문제를 해결하는 또 다른 방법은 업데이트 / 지속 후에 모든 해시 기반 모델을 간단히 다시 작성하는 것입니다.

결국, 그것은 당신에게 달려 있습니다. 나는 개인적으로 대부분 시퀀스 기반 접근법을 사용하고 외부 시스템에 식별자를 노출 해야하는 경우에만 UUID 접근법을 사용합니다.


0

instanceofJava 14 의 새로운 스타일을 사용하면 equals한 줄로 구현할 수 있습니다 .

@Override
public boolean equals(Object obj) {
    return this == obj || id != null && obj instanceof User otherUser && id.equals(otherUser.id);
}

@Override
public int hashCode() {
    return 31;
}

-1

UUID가 많은 사람들에게 정답이라면 비즈니스 계층의 팩토리 메소드를 사용하여 엔티티를 작성하고 작성시 기본 키를 지정하지 않는 이유는 무엇입니까?

예를 들면 다음과 같습니다.

@ManagedBean
public class MyCarFacade {
  public Car createCar(){
    Car car = new Car();
    em.persist(car);
    return car;
  }
}

이 방법으로 우리는 영속 제공자로부터 엔티티에 대한 기본 기본 키를 얻을 것이고, 우리의 hashCode () 및 equals () 함수는 그것에 의존 할 수 있습니다.

또한 Car의 생성자를 보호 된 것으로 선언 한 다음 비즈니스 방법에 반영을 사용하여 액세스 할 수 있습니다. 이런 식으로 개발자는 새로운 방법으로 자동차를 인스턴스화하지 않고 팩토리 방식을 통해 의도합니다.

어때요?


데이터베이스 조회를 수행 할 때 guid를 생성하는 두 가지 성능 모두를 기꺼이 감수하려는 경우 효과적입니다.
Michael Wiles 2016 년

1
단위 테스트 자동차는 어떻습니까? 이 경우 테스트를 위해 데이터베이스 연결이 필요합니까? 또한 도메인 개체는 지속성에 의존해서는 안됩니다.
jhegedus

-1

나는이 질문에 스스로 대답하려고 노력했지만이 게시물과 특히 DREW를 읽을 때까지 찾은 솔루션에 완전히 만족하지 않았습니다. 나는 그가 게으른 UUID를 만들고 그것을 최적으로 저장하는 방식을 좋아했습니다.

그러나 각 솔루션의 장점으로 엔터티가 처음 지속되기 전에 hashCode () / equals ()에 액세스 할 때만 더 유연하게 UUID 만 생성하십시오.

  • equals ()는 "객체가 동일한 논리적 개체를 참조 함"을 의미합니다
  • 작업을 두 번 수행하는 이유 (성능 문제)로 데이터베이스 ID를 최대한 많이 사용하십시오.
  • 아직 유지되지 않은 엔터티에서 hashCode () / equals ()에 액세스하는 동안 문제를 방지하고 실제로 지속 된 후에도 동일한 동작을 유지합니다.

아래의 혼합 솔루션에 대한 피드백을 실제로 정확하게 계산하겠습니다.

public class MyEntity { 

    @Id()
    @Column(name = "ID", length = 20, nullable = false, unique = true)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id = null;

    @Transient private UUID uuid = null;

    @Column(name = "UUID_MOST", nullable = true, unique = false, updatable = false)
    private Long uuidMostSignificantBits = null;
    @Column(name = "UUID_LEAST", nullable = true, unique = false, updatable = false)
    private Long uuidLeastSignificantBits = null;

    @Override
    public final int hashCode() {
        return this.getUuid().hashCode();
    }

    @Override
    public final boolean equals(Object toBeCompared) {
        if(this == toBeCompared) {
            return true;
        }
        if(toBeCompared == null) {
            return false;
        }
        if(!this.getClass().isInstance(toBeCompared)) {
            return false;
        }
        return this.getUuid().equals(((MyEntity)toBeCompared).getUuid());
    }

    public final UUID getUuid() {
        // UUID already accessed on this physical object
        if(this.uuid != null) {
            return this.uuid;
        }
        // UUID one day generated on this entity before it was persisted
        if(this.uuidMostSignificantBits != null) {
            this.uuid = new UUID(this.uuidMostSignificantBits, this.uuidLeastSignificantBits);
        // UUID never generated on this entity before it was persisted
        } else if(this.getId() != null) {
            this.uuid = new UUID(this.getId(), this.getId());
        // UUID never accessed on this not yet persisted entity
        } else {
            this.setUuid(UUID.randomUUID());
        }
        return this.uuid; 
    }

    private void setUuid(UUID uuid) {
        if(uuid == null) {
            return;
        }
        // For the one hypothetical case where generated UUID could colude with UUID build from IDs
        if(uuid.getMostSignificantBits() == uuid.getLeastSignificantBits()) {
            throw new Exception("UUID: " + this.getUuid() + " format is only for internal use");
        }
        this.uuidMostSignificantBits = uuid.getMostSignificantBits();
        this.uuidLeastSignificantBits = uuid.getLeastSignificantBits();
        this.uuid = uuid;
    }

"내가 유지되기 전에이 엔티티에서 생성 된 UUID 하루"란 무엇입니까? 이 경우에 대한 예를 들어 주시겠습니까?
jhegedus

할당 된 생성 유형을 사용할 수 있습니까? 신원 생성 유형이 필요한 이유는 무엇입니까? 할당보다 장점이 있습니까?
jhegedus

1) 새 MyEntity를 만들고 2) 목록에 넣고 3) 데이터베이스에 저장 한 다음 4) DB에서 해당 항목을 다시로드하고 5)로드 된 인스턴스가 목록에 있는지 확인하려고하면 어떻게됩니까? . 내 생각 엔 그래도 그래도 안 될 것입니다.
jhegedus

내가 분명하지 않다는 것을 보여준 첫 의견에 감사드립니다. 첫째, "지속되기 전에이 엔티티에서 생성 된 UUID"는 오타였습니다. "IT가 지속되기 전에"대신 읽어야했습니다. 다른 말에 대해서는 해결책을 더 잘 설명하기 위해 곧 게시물을 편집하겠습니다.
user2083808

-1

실제로 옵션 2 (기본 키)가 가장 자주 사용되는 것 같습니다. 자연스럽고 IMMUTABLE 비즈니스 키는 드물지만 합성 키를 생성하고 지원하는 것은 너무 무거워서 결코 일어날 수없는 상황을 해결할 수 없습니다. 한 번 봐 가지고 스프링 데이터 JPA AbstractPersistable (: 유일한 구현 최대 절전 모드 구현 사용을Hibernate.getClass ).

public boolean equals(Object obj) {
    if (null == obj) {
        return false;
    }
    if (this == obj) {
        return true;
    }
    if (!getClass().equals(ClassUtils.getUserClass(obj))) {
        return false;
    }
    AbstractPersistable<?> that = (AbstractPersistable<?>) obj;
    return null == this.getId() ? false : this.getId().equals(that.getId());
}

@Override
public int hashCode() {
    int hashCode = 17;
    hashCode += null == getId() ? 0 : getId().hashCode() * 31;
    return hashCode;
}

HashSet / HashMap에서 새 객체를 조작하는 것을 알고 있습니다. 반대로 옵션 1 (나머지 Object구현)은 직후에 중단됩니다 merge. 이는 매우 일반적인 상황입니다.

비즈니스 키가없고 REAL이 해시 구조에서 새 엔터티를 조작해야하는 hashCode경우 Vlad Mihalcea가 조언 한대로 상수로 재정의 합니다.


-2

아래는 스칼라를위한 간단 하고 테스트 된 솔루션입니다.

  • 이 솔루션은 질문에 제공된 3 가지 범주 중 어느 것도 적합하지 않습니다.

  • 모든 엔터티는 UUIDEntity의 하위 클래스이므로 DRY (Dont-repeat-yourself) 원칙을 따릅니다.

  • 필요한 경우 더 많은 의사 난수를 사용하여 UUID 생성을보다 정확하게 만들 수 있습니다.

스칼라 코드 :

import javax.persistence._
import scala.util.Random

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
abstract class UUIDEntity {
  @Id  @GeneratedValue(strategy = GenerationType.TABLE)
  var id:java.lang.Long=null
  var uuid:java.lang.Long=Random.nextLong()
  override def equals(o:Any):Boolean= 
    o match{
      case o : UUIDEntity => o.uuid==uuid
      case _ => false
    }
  override def hashCode() = uuid.hashCode()
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.