Whiteship's Note


하이버네이트 API : Detached 객체 다루기

Hibernate/Chapter 9 : 2008.03.11 15:06


수정된 detached 객체 reattaching 하기

  • update()를 사용하여 새로운 session에 reattach 할 수 있다.
item.setDescription(...); // Loaded in previous Session

Session sessionTwo = sessionFactory.openSession();
Transaction tx = sessionTwo.beginTransaction();

sessionTwo.update(item);

item.setEndDate(...);

tx.commit();
sessionTwo.close();
  • update()에 넘겨주는 객체가 그 전에 수정이 되었는지 안 되었는지는 중요하지 않다.
  • 새로운 Persistent Context에 묶이게 된다는 것이 중요하다.
  • 새로운 Persistent Context에서는 무조건 dirty로 인식하고 나중에 update문을 날리게 된다.
  • DB에서 실제 값과 비교하여 바뀐 부분이 있을 때에만 update문을 날리고 싶으면 select-before-update="true"를 설정한다.
  • 수정되지 않았다는 것이 활실할 때는 update를 사용하지 않는 reattach 방법을 사용한다.

수정되지 않은 detacked 객체를 reattaching 하기

  • lock()을 사용하면 그 시점 부터 DB에 반영을 합니다. 그 이전의 변경 사항은 반영하지 않습니다.
Session sessionTwo = sessionFactory.openSession();
Transaction tx = sessionTwo.beginTransaction();

sessionTwo.lock(item, LockMode.NONE);

item.setDescription(...);
item.setEndDate(...);

tx.commit();
sessionTwo.close();
  • 위에서 사용한 LockMode.NONE은 새로운 객체를 Session에 붙일 때, 버전 확인이나 DB 레벨의 롹을 가져오지 않겠다는 설정입니다.

detacked 객체를 transient 객체로 만들기

  • update()나 lock()를 호출하열 필요 없이 그냥 delete()를 사용합니다.
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

session.delete(item);

tx.commit();
session.close();
  • 그 안에서 해당 객체를 다시 Session에 붙였다가 tx.commit()이 호출되는 시점에 DB에서 삭제합니다.

detached 객체 mergeing 하기

  • reattachment의 보완제이자 대체제다.
Session session1 = sessionFactory.openSession();
Transaction transaction = session1.beginTransaction();

Member member = new Member();
member.setName("toby");
session1.save(member);

transaction.commit();
session1.close();

member.setName("whiteship");

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

Member member2 = (Member) session2.get(Member.class, member.getId());
Member member3 = (Member) session2.merge(member);

member2.setName("toby");

assertEquals("toby", member3.getName());

transaction2.commit();
session2.close();
  • NonUniqueObjectException: reattack 할 때, 이미 동일한 레코드를 표현하는 객체가 해당 Persistent Context에 존재할 때 발생하는 예외.
  • Persistent Context에 만약, 같은 DB 레코드를 나타내는 객체가 있다면, detached 객체의 값들을 해당 객체로 복사한다.
  • Persistent Context에 존재하지 않는 객체일 경우에는, DB에서 같은 id를 가진 녀석을 읽어온 다음에 merge() 한다.
  • detached객체나, 기존의 Persisitent Context에 있던 객체의 사용은 지양하고, merge()가 반환하는 객체를 사용하도록 한다.
top

Write a comment.


하이버네이트 API: 저장하고 읽어들이기

Hibernate/Chapter 9 : 2008.03.11 15:05


Unit of work 시작하기

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
  • SessionFactory는 매번 생성하지 말 것. 비싸다. Spring에서는 singleton으로 사용하니까 그럴 걱정은 없겠군. Thread Safe 하겠지?
  • Session은 비싸지 않다. 필요하기 전까지는 JDBC Connection조차 가져오지 않는다.
  • 위에서는 하이버의 Transaction API를 사용해지만, 꼭 저것만 사용해야 하는 건 아니다. 다음 챕터에서 자세히 다룬다.

객체를 persistent 상태로 만들기

Item item = new Item();
item.setName("Playstation3 incl. all accessories");
item.setEndDate( ... );

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

Serializable itemId = session.save(item);

tx.commit();
session.close();
  • save()를 호출하면 persistent context에 묶이게 된다.
  • 언젠가는 이 persistent context와 DB가 동기화 되어야 한다.
  • Transaction의 commit()을 호출할 때 flush가 일어나고, 이것을 flush()로 명시적으로 호출할 수도 있다.
  • session.save()이후의 모든 변경사항들은 추가적인 update문으로 DB에 반영된다.
  • session.beginTransaction()와 tx.commit()사이의 변경 사항들을 DB에 반영할 때, 예외가 발생하면 모든 SQL을 롤백한다.
  • 그러나 Persistent 객체의 메모리 상태는 롤백되지 않는다.

Persistent 객체 가져오기

  • 두 가지 방법이 있다. get(), load()
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

Item item = (Item) session.load(Item.class, new Long(1234));
// Item item = (Item) session.get(Item.class, new Long(1234));

tx.commit();
session.close();
  • session에서 가져오는 순간 persistent 상태이며, session이 끝나는 순간 detached 상태가 된다.
  • id에 대응하는 레코드가 없을 때, get()은 null을 반환한다. load()는 ObjectNotFoundException을 던진다.
  • load() 메소드는 실제로 DB에 다녀오지 않고 Proxy를 반환한다. 단, 현재 Persistent Context에 이미 대응하는 id를 가진 객체가 존재한다면, 초기화까지 되어 있는 객체를 돌려준다.
  • get() 메소드는 절대로 Proxy를 반환하지 않는다. 항상 DB에 다녀온다.
  • DB에 있는 객체 하나를 꺼내서 다른 객체에 세팅해줘야 하는 경우. Comment.setForAuction(item). 이 경우 굳이 item은 load()로 가져와도 된다. 굳이 DB에서 전부 가져올 필요가 없다. Comment를 저장할 때, item의 id를 외례키로 저장하게 되는데, load()로 가져온 Proxy가 딱 그 id만 가지고 있기 때문이다.

Persistent 객체 수정하기

  • Persistent 상태의 객체는 tx.commit()을 호출할 때 flush()를 통해서 DB에 반영된다.
  • automatic dirty checking

Persistent 객체를 Transient 상태로 만들기

  • delete()를 호출하면 Remonved 상태가 된다.
  • DB와 동기화는 Unit of work가 끝날 때 이루어 지고, 그러면 Transient 상태가 된다.
  • 그런데.. id값을 가지고 있는데, transient 객체라고 할 수 있는건가.. detached 객체랑 차이가 뭘까?
    => Transient 상태가 될 때, 식별자도 제거하려면 hibernate.use_identifier_rollback 이 설정을 해줘야돼.
sessionFactory 설정
<prop key="hibernate.use_identifier_rollback">true</prop>
session = sessionFactory.openSession();
transaction = session.beginTransaction();

session.delete(member);

transaction.commit();
session.close();
assertNull(member.getId());

객체 복사하기

  • 좀 특별한 경우에 한쪽 DB에서 가져온 다음에 다른쪽 DB에 저장하는 경우가 있을 수 있습니다.
  • 서로 다른 SessionFactory에서 얻어온 Session 사이에서 detached 객체를 받아서 persistent 객체로 전환해 줍니다.
Session session = sessionFactory1.openSession();
Transaction tx = session.beginTransaction();
Item item = (Item) session.get(Item.class, new Long(1234));
tx.commit();
session.close();

Session session2 = sessionFactory2.openSession();
Transaction tx2 = session2.beginTransaction();
session2.replicate(item, ReplicationMode.LATEST_VERSION);
tx2.commit();
session2.close();
  • ReplicationMode
    1. ReplicationMode.IGNORE: 같은 id를 가진 녀석이 이미 존재하면, 그 객체를 넣지 않는다.
    2. ReplicationMode.OVERWRITE: 같은 id를 가진 녀석이 존재하면, 덮어쓴다.
    3. ReplicationMode.EXCEPTION: 같은 id를 가진 녀석이 존재하면, 예외를 던진다.
    4. ReplicationMode.LATEST_VERSION: 같은 id를 가진 녀석의 버전을 확인해서 새것 보다 이전 버전이면 덮어쓰고 아니면 넣지 않는다. 이 걸 사용하려면 하이버의 optimistic concurrency control을 켜둬야 한다.
top

Write a comment.


Reattaching과 Merging

Hibernate/Chapter 9 : 2008.03.11 13:18


detached 상태의 객체를 persistent 객체로 전이할 때에는 Reattach가 간편하긴 하지만, 여러 개의 Session에 걸쳐 있을 경우에 예상치 못한 문제가 발생할 수 있습니다.

    @Test
    public void reattaching() {
        Session session1 = sessionFactory.openSession();
        Transaction transaction = session1.beginTransaction();
       
        Member member = new Member();
        member.setName("toby");
        session1.save(member);
       
        transaction.commit();
        session1.close();
       
       member.setName("whiteship");
       
        Session session2 = sessionFactory.openSession();
        Transaction transaction2 = session2.beginTransaction();
       
      Member member2 = (Member) session2.get(Member.class, member.getId());
      session2.update(member);
       
        assertEquals("whiteship", member.getName());
       
        transaction2.commit();
        session2.close();
    }

바로 이런 경우인데요. 위의 코드는 NonUniqueObjectException 예외가 발생합니다. 왜 그런지는 비밀입니다.
암튼 위의 코드야 둘이 붙어있으니까 왜 문제가 발생하는지 잘 보이지만, 여러 세션에 걸쳐서 detacked 객체를 붙였다 땟다 한다고 생각하면, 일일히 추적해서 update()를 사용한 줄을 위쪽으로 올려주는 작업을 해야 합니다. 고통이겠죠.

    @Test
    public void merging() {
        Session session1 = sessionFactory.openSession();
        Transaction transaction = session1.beginTransaction();
       
        Member member = new Member();
        member.setName("toby");
        session1.save(member);
       
        transaction.commit();
        session1.close();
       
        member.setName("whiteship");
       
        Session session2 = sessionFactory.openSession();
        Transaction transaction2 = session2.beginTransaction();
       
      Member member2 = (Member) session2.get(Member.class, member.getId());
      Member member3 = (Member) session2.merge(member);
       
        assertEquals("whiteship", member3.getName());
       
        transaction2.commit();
        session2.close();
    }

이럴 때 사용할 수 있는 것이 merge() 입니다. 이 녀석은 재밌게 동작합니다. 저렇게 하면 아무런 문제도 발생하지 않고 원하는 대로 동작합니다.

대신 주의 해야 할 것은 member2 와 member3가 같은 레퍼런스를 가지고 있기 때문에 아래와 같은 코드가 가능합니다.

        Member member2 = (Member) session2.get(Member.class, member.getId());
        Member member3 = (Member) session2.merge(member);
       
        member2.setName("whiteship2");
       
        assertEquals("whiteship2", member3.getName());

이런 상황이 용납이 안 되는 것은 아닙니다. 하지만 사방에서 저 객체의 값을 변경하면 좀 곤란하겠죠. member2와 member(detached 상태의 객체)는 merge()의 반환값을 가져온 이상. 사장되었다고 생각하고 작업을 하는 것이 좋겠습니다.

merget()와 Persistent Context의 dirty checking이 맞물리면, 정말 SQL이 재미있게 생성되는 모습을 볼 수 있습니다.

위의 예제에서 발생되는 쿼리는 간단합니다.
- member를 넣는 insert 문
- member2를 가져오는 select문
- member3을 가져올 때(merging time)member2가 참조하던 객체를 변경하는 update문


퀴즈 그렇다면 아래의 코드는 어떤 SQL들을 만들어 낼까요?

        Session session1 = sessionFactory.openSession();
        Transaction transaction = session1.beginTransaction();
       
        Member member = new Member();
        member.setName("toby");
        session1.save(member);
       
        transaction.commit();
        session1.close();
       
        member.setName("whiteship");
       
        Session session2 = sessionFactory.openSession();
        Transaction transaction2 = session2.beginTransaction();
       
        Member member2 = (Member) session2.get(Member.class, member.getId());
        Member member3 = (Member) session2.merge(member);
       
        member2.setName("toby");
       
        assertEquals("toby", member3.getName());
       
        transaction2.commit();
        session2.close();

위와 같이 간단하게 발생되는 쿼리 종류를 써주세요. 그리고 왜!! 왜 그런 결과가 생기는지도 설명해주세요~
top

  1. Favicon of https://jjaeko.tistory.com BlogIcon 째코 2008.03.30 23:37 신고 PERM. MOD/DEL REPLY

    첫번째 세션에서 "toby" 로 저장하고 준영속 객체의 값을 "whiteship" 으로 바꾼뒤
    두번째 세션에서 첫번째 세션에서 준영속 객체가 되어버린 식별값을 가지는 객체를 가져와서
    준영속 객체를 머지 했지만 다시 원래 값인 "toby"로 값을 바꿧기 때문에 변화를 감지하지 못하고
    결국 아무 일도 일어나지 않을거 같네요
    테스트도 통과할듯 싶은데요 member2와 member3의 참조값은 동일하니까..

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.03.31 08:37 PERM MOD/DEL

    와~ 맞습니다. toby->whiteship->toby로 다시 바꿨기 때문에 중간에 whiteship으로 update하는 불필요한 UPDATE문은 발생하지 않습니다.

    물론 테스트도 통과하구요. 딩동댕~!

Write a comment.


Persistence context 확장하기

Hibernate/Chapter 9 : 2008.02.24 14:02


특징

  • 요청을 처리한 다음에 Persistent context를 닫지 않고 DB Connection을 끊은 상태에서 사용자의 다음 요청을 기다린다. 사용자의 다음 요청이 오면 다시 DB에 연결하고 다음 요청을 처리한다. Conversation이 끝나면, Persistent context를 DB에 동기화하고, 닫는다. 다음 Conversation을 시작할 때는 새로운 Persistence context를 시작하고, 이전 Conversation에서 사용했던 entity 객체들을 재사용하지 않는다.
  • Detached 상태의 객체가 없다. 모든 객체는 Transient 상태이거나 Persistent 상태다.
  • 손수 reattachment 하거나 merging할 필요가 없다.
  • 이 책의 나중에 두 가지 Conversation 구현 전략에 대해 자세하게 다룰 것이다.

'Hibernate > Chapter 9' 카테고리의 다른 글

Hibernate VS JPA  (4) 2008.03.11
하이버네이트 API : Persistence context 관리하기  (0) 2008.03.11
하이버네이트 API : Detached 객체 다루기  (0) 2008.03.11
하이버네이트 API: 저장하고 읽어들이기  (0) 2008.03.11
Reattaching과 Merging  (2) 2008.03.11
Persistence context 확장하기  (0) 2008.02.24
The identity of detached objects  (0) 2008.02.24
The Scope of Object Identity  (0) 2008.02.24
Conversation 소개  (0) 2008.02.24
Persistence Context  (0) 2008.02.24
객체 상태  (0) 2008.02.24
top

Write a comment.


The identity of detached objects

Hibernate/Chapter 9 : 2008.02.24 14:02


특징

  • Reference to a detached object: 객체의 레퍼런스가 identity를 보장받는 범위를 벗어나는 것을 말한다.
  • Set Collection에 객체들을 담을 때, 동일한 주키로 읽어들인 Detached 상태의 객체와 Persistent 상태의 객체는 모두 동일한 레코드를 나타내는 것들이기 때문에, 이전 예제에서 다음의 코드의 결과 Set에는 하나의 객체만 들어가야 한다.
Set<Member> members = new HashSet<Member>();
members.add(loadedMember1);
members.add(loadedMember2);
members.add(loadedMember3);

assertEquals(1, members.size());
  • 그러나..2개가 들어가있다. loadedMember1과 loadedMember2는 같은 레퍼런스를 가지고 있으니까, loadedMember2는 추가되지 않고, loadedMember3는 전혀 다른 레퍼런스를 가지고 있기 때문에 추가 된다.
  • Set에 add()를 할 때, equals()로 비교를 하는데, Object 클래스에서 equals()를 ==으로 구현해 두었기 때문에 그렇게 된다.
  • 따라서 위의 결과가 1이 되게 하려면, equals()와 hashCode()를 재정의 해주어야 한다.

equals()와 hashCode() 이해하기

  • Detached 상태의 객체를 Set에 집어넣는 일이 절대로 없다면, 굳이 equals()와 hashCode()를 구현하지 않아도 된다.
  • Conversation 구현 전략으로 Extended Persistence Context를 선택하고 Detached 상태의 객체들을 애플리케이션에 제거하면 된다.
  • 이렇게 하면 당연히 Persistence Context도 Conversation으로 확장되었으니, Scope of Object identity의 범위도 Conversation으로 확장된다.
  • equlas()를 재정의 하면 반드시 hashCode()도 재정의 해야 한다. 왜? 같은 객체들은 반드시 같은 해시코드를 반환해야 하니까.

Data identity 비교하기

@Override
public boolean equals(Object other) {
if (this == other)
return true;
if (id == null)
return false;
if (!(other instanceof Member))
return false;
final Member that = (Member) other;
return this.id.equals(that.getId());
}

@Override
public int hashCode() {
return id == null ? System.identityHashCode(this) : id.hashCode();
}
  • 비추하는 방법니다.
  • Transient 객체를 Set에 넣은 다음에, Session.save()를 사용해서 Persistent 상태로 바꾸면 Set에 포함되어 있는 상태에서 hashCode 값이 바뀌게 된다. 이것은 Set의 제약을 위반하게 되는 것이다. 왜 그럴까? 생각해보자. 0이라는 hashCode를 가진 녀석이 Set에 들어간 다음에 1로 바꼈다. 그러면 나중에 1이라는 hashCode를 가진 녀석을 Set에 집어 넣으면 어떻게 될까? 처음에 0을 가지고 있다가 1로 바뀐 녀석은 사라지고, 뒤에 추가된 객체만 남아있을 것이다.(테스트 코드로 확인해 볼 것.)

주키 속성을 제외한 모든 속성을 비교하기

@Override
public boolean equals(Object other) {
if (this == other)
return true;
if (!(other instanceof Member))
return false;
final Member that = (Member) other;
if (!this.getName().equals(that.getName()))
return false;
return true;
}

@Override
public int hashCode() {
int result = 14;
result = 29 * result + getName().hashCode();
return result;
}
  • 콜렉션은 비교하지 않는다. 객체 하나의 동일성을 확인하려고 전체 객체 맵을 확인할 필요는 없으니까.
  • 단점1: 같은 레코드를 표현하는 두 개의 객체가 서로 다른 Session에서 존재하다가 누군가 속성을 바꾸면 그 둘은 다른 객체가 되버린다.
  • 단점2: 서로 다른 DB indentity를 가진 객체들이 같은 객체로 취급될 여지가 있다.

비즈니스 키를 사용해서 구현하기

  • business key: 각 개체들마다 유일한 값을 가지는 속성이나 속성들의 집합. natural primary key와 다른 점은 바뀔 수 있다는 것이다.
  • Entity 클래스에는 반드시 business key가 있어야 한다고 주장한다. 심지어 그 키가 개체의 모든 속성을 포함하는 것일지라도.(보통 immutable 클래스가 그렇다.)
  • surrogate key가 DB와 application이 사용하는 것이라면, business key는 사용자가 단일 레코드를 식별하기 위해 사용하는 것들이다.
@Override
public boolean equals(Object other) {
if (this == other)
return true;
if (!(other instanceof Member))
return false;
final Member that = (Member) other;
if (!this.email.equals(that.getEmail()))
return false;
return true;
}

@Override
public int hashCode() {
return email.hashCode();
}
  • 위에서 언급했던 모든 문제가 해결된다.
  • equals() 메소드에서 other객체의 속성에 접근할 때, .으로 바로 접근할 수 있는데도 굳이 gette를 사용하는 이유? 프록시 일수도 있기 때문에...

참조할 것

'Hibernate > Chapter 9' 카테고리의 다른 글

하이버네이트 API : Persistence context 관리하기  (0) 2008.03.11
하이버네이트 API : Detached 객체 다루기  (0) 2008.03.11
하이버네이트 API: 저장하고 읽어들이기  (0) 2008.03.11
Reattaching과 Merging  (2) 2008.03.11
Persistence context 확장하기  (0) 2008.02.24
The identity of detached objects  (0) 2008.02.24
The Scope of Object Identity  (0) 2008.02.24
Conversation 소개  (0) 2008.02.24
Persistence Context  (0) 2008.02.24
객체 상태  (0) 2008.02.24
Persistence Lifecycle  (0) 2008.02.24
top

Write a comment.


The Scope of Object Identity

Hibernate/Chapter 9 : 2008.02.24 14:01


특징

  • Java identity: a==b
  • Database identity: x.getId().equals(y.getId())
  • Scope of object identity: Java Identity와 Database identity모두 보장되는 상태.

Scope of object identity 종류

  • No identity scope: 하나의 DB 레코드가 같은 자바 객체로 여러번 애플리케이션으로 반환되든지 말든지 신경 안 쓴다. 문제가 생긴다. (만약에 두 개의 객체가 하나의 레코드를 표현하고 있는데, 그 두 개의 객체의 정보를 수정하면, DB에는 어떤 객체의 변화를 반영해야 하는가? 어떻게 하든 둘 중 하나는 손실 되겠군.)
  • Persistence context-scoped identity: 단일 Persistence context 내부에서는 오직 하나의 객체만이 하나의 DB 레코드를 표현할 수 있도록 보장한다. 위에서 언급했던 문제가 없어지고 context 레벨에서의 cache를 보장할 수 있다.
  • Process-scoped identity: 한 단계 더 나가서, 전체 JVM 프로세스 내부에서 오직 하나의 객체만이 하나의 DB 레코드를 표현할 수 있다.
  • 하이버는 persistence context-scope으로 구현했다.

Persistence context-scope 예제

@Test
public void persistenceContextScope() throws Exception {
Session session = sessionFactory.openSession();
Member member = new Member();
session.save(member);
Long memberId = member.getId();

Member loadedMember1 = (Member) session.get(Member.class, memberId);
Member loadedMember2 = (Member) session.get(Member.class, memberId);

// Java Identity In Persistence Context
assertTrue(loadedMember1 == loadedMember2);
// DB Identity In Persistence Context
assertTrue(loadedMember1.getId().equals(loadedMember2.getId()));
session.flush();
session.close();

Session session2 = sessionFactory.openSession();
Member loadedMember3 = (Member) session2.get(Member.class, memberId);

// Java Identity Out Persistence Context
assertFalse(loadedMember1 == loadedMember3);

// DB Identity Out Persistence Context
assertTrue(loadedMember1.getId().equals(loadedMember3.getId()));

session2.flush();
session2.close();
}
  • Persistent Context 내에서는(Persistent 상태) 같은 주키로 가져온 객체는 Java identity와 DB identity 모두 동일하다.
  • 하지만 Detached 상태의 객체와 Persistence 상태의 객체의 Java Identity는 보장되지 않는다. 물론 이 때도 주키 속성은 가지고 있으니까 DB Identity는 보장된다.
  • Detached 상태의 객체를 가지고 작업하는 것는 위와 같이 Scope of Object identity가 보장되지 않는 상태에서 작업하는 것이다.

'Hibernate > Chapter 9' 카테고리의 다른 글

하이버네이트 API : Persistence context 관리하기  (0) 2008.03.11
하이버네이트 API : Detached 객체 다루기  (0) 2008.03.11
하이버네이트 API: 저장하고 읽어들이기  (0) 2008.03.11
Reattaching과 Merging  (2) 2008.03.11
Persistence context 확장하기  (0) 2008.02.24
The identity of detached objects  (0) 2008.02.24
The Scope of Object Identity  (0) 2008.02.24
Conversation 소개  (0) 2008.02.24
Persistence Context  (0) 2008.02.24
객체 상태  (0) 2008.02.24
Persistence Lifecycle  (0) 2008.02.24
top

Write a comment.


Conversation 소개

Hibernate/Chapter 9 : 2008.02.24 13:59


특징

  • Persistence를 사용하는 여러 화면에 걸쳐 사용자의 요청을 처리해야 하는 경우.
  • 두 가지 구현 방법이 있다.

Detached Object를 이용하는 방법

  • session-per-request-with-detatched-objects
  • Persistent context는 오직 하나의 요청을 처리하는 동안만 지속되고, Detached 상태의 객체들은 Conversation 내부에서는 계속해서 reattach하거나 merge를 해서 다시 Persistent 상태로 만들어서 사용한다.

Persistence context를 확장하는 방법

  • session-per-conversation
  • Persistent context를 전체 작업의 단위로 확장시킨다.

'Hibernate > Chapter 9' 카테고리의 다른 글

하이버네이트 API : Persistence context 관리하기  (0) 2008.03.11
하이버네이트 API : Detached 객체 다루기  (0) 2008.03.11
하이버네이트 API: 저장하고 읽어들이기  (0) 2008.03.11
Reattaching과 Merging  (2) 2008.03.11
Persistence context 확장하기  (0) 2008.02.24
The identity of detached objects  (0) 2008.02.24
The Scope of Object Identity  (0) 2008.02.24
Conversation 소개  (0) 2008.02.24
Persistence Context  (0) 2008.02.24
객체 상태  (0) 2008.02.24
Persistence Lifecycle  (0) 2008.02.24
top

Write a comment.


Persistence Context

Hibernate/Chapter 9 : 2008.02.24 13:58


특징

  • 관리하고 있는 엔티티 개체에 대한 캐시라고 생각할 수 있다.
  • 하이버네이트에서는 하나의 Session이 하나의 내부 Persistent Context를 가지고 있다고 한다.
  • JP에서는 하나의 EntityManager가 하나의 Persistent Context를 가지고 있다.
  • Persistent 상태의 모든 엔티티들은 이 컨텍스트 안에 캐시된다.
  • 이 녀석이 유용한 이유
  1. 하이버는 automatic dirty checking과 transactional write-behind를 할 수 있다.
  2. 하이버는 이 녀석을 1차 캐시로 사용할 수 있다.
  3. 하이버는 가바 객체 식별자의 스콥을 보장할 수 있다.
  4. 하이버는 Persistent Context를 전체 Conversation 범위로 확장할 수 있다.

Automatic dirty checking

  • Persistent 상태의 객체는 Unit of work가 끝날 때 SQL을 날려서 DB와 동기화 된다. 이 과정은 다른 때에 발생시킬 수도 있다.
  • 하이버는 쿼리를 실행하기 전에 DB와 동기화 할 수 있다. ??? 이것으로 인해 쿼리는 unit of work 초기에 변경 사항을 알 수 있다. ??
  • dirty: DB에 반영되지 않은 변경 사항.
  • transparent transaction-level write-behind를 사용해서 하이버는 변경 사항들을 쥐도 새도 모르게 가능한한 나중에 DB에 반영할 수 있다.
  • 변경 사항을 가능한한 나중(DB 트랜잭션 끄트머리)에 반영함으로써 롹 타임을 최소화 한다.
  • 하이버는 객체의 상태를 수정하는 UPDATE문을 날릴 때, SessionFactory를 생성하면서 만들어두었던 SQL을 사용하는데, 그 SQL에는 모든 필드를 포함하고 있다.
  • 하이버는 정확히 어떤 속성이 변경 됐는지 알아내고 해당 컬럼만 UPDATE 문을 사용하여 수정하는 것이 가능하다. 이렇게 함으로써, DB에 부하를 줄이는 대신 약간의 작업이 필요하니까 성능이 약간 떨어질 수도 있다.(DB에 부하를 주느니, 메모리랑 CPU를 조금 더 쓰는게 좋겠지.)
  • 새로운 레코드를 추가할 때도 위와 같은 메카니즘이 구현되어 있다.
  • dynamic-update="true", dynamic-insert="true"를 사용하여 설정할 수 있다.
@Entity
@org.hibernate.annotations.Entity(
dynamicInsert = true, dynamicUpdate = true
)
@Test
public void dirtyChecking() throws Exception {
Group group = new Group();
group.setName("KSUG");
session.save(group);

group.setName("AJN");
}
  • 이 설정들은 컬럼이 50개 정도로 매우 많을 때 설정하기를 권장한다.
  • 커스텀 dirty checking 알고리즘을 구현하여 사용할 수도 있다.

Persistence Context Cache

  • Caching의 또 다른 장점 repeatable read
  • 주키를 사용하여 객체를 로딩하라는 지시를 받으면, 하이버는 현재 Unit of work의 Persistent Context 안에서 먼저 찾아 본다. 그 안에 있으면 DB를 다녀오지 않고 그 녀석을 돌려준다.
@Test
public void repeatablRead() throws Exception {
Group group = new Group();
session.save(group);

Group group2 = (Group) session.get(Group.class, group.getId());

assertEquals(group, group2);
}
  • 캐시는 성능 향상과 isolation level을 공짜로 향상시켰다.
  • Persistent context가 불필요한 DB 트래픽을 줄이는데 도움을 주지만, 그 보다 더 중요한 것들이 있다.
  1. Persistent laver에서 객체 그래프 내의 circular reference로 인한 스택 오버플로우를 야기시키지 않는다.
  2. Unit of work의 종료 시점에 같은 DB 레코드를 나타내는 여러 객체가 있을 수 없다. 오직 하나의 객체만 하나의 레코드를 나타낼 수 있고, 그에 대한 변경 사항은 안전하게 DB에 반영된다.
  3. 특정 Persistent context 내에서 실행되는 코드들은 모두 해당 Persistent context 내부에 있는 객체을 참조할 수 있다. repeatable read를 보장한다.
  • 이런 기능을 가능캐 하는 persistence context cache는 별도의 조작이 필요없이 항상 가동되어 있으며, 끌 수도 없다.

'Hibernate > Chapter 9' 카테고리의 다른 글

하이버네이트 API : Persistence context 관리하기  (0) 2008.03.11
하이버네이트 API : Detached 객체 다루기  (0) 2008.03.11
하이버네이트 API: 저장하고 읽어들이기  (0) 2008.03.11
Reattaching과 Merging  (2) 2008.03.11
Persistence context 확장하기  (0) 2008.02.24
The identity of detached objects  (0) 2008.02.24
The Scope of Object Identity  (0) 2008.02.24
Conversation 소개  (0) 2008.02.24
Persistence Context  (0) 2008.02.24
객체 상태  (0) 2008.02.24
Persistence Lifecycle  (0) 2008.02.24
top

Write a comment.


객체 상태

Hibernate/Chapter 9 : 2008.02.24 13:56


특징

  • ORM 솔루션마다 서로 다른 용어와 서로 다른 상태를 정의하고 영속성 라이프사이클의 상태 전이를 다룬다.
  • 하이버네이트는 네 가지 상태를 가지고 있다.

  • 이탤릭체의 메소드 이름은 JPA의 EntityManager와 Hibernate의 Session에서 공통으로 사용할 수 있는 API

Transient Object

  • new 연산자를 사용하여 생성한 객체는 곧바로 persistent 상태가 되지 않는다.
  • 데이터베이스의 레코드와 아무 연관이 없기 때문에, 메모리에서 없어지면 그 상태 정보도 없어진다.
  • JPA에는 이 상태를 나타내는 용어가 없다.
  • 이 상태의 객체는 nontransactional로, Persistence Context에 변경 사항이 기록되지 않는다. 따라서 롤백을 지원하지 않는다.
  • 이 상태의 객체가 참조하는 객체들도 기본적으로는 Transient 상태다.

Persistent Object

  • 데이터베이스 식별자를 가지고 있는 엔티티다.
  • Persistent 상태의 객체에 의해 참조되었기 때문에 이 상태가 되는 객체도 있다.
  • DB에서 쿼리, 식별자 룩업, 다른 Pesistent 객체로 부터 시작한 객체 그래프 네비게이션 등을 통해서 이 상태가 되는 객체도 있다.
  • 이 상태의 객체는 항상 Persistent Context와 연관을 맺고 있다.
  • 하이버네이트는 그들을 캐쉬하고 애플리케이션 내에서의 변경 사항을 감지할 수 있다.

Removed Object

  • 엔티티 개체를 여러 방법으로 제거할 수 있다.
  • Persistent Manager의 메소드를 사용하여 제거할 수 있다.
  • Orpahn Deletion(해당 하는 객체를 참조하는 녀석이 없을 때 지우기)을 할 수 있다.
  • Unit of work가 끝나면 곧 사라질 객체지만 여전히 Persistent Context에는 남아있을 수 있다.
  • 따라서, 애플리케이션 내에서 해당 객체에 대한 참조를 모두 제거해야 한다.

Detached Object

  • Unit of work를 마친 뒤에 여전히 애플리케이션에서 참조되고 있는 객체.
  • Persistent Context가 활성화 되어 있을 동안 만 Persistent 상태였다.
  • 이 상태의 객체들은 더이상 DB와 동기화 하지 않는다.
  • Persistent Context에 연관되어 있지 않다.
  • 이 상태의 객체를 가지고 여러 변경 작업을 할 수 있고, 후에 다시 Persistent 상태로 돌리고 싶을 수 있다.
  • 하이버는 그런 상황을 위해 reattachment와 merging을 제공한다.
  • JP는 오직 merging만 표준화했다.
  • Conversation: long units of work

'Hibernate > Chapter 9' 카테고리의 다른 글

하이버네이트 API : Persistence context 관리하기  (0) 2008.03.11
하이버네이트 API : Detached 객체 다루기  (0) 2008.03.11
하이버네이트 API: 저장하고 읽어들이기  (0) 2008.03.11
Reattaching과 Merging  (2) 2008.03.11
Persistence context 확장하기  (0) 2008.02.24
The identity of detached objects  (0) 2008.02.24
The Scope of Object Identity  (0) 2008.02.24
Conversation 소개  (0) 2008.02.24
Persistence Context  (0) 2008.02.24
객체 상태  (0) 2008.02.24
Persistence Lifecycle  (0) 2008.02.24
top

Write a comment.


Persistence Lifecycle

Hibernate/Chapter 9 : 2008.02.24 13:55


  • Persistence lifecycle: 영속화 시켜야 할 객체의 상태와 라이프 사이클
  • Unit of work: 한 그룹으로 생각하는 Operation 집합.
  • Persistence Context: 특정 작업 단위에 있는 객체들에 대한 수정과 상태의 변화를 기억하는 캐쉬
  • 객체 상태
  • Persistence Context

'Hibernate > Chapter 9' 카테고리의 다른 글

하이버네이트 API : Persistence context 관리하기  (0) 2008.03.11
하이버네이트 API : Detached 객체 다루기  (0) 2008.03.11
하이버네이트 API: 저장하고 읽어들이기  (0) 2008.03.11
Reattaching과 Merging  (2) 2008.03.11
Persistence context 확장하기  (0) 2008.02.24
The identity of detached objects  (0) 2008.02.24
The Scope of Object Identity  (0) 2008.02.24
Conversation 소개  (0) 2008.02.24
Persistence Context  (0) 2008.02.24
객체 상태  (0) 2008.02.24
Persistence Lifecycle  (0) 2008.02.24
top

Write a comment.


Hibernate VS JPA

Hibernate/Chapter 7 : 2008.02.20 18:33


Hibernate JPA
One-To-One 연관 맵핑에서 공유하는 주키의 자동 생성을 지원한다. 표준화 된 One-To-One 맵핑을 지원한다. 하이버네이트 확장을 이용하면 공유하는 주키를자동 생성할 수 있다.
하이버네이트는 모든 엔티티 연관 맵핑을 Join Table로 할 수 있다. 표준화된 연관 맵핑은 2차 테이블을 사용해서 가능하다.
하이버네이트는 인덱스를 가진 엔티티 리스트를 맵핑할 수 있다. 하이버네이트 확장 애노테이션이 있어야 가능하다.
하이버네이트는 모든 경우에 다형적인 연관을 지원한다. Table Per Concrete Class With Implicit Polymorphism은 빼고는 전부 지원한다.
top

Write a comment.


Table Per Concrete Class(with implicit polymorphism)에서 다형적인 연관

Hibernate/Chapter 7 : 2008.02.20 18:32


특징

  • Table Per Concrete Class With Implicite Polymorphism으로 맵핑했을 경우를 살펴본다.
  • 상위 타입과 OneToMany 콜렉션으로 맵핑할 수 없다.
  • 그래도 다형적인 쿼리를 사용하려면 핵을 사용해야 하는데, 이건 정말이지.. 최후의 선택이어야만 한다. 이러기 전에 union으로 설정을 바꾸는 것을 고려해보아라.
  • XML 설정에서는 핵을 사용해서 억지로 다형적인 연관을 맺게 할 수 있지만, SQL 테이블 조인식을 작성하기도 어려워지고, HQL도 그런 연관 맵핑을 지원하지 않는다. 그리고 JPA 애노테이션도 그런 설정을 지원하지 않는다.
  • 절대로 사용하지 말자. 따라서 패스.
top

Write a comment.


Union을 사용한 다형적인 연관

Hibernate/Chapter 7 : 2008.02.20 18:31


특징

  • Table Per Subclass와 Table Per Class Hierarchy에서의 다형적인 연관을 확인했다.
  • Table Per Concrete Class With Union에서도 똑같이 적용된다.
콜렉션 테스트
@Test
public void oneTomanyConfirm() throws Exception {
Session session = sessionFactory.openSession();

User user = (User) session.get(User.class, 1l);
Set<BillingDetails> bds = user.getBilllingDetailses();
for(BillingDetails bd : bds){
System.out.println("..");
}

session.flush();
session.close();
}
단일 클래스 연관 테스트
@Test
public void oneToOneConfirm() throws Exception {
Session session = sessionFactory.openSession();

User user = (User) session.get(User.class, 1l);
BillingDetails bd = user.getDefaultBillingDetails();
bd.pay();

session.flush();
session.close();
}
  • @OneToOne 이나 @ManyToOne으로 상위 타입과 연관을 맺고 있을 때, 외례키 제약은 하이버네이트가 만들어 줄 수 없다. 왜? 어떤 하위 클래스의 주키를 외례키로 가져야 하는지 판단하는게 쉽지 않다. 따라서 사용자가 적당한 제약 사항을 만들어야 한다.
top

Write a comment.


다형적인 콜렉션 연관

Hibernate/Chapter 7 : 2008.02.20 18:30


특징

  • Table Per Class Hierachy 또는 Table Per Subclass를 사용한 상속구조로 맵핑 했다면, 다음과 같이 해도 아무일 없다.
저장하기
CreditCard cc = new CreditCard();
cc.setNumber(ccNumber);
cc.setType(ccType);
cc.setExpMonth(...);
cc.setExpYear(...);
User user = (User) session.get(User.class, userId);
// Call convenience method that sets both sides of the association
user.addBillingDetails(cc);
// Complete unit of work
가져오기
User user = (User) session.get(User.class, userId);
for( BillingDetails bd : user.getBillingDetails() ) {
// Invoke CreditCard.pay() or BankAccount.pay()
bd.pay(paymentAmount);
}
  • @ManyToMany 와 @OneToMany의 기본 FetchMode는 LAZY다. 따라서, 위의 코드에서 이터레이터로 BillingDetails를 하나 가리키기 시작할 때 콜렉션을 가져온다.
  • 루프 돌지 않고, user.getBillingDetails() 호출하면, 쿼리는 날아가지 않는다.
top

Write a comment.


다형적인 ManyToOne 연관

Hibernate/Chapter 7 : 2008.02.20 18:28


특징

  • 다형적인 연관 관계는 Table per concrete class with unions, Table per class per hierachy, Table per subclass 모두 사용할 수 있다.
  • lazy=true로 설정했다면, 하위 타입으로 바로 캐스팅 할 수 없다. Proxy 객체를 가져왔기 때문이다.(XML 설정)
  • session.load()를 사용해서 해당 타입으로 다시 로딩하여 사용하거나, eager fetch query를 사용한다.
Proxy Narrowing
User user = (User) session.get(User.class, userId);
BillingDetails bd = user.getDefaultBillingDetails();
// Narrow the proxy to the subclass, doesn't hit the database
CreditCard cc =
(CreditCard) session.load( CreditCard.class, bd.getId() );
expiryDate = cc.getExpiryDate();
Eager Fetch Query
User user = (User)session.createCriteria(User.class)
.add(Restrictions.eq("id", uid) )
.setFetchMode("defaultBillingDetails", FetchMode.JOIN)
.uniqueResult();
// The users defaultBillingDetails have been fetched eagerly
CreditCard cc = (CreditCard) user.getDefaultBillingDetails();
expiryDate = cc.getExpiryDate();
  • OneToOne 연관도 이것과 같다.
  • 애노테이션을 사용한 맵핑에서는 기본 FetchMode가 EAGER다. 따라서 불필요한 쿼리가 생길 수도 있다.
top

Write a comment.


하이버네이트 애노테이션과 XML 설정 사이의 Fetching 기본값 차이

Hibernate/Chapter 7 : 2008.02.20 16:28


하이버 3.0부터는 lazy=true"가 기본값이라고 나와있습니다. (여기에.. Association fetching strategies 참조)

콜렉션이든 단일 클래스든 모두 Lazy Loading.

하지만 이 말은 XML 설정에 국한 된 말입니다. 애노테이션 설정에서는 그렇치 않습니다.
여기에 2.2.5.5. Association fetching 이 부분을 참조하면 다음과 같이 나와있습니다.

@OneToMany와 @ManyToMany 의 FetchMode는 Lazy
@OneToOne과 @ManyToOne의 FetchMode는 Eager

따라서, XML 설정으로 lazy="true"를 생각하고 코딩한 것을 애노테이션으로 옮길 때는 모두 명시적으로 fetch=FetchMode.LAZY로 설정해주어야 예상치 못한 쿼리가 발생하는 것을 방지할 수 있습니다.

예상치 못한 쿼리는 다음과 같이 발생합니다.
사용자 삽입 이미지

이런 상황(상속 구조는 Table Per Subclass로 맵핑)에서.. User가 가지고 있는 DefaultBillingDetails를 가져온다고 가정합니다. User와 DefaultBillingDetails는 @OneToObe 이나 @ManyToOne으로 맵핑할 수 있습니다. 그러면 default가 EAGER죠.

        User loadedUser = (User) session.get(User.class, 1l);
        assertNotNull(loadedUser);
        BillingDetails billingDetails = loadedUser.getDefaultBillingDetails();
        System.out.println("읽어올 때 타입을 알 수 있나? " + new Boolean(billingDetails instanceof CreditCard).toString());

- default 상황에서 발생하는 쿼리

    select
        user0_.id as id3_2_,
        user0_.DEFAULT_BILLING_DETAILS_ID as DEFAULT3_3_2_,
        user0_.name as name3_2_,
        billingdet1_.BILLING_DETAILS_ID as BILLING1_0_0_,
        billingdet1_.USER_ID as USER2_0_0_,
        billingdet1_1_.NUMBER as NUMBER1_0_,
        billingdet1_2_.ACCOUNT as ACCOUNT2_0_,
        case
            when billingdet1_1_.BILLING_DETAILS_ID is not null then 1
            when billingdet1_2_.BILLING_DETAILS_ID is not null then 2
            when billingdet1_.BILLING_DETAILS_ID is not null then 0
        end as clazz_0_,
        user2_.id as id3_1_,
        user2_.DEFAULT_BILLING_DETAILS_ID as DEFAULT3_3_1_,
        user2_.name as name3_1_
    from
        User user0_
    left outer join
        BILLING_DETAILS billingdet1_
            on user0_.DEFAULT_BILLING_DETAILS_ID=billingdet1_.BILLING_DETAILS_ID
    left outer join
        CREDIT_CARD billingdet1_1_
            on billingdet1_.BILLING_DETAILS_ID=billingdet1_1_.BILLING_DETAILS_ID
    left outer join
        BANK_ACCOUNT billingdet1_2_
            on billingdet1_.BILLING_DETAILS_ID=billingdet1_2_.BILLING_DETAILS_ID
    left outer join
        User user2_
            on billingdet1_.USER_ID=user2_.id
    where
        user0_.id=?

User와 User가 가지고 있는 BillingDetails를 left outter join으로 가져옵니다.
BillingDetails를 Table Per Subclass로 맵핑했기 떄문에 CREDIT_CARD와 BANK_ACCOUNT에 left ountter join을 사용하여 모든 레코드를 가져옵니다.
마지막으로 BillingDetails가 ManyToOne으로 참조하는 User를 가져옵니다.(이건 User의 DefaultBillingDetail과 연관을 맺고 있는 User가 아닙니다. 그런 속성은 없습니다. 이 속성은 User가 가지고 있는 BillingDetail 콜렉션과 양방향으로 연관을 맺고 있는 속성입니다.)

굉장한 쿼리가 만들어졌습니다. User가 defaultBillingDetail을 가져와서 아직 사용하지도 않았는데 말이죠.

- @OneToOne 또는 @ManyToOne에서 fetch=FetchMode.LAZY 로 설정한 경우

    select
        user0_.id as id3_0_,
        user0_.DEFAULT_BILLING_DETAILS_ID as DEFAULT3_3_0_,
        user0_.name as name3_0_
    from
        User user0_
    where
        user0_.id=?

딱 User의 정보만 가져왔습니다. 분명 가져오라고 했는데... 쿼리를 날리지 않았습니다. 그러면 이 상태에서 BillingDetail에 있는 pay()라는 메소드를 실행하면 NullPointerException이 떨어질까요?

        User loadedUser = (User) session.get(User.class, 1l);
        assertNotNull(loadedUser);
        BillingDetails billingDetails = loadedUser.getDefaultBillingDetails();
        System.out.println("읽어올 때 타입을 알 수 있나? " + new Boolean(billingDetails instanceof CreditCard).toString());
        billingDetails.pay();
        System.out.println("이제는 읽어올 때 타입을 알 수 있을까? " + new Boolean(billingDetails instanceof CreditCard).toString());

이렇게 해봤습니다.

Hibernate:
    select
        user0_.id as id3_0_,
        user0_.DEFAULT_BILLING_DETAILS_ID as DEFAULT3_3_0_,
        user0_.name as name3_0_
    from
        User user0_
    where
        user0_.id=?
읽어올 때 타입을 알 수 있나? false
Hibernate:
    select
        billingdet0_.BILLING_DETAILS_ID as BILLING1_0_1_,
        billingdet0_.USER_ID as USER2_0_1_,
        billingdet0_1_.NUMBER as NUMBER1_1_,
        billingdet0_2_.ACCOUNT as ACCOUNT2_1_,
        case
            when billingdet0_1_.BILLING_DETAILS_ID is not null then 1
            when billingdet0_2_.BILLING_DETAILS_ID is not null then 2
            when billingdet0_.BILLING_DETAILS_ID is not null then 0
        end as clazz_1_,
        user1_.id as id3_0_,
        user1_.DEFAULT_BILLING_DETAILS_ID as DEFAULT3_3_0_,
        user1_.name as name3_0_
    from
        BILLING_DETAILS billingdet0_
    left outer join
        CREDIT_CARD billingdet0_1_
            on billingdet0_.BILLING_DETAILS_ID=billingdet0_1_.BILLING_DETAILS_ID
    left outer join
        BANK_ACCOUNT billingdet0_2_
            on billingdet0_.BILLING_DETAILS_ID=billingdet0_2_.BILLING_DETAILS_ID
    left outer join
        User user1_
            on billingdet0_.USER_ID=user1_.id
    where
        billingdet0_.BILLING_DETAILS_ID=?

pay() 메소드를 호출하는 순간, BillingDetails 하나를 가져오고 해당 메소드를 호출해 줍니다.
top

Write a comment.


Migrate To Hibernate 3.2

Hibernate/migration : 2008.02.14 19:05


API 변경사항

BytecodeProvider extension

  • 하이버네이트의 바이트코드 처리에 필요한 라이브러리를 추가할 수 있는 기능을 추가했다. 이전에는 CGLIB만 사용했는데, 이제는 CGLIB과 Javassist 중에 하나로 선택할 수 있다.

행동 변경사항

Non-transactional access

  • FlushMode.AUTO는 Session내의 변경을 모두 SQL을 실행해서 반영한다. 트랜잭션 범위가 아닐 경우에 auto flush는 생략된다.
  • Second has to do with identifiers generated via an "in-database" strategy (the so-called post-insert id generators). Saves to such entities in previous versions caused an immediate SQL INSERT to be issued in order to determine the generated identifier value. Starting with 3.2, these INSERTS will be delayed when done outside of a transaction.

쿼리 언어 변경사항

Implicit joins are more deterministic

<class name='Currency' lazy='false'>
....
</class>

<class name='Asset'>
<id name='id' ... </id>
<many-to-one name='currency' class='Currency' fetch='select'/>
</class>
  • select a.id, a.currency from Asset a 이런 쿼리는 inner join을 생성한다.
  • select a.id, c from Asset a left join a.currency as c 이렇게 해야 currency가 Null Asset도 전부 가져온다.

Path expressions joining across a collection throw exception

  • select f.bars.baz from Foo f 이런 네비게이션은 이제 에러난다.
  • select b.baz from Foo f join f.bars b 이렇게 써야 한다.

Changed aggregation (count, sum, avg) function return types

  • 스펙에 맡게 리턴타입이 바꼈다.
  • COUNT는 Long을 반환한다.
  • MAX와 MIN은 적용된 필드 타입을 반환한다.
  • AVG는 Double을 반환한다.
  • SUM은 정수타입이 경우에는 Long을 반환한다. 실수일 경우에는 Double, BidInteger일 경우에는 BidInteger, BigDecimal일 경우에는 BidDecimal.
  • 이전 타입을 쓰려면 다음의 코드를 추가한다.
Configuration classicCfg = new Configuration(); 
classicCfg.addSqlFunction( "count", new ClassicCountFunction());
classicCfg.addSqlFunction( "avg", new ClassicAvgFunction());
classicCfg.addSqlFunction( "sum", new ClassicSumFunction());
SessionFactory classicSf = classicCfg.buildSessionFactory();

Improved parameter type guessing

  • 이전에는 파라미터의 타입을 넘어온 값으로 판단했는데, 이제는 쿼리르 보고 판단한다.

Expanded component support

  • First is the ability to bind complete components as parameter values.
Name name = new Name();
name.setFirst( "John" );
name.setLast( "Doe" );
List johnDoes = session.createQuery( "from Person where name = :name" )
.setParameter( "name", name )
.list();
  • ANSI SQL 처럼 "row balue contructor"를 사용할 수 있다.
List johnDoes = session.createQuery( "from Person where name = ('John', 'Doe')" ).list();

Improved boolean literal and parameter handling

  • List pregs = session.createQuery( "from Animal where pregnant = true" ).list(); 이런 쿼리에서의 true 역시 쿼리를 분석하여 SQL을 만들어 낸다.

Native SQL 쿼리 변경사항

Sequence of return values from native sql queries (createSQLQuery() and getNamedQuery())

  • 이전에는 scalar 먼저, entity는 그 다음 순으로 반환했다.
  • 3.2에서 그 순서는 맵핑이나 코드에 있는 것으르 따른다.
  • 따라서 다음의 코드는 에러가 나지 않지만, 만약에 Entity를 먼저 add했으면 에러가 날 것이다.
List result = s.createSQLQuery("select o.*, o.value as anumber from ORDER as o").addScalar("anumber").addEntity(Order.class).list();
Object[] row = (Object[])result.get(0);
Integer number = (Integer)row[0];
Order order = (Order)row[1];

Stored procedures no longer require OUT parameter

  • 이전에는 영향을 받는 row의 수를 반환하는 out 파라미터를 필요로 했었는데 이젠 필수사항이 아니다.
  • check 속성에 none, count, param 을 사용할 수 있다.

'Hibernate > migration' 카테고리의 다른 글

Migrate To Hibernate 3.2  (0) 2008.02.14
Migrate To Hibernate 3.1  (0) 2008.02.14
Migrate To Hibernate 3.0  (2) 2008.02.14
top

Write a comment.


Migrate To Hibernate 3.1

Hibernate/migration : 2008.02.14 19:03


API 변경사항

NamingStrategy 인터페이스로 업데이트

  • 인터페이스에 추가된 새로운 메소드를 구현하거나, DefaultNamingStrategy 또는 ImprovedNamingStrategy 클래스를 상속받아라.

이벤트 리스너

  • 이벤트 하나에 여러 개의 리스너를 등록할 수 있다. 이벤트 리스너의 API들이 리스너 배열을 받도록 수정되었다.

설정 변경사항

JDBC 커넥션 release mode

  • 이전(2.0, 3.0)까지는 기본값이 ON_CLOSE였는데, 기본값이 이제(3.1)는 auto로 바꼈다.
  • session.connection()을 호출했을 때는 close()를 호출해야한다. 이 뒤에 배포에서 이 부분은 변경 될 것이다.

이벤트 리스너

  • 이벤트에 여러 개의 리스너를 등록할 수 있다.
  • hibernate.cfg.xml에서 <listener> 대신에 좀 더 정확한 <event> 엘리먼트를 사용한다.

쿼리 언어 변경사항

Stricter checking of invalid queries

  • from Entity e where e.collection.property 이런 쿼리는 작성할 수 없다. 명시적으로 join을 사용해야 한다.
  • fetch를 사용할 때, 주가 되는 쪽을 가져오지 않는 쿼리는 의미가 없으므로 예외를 발생시킨다. ex) select b from A join fetch a.bees b

hbm2java

  • 별도의 툴이 아니라, tools.hibernate.org에 통합됐다. 이 과정에서 깨지거나 유지보수 하지 않는 기능은 제거했다. Hbm2javaCompability을 참조하라.

'Hibernate > migration' 카테고리의 다른 글

Migrate To Hibernate 3.2  (0) 2008.02.14
Migrate To Hibernate 3.1  (0) 2008.02.14
Migrate To Hibernate 3.0  (2) 2008.02.14
top

Write a comment.


Migrate To Hibernate 3.0

Hibernate/migration : 2008.02.14 19:02


참조: Hibernate Migration Reference

API 변경사항

패키지 이름

  • net.sf.hibernate에서 org.hibernate로 변경.
  • net.sf.hibernate.expression에서 org.hibernate.criterion으로 변경.
  • 하이버네이트를 참조하는 외부 라이브러러(EHCache 같은 경우) net.sf.ehcache.hibernate.Provider에서 org.hibernate.cache.EhCacheProvider로 변경.

Deprected Interfaces

  • org.hibernate.classic 패키지로 이동됐다.
  • Session 인터페이스의 find(), iterate(), filter(), delete() 대신에 createQuery() 사용해야 된다.
  • Session 인터페이스의 saveOrUpdateCopy()대신에 delete, merge() 해야된다.
  • 배열을 인자로 받는 createSQLQuery()는 deperecated됐다.
  • Lifecycle과 Validatable 인터페이스는 deprecated됐다. 그 대신 Interceptor나 Hibernate 3 이벤트 프레임워크를 사용하라.

종속성

  • lib/README.txt 참조할 것.

예외 모델

  • HibernateExcpetion을 비롯해 모든 예외는 Uncheched Exception으로 바꼈다.

Interceptor 인터페이스

  • 두 개의 새로운 메소드 추가 됐다. 따라서 이 인터페이스 구현체들은 비어있는 메소드 두 개를 구현해야 한다.
  • instantiate()의 인자로 Class 객체 대신에 Entity 이름을 받는 String 값을 받도록 바꼈다.
  • isUnsaved())에서 isTransient()로 메소드 이름이 바꼈다.

UserType, CompositeUserType

  • org.hibernate.usertype 패키지로 이동됐고, 새로운 메소드 몇 개가 추가 됐다.
  • ParameterizedType 인터페이스 추가됐다.

FetchMode

  • FetchMode.Lazy와 FetchMode.EAGER는 deprecated됐다. 좀 더 정확한 이름으로 FetchMode.SELECT와 FetchMode.JOIN으로 바꼈다.

PersistentEnum

  • PersustentEnum 클래스는 Hibernate3에서 제거됐다. UserType을 사용하라.

Blob과 Clob 지원

  • Blob이나 Clob 타입을 detached, serialized, merge() 메소드에 넘길 수 있고, 특정 밴더의 타입으로 다음과 같이 변환할 수도 있다. getWrappedClob(), getWrappedBlob() 사용한다.
clob = (oracle.sql.CLOB) ( (org.hibernate.lob.SerializableClob) foo.getText() ).getWrappedClob();

확장 API

  • org.hibernate.criterion, org.hibernate.mapping, org.hibernate.persister 그리고 org.hibernate.collection 패키지는 상당히 많이 리팩터링 했다.

Metadata 변경사항

Association Fetching 전략

  • lazy="true"를 기본값으로 바꿨다. 따라서 이 설정을 하지 않은 클래스와 컬렉션에 모두 lazy="false"를 붙여야 한다.
  • outer-join 속성이 deprecated됐다. outer-join="true" 대신에 fetch="join", outer-join="false" 대신에 fetch="select"를 사용하라.

식별자 맵핑

  • unsaved-value="0"을 기본값으로 사용한다.
  • 하이버네이트 3에서 natural key(assigned identifier 또는 복합키)를 사용하거나 detached 객체를 쓸 때, 더이상 Interceptor.isUnsaved() 메소드를 구현할 필요가 없다. 힌트 없으면 DB에 쿼리 날려서 객체가 새로 만든 것인지 detached 인지 알아낸다. 따라서 그냥 isUnsaved() 사용하는게 성능에 좋겠다.

콜렉션 맵핑

  • <index>는 준-deprecated 됐다. <list-index>와 <map-key>를 권장한다. <key-many-to-many> 대신에 <map-key-many-to-many>, <composite-index> 대신에 <composite-map-key>를 사용하라.

DTD

Query Language Changes

  • 하이버네이트 3은 ANTLR-based HQL/SQL query translator를 사용한다. 2.1의 쿼리 파서도 사용할 수 있다.
  • 예전 쿼리 파서를 사용하려면 hibernate.query.factory_class 속성에 org.hibernate.hql.classic.ClassicQueryTranslatorFactory
  • 새로운 파서를 사용하려면, org.hibernate.hql.ast.ASTQueryTranslatorFactory를 설정한다.
  • Note: there is a known bug affecting dialects with theta-style outer joins (eg. OracleDialect for Oracle 8i, TimesTen dialect, Sybase11Dialect). Try to use a dialect which supports ANSI-style joins (eg. Oracle9Dialect), or fall back to the old query parser if you experience problems.
  • elements() 대신에 명시적인 join을 사용하라.

설정 변경

  • BEA Weblogic issues 패스.

'Hibernate > migration' 카테고리의 다른 글

Migrate To Hibernate 3.2  (0) 2008.02.14
Migrate To Hibernate 3.1  (0) 2008.02.14
Migrate To Hibernate 3.0  (2) 2008.02.14
top

  1. Favicon of https://jjaeko.tistory.com BlogIcon 째코 2008.02.16 18:52 신고 PERM. MOD/DEL REPLY

    얼마전에 1-1연관 실습하면서 fech가 디폴트로 select라고 알고 있는데 lazy로딩이 안된적이 있었죠
    그래서 fech 속성이 디폴트로 select임에도 불구하고 명시적으로 지정하니까 그제서야 select가 되는걸보고 이상하다고 생각 했는데 outer-join 속성의 영향때문이었군요??

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.02.16 22:48 PERM MOD/DEL

    네. 이전에는 패칭 기본값이 true였는데, 이런 상태면...대부분의 상황에서는 필요 이상의 굉장히 무거운 객체 덩어리를 가져오게 되는데.. 그래서 개발자들은 명시적으로 항상 lazy="true" 라고 매번 설정해 주어야했고.. 그런 불편을 줄이려고 하이버3에서 기본값을 패칭 전략을 아예 lazy loading으로 바꿔버린 것 같습니다.

    그래서... 개발자들에게 혼란과 마이그레이션 할 때의 불편함을 야기했지만 뭐.. 일장일단이니까 쌤쌤인것 같습니다.

Write a comment.


맵 맵핑하기

Hibernate/Chapter 7 : 2008.02.09 10:24


Entity를 참조하는 값

특징

  • Map<Long, BId> 처럼, Bid의 주키값을 맵의 키값으로 가지는 콜렉션으로 맵핑할 수 있다.
  • 애플리케이션에서만 다를 뿐, 테이블은 변한 거 없다. Bid에 ITEM_ID 외례키 컬럼이 생긴다.

맵핑하기

Item.java
@MapKey(name = "id")
@OneToMany(mappedBy="item")
private Map<Long, Bid> bids = new HashMap<Long, Bid>();
Bid.java
@ManyToOne
private Item item;

ternary association

특징

  • 3항 연관을 맵을 사용해서 표현할 수 있다.
  • Category, Item, User의 관계에서 Category가 Item을 키, User을 값으로 가지는 콜렉션을 가지도록 설정할 수 있다. 왜? Category에 속하는 Item은 유일하고, User는 여러 개의 Item을 추가할 수 있으니까.

맵핑하기

Category.java
@ManyToMany
@MapKeyManyToMany(joinColumns = @JoinColumn(name = "ITEM_ID"))
@JoinTable(name = "CATEGORY_ITEM", joinColumns = @JoinColumn(name = "CATEGORY_ID"), inverseJoinColumns = @JoinColumn(name = "USER_ID"))
private Map<Item, User> itemsAndUser = new HashMap<Item, User>();
  • CATEGORY_ITEM 테이블을 만들고, 이 테이블의 주키를 CATEGORY_ID로 설정한다.
  • 맵의 키는 ITEM_ID, 값은 USER_ID로 맵핑된다.
top

Write a comment.


Join Table에 컬럼 추가하기

Hibernate/Chapter 7 : 2008.02.09 10:22


특징

  • 연관 테이블에 추가 속성이 필요할 수 있다.

연관 테이블을 Intermediate Entity로 맵핑하기

특징

  • 연관 테이블로 맵핑 될 새로운 테이블을 작성한다.
  • 양방향 네이게이션이 가능하다.(장점)
  • 연관 클래스를 생성하고 제거하는데 관리해야하는 코드가 늘어난다.(단점)
  • Category나 Item을 추가할 때 CategoryItem에 Cascade 옵션을 사용해서 transitive persistence를 사용할 수 있다.(12장에서 다룸)

맵핑하기

CategoryItem.java
@Entity
public class CategoryItem {

@Embeddable
public static class Id implements Serializable {

@Column(name = "CATEGORY_ID")
private Long categoryId;

@Column(name = "ITEM_ID")
private Long itemId;

public Id() {
}

public Id(Long categoryId, Long itemId) {
this.categoryId = categoryId;
this.itemId = itemId;
}

public boolean equals(Object o) {
if (o != null && o instanceof Id) {
Id that = (Id) o;
return this.categoryId.equals(this.categoryId)
&& this.itemId.equals(that.itemId);
} else {
return false;
}
}

public int hashCode() {
return categoryId.hashCode() + itemId.hashCode();
}
}

public CategoryItem() {
}

public CategoryItem(Category category, Item item) {
this.category = category;
this.item = item;

id.categoryId = category.getId();
id.itemId = item.getId();

item.getCategoryItems().add(this);
category.getCategoryItems().add(this);
}

@EmbeddedId
private Id id = new Id();

@Column(name = "ADDED_ON")
private Date dateAdded = new Date();

@ManyToOne
@JoinColumn(name = "ITEM_ID", insertable = false, updatable = false)
private Item item;

@ManyToOne
@JoinColumn(name = "CATEGORY_ID", insertable = false, updatable = false)
private Category category;

//getter, setter
}
Category.java
@OneToMany(mappedBy = "category")
private Set<CategoryItem> categoryItems = new HashSet<CategoryItem>();
  • 생성자에 참조 무결성을 지키기 위한 코드 추가.
  • 이 객체가 연관을 맺고 있는 클래스들의 상태는 Detached 이거나, Persistent 상태여야 한다. Transient 상태에서 Fake Object를 활용해도 되지 않는다.
Error Log
Hibernate: insert into CategoryItem (ADDED_ON, CATEGORY_ID, ITEM_ID) values (?, ?, ?)
2008-02-08 09:51:31,381 WARN [org.hibernate.util.JDBCExceptionReporter] -
<SQL Error: 0, SQLState: null>
2008-02-08 09:51:31,381 WARN [org.hibernate.util.JDBCExceptionReporter] -
<SQL Error: 0, SQLState: null>
2008-02-08 09:51:31,381 ERROR [org.hibernate.util.JDBCExceptionReporter] - <failed batch>
2008-02-08 09:51:31,381 ERROR [org.hibernate.util.JDBCExceptionReporter] - <failed batch>
2008-02-08 09:51:31,381 ERROR [org.hibernate.event.def.AbstractFlushingEventListener] -
<Could not synchronize database state with session>

연관 테이블을 컴포넌트의 콜렉션으로 맵핑하기

특징

  • 라이프사이클 관리를 별도로 하지 않아도 된다. 컴포넌트 콜렉션을 가지고 있는 Entity에 추가하거나 삭제하기만 하면 된다.
  • 양방향 네비게이션을 할 수 없다. 컴포넌트는 공유될 수 없기 때문에, Item에서 CategoryItem으로 이동하지 못한다. 그치만 SQL을 잘 작성해서 가져올 순 있다.

맵핑하기

CategoryItem.java
@Embeddable
public class CategoryItem {

public CategoryItem() {
}

public CategoryItem(Category category, Item item){
this.category = category;
this.item = item;
}

@Parent
private Category category;

@ManyToOne
@JoinColumn(name = "ITEM_ID", insertable = false, updatable = false)
private Item item;

@Temporal(TemporalType.TIMESTAMP)
@Column(name = "ADDED_ON", nullable = false)
private Date dateAdded = new Date();

// getter, setter, equals, hashcode
}
Category.java
@CollectionOfElements
@JoinTable(name = "CATEGORY_ITEM", joinColumns = @JoinColumn(name = "CATEGORY_ID"))
private Set<CategoryItem> categoryItems = new HashSet<CategoryItem>();
  • 양방향 관계를 맵핑할 수 없다. Item에서 CategoryItems 콜렉션을 가질 수 없다. 오직 하나의 Entity에 포함된다. 공유 자원이 아니니까.
  • CategoryItem에 있는 모든 속성은 Not Null이어야 한다. 주키 없으니까. 모든 속성을 복합키로 사용하기 때문에.
top

Write a comment.


ManyToMany 관계 맵핑

Hibernate/Chapter 7 : 2008.02.09 10:20


단방향 다대다 관계

  • 대부분 추가적인 정보가 필요하기 때문에, 별도의 assiation class를 만들게 된다. 여기서는 간단한 다대다 맵핑.

Set 타입으로 맵핑하기

Category.java
@ManyToMany
@JoinTable(name = "CATEGORY_ITEM", joinColumns = @JoinColumn(name = "CATEGORY_ID"),
inverseJoinColumns = @JoinColumn(name = "ITEM_ID"))
private Set<Item> items = new HashSet<Item>();
  • 이 때는 Category 클래스에 별도의 컬럼이 생기지 않는다. @ManyToOne에서 Join Table을 만들었을 때는 inverseJoinColumns의 컬럼이 생겼었다.
  • 콜렉션에 추가할 때(category.getItems().add(itme)) 다음의 SQL 날린다. insert into CATEGORY_ITEM (CATEGORY_ID, ITEM_ID) values (?, ?)

Idbag 타입으로 맵핑하기

Category.java
@ManyToMany
@CollectionId(
columns=@Column(name="CATEGORY_ITEM_ID"),
generator = "sequence",
type = @Type(type="long")
)
@JoinTable(name = "CATEGORY_ITEM", joinColumns = @JoinColumn(name = "CATEGORY_ID"),
inverseJoinColumns = @JoinColumn(name = "ITEM_ID"))
private Set<Item> items = new HashSet<Item>();
  • surrogate 키를 가지고 있기 때문데, 중복을 허용한다.
  • @CollectionId, @Type은 하이버네이트의 애노테이션이다.
  • 콜렉션에 추가할 때 다음의 SQL 날린다. insert into CATEGORY_ITEM (CATEGORY_ID, CATEGORY_ITEM_ID, ITEM_ID) values (?, ?, ?)

List 타입으로 맵핑하기

Category.java
@ManyToMany
@JoinTable(name = "CATEGORY_ITEM", joinColumns = @JoinColumn(name = "CATEGORY_ID"),
inverseJoinColumns = @JoinColumn(name = "ITEM_ID"))
@IndexColumn(name = "DISPLAY_POSITION")
private List<Item> items = new ArrayList<Item>();
  • 콜렉션에 추가할 때 다음의 SQL 날린다. insert into CATEGORY_ITEM (CATEGORY_ID, DISPLAY_POSITION, ITEM_ID) values (?, ?, ?)

양방향 다대다 관계

Item.java
@ManyToMany(mappedBy="items")
private Set<Category> categories = new HashSet<Category>();
  • 양방향 관계에서 1쪽의 inverse로 맵핑했었던 이유? 외례키 컬럼을 두 번이나 사용했으니까.
  • 이번에도 마찬가지로, 두 쪽 중에 한 쪽은 inverse로 설정해 주어야 한다.
  • cascade 설정 중에 all, delete, delete-orphans는 다대다 관계에서 의미가 없다. 왜? Value 타입이면 모를까, Entity 타입인데, 다른 Entity가 지워진다고 해서, 해당 Entity까지 지워지라는 보장은 없다. 거의 그런 경우는 없을 것이다. 따라서 의미가 없다. save or update만 사용하는게 좋겠다.
  • 양방향 관계에 있는 콜렉션 타입 결정은 inverse와 관련이 있다. inverse쪽에는 bag과 set처럼 id가 없는 것들만 올 수 있다. 왜? index나 키를 생성하는 쪽 SQL을 무시해버리면 indexColumn에 필요한 정보가 들어갈리 없으니까. 결론적으로, 양쪽 모두 index나 키를 가지는 콜렉션끼리 다대다 관계를 가질 수 없다. 왜? 양쪽 중 한 쪽은 inverse로 설정할 텐데 그러면 그 쪽에 필요한 정보가 생성되지 않을테니까.

'Hibernate > Chapter 7' 카테고리의 다른 글

다형적인 콜렉션 연관  (0) 2008.02.20
다형적인 ManyToOne 연관  (0) 2008.02.20
하이버네이트 애노테이션과 XML 설정 사이의 Fetching 기본값 차이  (0) 2008.02.20
맵 맵핑하기  (0) 2008.02.09
Join Table에 컬럼 추가하기  (0) 2008.02.09
ManyToMany 관계 맵핑  (7) 2008.02.09
OneToMany 관계 맵핑  (1) 2008.02.09
Multy-valued entity 연관  (0) 2008.02.09
OneToOne Join Table 맵핑  (0) 2008.02.09
OneToOne 외례키 맵핑  (0) 2008.02.09
주키를 공유하는 맵핑  (0) 2008.02.09
top

  1. seeyoung 2008.03.18 22:08 PERM. MOD/DEL REPLY

    ManyToMany로 세개의 테이블이 연결될때 두개까지는 문제가 없는데, 세개부터는 Table생성이 되지 않고 있습니다. 어떤 문제 일까요?

    @ManyToMany()
    @JoinTable(
    name="ROLE_AUTHORITY",
    joinColumns=@JoinColumn(name="ROLE_ID",
    inverseJoinColumns=@JoinColumn(name="AUTHORITY_ID"
    )
    private Set<Authority> authorities = new HashSet<Authority>();

    @ManyToMany()
    @JoinTable(name = "GROUP_ROLE",
    joinColumns = @JoinColumn(name = "GROUP_ID",
    inverseJoinColumns = @JoinColumn(name = "ROLE_ID";)
    private Set<Role> roles = new HashSet<Role>();

    Authority, Role, ROLE_AUTHORITY, GROUP_ROLE까지는 테이블 생성을 확인했는데, Group 테이블은 생성이 되어 있지 않고, Group 객체를 만들어서 save하려고 하자 에러가 발생합니다.

    ManyToMany에 대해 세개까지 연결되는게 문제가 있는지요?

    seeyoungAtgmail.com으로 알려주시면 대단히 감사하겠습니다.

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2008.03.18 22:33 신고 PERM MOD/DEL

    Group ---> Role ---> Authority
    세 개의 Entity들이 이렇게 연관되어 있나보군요.
    재미있겠네요. 해보고 블로그에 올리겠습니다.

    저런 상황에서 JoinTable을 사용하여 맵핑하고, Group을 저장하면 되는거군요.

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.03.18 23:11 PERM MOD/DEL

    아항.. Group이라는 이름이 사용하시는 DB에 키워드로 잡혀있어서 테이블을 못 만든것 같습니다.

    Group위에 붙인 @Entity에 name 속성에 다른 이름을 주고 해보시면 잘 될 겁니다. :)

  2. seeyoung 2008.03.19 11:23 PERM. MOD/DEL REPLY

    Thanks SO MUCH!!!

    맞습니다. DB Keyword인듯 합니다. Class명을 아여 UserGroup으로 변경했는데, 잘 되는군요.

    정말 감사합니다.

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.03.19 12:28 PERM MOD/DEL

    :)

  3. seeyoung 2008.03.19 11:36 PERM. MOD/DEL REPLY

    아하... group by에 group 키워드에 걸린거 같군요.

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.03.19 12:27 PERM MOD/DEL

    오호~ 그렇군요.ㅋㅋ

Write a comment.


OneToMany 관계 맵핑

Hibernate/Chapter 7 : 2008.02.09 10:18


Bag으로 맵핑하기

  • Bag은 1대다 양방향 관계를 맵핑하는 가장 효율적인 콜렉션이다. 왜? index도 없고, 중복도 허용하기 때문에, 그냥 넣기만 하면 된다.
  • 그러나 2개의 bag 타입 콜렉션을 동시에 eaget-fetching 할 수 없다.(13장에서 다룬다.)
  • Bag 타입을 사용하려면 변수의 타입은 Collection, 구현체는 ArrayList를 사용한다.
  • 중복 데이터를 넣는 일은 코딩을 잘 해서 방지하면 되지만, 어차피 mappedby 속성(inverse)를 Collection에 걸어두었기 때문에, 여러번 추가해도 SQL은 실행되지 않는다.(그래도.. 메모리 상에서는 콜렉션에 들어갈텐데, 이러면, 안 되지 않을까.. 흠)
Item.java
@OneToMany(mappedBy = "item")
private Collection<Bid> bids = new ArrayList<Bid>();
Bidjava
@ManyToOne
private Item item;

List로 맵핑하기

  • 콜렉션에 있는 Entity들의 index가 필요하다면, List로 맵핑하면 된다.
  • 콜렉션을 맵핑할 때, 콜렉션의 엔티티는 index를 가지고 있을 추가적인 컬럼을 설정한다.(@IndexColumn)
  • 양방향 설정할 때, 콜렉션에 index가 있는 경우(List, Map, 배열)에는 One쪽을 무시(inverse)한다.
  • 왜? 콜렉션이 DB를 수정할 때 필요로 하는 정보(index)를 가지고 있기 때문에, 콜렉션을 기준으로 DB를 수정해야 한다. -> Bid테이블에 Bid_Index 컬럼이 있는데, 이 안에 넣을 값은 Item에 있는 List<Bid> 콜렉션에 넣어야 알 수 있자나. 그러니까 Bid를 기준으로 SQL을 만들 수 없지. 콜렉션에 넣을 때(item.getBids().add(bid)) Bid_Index에 넣을 값을 알 수 있으니까, 콜렉션을 기준으로 SQL을 생성하고, 그 반대쪽 SQL은 발생하지 않도록 inverse 해줘야 해.
  • 따라서 Item과 Bid의 관계에서 Bid쪽의 변화를 무시해야 하는데, @ManyToOne에는 mappedBy 속성이 없다. 그래서 꼼수가 필요하다.
Item.java
@OneToMany
@JoinColumn(name="ITEM_ID", nullable = false)
@IndexColumn(name = "BID_POSITION")
private List<Bid> bids = new ArrayList<Bid>();
Bid.java
@ManyToOne
@JoinColumn(name="ITEM_ID", updatable=false, insertable=false)
private Item item;
  • item.getBids().add(bid); 이렇게 할 때 SQL(update Bid set ITEM_ID=?, BID_POSITION=? where id=?)을 날린다.
  • Bid 와 Item에 설정한 JoinColumn은 같은 이름이어야 한다. 둘 다 Many쪽(여기서는 Bid)에 있는 외례키 컬럼을 나타낸다.
  • JPA는 indexed list를 지원하지 않기 때문에, 하이버네이트의 @IndexColumn 애노테이션을 사용한다.

Join Table로 맵핑하기

  • OneToMany관계가 optional인 경우에 사용하면 좋다. 그러지 않고, 외례키 컬럼이 Null을 허용하도록 해도 되긴 되지만 비추한다.
  • OneToOne관계를 Join Table로 맵핑한 것과 비슷하지만, 그 때는 두 개의 키를 모두 unique로 설정했지만, 이번에는 Many쪽의 주키만 unique로 설정한다. 왜? Join Table에서 One쪽의 주키값은 여러 번 등장할 수 있고, Many쪽의 주키값은 한 번만 등장할 수 있다. 그래야 OneToMany 관계가 되니까.
Item.java
@ManyToOne
@JoinTable(
name="ITEM_USER",
joinColumns=@JoinColumn(name="ITEM_ID"),
inverseJoinColumns=@JoinColumn(name="USER_ID")
)
private User user;
  • 양방향으로 설정할 때는 User에 Item의 콜렉션을 추가한다.
  • ITEM_USER라는 연관 테이블을 만들고, 그 테이블에 ITEM_ID라는 컬럼과 USER_ID 컬럼을 만든다.
  • ITEM_USER 테이블과 Item 테이블과의 연관을 맺을 컬럼은 USER_ID 컬럼이다. 즉 이 컬럼이 Item 테이블에 생긴다.(InverseJoinColumns)
@OneToMany(mappedBy = "user")
private Set<Item> items = new HashSet<Item>();
  • 콜렉션의 변화는 무시한다.

'Hibernate > Chapter 7' 카테고리의 다른 글

다형적인 ManyToOne 연관  (0) 2008.02.20
하이버네이트 애노테이션과 XML 설정 사이의 Fetching 기본값 차이  (0) 2008.02.20
맵 맵핑하기  (0) 2008.02.09
Join Table에 컬럼 추가하기  (0) 2008.02.09
ManyToMany 관계 맵핑  (7) 2008.02.09
OneToMany 관계 맵핑  (1) 2008.02.09
Multy-valued entity 연관  (0) 2008.02.09
OneToOne Join Table 맵핑  (0) 2008.02.09
OneToOne 외례키 맵핑  (0) 2008.02.09
주키를 공유하는 맵핑  (0) 2008.02.09
Single-valued entity 연관  (0) 2008.02.09
top

  1. Favicon of https://java.ihoney.pe.kr BlogIcon 허니몬 2013.05.01 10:59 신고 PERM. MOD/DEL REPLY

    아!!

    역시... 기본적인 이론을 바탕을 가지고 시작했어야 하는 하이버네이트 ㅎㅎ
    주먹구구식으로 도전했다가 낭패만 보다가 여기서 작은 깨달음을 얻습니다.
    ^^ 좋은 정보 감사합니다.

Write a comment.


Multy-valued entity 연관

Hibernate/Chapter 7 : 2008.02.09 10:16


  • 1대다 관계
  • 다대다 관계
  • Join Table에 컬럼 추가하기
  • 맵 맵핑하기

'Hibernate > Chapter 7' 카테고리의 다른 글

다형적인 ManyToOne 연관  (0) 2008.02.20
하이버네이트 애노테이션과 XML 설정 사이의 Fetching 기본값 차이  (0) 2008.02.20
맵 맵핑하기  (0) 2008.02.09
Join Table에 컬럼 추가하기  (0) 2008.02.09
ManyToMany 관계 맵핑  (7) 2008.02.09
OneToMany 관계 맵핑  (1) 2008.02.09
Multy-valued entity 연관  (0) 2008.02.09
OneToOne Join Table 맵핑  (0) 2008.02.09
OneToOne 외례키 맵핑  (0) 2008.02.09
주키를 공유하는 맵핑  (0) 2008.02.09
Single-valued entity 연관  (0) 2008.02.09
top

Write a comment.


OneToOne Join Table 맵핑

Hibernate/Chapter 7 : 2008.02.09 10:15


특징

  • 만약 One To One 관계에 있는 두 Entity가 optional 하다면...
  • 이전에 배운 외례키 사용하는 맵핑을 한 다음에, 외례키를 nullable하게 설정하게 할 수 있겠지만...
  • 관계에 추가적인 속성이 추가될 수도 있을테니, 두 Entity의 id를 모두 unique하게 가지고 있고, Join Table을 만들 수도 있다. ORM이 이런 작업을 해줄 수 있다.
  • CaveatEmptor에서는 Shipment와 Item의 관계. Shipment가 Item을 가지고 있을 수도 있고(물건을 배송 했다.), 없을 수도(배송을 안해서 물건 판 사람이 돈을 못 받을 것이다.) 있다. 배송을 했을 경우에만 두 관계를 나타내는 테이블에 레코드를 추가해야 할 것이다.

맵핑하기

Shipment.java
@OneToOne
@JoinTable(name = "ITEM_SHIPMENT", joinColumns = @JoinColumn(name = "SHIPMENT_ID"), inverseJoinColumns = @JoinColumn(name = "ITEM_ID"))
private Item auction;
  • Item과 Shipment는 별개의 라이프 사이클을 가지고 있다가, Shipment에 Item을 setting하는 순간, ITEM_SHIPMENT에 레코드가 추가된다.
  • 만약 Join Table에 별도의 속성이 더 필요할 때가 있는데, 그럴 때는 2차 테이블(8장에서 살펴볼 예정)을 사용해서 설정할 수 있다.
@Entity
@Table(name = "SHIPMENT")
@SecondaryTable(name = "SHIPMENT_ITEM")
public class Shipment {

...

@OneToOne
@JoinColumn(table = "SHIPMENT_ITEM", name = "ITEM_ID")
private Item auction;

...
}
  • SecondTable을 정의해 두고, 그 테이블로 옮길 속성을 JoinColumn에서 table 속성을 사용하여 설정한다.

'Hibernate > Chapter 7' 카테고리의 다른 글

다형적인 ManyToOne 연관  (0) 2008.02.20
하이버네이트 애노테이션과 XML 설정 사이의 Fetching 기본값 차이  (0) 2008.02.20
맵 맵핑하기  (0) 2008.02.09
Join Table에 컬럼 추가하기  (0) 2008.02.09
ManyToMany 관계 맵핑  (7) 2008.02.09
OneToMany 관계 맵핑  (1) 2008.02.09
Multy-valued entity 연관  (0) 2008.02.09
OneToOne Join Table 맵핑  (0) 2008.02.09
OneToOne 외례키 맵핑  (0) 2008.02.09
주키를 공유하는 맵핑  (0) 2008.02.09
Single-valued entity 연관  (0) 2008.02.09
top

Write a comment.


OneToOne 외례키 맵핑

Hibernate/Chapter 7 : 2008.02.09 10:12


특징

  • 주키를 공유하는 것이 아니라, 외례키로 관계를 맺는다.
  • User---->Address 의 관계에서 User는 Source, Address는 Target.
  • Source에서 Target이 한 개씩 맵핑되기 때문에, ManyToOne으로 Unique 속성을 true로 맵핑한다.(XML에서만)
  • 양방향으로 맵핑할 때는 Address---->User로 맵핑도 해줘야 한다.
  • 하이버네이트에 Address의 user가 반대쪽 관계를 나타내는 속성의 inverse라는 것을 표현해준다.

맵핑하기

  • 단뱡향 맵핑
User.java
@OneToOne
@JoinColumn(name="SHIPPING_ADDRESS_ID")
@Cascade( { CascadeType.SAVE_UPDATE })
private Address shippingAddress;
  • Address의 주키 속성은 그냥 일반적인 id로 설정하고 기본 주키 생성기를 사용한다. 커스텀 주키 생성기 사용할 필요 없슴.
  • 양방향 맵핑
Address.java
@OneToOne(mappedBy = "shippingAddress")
private User user;
  • mappedBy를 설정해 주어야 Address에 별도의 외례키 컬럼을 만들지 않는다. 왜? inverse라는 걸 알려줬으니까. inverse? 이건 이미 이 반대 방향의 관계에서 이 둘의 관계에 필요한 속성이 있으니까 불필요한 컬럼(또는 추가적인 SQL)을 만들지 않도록 하이버한테 알려주는 거지.

'Hibernate > Chapter 7' 카테고리의 다른 글

다형적인 ManyToOne 연관  (0) 2008.02.20
하이버네이트 애노테이션과 XML 설정 사이의 Fetching 기본값 차이  (0) 2008.02.20
맵 맵핑하기  (0) 2008.02.09
Join Table에 컬럼 추가하기  (0) 2008.02.09
ManyToMany 관계 맵핑  (7) 2008.02.09
OneToMany 관계 맵핑  (1) 2008.02.09
Multy-valued entity 연관  (0) 2008.02.09
OneToOne Join Table 맵핑  (0) 2008.02.09
OneToOne 외례키 맵핑  (0) 2008.02.09
주키를 공유하는 맵핑  (0) 2008.02.09
Single-valued entity 연관  (0) 2008.02.09
top

Write a comment.


주키를 공유하는 맵핑

Hibernate/Chapter 7 : 2008.02.09 10:11


특징

  • 두 테이블에서 같은 주키값을 사용한다.
  • 문제는 A-->B 에서 B가 A의 주키값을 사용하도록 하는 것이다. 주기를 공유하도록 설정해야 한다.

맵핑하기

  • JPA는 공유할 주키 생성기를 지원하는 표준화된 방법이 없다.
  • 하이버네이트는 커스텀 id 생성기를 위한 확장 애노테이션을 제공한다.
User.java
@OneToOne
@PrimaryKeyJoinColumn
@Cascade( { CascadeType.SAVE_UPDATE })
private Address shippingAddress;
  • @PrimaryKeyJoinColumn: JOINE을 이용한 상속구조(Table Per Subclass)에서 하위 타입에서 상위 타입의 주키를 자신의 주키이자 외례키로 설정할 때 사용. 여기(1:1맵핑)서는 참조하는 쪽(User)의 주키를 참조되는 쪽(Address)의 외례키로 사용하겠다는 설정이다.
Address.java
@Id
@GeneratedValue(generator = "addressIdGenerator")
@GenericGenerator(name = "addressIdGenerator", strategy = "foreign", parameters = @Parameter(name = "property", value = "user"))
@Column(name = "ADDRESS_ID")
private Long id;

@OneToOne
private User user;
  • JPA에서는 커스텀 식별자 생성기를 만들 수 없기 때문에, 하이버네이트 애노테이션을 이용해야 한다.
테스트 코드
@Test
public void userAndAddress() throws Exception {
Session session = sessionFactory.openSession();
User user = new User();

Address address = new Address();
// Address의 user 속성은 null이면 안 된다. id를 세팅해 주려고 할 때 identifierGeneration 에러 난다.
address.setUser(user);

user.setShippingAddress(address);
session.save(user);

session.flush();
session.close();
assertNotNull(user.getId());
assertNotNull(user.getShippingAddress().getId());
}
  • Fake Object인가... 저렇게 하지 않으면 에러 발생.

'Hibernate > Chapter 7' 카테고리의 다른 글

다형적인 ManyToOne 연관  (0) 2008.02.20
하이버네이트 애노테이션과 XML 설정 사이의 Fetching 기본값 차이  (0) 2008.02.20
맵 맵핑하기  (0) 2008.02.09
Join Table에 컬럼 추가하기  (0) 2008.02.09
ManyToMany 관계 맵핑  (7) 2008.02.09
OneToMany 관계 맵핑  (1) 2008.02.09
Multy-valued entity 연관  (0) 2008.02.09
OneToOne Join Table 맵핑  (0) 2008.02.09
OneToOne 외례키 맵핑  (0) 2008.02.09
주키를 공유하는 맵핑  (0) 2008.02.09
Single-valued entity 연관  (0) 2008.02.09
top

Write a comment.


Single-valued entity 연관

Hibernate/Chapter 7 : 2008.02.09 10:10


  • User에 Component로 맵핑했던 Address를 별도의 테이블로 구분하고 싶을 수 있다. 다른 Entity에서 Address를 공유할 수 있다. 이럴 때 1:1 맵핑이 된다.
  • 주키를 공유하는 맵핑
  • OneToOne 외례키 맵핑
  • Join Table 맵핑

'Hibernate > Chapter 7' 카테고리의 다른 글

다형적인 ManyToOne 연관  (0) 2008.02.20
하이버네이트 애노테이션과 XML 설정 사이의 Fetching 기본값 차이  (0) 2008.02.20
맵 맵핑하기  (0) 2008.02.09
Join Table에 컬럼 추가하기  (0) 2008.02.09
ManyToMany 관계 맵핑  (7) 2008.02.09
OneToMany 관계 맵핑  (1) 2008.02.09
Multy-valued entity 연관  (0) 2008.02.09
OneToOne Join Table 맵핑  (0) 2008.02.09
OneToOne 외례키 맵핑  (0) 2008.02.09
주키를 공유하는 맵핑  (0) 2008.02.09
Single-valued entity 연관  (0) 2008.02.09
top

Write a comment.


객체 상태 전이하기

Hibernate/Chapter 6 : 2008.02.07 22:22


Parent와 Child

  • Parent Entity와 Child Entity 사이의 관계를 다루는 세 가지 방법
    1. 각각을 별도의 객체로 다룬다: Bid 객체를 매번 save(), delete() 해준다. 물론 객체쪽 콜렉션에서도 이것을 다루기 위한 add()와 remove() 같은 메소드가 필요하다.
    2. Child 를 Value Type으로 만든다: Bid를 @Embeddable로 만들고 Parent의 라이프사이클을 따르도록 한다. 그러나 Bid는 분명 Entity다. Item만 Bid을 가지는게 아니라, User도 똑같은 Bid를 가질 수 있고, Item과 연관을 맺는 successfulIBid의 경우를 봐도, Bid는 분명히 공유되는 것이기 때문에, Entity가 되어야 한다.
    3. Transitive Persistence 기능을 사용한다: 하이버네이트가 연관된 Entity 객체의 라이프사이클을 자동으로 관리하도록 설정하여, 코드 수를 줄일 수 있다.

Transitive Persistence

  • Item을 저장하거나 수정할 때 그와 연관된 Bid 객체를 같이 Persistent 상태로 전이할 수 있다.
  • cascade 속성을 사용하여 설정한다. 두 방향 중 한 방향에만 설정할 수 있다. 그런데 Bid는 나중에 생성되는 객체니까, 거기에 설정해봤자 별 의미가 없다.
	@OneToMany(mappedBy = "item")
@Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE)
private Set<Bid> bids = new HashSet<Bid>();
  • JPA 애노테이션을 사용해서 cascade 설정하면, Hibernate session에서는 안 먹힌다.

삭제 전이

  • Parent를 삭제할 때 그것과 연관된 모든 Child를 삭제하도록 설정할 수 있다. 이것도 역시 코딩을 줄여준다.
	@OneToMany(mappedBy = "item")
@Cascade( { org.hibernate.annotations.CascadeType.SAVE_UPDATE,
org.hibernate.annotations.CascadeType.DELETE })
private Set<Bid> bids = new HashSet<Bid>();
  • for 루프를 돌리면서 Item이 가지고 있는 모든 Bid를 삭제한 뒤에, Item을 지우는 것과 같은 결과가 된다. 역시 코딩을 줄여준다.
  • 문제는 만약에 User가 삭제될 Bid를 가지고 있다면, 그 Bid는 메모리 상에서는 삭제 되지 않을 것이다. 그런데 DB에서는 삭제 된다. 불일치가 생긴다.
  • 그래서 메모리 상에서도 지워주기 위해서, 삭제될 Item이 가지고 있는 Bid를 가지고 있는 User를 찾아서 그 User가 가지고 있는 Bid 목록에서 삭제할 Bid를 remove 해야한다. 다량의 어지러운 코딩이 필요하다.

Orphan 삭제 전이

  • Value Type의 콜렉션에 들어있는 객체 하나를 메모리 상에서 삭제하면, 하이버네이트는 그걸 바로 DB에 반영해서 해당 Row를 지워준다. 왜? Value Type이니까 Parent에 종속적인 라이프사이클을 가지고 있고, 공유되지도 않을테니까.
  • Entity Type의 콜렉션에 들어있는 객체 하나를 지우려면, 별도의 라이프사이클을 가지고 있기 때문에, Parent의 콜렉션에서 지워주는 일은 당연히 해야 되고, session.delelte()를 호출해 주어야 한다. 모든 Parent를 찾아서 지워주는 일도 보통 일이 아니고, 잘못하면 외례키 제약을 깨트릴 수도 있다.
  • Orpahn Delete Cascade 속성은 컬렉션에서 해당 객체를 지울 때, 이제 그 객체를 참조하는 다른 Entity들이 없으니까, 이만 DB에서 지워도 된다는 설정이다.
	@OneToMany(mappedBy = "item")
@Cascade( { org.hibernate.annotations.CascadeType.SAVE_UPDATE,
org.hibernate.annotations.CascadeType.DELETE,
org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
private Set<Bid> bids = new HashSet<Bid>();
  • 이렇게 설정해 두면, 원래는 anItem.getBids().remove(aBid); session.delete(aBid); 이렇게 해야 했는데, anItem.getBids().remove(aBid); 이렇게만 호출해도 DB에서 삭제해 준다.
top

Write a comment.


1대다 관계 맵핑하기

Hibernate/Chapter 6 : 2008.02.07 22:21


단방향 맵핑하기

public class Bid {

@ManyToOne(targetEntity = chapter6.entityConllection.Item.class)
@JoinColumn(name = "ITEM_ID", nullable = false)
private Item item;

}
  • Bid 테이블에 ITEM_ID라는 이름으로 Item 클래스의 주키값을 가지는 외례키 컬럼이 생긴다.
  • @ManyToOne 애노테이션의 targetEntity 속성은 생략해도 된다.
  • @JoinColumn은 생략하면, 기본으로 타겟 Entity 이름과 주키를 언더바로 합친 이름을 사용하지만, 어차피 nullable=false 설정하려면 명시적으로 표기해야 한다.

양방향 맵핑하기

	@OneToMany(mappedBy = "item")
private Set<Bid> bids = new HashSet<Bid>();

public void add(Bid bid){
bid.setItem(this);
bids.add(bid);
}
  • Convenient Mathod를 사용해서 양방향 관계의 객체가 서로 관계를 맺도록 한다.
  • mappedBy는 XML에서 inverse 속성과 동일하다.
  • inverse 속성을 사용하는 이유? 이걸 설정해 두지 않으면, 위의 add 메소드는 두 번의 SQL을 발생시킨다.(예제를 만들어서 확인해봤으나, 에러가 발생.) 그러나 inverse 속성을 설정하여, 둘 중 한 곳에서의 변화만 SQL로 반영하도록 설정한다.
  • 위와 같이 설정한 경우 bids.add(bid); 이 메소드는 SQL을 발생시키지 않는다. bid.setItem(this); 이 메소드를 호출 할 때 SQL을 날린다.
  • @ManyToOne 쪽에 inverse 속성은 없지만, 다른 방법으로 설정할 수 있다.(다음 챕터에서 다룸)
top

Write a comment.




: 1 : 2 : 3 : 4 : 5 : ··· : 7 :