Whiteship's Note


Table 애노테이션

Hibernate/Annotation : 2008.06.19 09:00


패키지: javax.persistence

라이브러리: persistence-api-1.0.jar

속성:
- catalog: 테이블의 카탈로그
- name: 테이블 이름. 기본값은 entity 이름
- schema: 테이블의 스키마
- uniqueConstraints: Unique 제약을 걸 컬럼 설정.

예제
@Table(uniqueConstraints = @UniqueConstraint(columnNames = { "date", "item_id", "bay_id" }))
pubic class Inv { ... }

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

@CollectionOfElements 애노테이션  (0) 2008.09.19
Table 애노테이션  (0) 2008.06.19
JPA @Transient  (0) 2007.09.27
JPA @JoinColumn  (1) 2007.09.22
JPA @ManyToOne  (0) 2007.09.22
JPA @OneToMany  (0) 2007.09.22
top


"물개선생님과 하이버네이트를" 다시보기



OpenSeed 회고 - Hibernate와 OSAF 스크린캐스트


토비 사부님이 블로깅 4년치를 날리신 다음 티스토리에 새로 블로그를 만드시고 2틀 연속 블로깅을 하고 계십니다. 캬캬 이번에는 아주 재미난 글이 올라왔습니다. 제가 처음 하이버네이트에 입문했던 동영상입니다. 학습 곡선이 결코 낮지 않은 하이버네이트를 아주 아주 재밌게 배웠습니다. 깔끔하게 정리하여 올려주신 토비 사부님께 감사드립니다. 물개선생님께도 역시나 감사드립니다. 캬~ 지금 다시 봐도 명강의입니다.

강의 학습 노트

1-1. 프로젝트 관련 라이브러리 다운로드 (2)
1-2. 필요한 라이브러리들 추가
1-3. 기본 설정 하기 (2)
1-4. 모델 만들기
1-5. 모델 사용하기
1-6. 모델 클래스 수정하기
1-7. 레코드 update 하기
1-8. Hibernate Application 에서 사용되는 객체의 상태

1. 주소록 ERD
2. 주소록 ERD 수정
3. 화면 만들기
4. CSS 적용
5. 모델, DAO 까지 개발 공정
    5-1. 모델 만들기
    5-2. 간단한 모델 테스트
    5-3. DAO 만들기
    5-4. DAO 테스트 만들기 (2)
6. 연관 관계 매핑하기
    6.1. 모델 간의 연관 관계 파악
    6.2. 필요한 멤버 변수 추가
    6.3. 연관 매핑 정보 입력하기 (2)
    6.4. 연관 관계 처리를 위한 메소드 구현.
    6.5. DAO 테스트에서 연관 관계 테스팅 (5)
7. Enumeration 사용하도록 리팩터링
    7.1. MessengerType 클래스 작성.
    7.2. 기존 코드 수정하기.
    7.3. 새로운 타입으로 맵핑하기.
8. DAO 기능 구현하기 (4)
    8.1. DbUnit 사용하기
    8.2. HQL 공부하기
        8.2.1. HQL 공부하기 - select절
        8.2.2. HQL 공부하기 - where절
        8.2.3. HQL 공부하기 - order by절
        8.2.4. HQL 공부하기 - inner join
    8.3. Criteria 공부하기
    8.4. 기능 구현
9. Tag만들기
    Tag를 만들어 쓰면 좋은 이유

top


OSIV 사용시 주의 할 것

모하니?/Coding : 2008.05.11 23:32


OSIV 기본 지식 - 참조 http://www.hibernate.org/43.html

먼저 OSIV는 Open Session In View 패턴의 약자로 보통 OSIV 필터나 인터셉터 중 하나를 사용합니다. 사용하는 이유는? 뷰 랜더링을 완료 할 때까지 세션을 유지하기 위함이다. 세션이 닫힌 상태에서 프록시로 읽어온 콜렉션이나 레퍼런스의 속성에 접근하면 LazyInitializationException이 발생하고, 이 해결책으로 뷰를 랜더링 하기 위한 세션을 새로 열수도 있겠지만, 이 방법은 그리 좋치 않다. 일단 이 작업이 이전 세션에 포함되어야 적당하지 개별적인 작업 단위로 보기는 뭐시기하기 때문이다. 요청이 오면 새로운 Session과 Transaction을 생성하고 응답을 클라이언트에 보내기 직전에 Transaction을 커밋하고 Session을 닫는게 가장 단순한 OSIV의 기능이 되겠다.

여기에 Conversation을 고려하고 서브 트랜잭션(서브 트랜잭션을 지원한다면, 두 개의 트랜잭션으로 나워서 읽고/쓰기 작업을 하는 트랜잭션과 뷰에 랜더링 하는 읽기 전용 트랜잭션으로 쪼개는 것이 좋다. 쓰기롹을 빨리 반환할 수 있으니까.)그리고 예외 처리까지 고려해서 인터셉터나 필터를 만들어야 한다.

하이버네이트의 update() 기본 지식 - 참조 http://whiteship.tistory.com/1616

Persistent Context에 이미 Persistent 상태로 로딩되어 있는 객체가 있을 때, 그와 같은 id를 가진 객체를 또 다시 Persistent Context에 붙이려는 시도가 있을 때 NonUnique뭐시기 에러가 발생합니다. 흔히 Detached 상태의 객체를 update() 메소드를 사용하여 Persistent Context에 Reattch를 시도할 때 이런 예외가 발생할 수 있는데, 그럴 때는 merge를 하여 기존의 Persistent Context에 있는 객체의 값을 새로운 객체 값으로 덮어쓸 수도 있지만 이 때 merge() 메소드로 넘겨준 객체의 상태가 Persistent 상태로 변하지 않고 그대로 유지 되되며, merge()가 반환하는 레퍼런스와 기존에 Persistent Context에 존재하는 레퍼런스 두 개가 동일한 데이터를 가리키게 됨으로 프로그래밍에 혼란을 줄 수 있다. 따라서 해당 객체를 evict()를 사용하여 Persistent Context에서 빼내고 update()의 인자로 넘겨준 객체를 Persistent 상태로 만드는 것이 적절한 해결책일 것이다.

자... 이제 OSIV 필터를 사용하고 있을 때 Validator에 다음과 같은 코드가 있습니다.

public class MemberValidator implements Validator {
...
  @Autowired
  MemberService memberService;

  ...
  Member memberCommand = (Member)command;
  Member existingMember = memberService.get(memberCommand.getEmail());
  if(existingMember != null)
    errors.reject("email", "duplicated", "해당 이메일은 이미 가입되어 있습니다.");
  ...
 
...
}

위의 코드는 일단 상당히 별로 입니다. memberSerivce.isExistingEmail(memberComman); 라는 메소드를 만들어서 그 반환값을 가지고 조건을 거는게 더 좋은 API로 생각됩니다. MemberService의 isExistingEmail() 에서 데이터베이스에 접근하는 코드는 memberDao를 사용해서 Member 객체를 가져오고 지지고 볶는게 훨씬 좋습니다. 그리고 사실 저런 경우 멤버 객체를 가져올 필요도 없고 레코드 갯수만 가져오면 되겠죠. 그런데 그냥.. 여기서는 그냥. 저렇게 코딩을 했다고 가정하겠습니다.

이런 상황에서 방금 말씀드린 코드의 책임 문제를 떠나 정말 중대한 문제가 있습니다.

그 문제가 뭔지는 알 갈쳐드립니다. 비밀이에요. (ㅋㅋ이미 문제의 원인과 해결책은 위의 기본 이론에 다 설명이 되어 있습니다.) 토비 사부님께 듣기로는 물개 선생님께서도 이와 같은 문제를 겪은 적이 있다는 얘기를 들었습니다. 이런 해프닝을 겪으면서 느낀 건 아무리 공부를 해도 역시.. 코딩을 해봐야... 알 수 있고.. 문제의 원인과 그 원인 해결책은 다시 공부를 해야 이해할 수 있다는 것입니다.

공부와 코딩을 떨어질래야 떨어질 수 없는 친구인거죠.
top


하이버네이트의 update() 와 merge()

Hibernate/study : 2008.04.29 18:37


찬욱군의 블로그를 보다가 merge()를 save(), update() 대용으로 사용하는 코드를 봤습니다. 스프링의 샘플 코드더군요. 해당 코드에 보면 주석으로 모라모라고 달려있는데 그걸 찬욱군이 블로그에 잘 풀어서 설명해두었습니다. (하지만 잘 이해가... @.@;;)

왜 그렇게 코딩을 해야 하는지 모르겠더군요.

1. save() 대용으로 사용한 경우.

해당 코드는 아래와 같습니다.

    public void storeOwner(Owner owner) {
        // Note: Hibernate3's merge operation does not reassociate the object
        // with the current Hibernate Session. Instead, it will always copy the
        // state over to a registered representation of the entity. In case of a
        // new entity, it will register a copy as well, but will not update the
        // id of the passed-in object. To still update the ids of the original
        // objects too, we need to register Spring's
        // IdTransferringMergeEventListener on our SessionFactory.
        sessionFactory.getCurrentSession().merge(owner);
    }

    public void storePet(Pet pet) {
        sessionFactory.getCurrentSession().merge(pet);
    }

    public void storeVisit(Visit visit) {
        sessionFactory.getCurrentSession().merge(visit);
    }
코드 출처 : spring 소스/samples/petclinic/src/.../HibernateClinic.java

희한합니다. 전부 저장하는 류의 메소드들인데 merge()를 쓰고 있네요. 이 녀석들을 사용한 코드를 보니 AddXXFrom 류의 클래스들에서 사용하고 있었습니다. 왜 그랬는지 잘 모르겠습니다. 저 메소드들에 넘겨준 객체의 상태를 Persistent로 바꾸기 싫었다고 생각할 수 밖에 없습니다.(merge()의 특성은 조금 뒤에 살펴보겠습니다.) 그런데도 위의 주석을 보면 id값만은 어떻게든 가지고 싶어서 IdTransferringMergeEventListener 이런 녀석을 사용할 수도 있다고 나와있습니다.

그럼 결론은..

this.clinic.storeOwner(owner);

이렇게 넘겨준 owner라는 객체의 상태는 그대로 Transient로 유지하고 Persistent로 바꾸지 않으면서도 id 값은 가지고 있도록.. 하고 싶을 때 저런 방법을 사용할 수 있습니다. 귀찮게 왜 그럴까요? 몰겠습니다. 그냥 save(owner) 하면 넘겨준 owner 객체가 Persistent 상태가 되면서 id도 가지게 될텐데 말이죠.

2. update() 대용으로 사용하는 경우

update()에 대한 간략한 설명을 해야겠네요. update()는 그냥 DB의 UPDATE 문이 아닙니다. reattach입니다. reattach가 뭐냐면 "다시 붙이기"입니다. detached 상태의 객체를 Persistence Context에 다시 붙이는 것(해당 객체는 Persistent 상태가 되겠죠.)을 뜻합니다. update(owner); 를 하게되면 owner 객체를 다시 Persistent Context에 붙이고 그럼 owner 객체는 Persistent 상태가 됩니다. 이 때 다음과 같은 문제가 발생할 수 있습니다.

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

두 줄 모두 하이버의 Unit of work 내에서 실행된다면, NonUniqueObjectException()이 발생합니다. 말 그대로 입니다. Persistent Context 내부에 단일 레코드를 나타내는 둘 이상의 객체가 존재하기 때문에 발생하는 것입니다. 이 현상이 나쁜건가요? 당연한 겁니다. 대체 하이버는 누굴 기준으로 더티 체킹을 해야하죠?? 이 예외를 피해가야 할까요? 아니죠. 소스 코드를 손봐야 하는 겁니다. 어떻게요? 순서를 바꿔주면 됩니다.

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

만약 왜 위에는 에러가 나고 아래는 에러가 안 나는지 모르시겠다면, 하이버네이트 공부를 하시면 됩니다. 간략하게 설명 드리면, member 객체가 먼저 Persistent Context에 들어가서 Persistent 상태가되고, 그 다음 get()을 하면 DB에서 읽어오는게 아니라 Persistent Context에서 가져오기 때문에 아무런 문제가 없습니다.

그런데 같은 문제를 merge()를 사용해서도 해결할 수 있습니다.

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

이렇게 말이죠. 그런데 여기서 중요한 건 member2와 member3은 Persistent 객체이고 member와 같은 값을 가지고 있지만, member만 여전히 Detached 상태라는 것입니다. 그리고 member2와 member3에 대한 변경(dirty) 사항이 양쪽 모두에 적용이 됩니다. 얼마나 아리까리 합니까? 그래서 하이버 책에서는 merge()해서 돌려받은 객체(여기서는 member3)만 사용하라고 권장하고 있습니다. 그런데 객체가 막 돌아다닐텐데 권장사항대로 잘 되진 않겠죠.

merge()는 넘겨받은 객체의 값들과 콜렉션을 복사합니다. 그리고 그 객체가 가지고 있는 id와 같은 id를 갖고 있는 녀석을 Persistent Context에서 찾아서 가져옵니다.(SELECT 쿼리 안 날아감.) Persistent Context에 없으면 DB에서 가져옵니다.(SELECT 쿼리 날아감.) 그런 다음에 값들을 가져온 객체(Persistent 상태겠죠.)에다가 덮어씌웁니다. 그리고 그녀석을 반환해 줍니다. 따라서 Detached 상태로 넘겨준 객체는 여전히 Detached 상태로 남아있고 그 객체와 같은 값을 가진 새로운 Persistent 객체가 만들어지게 됩니다.

그런데 넘겨준 객체가 Detached 객체가 아니라 Transient 객체라면?? 즉 save() 대용으로 merge()를 사용하는 경우가 이 경우에 해당하겠죠. 그렇다면, id가 없고 그럼 Persistent Context에서 찾을 것도 없고 DB에서 가져올 것도 없습니다. 대신 하나를 새로 만들어야겠죠. 대신 이 녀석도 마찬가지로 넘겨받은 객체의 값들을 복사해서 새로운 객체를 만들고 나중에 Unit of Work가 끝난 뒤 INSERT 문이 날아갈 준비가 됩니다. 다시 한번 주의할 것은 넘겨 받은 객체 자체를 Persistent 상태로 만들지는 않는다는 것입니다. merge()는 넘겨받은 객체의 상태를 바꾸지 않습니다.

결론을 내리자면, save() 대용으로 merge()를 사용하는 건 제 생각으로는 비추입니다. 애초에 merge()는 Detached 객체를 reattach 하기위한 용도이지, Transient 객체를 위한 용도가 아닙니다. update()의 대용으로 사용하는 걸 생각해볼 수는 있지만, merge()와 update()의 특징에 따라 원하는 것을 사용하시는게 좋겠습니다. 단순하게 예외를 피하기 위한 용도로 사용하는 것은 비추입니다.

참조 : Java Persistence With Hibernate 9장
top


하이버네이트에서 Nontransactional data access

Hibernate/Chapter 10 : 2008.04.01 17:11


Session session = sessionFactory.openSession();
session.get(Item.class, 123l);
session.close();
  1. Session이 열리고 이 순간 Connection을 얻어오진 않는다.
  2. get()을 호출하는 순간 Select 문을 날리는데, 이 때 Connection을 pool에서 꺼낸다. 하이버네이트는 기본으로 그 즉시 autocommit mode를 끈다. setAutoCommit(false). 이렇게 효율적으로 JDBC 트랜잭션을 시작한다.
  3. SELECT는 JDBC 트랜잭션 내부에서 실행된다. 하이버네이트는 Connection의 close()를 호출하여 Connection을 poll에 반납한다. 이 때 남아있는 트랜잭션은 어떻게 될까?
  • 사용하는 DB에 달려있다. JDBC 스펙에서는 이 것과 관련되서 정해논게 없다.
  • 오라클은 트랜잭션을 커밋한다.
  • 다른 많은 JDBC 밴더들은 트랜잭션을 롤백한다.
  • 위는 Select 문이라서 상관없지만, sequence를 가져온 다음에 INSERT은 날리지 않고 flush 될 때까지 기다린다. 그러다 그냥 끝나게 되니까 INSERT 문이 날아가지 않는다.
  • idendity 전략으로 PK를 생성할 때에는 DB에 들어가야 얻을 수 있으니까, INSERT문이 바로 날아간다.
  • 이렇게 트랜잭션 경계를 설정하지 않으면 위와 같은 일이 발생하는데, 이때에는 JDBC 커넥션을 오토커밋 모드로 설정해준다.
<property name="connection.autocommit">true</property>
  • 즉 DB Connection을 가져올 때 setAutoCommit(false) 이걸 호출하지 않는다.
top


autocommit에 관한 오해

Hibernate/Chapter 10 : 2008.04.01 17:11


트랜잭션 없이 쿼리를 날릴 수 있는가?

  • 트랜잭션 범위 밖에서 DB와 뭔가를 할 수가 없다. 불가능하다. 어떤 SQL 문도 DB 트랜잭션 밖에서 날릴 수는 없다.
  • nontransactional은 명시적인 트랜잭션 경계가 없다는 것이다. 시스템 트랜잭션이 없다는 것이다. 그렇게 하면 데이터에 접근할 때 autocommit 모드로 실행된다.

성능 향상

  • 잘 생각해봐야한다. 모든 SQL 문마다 트랜잭션을 열고 닫고 할 텐데 아마도 성능이 떨어질 것이다.

애플레이션 확장성 증가

  • 잘 생각해봐야한다. DB 트랜잭션이 길어지면 분명 락을 가지고 있는 시간이 길어지니까 확장성이 안 좋을 수 있다. 하지만 하이버네이트의 persistence context와 write-behind 특징을 생각해보면 모든 쓰기 락들은 매우 짧은 기간 동안만 가지고 있게 된다. isolation level을 높였을 때 사용하게 되는 읽기 락의 비용은 무시해도 좋은 수준이다. 아니면 아예 읽기 락이 아닌 multiversion concurrency를 지원하는 DB(오라클, PostgreSQL, Infomix, Firebird)를 사용하면 된다.

포기해야 하는 것들

  • SQL 문장들을 그룹핑하여 원자성을 보장하는 것이 불가능.
  • 데이터가 병렬적으로 수정될 때 isolation level이 낮아진다. repeatable read가 불가능하다.

Data Access Type

  • 보통의 트랜잭션
  • Read-only 트랜잭션
  • Nontransactional
top


Isolation 단계 더 높이기

Hibernate/Chapter 10 : 2008.03.30 00:25


Explicit pessimistic locking

  • 격리 수준을 read comitted 보다 높게 설정하는 것은 애플리케이션의 확장성을 고려할 때 좋치 않다.
  • Persistence context cache가 repeatable read를 제공하긴 하지만 이걸로 항상 만족스럽지 않을 수도 있다.
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Item i = (Item) session.get(Item.class, 123);
String description = (String)
session.createQuery("select i.description from Item i" +
" where i.id = :itemid")
.setParameter("itemid", i.getId() )
.uniqueResult();
tx.commit();
session.close();
  • 위의 코드는 DB에서 같은 데이터를 두 번 읽어온다. 이 때 isolation level이 read committed 였다면, 두 번째에 읽어오는 값이 처음 읽어온 데이터와 다를 수 있다.(둘 사이에 어떤 트랜잭션이 해당하는 값을 바꾸고 커밋했을 수 있다.)
  • 전체 트랜잭션의 isolation level을 높이는 것이 아니라 lock() 메소드를 사용하여 해당하는 부분의 트랜잭젼의 isolation level을 높일 수 있다.
Session session = sessionFactory.openSession(); 
Transaction tx = session.beginTransaction();
Item i = (Item) session.get(Item.class, 123);

session.lock(i, LockMode.UPGRADE);

String description = (String)
session.createQuery("select i.description from Item i" +
" where i.id = :itemid")
.setParameter("itemid", i.getId() )
.uniqueResult();
tx.commit();
session.close();
  • 위의 LockMode.UPGRADE 는 item 객체 대응하는 레코드의 pessimistic lock을 가지고 다니게 된다.
Item i = (Item) session.get(Item.class, 123, LockMode.UPGRADE);
  • 위와같이 코드를 한 줄 줄일 수도 있다.
  • LockMode.UPGRADE는 롹을 가져올 때까지 대기하게 된다. 대기 시간은 사용하는 DB에 따라 다르다.
  • LockMode.NOWAIT는 롹이 없으면 기다리지 않고 쿼리가 바로 fail하도록 한다.

The Hibernate lock modes

  • LockMode.NONE - 락 사용하지 않음. 캐시에 객체가 존재하면 그 객체를 사용.
  • LockMode.READ - 모든 캐시를 무시하고 현재 메모리에 있는 엔티티의 버전과 실제 DB에 있는 버전이 같은지 확인한다.
  • LockMode.UPGRADE - LockMode.READ가 하는 일에 더해서 DB에서 pessimistic upgrade lock을 가져온다. SELECT ... FOR UPDATE 문을 지원하지 않는 DB를 사용할 때는 자동으로 LockMode.READ로 전환된다.
  • LockMode.UPGRADE_NOWAIT - UPDGRADE와 동일한데, SELECT ... FOR UPDATE NOWAIT를 사용한다. 락이 없으면 바로 예외를 던진다. NOWAIT를 지원하지 않으면 자동으로 LockMode.UPGRADE로 전환된다.
  • LockMode.FORCE - 객체에 버전을 DB에서 증가시키도록 강제한다.
  • LockMode.WRITE - 하이버네이트가 현재 트랜잭션에 레코드를 추가했을 때 자동으로 얻어온다.(사용자가 명시적으로 애플리케이션에서 사용할 일 없음.)
  • load()와 get()은 기본으로 LockMode.NONE을 사용한다.
  • Detached 상태의 객체를 reattach 할 때 LockMode.READ 를 유용하게 사용할 수 있다. 자동으로 reattach까지 해주니까.(하이버네이트의 lock()메소드만 reattch까지 해주지, JP의 lock()메소드는 reattch해주지 않느다.)

reattche를 할 때 반드시 lock() 메소드를 사용해야 하는 것은 아니다. 이전에도 살펴봤듯이 Session에 update() 메소드를 사용하면 Transiecnt 상태의 객체가 Persistent 상태가 된다. lock(LockMode.READ)는 Persistent 상태로 사용하려는 객체의 데이터들이 이전에 로딩된 그 상태 그대로 인지, 혹시 다른 트랜잭션에 의해 데이터들이 변경되지는 않았는지 확인하기 위한 용도다. 그렇게 확인을 함과 동시에 덤으로 Persistent 상태로 전환(reattach)시켜 주는 것이다. 즉, Transient 상태의 객체를 lock() 메소드의 인자로 넘겨줄 수 있다는 것인데, 이것은 하이버네이트에서만 할 수 있다. JP에서는 이미 Persistent 상태인 객체한테만 lock()을 호출할 수 있다.

Item item = ... ; 
Bid bid = new Bid();
item.addBid(bid);
...
Transaction tx = session.beginTransaction();
session.lock(item, LockMode.READ);
tx.commit();

Forcing a version increment

  • 하이버네이트가 개발자가 수정한 내용을 버전을 올려야 하는 변경사항인지 모를 수가 있다. 이럴 때 명시적으로 버전을 올리라고 알려줘야 한다.
Session session = getSessionFactory().openSession(); 
Transaction tx = session.beginTransaction();

User u = (User) session.get(User.class, 123);
u.getDefaultBillingDetails().setOwner("John Doe");

tx.commit();
session.close();
  • 하이버네이트는 객체와 직접적으로 닿아있는 값의 변화만을 알지 한 단계 걸친 변화는 해당 객체의 수정사항으로 인식하지 않는다.
  • 위의 코드에서 BillingDetail 객체만 버전을 올리게 된다. 하지만 개발자는 정보를 수정한 BillingDetail(aggregate)을 가지고 있는 User(root object) 역시 버전을 올리고 싶어 할 수 있다.
Session session = getSessionFactory().openSession(); 
Transaction tx = session.beginTransaction();

User u = (User) session.get(User.class, 123);
session.lock(u, LockMode.FORCE);
u.getDefaultBillingDetails().setOwner("John Doe");

tx.commit();
session.close();
  • 이렇게 하면 현재 User 객체에 대응하는 레코드를 가지고 작업하고 있는 모든 Unit of work들이 해당 객체의 버전이 올라갔다고 인식한다.
  • 심지어 아무런 변경을 가하지 않았더라도 해당 Root 객체의 버전은 올라간다.

공부할 것

  • 사용하는 DB에 따라 다른 결과가 나올 수 있다. Select ... FOR UPDATE NOWAIT 문을 지원하느냐 안 하느냐에 따라 LockMode.UPGRADE 와 LockMode.UPGRADE_NOWAIT의 결과가 LockMode.READ 와 같게 나올 수도 있다.
  • 결국 원하는 Isolation level을 정하는 것이 중요하고, repeatable read를 보장하려면 LockMode.UPGRADE 또는 LockMode.UPGRADE_NOWAIT를 사용하여 pessimisitc locking하면된다.
  • LockMode.READ는 DB에서 데이터를 읽어와서 버전을 확인한다. isolation level을 미리 올려두는 것이 아니라, optimistic 한 방법으로 DB에 쓰기 직전에 확인하기 위한 용도라고 생각된다.
  • 자동 버전 증가는 오직 엔티티가 직접적으로 물고 있는 속성, 콜렉션 자체의 변화만 인식한다. 객체 맵의 루트를 올려야 한다면, 해당 루트 객체를 가져올 때 LockMode.FORCE를 사용하며 이 녀석은 isolation level과 별 상관이 없어 보이지만, 해당 엔티티를 사용하는 트랜잭션들의 isolation level을 repeatable read로 보장해야 하는 경우에 유용하게 사용할 수 있을 것 같다.
  • 결국 테스트 코드를 많이 만들어서 테스트해봐야겠다.
top


낙천적인 동시접근 제어

Hibernate/Chapter 10 : 2008.03.28 19:30


특징

  • 모든 게 다 잘 될거라고 가정을 하는 접근법이다.
  • unit of work의 마지막에 데이터를 쓰려고 할 때 에러를 발생시킨다.

낙천적인 전략 이해하기

사용자 삽입 이미지

  • 두 트랜잭션 모두 read commited는 기본이니까 dirty read는 허용하지 않는다.
  • 하지만 non repeatable read는 가능하다. 그리고 둘 중 하나의 update가 분실 될 수도 있다.
  • lost updat를 처리할 수 있는 세 가지 방법
    1. Last commit wins - 마지막에 커밋하는 녀석이 앞선 변경 사항을 덮어쓴다. 에러 메시지 없다.
    2. First commit wins - 두 번째로 같은 데이터에 커밋하는 녀석이 있을 때 예외를 던진다. 사용자는 새로운 컨버세이션을 시작여 새로운 데이터를 가져와서 다시 작업해야 한다.
    3. 충돌하는 업데이트 병합하기 - 두 번째로 같은 데이터에 커밋하려는 녀석이 있을 때 예외를 던지고 사용자가 선택적으로 무조건 덮어 쓸 수 도 있고 다시 시작할 수도 있도록 한다.
  • optimistic concurrency control이 없거나 기본값인 상태에서는 무조건 last commit wins 전략으로 동작한다. 비추다.
  • 하이버네이트와 JP는 automatic optimistic locking을 사용하여 first commit wins 전략을 사용하게 해준다.
  • Merge conflicting changes가 가장 사용자 입장에서 좋은데, 이런 창을 띄우고 메시지를 출력하는 것은 개발자의 몫이다.

하이버네이트에서 Versioning 하기

  • 각각의 Entity들은 version을 가지고 있고 숫자나 타입스탬프로 표현할 수 있다.
  • 하이버네이트는 이 version을 Entity가 수정될 때마다 버전을 증가 시키고 만약 충돌이 발견되면 예외를 던진다.
  • 이 version 속성을 Entity에 추가해준다.
public class Item {
...
private int version;
...
}
  • XML에서 설정할 때 이 속성에 대한 맵핑은 반드시 id 속성 설정 바로 다음에 위치해야 한다.
  • 세터를 사용하지 말고 필드에 직접 쓰도록 설정하고 세터를 만들어 두지 않는게 좋다. 하이버만 쓸 수 있도록...
public class Item {
...
private Date lastUpdated;
...
}
  • Timestamp를 사용할 수도 있는데 이건 약간 덜 안전하다. 비추한다.

비추하는 이유

Clustered 환경에서 JVM으로부터 현재 시간을 가져오는 건 위험하다. jvm이 두 개니까 둘이 같을 수도 있겠지. 한 개에서 가져오면 같을 일이.. 없겠지만, 어쨋든 그래서 타입스탬프를 DB에서 가져오도록 설정할 수 있다. source="db"라고 <timestamp> 맵핑에 추가하면 된다. 그런데 이것도 DB를 매번 다녀오니까 추가비용이 발생하고 하이버네이트의 모든 SQL Dialect가 이걸 지원하는 것도 아니다.

자동 버전 관리


자동 버전 관리 동작 원리

두 개의 트랜잭션이 같은 데이터에 가져온다. 이때 버전 넘버를 확인한다. 1이라고 하자. 그뒤에 Update문이 실행 될 때 다시 버전 넘버를 가져와서 확인한다. 맨 처음에 가져온 넘버와 같으면 커밋하고 버전 넘버를 증가시킨다. 버전 넘버가 다르면 누군가 데이터를 변경한 것이기 때문에 현재 트랜잭션은 예전 데이터를 가지고 작업하고 있는 것이다. 그러면 Update문을 날리지 않는다. 그럼 SQL의 결과 수정된 레코드의 수가 0이다. 이 숫자가 0이면 하이버는 StaleObjectStateException을 던진다. 이 예외를 잡아서 화면에 에러 메시지 보여주고 사용자가 컨버세이션을 다시 시작하도록 할 수 있다.

  • 언제 Entity의 버전을 올리는가? Entity 객체가 가지고 있는 속성들이 dirty 할 때에만 올린다.

버전 넘버나 Timestamp 없이 버전 관리하기

  • 레거시 DB나 기존의 자바 클래스를 사용하고 있어서 버전 컬럼을 추가할 수 없어도, 하이버네이트는 자동 버전 관리를 할 수 있다.
  • 단, 객체를 가져오고 수정하는 Persistent Context(Session)가 같아야 한다. 따라서 Detached 객체를 가지고 Conversation을 구현할 때에는 자동 버전 관리가 불가능하다. 그 때는 버전 넘버나 Timestamp가 필요하다.
  • 버전 컬럼 대신에 모든 컬럼을 비교한다. optimistic-lock="dirty" 라고 설정하면 dirty 상태의 속성만 비교한다. 이때에는 update문을 dirty한 컬럼으로만 생성해야 하니까 dynamicUpdate를 true로 설정해야 한다.

Java Persistent 사용하여 버전 관리하기

  • Java Persistent는 동시 접근을 Versioning을 사용하여 낙천적으로 관리한다고 가정한다.
@Entity
public class Item {
...
@Version
@Column(name = "OBJ_VERSION")
private int version;
...
}
  • JPA는 버전 속성 없이 optimistic versioning을 못하니까 하이버 애노테이션을 사용해야 한다.
@Entity
@org.hibernate.annotations.Entity(
optimisticLock = org.hibernate.annotations.OptimisticLockType.ALL
)
public class Item {
...
}
  • 락을 OptimisticLockType.DIRTY로 설정할 수 있다. 그럴 때는 dynamicUpdate 속성의 값도 true로 설정해야 줘야 한다.
top


DB 수준에서 동시접근 이해하기

Hibernate/Chapter 10 : 2008.03.28 10:23


특징

  • 하이버네이트 애플리케이션 개발자로써 반드시 갖춰야 할 역량은 사용하는 DB의 기능과 특정 상황에서 DB isolation 레벨을 어떻게 조정할 수 있을지 이해하는 것이다.
  • 완벽한 격리(Isolation)는 비용이 크다. 그 단계(lebel)를 조정해서 완전한 분리의 정도를 낮추고 시스템의 성능과 확장성을 높일 수 있다.

트랜잭션 격리 이슈

  • 이슈들을 지칭하는 용어는 ANSI SQL 표준에 정의되어 있다.
  • lost update: 두 개의 트랜잭션이 하나의 데이터에 업데이트를 할 때 나중에 업데이트한 트랜잭션이 롤백 되버리면 첫 번째 트랜잭션이 커밋한 데이터까지 날아가 버린다.

사용자 삽입 이미지

  • dirty read: 하나의 트랜잭션이 다른 트랜잭션이 아직 커밋하지 않은 데이터를 읽었을 때 다른 트랜잭션이 롤백 될 수 있기 때문에 이상한 데이터를 읽은 꼴이 될수도 있다.

사용자 삽입 이미지

  • unrepeatable read: 하나의 트랜잭션이 두 번 같은 데이터를 읽어왔는데 그 사이에 다른 트랜잭션이 해당 데이터를 조작하여 그 값이 다를 수 있다.

사용자 삽입 이미지

  • second lost updates problem: unprepeatble read의 한가지 형태로 첫 번째 트랜잭션이 데이터에 쓰기를 하고 커밋을 했는데 다른 트랜잭션이 같은 데이터에 쓰기를 하고 커밋을 하면 앞선 트랜잭션의 결과는 날아가 버린다. 근데 이건 어쩌면 당연한 걸 수도 있다. 나중에 자세히 다룬다.
  • phantom read: 트랜잭션이 쿼리를 두 번 날렸는데 그 사이에 다른 트랜잭션이 데이터를 추가하거나 삭제해서 그 결과가 다를 수 있다.

사용자 삽입 이미지

ANSI 트랜잭션 격리 레벨

Read uncommitted

  • lost update는 막고 dirty read는 허용.
  • 다른 트랜잭션에 의해 커밋되지 않은 데이터에 쓰기 작업을 할 수 없다. 하지만 읽기는 가능하다.
  • exclusive write lock을 사용한다.

Read committed

  • dirty read는 막고 unrepeatable read는 허용.
  • 커밋되지 않은 쓰기 트랜잭션은 다른 트랜잭션들이 해당 레코드에 접근도 못하게 한다. 읽기 쓰기 전부 안 됨. 하지만 읽기 트랜잭션은 다른 트랜잭션이 자신이 읽고 있는 레코드에 (읽기 쓰기 모두) 접근하는 것을 허용한다.
  • shared read lock과 exclusive write lock을 사용한다.

Repeatable read

  • unrepeatable read와 dirty read 둘 다 막는다. phantom read는 허용.
  • 읽기 트랜잭션이 사용하는 데이터에 대한 쓰기 트랜잭션을 막는다. 쓰기 트랜잭션이 사용하는 데이터에 접근하는 다른 모든 트랜잭션을 막는다.

Serializable

  • 트랜잭션들을 일렬로 세워서 차례 차례 실행시키는 것과 같다.
  • low-level lock만 사용해서는 구현하기 어렵다.

격리 수준(Isolation level) 선택하기

  • 다음은 권고 사항이지 돌에 새겨넣은 말 같은 것이 아니다.
  • read uncommitted 격리 수준은 사용하지 말아라.
  • 대부분의 경우 serializable 격리 수준 까지느 필요없다. phantom read가 보통 문제를 일어키지는 않는다. 성능에 심각한 영향을 끼친다.
  • 그러면 read committed와 repeatable read 둘 만 남았네.
  • repeatable read: read lock이 write lock을 막는다. 이 방법 말고 versioned data를 사용하면 하이버네이트가 알아서 해준다. 하이버네이트의 Persistence context cache와 versioning이 repeatable read를 보장해준다. 따라서, versioned data를 사용하기만 하면 모든 DB 트랜잭션이 이 격리 수준을 사용할 수 있다.

격리 수준 설정하기

  • DBMS마다 기본 격리 수준이 있다. 보통 read committed 또는 repeatable read 중 하나다.
  • hibernate.connection.isolation = 4 이렇게 설정할 수 있다.
    • 1—Read uncommitted isolation
    • 2—Read committed isolation
    • 4—Repeatable read isolation
    • 8—Serializable isolation
  • 이렇게 하이버 설정에 설정해버리면 이건 global 한 설정이 된다.
  • 때로는 특정 트랜잭션 마다 설정할 수 있다.

top


동시접근 제어하기

Hibernate/Chapter 10 : 2008.03.28 10:19


  • transaction isolation: 각각의 트랜잭션 입장에서 보면 현재 진행중인 트랜잭션이 자기 밖에 없는 것으로 보이는 것.
  • 예전에는 이것을 locking으로 보장했었다. 트랜잭션들이 특정 데이터에 대한 lock을 가지고 다녔디.
  • multiversion concurrency control(MVCC): 몇몇 오라클이나 PostreSQL와 같은 DB는 transaction isolation을 이걸 사용해서 구현했다.
  • DB가 동시 접근 제어를 어떻게 구현했는지는 하이버네이트나 Java Persistence 애플리케이션을 사용할 때 가장 중요한 사항이다.
  • 낙천적인 동시접근 제어
  • Isolation 단계 더 높이기
top


하이버네이트 애플리케이션의 트랜잭션

Hibernate/Chapter 10 : 2008.03.27 07:52


Java SE에서의 코딩을 통한 트랜잭션

  • hibernate.transaction.factory_class 설정 기본 값이 org.hibernate.transaction.JDBCTransactionFactory로 되어 있기 때문에 별도의 설정이 필요없다.
  • TransactionFactory 구현체를 만들어서 Transaction 인터페이스를 확장하거나 커스터마이징 할 수 있다.
Session session = null;
Transaction tx = null;
try {
session = sessionFactory.openSession();
tx = session.beginTransaction();
concludeAuction(session);
tx.commit();
} catch (RuntimeException ex) {
tx.rollback();
} finally {
session.close();
}
  • JDBC Connection은 트랜잭션이 시작할 때 얻어온다.
  • beginTransaction()은 setAutoCommit(false)를 설정하게 된다.
  • 이렇게 하면 Session이 db 커넥션과 묶이게 된다.
  • 트랜잭션을 commit() 하면 db 커넥션은 반환되고, Session을 풀리게 된다. 또 다른 트랜잭션을 해당 Session에서 시작하면, 새로운 커넥션을 얻어온다.

예외 처리하기

  • 3쩜대 버전 부터는 하이버네이트의 모든 메소드가 RuntimeException을 던지게 되어 있다.
  • tx.rollback() 마져도 RuntimeException을 던지기 때문에, 로깅을 남길 때, Exception객체를 인자로 넘겨주는 것을 잊지 않도록 한다.
  • tx.setTimeout(int second)를 사용하여 일정 시간 이내에 처리되지 않으면 예외를 던지도록 설정할 수 있다.
  • Exception들이 분류되어 있다.
  1. HibernateExcepion은 getCause()를 사용해서 좀 더 구체적인 정보를 확인해봐야 한다.
  2. JDBCException은 SQL 문에서 발생한 에러이다. getSQL()을 호출하여 SQL을 확인해볼 수 있다. DB 벤더 에러 코드를 보려면 getErrorCode()를 호출하면 된다.

JTA를 사용하여 코드에서 트랜잭션 처리하기

  • JTA의 javax.transaction.UserTransaction 인터페이스를 사용하여 리소스를 관리한다.
  • 꼭 Application Seerver가 있어야만 JTA를 사용할 수 있는 건 아니다. JBoss Transactions는 오픈소스 독립적인 JTA 프로바이더를 제공한다. 이것을 Tomcat에서 돌리고 있는 하이버네이트 애플레키에션에 설치할 수 있다.
  • JTA로 관리되는 리소스를 사용하는 장점
  1. 트랜잭션 관리 서비스는 모든 리소스를 통합하고 트랜잭션 제어를 단일 표준 API로 관리할 수 있도록 해준다.
  2. JEE 트랜잭션 관리자는 여러개의 리소스를 하나의 트랜잭션으로 묶어서 관리할 수 있다.
  3. JTA 구현체의 품질이 간단한 JDBC 커넥션 풀 보다 좋다.
  4. JTA 프로바이더는 런타인시에 불필요한 오버헤드를 발생시키지 않는다.
  • 하이버에서 JTA 트랜잭션 사용하기
  1. 애플리케이션 코드는 수정할 필요 없이 그냥 하이번네이트의 Transaction API를 사용한다.
  2. 설정파일에서 hibernate.transaction.factory_class 속성의 값을 org.hibernate.transaction.JTATransactionFactory로 설정한다.
  3. hibernate.transaction.manager_lookup_class 속성의 값에 JTA 구현체의 Lookup 클래스를 설정해준다.(ex. org.hibernate.transaction.JBossTransaction-ManagerLookup)
  4. 하이버는 더이상 JDBC 커넥션 풀을 관리하지 않는다. 커넥션들을 JNDI를 통해서 JTA 프로바이더로부터 받아온다.
  • JTA를 사용할때와 그렇지 않을 때의 차이
    • JTA를 사용할 때: DB 커넥션을 Session을 사용하려고 할 때 가져온다.(more aggressive), 같은 트랜잭션 내에서는 항상 같은 커넥션을 가져오도록 Application Server가 보장해준다.
    • JTA를 사용하지 않을 때: DB 커넥션을 트랜잭션 시작할 때 가져와서 끝날 때 반환한다.
  • JTA 시스템은 전역적인 트랜잭션 타임아웃을 지원한다.
UserTransaction utx = (UserTransaction) new InitialContext()
.lookup("java:comp/UserTransaction");
Session session1 = null;
Session session2 = null;
try {
utx.begin();
session1 = auctionDatabase.openSession();
session2 = billingDatabase.openSession();
concludeAuction(session1);
billAuction(session2);
session1.flush();
session2.flush();
utx.commit();
} catch (RuntimeException ex) {
try {
utx.rollback();
} catch (RuntimeException rbEx) {
log.error("Couldn't roll back transaction", rbEx);
}
throw ex;
} finally {
session1.close();
session2.close();
}
  • JTA 인터페이스를 사용하여 구현할 때에 기본 설정은 명시적으로 flush()를 해야하며, session도 명시적으로 닫아줘야 한다.
  • hibernate.trans-action.flush_before_completion 속성과 hibernate.transaction.auto_close_session 속성을 설정하면 코드를 줄일 수 있다.
UserTransaction utx = (UserTransaction) new InitialContext()
.lookup("java:comp/UserTransaction");
Session session1 = null;
Session session2 = null;
try {
utx.begin();
session1 = auctionDatabase.openSession();
session2 = billingDatabase.openSession();
concludeAuction(session1);
billAuction(session2);
utx.commit();
} catch (RuntimeException ex) {
try {
utx.rollback();
} catch (RuntimeException rbEx) {
log.error("Couldn't roll back transaction", rbEx);
}
throw ex;
}
  • 가능하면 JTA를 직접 사용하기를 권하고 있다. 이식성을 애플리케이션 밖으로 끌어내기 위해서..

컨테이너가 관리하는 트랜잭션

  • 선언적인 트랜잭션 경제는 컨테이너에게 트랜잭션 관리의 책임을 넘기는 것이다.(스프링은 IoC 컨테이너에게 넘긴다. 여기서는 WAS를 얘기하고 있음.)
  • CMT는 독입적인 JTA 구현체에서는 제공하지 않는다.
@Stateless
public class ManageAuctionBean implements ManageAuction {
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void endAuction(Item item) {
Session session1 = auctionDatabase.openSession();
Session session2 = billingDatabase.openSession();
concludeAuction(session1, item);
billAuction(session2, item);
}
...
}
  • 컨테이너가 TransactionAttribute를 읽은 뒤, 그에따라 트랜잭션을 적용한다.
  • 위에서는 REQUIRED기 때문에 현재 트랜잭션이 있으면 그걸 사용하고 없으면 새로 만들어서 사용한다.
  • 필요한 설정
    • hibernate.transaction.factory_class 속성을 org.hibernate.transaction.CMTTransactionFactory 로 설정.
    • hibernate.transaction.manager_lookup_class 속성을 사용하는 application server의 lockup 클래스로 설정.
top


데이터베이스와 시스템 트랜잭션

Hibernate/Chapter 10 : 2008.03.27 07:50


특징

  • 모든 SQL은 반드시 트랜잭션 안에서 수행된다.
  • 트랜잭션은 Commit 되거나 Rollback 된다.
  • Unit of work의 경계를 정해주어야 한다.
  • 특정 시점에 트랜잭션을 시작하고, 나중에 커밋하거나 예외가 발생했을 때 롤백하도록 해야 한다. 이것을 Transaction demarcation이라고 한다.

코딩을 통한 transaction demarcation

  • 일반적인 JDBC API를 사용할 때에는 Connection의 setAutoCommit(false)로 설정한 다음, 작업이 끝날 때, commit()을 호출하고, 예외가 발생하면, rollback()을 호출한다.
  • 여러 DB를 사용하는 작업을 하는 경우, JDBC만 가지고는 원자성을 처리 할 수 없다. 하나의 시스템 트랜잭션으로 여러 자원을 묶어서 처리하는 transaction manager가 필요하다.
  • Java Transaction API가 바로 그러한 트랜잭션 처리를 위한 용도이다. JTA의 UserTransaction 인터페이스에 begin()과 commit()을 사용하여 시스템 트랜잭션을 처리할 수 있다.
  • 하이버네이트는 자체에서 Transaction 인터페이스를 제공한다. 이 API를 쓰도록 강요하지는 않는다. JDBC의 트랜잭션 API를 사용할 수도 있다. 하지만 그러면 여러분의 코드가 JDBC에 종송적이게 된다.
  • 하이버네이트의 Transaction은 JTA 위에서도 동작한다.

선언적인 transactiondemarcation

  • EJB 컨테이너가 선언적인 트랜잭션 서비스를 제공하며, 이것을 CMT라고 부른다.
  • 스프링도 제공해 주는데, 이 책은 스프링 얘기가 하나도 없네.
top


트랜잭션 기초

Hibernate/Chapter 10 : 2008.03.27 07:49


  • Atomic: 원자성
  • Consistency: 비지니스 룰을 깨트리면 안 된다.
  • Isolation: 동시에 같은 데이터에 대한 여러 작업이 독립적으로 수행되어야 한다.
  • Durability: 트랜잭션의 결과는 영속적으로 유지되어야 한다.
top


Hibernate의 Dynamic Instantiation 사용하기

모하니?/Coding : 2008.03.26 23:15


s.createQuery(
"select item.itemid, count(foo), sum(bar) " +
"form Item item " +
...

뭐 이런 HQL이 있을 때 s.list() 의 결과는 Object[] 의 리스트입니다.
따라서 저기서 값을 꺼내서 새로운 객체 넣어줄 때 다음과 같은 코드를 작성하게 됩니다.

List list = s.list();
for(int i = 0 ; i < list.size() ; i++) {
    Object[] result = list.get(i)
    Baz baz = new Baz();
    baz.setItemId((Integer)result[0]);
    baz.setFoo(((Long)result[1]).intValue());
    baz.setBar(((Double)result[2]).intValue());
    ...
}

배열에서 일일히 꺼내는 코드가 짜증날 뿐만 아니라, 꺼내는 데이터 타입이 count일 경우에는 Long 타입이 나오고 sum의 경우에는 Double 타입의 값이 나오기 때문에, 가장 자주 사용하는 Integer로 세팅해야 하는 경우에는 위와 같이 어지러운 괄호를 사용해가며 파싱을 하고 다시 int값으로 세팅해줘야 합니다. 번거로운건 둘째치고 코드가 지져분해졌습니다.

좀.. 짜증나는 코드입니다. 마치 JDBC의 ResultSet 쓰듯이 사용한건데, 저는 JDBC 라이브러리를 쓰지 않고 엘레강트한 하이버네이트를 쓰고 싶었는데, 하이버네이트에서도 이건 어쩔 수 없는건가 하면서 무심코 그냥 저렇게 코딩을 했었습니다.

그런데.. 정말 엘레강트한 방법이 있더군요...

하이버네이트의 "다이내믹 인스턴세이션" Dynamic Instantiation 이라는 기능을 사용하면 정말 깔끔하게 코딩할 수 있습니다.

먼저 DTO를 만듭니다.

class DTO {

private Integer itemId;

private Integer foo;

private Integer bar;

public DTO(Integer itemId, Long foo, Double bar) {
    // parsing
}

// setter and getter

}

그리고 HQL을 다음과 같이 수정합니다.

select new whiteship.baz.DTO(item.itemId, count(foo), sum(bar))
...//나머지 동일

그러면 이제 저 쿼리의 결과인 s.list()를 호출하면 DTO 객체들의 컬렉션을 받을 수 있습니다. 즉 List<DTO> 타입으로 받을 수 있죠. 그러면 파싱하는 쪽 코드가 깔끔해집니다.

1. ForEach 문 사용가능.(한 줄이 줄어 들고,  for문이 짧아짐, i라는 변수 사라짐)
2. 형변환 하지 않아도 됨.(DTO의 생성자에서 하기 때문에 지져분한 코드 사라짐)

음.. 하이버네이트는 정말 엘레강트 한 것 같습니다. 
Hibernate is ROCK!!!

ps : 14장에 나오는 내용인데, 현재 10장을 보고 있습니다. 10장의 트랜잭션과 동기화, 11장 컨버ㄹ세이션보다 14, 15장의 쿼리에 관한 내용이 먼저 나왔으면 좋치 않았을까 싶습니다. 책을 끝까지 보기 전까지는 하이버네이트 쓸 생각을 하지 말라는 걸지도.. ㅋㅋㅋ
top


Hibernate VS JPA

Hibernate/Chapter 9 : 2008.03.11 15:09


Hibernate JPA
네 가지의 상태(transient, persistent, removed, detached)가 있다. 이것들을 표준화했다.
detached 객체를 merging하거나 reattach할 수 있다. merging만 지원한다.
flush 시점에서 save()와 update()는 모든 연관된 객체들에 영향을 준다. persist()는 호출 시점에 연관되어 있는 객체들에만 영향을 준다. flush 시점에서 persist()는 모든 연관된 객체들에 영향을 준다. save()와 update()는 해당 메소드를 호출하는 시점에 연관되어 있는 객체들에만 영향을 준다.
get()은 DB에 다녀오고, load()는 프록시를 반환한다. find()는 DB를 다녀오고, getReference()는 프록시를 반환한다.
EJB에서 Session을 사용할 때 Depedency Injection은 JBoss Application Server만 지원한다. EntityManager의 DI는 모든 EBJ 3.0 호환 서버에서 지원한다.
top


하이버네이트 API : Persistence context 관리하기

Hibernate/Chapter 9 : 2008.03.11 15:07


Persistence Context 캐시 제어하기

  • Persistent 객체들의 스냅샷들을 캐시에 복사해둔다.
  • 이 캐시들을 사용하여 dirty checking을 하여 persistent 객체들의 변경 사항들을 찾아낸다.
  • 수천개의 객체들을 로딩하면, OutOfMemoryException을 내고 죽어버릴 수가 있다.
  • 캐시를 줄이거나 메모리 공간을 제약하려면 다음과 같이 해야 한다.
    1. 필요한 객체만 Persistent 상태로 유지하라. 전체 객체 그래프를 모두 끌어오다가는...
    2. session.evict(object)를 사용해서 persistent 상태의 객체를 명시적으로 detached 시킬 수 있다.
    3. session.clear()를 사용해서 Persistent Context에 있는 모든 객체를 detached로 만들 수 있다. 이 녀석들은 dirty checking하지 않는다.
    4. session.setReadOnly(object, true)를 사용해서 특정 객체에 대해서는 dirty checking을 하지 않도록 설정할 수 있다. false를 주면 다시 한다. 객체의 상태는 바꾸지 않는다.

Persistent Context flushing 하기

  • 하이버는 Persistent 객체들에 대한 변경 사항을 바로바로 DB에 반영하지 않는다.
    1. DB 요청을 최소화 할 수 있다.
    2. DB 롹킹 기간을 최소화 한다.
    3. JDBC batch API의 장점을 취할 수 있다.
  • flush(): DB와 Persistent Context의 동기화
    1. Transaction(하이버의 API)이 commit() 될 때.
    2. 쿼리를 실행하기 전에..
    3. session.flush()를 호출 할 때.
  • session.setFlushMode()를 사용할 수 있다. 기본값은 FlushMode.AUTO.
  • FlushMode.COMMIT으로 설정하면
    1. 쿼리를 실행하기 전에.. 동기화를 하지 않는다.
    2. 오직 Transaction.commit()과 session.flush()를 할때 만 동기화한다.
    3. 쿼리 -> 수정 -> 쿼리 -> 수정. 이런 경우에 FlushMode를 Commit으로 설정해주면 효율적이다.
  • FlushMode.MONUAL로 설정하면
    1. 오직 session.flush()를 호출 할 때만 동기화한다.
  • FlushMode 제어는 나중에 Persistent Context를 Conversation으로 확장할 때 사용할 것이다.
  • 중요한 것은 flush 처리의 성능은 persistent context의 크기에 따라 달라진다는 것이다.
top


하이버네이트 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


하이버네이트 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


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


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


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


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


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


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


객체 상태

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


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


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


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


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


다형적인 콜렉션 연관

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




: 1 : 2 : 3 : 4 :