Whiteship's Note

[하이버네이트] detached 객체의 동일성

Hibernate/Chapter 9 : 2009.06.25 13:54


참조: JPWH 9.2.2 ~ 9.2.3

두 가지 동일성이 있다. 자바 객체 동일성과 DB 동일성이 있다. 자바 동일성은 == 으로 비교를 하고, DB 동일성은 주키 값을 비교한다. 자바 동일성과 DB 동일성이 모두 같을 조건을 Scope of object identity 라고 한다.

그 중에 세 가지 조건은 다음과 같다.
- No identity scope
- Persistence context-scoped identity
- Process-scoped identity

이 중에 하이버네이트는 Persistence context-scoped identity를 구현했다.

Session session1 = sessionFactory.openSession();
Transaction tx1 = session1.beginTransaction();

// "1234" 식별자로 Item 가져오기
Object a = session1.get(Item.class, new Long(1234) );
Object b = session1.get(Item.class, new Long(1234) );

( a==b ) // True, a와 b는 동일하다.
tx1.commit();
session1.close();

// a와 b 레퍼런스는 detached 상태 객체가 된다.

Session session2 = sessionFactory.openSession();
Transaction tx2 = session2.beginTransaction();

Object c = session2.get(Item.class, new Long(1234) );
( a==c ) // False, detached 객체 a와 persistent 객체 c는 동일하지 않다.(session context가 다르기 때문에..)

tx2.commit();
session2.close();

따라서 이렇게 된다. 하지만 여전히 DB id는 가지고 있기 때문에, a, b, c를 id 값으로 비교하면 모두 같은 객체로 인식할 수 있다. equlas()로 비교할 땐 equals()를 어떻게 구현하느냐에 따라 달리질 것이다. equals()를 별도로 구현하지 않으면 Object의 equals()를 사용할테니 == 비교와 다를 바가 없다.

equals() 비교가 중요해지는 시기는 Set 컬렉션을 사용할 때다. 아시다시피 Set은 컬렉션에 요소를 추가하기 전에 기본에 추가되어 있는 것들과 equals() 비교를 해본 다음에 false인 것만 추가한다.

그렇다면, 위의 코드가 모두 깥는 뒤 (detached) 객체 3개(a, b, c)를 Set에 추가하면 어떻게 될까? 과연 Set에는 몇 개의 객체가 들어갈까?

2개다. 일단, equals()를 별도로 구현하지 않아서, == 비교를 할 텐데 이미 위에서 == 비교를 해봤더니 a와 b는 같은 객체를 참조하고 있고, c는 또 다른 객체기 때문이다.

원하던 결과는 몇 개 일까? 한 개일 것이다. 셋 다 같은 DB 레코드를 가리키기 때문에, Set에는 한 개만 들어가는게 맞을 것이다. 그렇게 하려면 어떻게 해야 할까?

DB id를 사용해서 동일성을 비교하는 equals()를 재정의하면 될 것이다. equals()를 재정의하면 hashCode()도 반드시 재정의해서 같은 객체는 같은 해쉬코드를 반환하도록 해야겠다.

결론적으로, detached 객체를 다룰 때는 동일성에 주의하고, 동일성 문제가 발생할 시 equals()와 hashCode()를 적절하게 재정의 할 것.




top

  1. Favicon of http://blog.lckymn.com BlogIcon Kevin 2009.06.26 01:48 PERM. MOD/DEL REPLY

    제가 알기로 HashSet같은 Set은
    이미 저장된 객체들의 equals() 를 먼저 비교하는게 아니라
    저장된 객체들의 hashCode()를 먼저 다 비교해 본후에 동일 hash code가 없으면
    동일 객체가 없는것으로 판단해서 저장을 하고,
    동일 hash code가 발견되면 그때 equals() 비교를 해서
    true면 동일 객체로 판단해서 저장을 하지 않고,
    false면 다른 객체로 판단해서 저장을 하는것으로 알고 있습니다.

    위의 경우 말고도 문제가 되는 경우가 있겠죠.
    가령 DB에서 id로 쓰는 자동생성되는 surrogate key 같은걸
    equals()와 hashCode() 에 사용해 버린 객체를 DB저장전에
    HashSet에 넣었다가 DB에 저장을 하면
    저장 전에는 id값이 null (Integer 나 Long 타입) 이나 0 이었다가
    저장 되면서 id값을 받게 되면서, hashCode() 와 equals() 결과값이 변해서
    이미 저장해 놓은 HashSet에 존재하지 않는 새로운 객체로 인식을 해버리는
    문제가 생길수 있겠죠.
    (둘중 하나만 달라도 충분히 다른 객체로 인식하겠지만요.)

    아... 오늘도 묻어가기...ㅡ_ㅡ;;;

    Favicon of http://whiteship.me BlogIcon 기선 2009.06.26 08:30 PERM MOD/DEL

    그렇군요; hashCode()가 먼저였군요.

    ㅎㅎ오늘도 감사합니다.

  2. Favicon of http://kwon37xi.egloos.com BlogIcon 권남 2009.06.29 09:30 PERM. MOD/DEL REPLY

    Kevin 님이 하신말씀에 부연설명 합니다.

    ID 값만으로 hashCode와 equals를 생성하면 id == null인 상태의 객체 여러개를 Set에 저장할 때 문제를 일으킵니다. 따라서 절대로 그런일 이 없게 하거나, 아니면 createdAt 같이 객체의 ID 값 처럼 절대 불변하는 어떤 값도 함께 equals와 hashCode에 추가해주어야 합니다.

    최선책 : 객체의 ID와 그외의 불변하는 어떤 을 더 추가하며 hashCode/equals를 만든다.
    차선책 : ID만으로 hashCode/equals를 구현하고 절대로 DB에 저장하기 전에 먼저 Set에 저장하는 일이 없도록 개발자들에게 경고한다.

    Favicon of http://whiteship.me BlogIcon 기선 2009.06.29 18:32 PERM MOD/DEL

    넹. transient 상태 객체는 아직 id가 없을테니깐 보조키(?)도 같이 활용해서 hashCode와 equals를 구현하는게 좋겠네요.

Write a comment.




: 1 : 2 : 3 : 4 : 5 : ··· : 13 :