Whiteship's Note

'Hibernate'에 해당되는 글 183건

  1. 2010.07.27 [하이버네이트 VS JPA] 객체 다루기 (2)
  2. 2010.07.23 [하이버네이트 배치 추가] flush와 clear (2)
  3. 2009.09.07 [하이버네이트] 2차 캐싱 적용하기
  4. 2009.08.07 [하이버네이트] 애매한 에러 메시지 때문에 삽질.. @_@ (6)
  5. 2009.07.24 JPA 구현 패턴 (4)
  6. 2009.06.30 [하이버네이트 툴] hbm2doc
  7. 2009.06.30 [JBoss Tools] 이클립스 업데이트 사이트 (2)
  8. 2009.06.27 [하이버네이트] Session-Per-XXX
  9. 2009.06.25 [하이버네이트] detached 객체의 동일성 (4)
  10. 2009.06.24 [하이버네이트 퀴즈] Flush (8)
  11. 2008.11.19 하이버네이트 사용시 Association Fake Object라는 기술을 사용해 보세요. (2)
  12. 2008.10.08 스프링에서 하이버네이트와 JDBC 같이 사용할 때 트랜잭션 처리는?
  13. 2008.09.22 Cascade.DELETE를 적용하려면 Session.delete(Object)를 사용하세요.
  14. 2008.09.19 @CollectionOfElements 애노테이션
  15. 2008.09.09 하이버네이트 VS JPA
  16. 2008.09.08 하이버네이트를 이용한 DAO 코드에서 int 타입의 리턴값과 SQLException은 무슨 의미가 있을까...
  17. 2008.08.16 하이버네이트 3.3.0 GA 릴리즈~
  18. 2008.06.19 Table 애노테이션
  19. 2008.04.29 하이버네이트의 update() 와 merge() (2)
  20. 2008.04.01 하이버네이트에서 Nontransactional data access
  21. 2008.04.01 autocommit에 관한 오해
  22. 2008.03.30 Isolation 단계 더 높이기 (2)
  23. 2008.03.28 낙천적인 동시접근 제어 (2)
  24. 2008.03.28 DB 수준에서 동시접근 이해하기 (4)
  25. 2008.03.28 동시접근 제어하기
  26. 2008.03.27 하이버네이트 애플리케이션의 트랜잭션
  27. 2008.03.27 데이터베이스와 시스템 트랜잭션
  28. 2008.03.27 트랜잭션 기초
  29. 2008.03.11 Hibernate VS JPA (4)
  30. 2008.03.11 하이버네이트 API : Persistence context 관리하기

[하이버네이트 VS JPA] 객체 다루기

Hibernate/etc : 2010.07.27 17:09


JPA를 언젠간 써야 할텐데 아직도 하이버네이트가 그냥 편해서... @_@;; 암튼 이 둘은 객체를 다루는 API가조금 다른데 그걸 정리해둡니다.

 하이버네이트(Session) JPA(EntityManager) 설명 
save() persist()  저장(정확하게는 Pesistent 상태로 변경) 
 get() find()  DB에서 가져오기 
 load()  getReference() 프록시 가져오기 
 delete() remove()  삭제(정확하게는 Deleted 상태로 변경) 
update()  없음  reattach 다시 부착하기(정확하게는 Detached 상태에서 Persistent 상태로 변경) 
 merge() merge()  merge 병합하기(get() 해온 다음에 Detached 객체의 상태를 복사해간다. 


왠지 CRUD가 다 있어 보이지만 사실 아래 두 줄은 Update 관련 API가 아니라 Detached 상태의 객체를 Persistent 상태로 만들기 용 메서드가 뭐 이것들을 이용해서 Detached 상태 객체를 DB에 반영해서 Update 쿼리를 발생시킬 수도 있지만.. 사실 진정한 Update는 API로 존재하지 않는다. 

즉.. Persistent 상태의 객체를 가지고 어떤 속성을 변경했다 치자.. 이때 굳이 어떤 API를 써서 Update 문을 발생시키지 않아도 된다는 것이다. 

Session session = getSession(); 
Transaction tx = session.beginTransaction(); 
Book book = (Book) session.get(Book.class, 12); 
book.setName("토비의 스프링 3"); 
tx.commit(); 
session.close(); 

저렇게 변경하고 아무것도 실행하지 않는다. 왜일까? 퀴즈닷.
top

  1. Favicon of http://lf.hisfy.com/ BlogIcon 엽우 2010.07.27 20:49 PERM. MOD/DEL REPLY

    setName을 호출하고 트랜잭션이 커밋되면서(아니면 세션이 닫히면서?) 자동으로 업데이트가 되는 거 아닌가요? 하이버네이트는 대충 공부해서 쓰다 보니 정확한 답은 모르겠네요. ^^;

    Favicon of http://whiteship.me BlogIcon 기선 2010.07.28 07:25 PERM MOD/DEL

    맞습니다. 그런걸 바로 Auto Dirty Checking 이라고 합니다.

Write a comment.


[하이버네이트 배치 추가] flush와 clear

Hibernate/etc : 2010.07.23 14:18


배치 작업이라는 것이 DB에서 데이터를 읽어온 다음 뭔가 수정하고 뭔가를 다시 DB에 넣는 작업인데 이런 작업을 하이버네이트로 할 때 조심해야 할 것이 있습니다.

                InvDailyClosing invDailyClosing = new InvDailyClosing();
                invDailyClosing.setDate(today);
                invDailyClosing.setLocation(location);
                invDailyClosing.setItem(item);
                invDailyClosing.setQtyStart(qtyStart);
                invDailyClosing.setInvInList(invInList);
                invDailyClosing.setInvOutList(invOutList);
                invDailyClosing.closing();
                dao.add(invDailyClosing);

이렇게 작성하면 끝난것 같지만 사실 좀 위험한 코드입니다. 만약 저 코드가 for 루프 안에 있고 굉장히 여러번 반복 된다면 언젠가는 MemoryOutOfException을 보게 될 겁니다. "아니 왜?" 라고 하시는 분들이 계실텐데요. 

흠... 하이버네이트 1차 캐시 때문에 그런 현상이 생깁니다. 하이버네이트는 Persistent 상태의 객체를 하이버네이트 Session에 담아둡니다. 여기사 1차 캐시입니다. 캐시니까 저안에 담아놓고 재사용할 수 있습니다. DB에 다녀오는 횟수를 줄일 수 있겠죠. 그런데 언젠가는 이 캐시를 DB와 동기화 시켜야 합니다. 그래야 Session에 담겨있는 객체(Pesistent 객체)를 지지고 볶은 것이 DB에 반영이 되겠죠. 그렇게 DB와 Session 상태를 동기화 시키는것을 Flush라고 하는데.. 또 Persistent 상태라는 것은... 아.. 이런.. 안되겠군요. @_@ 그냥 하이버네이트 완벽 가이드를 읽어주세요.

아무튼 너무 많이 쌓여서 메모리가 부족해지는 상황이 발생하지 않도록 계속해서 Session을 DB에 동기화 시키고 Session을 비워주는게 좋습니다.

하이버네이트 Session의 flush()와 clear()이 바로 그런 용도로 존재하죠. 그래서 

                InvDailyClosing invDailyClosing = new InvDailyClosing();
                invDailyClosing.setDate(today);
                invDailyClosing.setLocation(location);
                invDailyClosing.setItem(item);
                invDailyClosing.setQtyStart(qtyStart);
                invDailyClosing.setInvInList(invInList);
                invDailyClosing.setInvOutList(invOutList);
                invDailyClosing.closing();
                dao.add(invDailyClosing);
             dao.flushAndClear();

이렇게 하이버네이트용 GenrericDao에 flushAndClear()라는걸 만들어 놓고 써주면 됩니다. 주의하실 것은... 반드시 flush 먼저 하고 나서 clear 해야 합니다. 반대로 하면 음식 담긴 쟁반을 서빙도 안하고 설것이 해버리는거나 마찬가지..

top

  1. Favicon of https://dazzilove2.tistory.com BlogIcon dazzi 2010.07.24 12:22 신고 PERM. MOD/DEL REPLY

    난 개인적으로 배치는 pl/sql 추천! 데이터베이스의 성능을 최대한 이용하는것을 더 선호하는편이얌. 특히 대형사이트에서 자바배치 돌리는건 상당히 무모해보인다는. ^^;;; 새벽에 다 돌아줘야하는 배치들이 아침까지 돌고있거나 순차적으로 돌아야하는 디펜던시 걸려버리면 진짜 답 안나옴 --;; 쿼리도 oltp성이냐 아니냐에따라 플랜을 다르게 가져가야 하듯이 배치도 상황에따라 수행주체를 바꿔줘야할 필요성이 있는것같오. ㅋㅋ. 생각이 그렇다는거~~ 아무래도 내가 디비도 좋아하다보니 이런글에 굳이 덧글달고 있는듯 ^^;;;

    Favicon of http://whiteship.me BlogIcon 기선 2010.07.26 14:25 PERM MOD/DEL

    티스토리가 이상하네요. 댓글 달렸는데 제목 목록에 표시도 안되고 댓글 목록에도 표시가 안되고.. 흠..

    전 딱히 배치를 꼭 자바로 하자 DB로 하자는 주위는 아닌데 아직 배치 로직 검증 단계라.. (일단 제가 후딱 해볼 수 있는)자바로 배치 로직짜서 검증하고 나중에 그걸 SQL로 바꾸던지 해도 될 것 같네요.

    대용량이 아니라 하더라도 20초면 될 일을 20분 동안 하게 되는 사태는 벌어지면 안되겠죠.

Write a comment.


[하이버네이트] 2차 캐싱 적용하기

Hibernate/etc : 2009.09.07 18:43


참조
Java Persistence With Hibernate
http://whiteship.tistory.com/1256
Improving Hibernate's Performance
19.2. The Second Level Cache
Hibernate: Truly Understanding the Second-Level and Query Caches
Hibernate Caching

이론적으로 정리할 것 들

- 2차 캐싱이라고 하는 이유? 1차 캐시(persistence context cache)는 있으니깐.
- 하이버네이트 캐싱 종류(트랜잭션 스콥 캐싱, 프로세스 스콥 캐싱, 클러스터 스콥 캐싱)
- 캐싱에 적당한 클래스(데이터 변경이 드물고, (금전적인 데이터 처럼)핵심적인 데이터가 아니며, 공유하는 데이터가 아니고 애플리케이션에 국한된 데이터)
- 동시성 전략(Transactional, Read-write, Nonstrict-read-write, Read-only)
- 캐싱 공급자 선택하기(EHCache, OSCache, SwarmCacahe, JBoss Cache)

적용하기

1. 캐싱 동시성 전략 선택하기

read-wirte 모드를 선택함. read committed isolation 수준을 유지하려고.

@Entity
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public class Study implements Serializable{

2. SessionFactory 설정에 2차 캐싱 관련 설정 추가.

    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan" value="springsprout.domain" />
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
                <prop key="hibernate.generate_statistics">true</prop>
                <prop key="hibernate.cache.use_second_level_cache">true</prop>
                <prop key="hibernate.cache.use_query_cache">true</prop>
                <prop key="hibernate.cache.provider_class">net.sf.ehcache.hibernate.EhCacheProvider</prop>
            </props>
        </property>
    </bean>

2차 캐시와 함께 쿼리 캐시도 켜주고, 사용할 캐싱 공급자를 설정해 줍니다.

3. Query 또는 Criteria API에 cacheable 설정 true로 변경.

기본값이 false기 때문에 이렇게 해주지 않으면 캐시가 적용되지 않습니다.

    public List<Study> getStudyList() {
        Criteria c = getCurrentSession().createCriteria(Study.class);
        c.setCacheable(true);
        return c.list();
    }

4. 테스트

    @Test
    public void getAll() throws Exception {
        insertXmlData("testData.xml");
        assertThat(sr.getStudyList().size(), is(2));
        assertThat(sr.getStudyList().size(), is(2));
    }

콘솔에 select 쿼리가 한 번만 찍히는지 확인합니다.

5. (optional) ehcache.xml 설정 파일 추가.

<ehcache>
    <diskStore path="java.io.tmpdir"/>
    <defaultCache maxElementsInMemory="500" eternal="true"
        overflowToDisk="false" memoryStoreEvictionPolicy="LFU" />
    <cache name="springsprout.domain.study.Study"
        maxElementsInMemory="500" eternal="true" overflowToDisk="false"
        memoryStoreEvictionPolicy="LFU" />
    <cache name="springsprout.domain.study.Meeting"
        maxElementsInMemory="500" eternal="true" overflowToDisk="false"
        memoryStoreEvictionPolicy="LFU" />
    <cache name="org.hibernate.cache.StandardQueryCache"
        maxElementsInMemory="5" eternal="false" timeToLiveSeconds="120"
        overflowToDisk="true" />
    <cache name="org.hibernate.cache.UpdateTimestampsCache"
        maxElementsInMemory="5000" eternal="true" overflowToDisk="true" />
</ehcache>

이런식으로 만든다음 src 폴더 바로 밑에 추가해주면 끝.

6. 고민하기

- 콘솔을 눈으로 확인하는;; 수동 테스트인데 이걸 어떻게 하면 자동화 테스트로 바꿀 수 있을까요. 흠..
- SessionFactory 자체에 아예 모든 Qeury와 Criteria가 캐시를 사용하도록 설정할 수는 없을까요?
- 스프링 AOP를 이용한 애플리케이션 차원의 캐시와 지금 사용한 시스템적인 캐시 중에 어떤것을 어떤 경우에 사용해야 적당한 것일까?

top

Write a comment.


[하이버네이트] 애매한 에러 메시지 때문에 삽질.. @_@

Hibernate/etc : 2009.08.07 21:50


DEBUG - OpenSessionInViewFilter.doFilterInternal(207) | Closing single Hibernate Session in OpenSessionInViewFilter
DEBUG - SessionFactoryUtils.closeSession(784) | Closing Hibernate Session
2009. 8. 7 오후 9:30:52 org.apache.catalina.core.StandardWrapperValve invoke
심각: Servlet.service() for servlet springsprout threw exception
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: springsprout.domain.Member.studies, no session or session was closed
    at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:358)
    at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:350)
    at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:343)
    at org.hibernate.collection.PersistentSet.add(PersistentSet.java:189)
    at springsprout.domain.Member.addManegedStudy(Member.java:154)
    at springsprout.modules.study.StudyService.add(StudyService.java:24)
    at springsprout.modules.study.StudyService$$FastClassByCGLIB$$6d06dbc0.invoke(<generated>)
    at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:149)
...

에러의 원인이 무엇일까? 에러 로그에서 가장 중요한 부분이라고 생각하는 부분을 진하게 표시했다. 제 1의 단서는 에러 메시지이고, 제 2의 단서는 에러가 발생한 지점 중 내가 코딩을 한 부분이다. 스프링, 하이버는 많은 유저와 테스트 그리고 지속적인 관리를 통해 내가 작성한 코드보다 더 신빙성이 높기 때문이다.

어쨋든.. 생각해보자. 에러 메시지가 뭐라고 하는가?? 세션이 없다고?? 왜??? 난 분명 @Transactional을 사용했다. 트랜잭션이 없을리 없는데.. 왜 그럴까.. 이해가 되지 않는다. 세션이 닫혔다고?? 왜??? 이해가 되지 않는다. 이런 일이 발생한 적은 없었다.

혹시나해서 DEBUG 모드로 스프링 로그를 찍어보았다. 해당 에러가 나기 직전에 이러한 로그를 볼 수 있다.

DEBUG - HibernateTransactionManager.doBegin(504) | Preparing JDBC Connection of Hibernate Session [org.hibernate.impl.SessionImpl@7650bc]
DEBUG - HibernateTransactionManager.doBegin(569) | Exposing Hibernate transaction as JDBC transaction [com.mchange.v2.c3p0.impl.NewProxyConnection@a681c3]
DEBUG - HibernateTransactionManager.doGetTransaction(437) | Found thread-bound Session [org.hibernate.impl.SessionImpl@7650bc] for Hibernate transaction
DEBUG - AbstractPlatformTransactionManager.handleExistingTransaction(468) | Participating in existing transaction

분명 Session을 잘 가져왔고, 세션이 닫힌다는 얘기도 없다. 이 바로 다음에 에러가 발생한다. 막막하군.. 그럼 이제 내가 작성한 코드를 살펴보자.

    public void add(Study study) {
        Member currentMember = securityService.getCurrentMember();
        currentMember.addManegedStudy(study);
        repository.add(study);
    }

    public void addManegedStudy(Study study) {
        getStudies().add(study);
        study.addMember(this);
        getManagedStudies().add(study);
        study.setManager(this);
    }

빨간색으로 표시한 부분이 에러가 발생하는 지점이고 최종적으로 getStudies()를 호출할 때 발생한다. 모르겠다. 무엇이 문제일까?

테스트를 만들어보자.

    @Test
    public void add() throws Exception {
        Study study = new Study();
        Member member = new Member();
        memberService.addWithoutConfirm(member);
       
        service.add(study, member);

        assertEquals(member, study.getManager());
        assertTrue(study.getMembers().contains(member));
    }

스프링 통합 테스트를 만들어서 테스트해보았다. 이 테스트는 잘 돌아간다. 더 미궁속으로 빠져든다. 그런데, 테스트와 실제 코드가 같지 않다. 테스트를 편하게 하기 위해 조금 다른 코드를 이용했다. 둘의 차이는 무엇일까?

테스트에서의 member 객체 상태는 Persistent다. 그러나... 실제 코드에서 member 객체 상태는 어떨까? 아차차.. 이런.. 문제의 실마리가 보이는 듯 하다.

그렇다. 실제 코드에서 currentMember 객체는 detached 상태인 것이다. 아.. 이런.. 단서에 너무 의존하다가 눈앞에 있는 객체 상태를 보지 못했다. 나의 실수다. 하이버네이트를 다룰 때 가장 중요한 것 중 하나가 바로 객체의 상태인데. 그것을 못 보다니 한심하다는 생각이 밀려온다. 이미 지난 일이다. 어쩔 수 없다. 다음번엔 잘 찾아내야지.

단 하나.. 바라는게 있다면, 세션이 없다는 얼토당토 않은 에러 메시지 말고, detached 객체에서 lazy loading이 발생한다고 메시지를 출력해주면 좋겠다. 그랬다면... 좀 더 쉽게 해결할 수 있었을 법한 문제였다.
top

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

    이거는 Hibernate 사용자라면 적어도 한번쯤은 겪는 그런 문제인거 같네요. :)

    본문에 다른부분까지 있는 좀더 자세한 코드가 없어서 확신할수는 없지만,
    Hibernate 입장에서는 저문제가 Session이 없는게 (닫힌게) 맞을껍니다. :)
    그러니까 detached 상태인 Entity object의
    field를 접근할때 이녀석이 lazy loading 돼야하는 field면
    아마 Hibernate가 얘를 initialise 시켜서 lazy loading을 시도 하는걸로
    알고 있습니다. (아...아닌가?ㅡ_ㅡ?)
    아무튼 그 시도를 위해서 Session을 찾는데, 쓸수있는 세션이 없겠죠.
    Spring 쓰시면서 @Transactional을 쓰셨을텐데 @Transactional 설정한 method
    실행이 종료되면서 Session이 닫혀 버렸을테니까요.
    그러니까,
    Entity object의 field를 lazy loading 해야함 -> 어? detached 됐네? -> 알았어 너를 다시 Entity로 만들어서 읽어줄께 -> Session아 어딨니? -> 어? Session이 닫혔네? -> 에라이, LazyInitializationException!
    요렇게 되는거 같습니다. 아... 사실 이건 저한테 발생했던 상황입니다...ㅡ_ㅡ;;;

    방법이야 LAZY fetching 말고 EAGER fetching을 하면 되긴하는데,
    안 쓸지도 모르는 field를 꼭 eager fetching 한긴 싫고...

    저는 View쪽에서 XML 생성하는데 lazy loading 설정한 부분에서 저 Exception이 발생해서
    해결방법으로 혼자서 그냥 알아낸 이상한(?) 방법이...
    createQuery() method를 써서 query (e.g. "from EntityObject id = :id" ) 로
    Entity를 가져오고 이 method에는 @Transactional 설정을
    안 하는겁니다. 그럼 나중에 lazy loading 할때 제대로 되더군요.
    근데 이렇게 해도 entityManager.find() 메소드를 쓴 경우는 안 되고 역시 LazyInitializationException...
    역시 이 방법은 좀 아닌거 같아서, 해결방법을 찾아보니

    web.xml에
    <filter>
    <filter-name>openEntityManagerInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
    </filter>
    <filter-mapping>
    <filter-name>openEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
    이렇게 설정하는 방법이 있더군요. 이렇게 하면 OpenEntityManagerInViewFilter가
    알아서 EntityManager (Session) 문제를 해결해 주는거 같습니다.

    이문제가 스프링 포럼에서도 자주 거론되는거 같은데, 너무 대충 찾아봐서 그런지
    이얘기 저얘기 많기만 하고 아주 명확한 답은 안 보이네요.
    사실 너무 대충 찾아봐서 제가 놓쳤을 가능성이 큽니다만...
    Spring document에서는 이문제에 대한 방법이나 해결방법은 못본거 같은데...

    저런 방법들 보다 훨씬 좋은 해결법이 있는지 나중에 토비님께 한번 여쭤봐야겠네요...^^;

    Favicon of http://whiteship.tistory.com BlogIcon 2009.08.08 10:21 PERM MOD/DEL

    네. 이번에 발생한 상황과 비슷한 상황이네요.

    스프링 시큐리티에서 관리중인 member 객체를 가져왔더니.. 그 녀석은 이미 하이버네이트 세션 컨텍스트에서 나가버린지 한 참 된 detached 상태의 객체였던 거죠.

    그런 녀석한테 lazy loading으로 컬렉션을 가져오라고 시켰으니... 세션이 없다는 에러가 나올법도 하지만.. 조금 만 더 직관적으로 detached 객체라는 사실을 알려줬으면 하는 바램이에요. ㅠ.ㅠ

  2. Favicon of http://toby.epril.com BlogIcon 2009.08.08 05:57 PERM. MOD/DEL REPLY

    씸을 쓰면 detached가 아예 없이 하이버 개발이 가능하니 바꺼. 저런 문제 전혀 신경 안써도 댄다고.

    아니면 HIA2에 나오는 contextual session 방식을 쓰덩가. seam이 엄다면 쩜 복잡하긴 하지만.

    Favicon of http://whiteship.tistory.com BlogIcon 2009.08.08 10:23 PERM MOD/DEL

    씸은 다음 기회에~
    contextual session은 한 번 고려해보죠.

  3. Favicon of http://blog.lckymn.com BlogIcon Kevin 2009.08.12 23:04 PERM. MOD/DEL REPLY

    헐... 이제 Seam까지 써야 하나요? @_@;;;
    이거, 본 application보다 frameworks만 자꾸 늘어나는 느낌이... 덜덜덜...
    암튼 처음에 저 문제 때문에 짜증나서, DB쪽은 그냥 Spring 쓰지 말고,
    ThreadLocal 로 만들어서 쓸까? 하는 생각도 했습니다.

    기선 2009.08.13 09:05 PERM MOD/DEL

    ㅎㅎ그러게요.
    씸도 공부해보고 싶어요.
    톱님이 하두 좋다고 하시니.. 더 궁금해져요.

Write a comment.


JPA 구현 패턴

Hibernate/etc : 2009.07.24 23:52


http://blog.xebia.com/2009/07/13/jpa-implementation-patterns-wrap-up/

차근 차근 봐둬야 할 글목록이 있군요.

Basic patterns

Advanced patterns


맨 위에 있는 DAO랑 맨 마지막에 있는 Testing만 읽어봤는데, 내용이 괜찮네요. DAO는 현재 사용하는 방식과 비슷하고, 테스트 쪽엔 모르는 것들이 있더군요. ObjectMother랑 Finess를 봐봐야겠습니다.

테스트 데이터를 DBUnit으로 넣을까 ObjectMother를 사용할까.. 고민이로군요. 간단한건 DBUnit으로 XML 데이터 만들어서 넣고, 복잡한 객체 집합은 ObjectMother를 쓸까나?? 아니.. 그냥 둘 중 한 방법으로 쓴느게 햇갈리지도 않고 좋겠죠? 그러고보면 DB 스키마 기반으로 코딩한 것도 아닌데 굳이 XML로 DB 데이터 만들어 넣는건 좀.. 그렇네요. 객체 기반으로 코딩했으니... 테스트 데이터도 팩토리를 이용해서 만드는게.. 어울리는 듯하고.. 흠...

일단은 댄스 연습 좀 하고 자야겠네요.

top

  1. Favicon of https://helols.tistory.com BlogIcon is윤군 2009.07.25 02:53 신고 PERM. MOD/DEL REPLY

    엄청 공부하면 머리커진데요;;; ㅋ

    댄스연습도 하고 ~~ 잼있겠어요;;ㅋㅋ 신혼생활..ㅋㅋ

    Favicon of http://whiteship.tistory.com BlogIcon 2009.07.26 09:39 PERM MOD/DEL

    댄스 재밌쥐~ 스탭~ 스탭~ 롹 스탭~

  2. Favicon of http://blog.lckymn.com BlogIcon Kevin 2009.07.29 00:00 PERM. MOD/DEL REPLY

    전 이 아이디어에
    http://www.ibm.com/developerworks/java/library/j-genericdao.html
    개빈 킹이 쓴 하이버네이트 책에 나온 샘플 코드에 있는 generic parameter 가져오는
    reflection 코드를 참고해서 만들어 썼는데,
    (전엔 주로 JDK 1.4 위주로 써서 generics 하고 별로 안 친했거든요. :) )
    결국 기선님게서 거신 링크에 있는 글이나 그게 그거네요.

    아참, 거기다가 개인적으로 AspectJ를 이용해서
    EntityManager 사용전에 null인지 아닌지
    확인하게 만들어서 쓰고 있습니다. null 체크는 method 마다
    들어가기 때문에, crosscutting concern 으로 분류해 버린거죠.

    이러면 Aspect하나로 Generic Repository 상속해서 구현한
    repository들 전부 null 테스트가 가능해서 편하긴 한데,
    현재는 privileged aspect로 EntityManager field에
    접근할때마다 검사하게 해놔서
    method안에서 EntityManager를 두번이상 사용하는 경우
    null 테스트도 그 갯수만큼 하는 상황이...ㅡ_ㅡ;
    그래서 repository 안에 있는 method별로
    method 실행전에 검사하는걸로 바꾸려고 하고 있습니다. :)

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2009.07.28 21:16 신고 PERM MOD/DEL

    오호.. AspectJ를 이용해서 null 체크를 하는 AOP를 하시는군요. 좋네요~ ㅎㅎ AOP 공부는 쬐끔했어도.. 잘 써먹질 못하고 있는데.. 잘 활용하시는 분들 보면 멋져요~

Write a comment.


[하이버네이트 툴] hbm2doc

Hibernate/etc : 2009.06.30 20:37


하이버네이트 설정 정보를 통해, Entity 정보와 Table 정보를 HTML 문서로 만들어 주는 도구를 제공합니다.
하이버네이트 툴 3.2.0 베타9 버전 설명에 보면, 다이어그램까지 만들어 주는데.. 좀.. 멋진 툴인 듯 합니다.



문제는 Description에 부가 설명을 넣을 길이 없다는 건데 @_@.. 이 부분에 대한 패치를 만든 사람이 있길래 고대로 따라서 구현해볼까 합니다. 부디 잘 되기를...;;
top

Write a comment.


[JBoss Tools] 이클립스 업데이트 사이트

Hibernate/etc : 2009.06.30 11:31


정리가 완전 잘 되어 있네요. http://jboss.org/tools/download.html

정식 버전: JBoss Tools 3.0(for 이클립스 3.4) 
http://download.jboss.org/jbosstools/updates/stable/

개발자 버전: JBoss Tools 3.1(for 이클립스 3.5)
http://download.jboss.org/jbosstools/updates/development/

나이틀리 버전: JBoss Tools 3.1(for 이클립스 3.5)
http://download.jboss.org/jbosstools/updates/nightly/trunk/

이 플러긴에 포함되어 있는 툴 목록은 다음과 같습니다.

JBoss AS Tools
Birt Tools
Core Tools
ESB Tools
Project Examples
Hibernate Tools
jBPM Tools
JMX Tools
JST/JSF tools
Portal tools
Seam Tools
Smooks Tools
Visual Page Editor
JBoss Webservice Tools

와우. 많기도 하죠.
top

  1. Favicon of http://blog.lckymn.com BlogIcon Kevin 2009.07.01 22:22 PERM. MOD/DEL REPLY

    깔아놨다가 다 지워버려서 제대로 써본적이 없는데, 좋은가요? ^^?
    툴이 너무 많이서 툴사용법 익히는데만도 오랜 시간이 필요한건 아닌지 모르겠습니다. @_@;

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

    저는 하이버네이트 툴 때문에 설치를 했는데, 아직은.. 그다지... 흠... 잘 모르겠네요.ㅋ

Write a comment.


[하이버네이트] Session-Per-XXX

Hibernate/Chapter 11 : 2009.06.27 17:52


참조: JPWH 11장

Session-Per-Operation: 479p. 안티 패턴, 하나의 오펴레이션(메서드) 당 새로운 세션을 만들어 사용하는 것. 성능상 병목지점이 될 수 있다.

Session-Per-Request: 479p. pesistence context 범위를 데이터베이스 트랜재션 범위와 동일하게 유지하는 것. 즉 트랜잭션 당 새로운 세션을 만들어 사용하는 것으로 볼 수 있다. 짧은 conversation(하나의 request-response 쌍)을 처리할 때 적당하다.

Session-Per-Conversation: 489p. persistence context를 복잡한 conversation(여러 request-response 쌍) 스콥으로 확장(extending)하는 방법. conversation 마다 새로운 session을 만든다. detached 객체를 사용하여 conversation을 구현하는 방법의 대안책이 될 수 있다.

ps: 복잡하다. persist(), saveOrUpdate()가 괜히 있는게 아니였다. SPC의 경우 인터셉터를 이용할 수 있고, currentSession을 어딘가에 저장해 두었다가 다음 request 처리시에 재사용해야 한다. 또한, detached 객체를 사용한 Conversation 구현과, SPC를 사용한 Conversation 구현은 맘대로 정하는게 아니다. 경우에 따라, SPC를 사용해야만 하는 경우가 있다. 자세한건.. 내일??

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

[하이버네이트] Session-Per-XXX  (0) 2009.06.27
top

Write a comment.


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

Hibernate/Chapter 9 : 2009.06.25 13:54


참조: JPWH 9.2.2 ~ 9.2.3

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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




top

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

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

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

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

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

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

    ㅎㅎ오늘도 감사합니다.

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

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

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

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

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

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

Write a comment.


[하이버네이트 퀴즈] Flush

Hibernate/etc : 2009.06.24 14:08


    @Transactional
    @Test
    public void crud() throws Exception {
        Emp emp = new Emp();
        emp.setName("ks");
        ed.save(emp);

        assertThat(ed.getAll().size(), is(1));
        assertThat(ed.get(emp.getId()).getName(), is("ks"));

        emp.setName("tb");
        ed.update(emp);
        assertThat(ed.get(emp.getId()).getName(), is("tb"));

        ed.delete(emp);
        assertThat(ed.getAll().size(), is(0));
    }

이런 테스트가 있는데, 콘솔 창에 쿼리를 봤더니.

Hibernate: insert into Emp (id, dept_id, name) values (null, ?, ?)
Hibernate: call identity()
Hibernate: select emp0_.id as id4_, emp0_.dept_id as dept3_4_, emp0_.name as name4_ from Emp emp0_
Hibernate: delete from Emp where id=?
Hibernate: select emp0_.id as id4_, emp0_.dept_id as dept3_4_, emp0_.name as name4_ from Emp emp0_

update문이 빠져있다. 여기서 발생하는 의문점이 한 두가지가 아니다.

1. DB에 update가 되지도 않았는데 테스트는 어떻게 통과한 것일까?

2. 왜 update 문은 날아가지 않은 것일까?

3. 역으로, 왜 insert와 delete는 날아간 것일까?

이 세 가지 의문을 해결하려면 위에서 작성한 코드를 좀 더 자세히 살펴볼 필요가 있다. 바로 ed.save(), ed.get(), ed.getAll(), ed.update(), ed.delete()  들이다. 이 녀석들이 어떻게 구현되어 있는지 보지 않고서는 알 수 없다. 또하나 Flush 모드 역시 알아야 한다.

- Flush 모드는 기본 모드인 AUTO를 사용했다.
- save(), get(), update(), delete()는 하이버네이트의 Session API와 동일하다고 생각하면 되며, getAll()은 다음과 비슷하게 구현되어 있다. session.createQuery("from Emp"); 실제로는 이 모든게 GenericDao 구현체에 들어있어서 약간 다르긴 하지만, 본질은 그렇다.
- 테스트는 @Transactional한 녀석으로 기본으로 rollback될 녀석이다.

자.. 이제 위 세가지 질문에 대답할 수 있을 것이다. 그랬다면, 다음 퀴즈도 덤으로 풀어보자.
update 쿼리를 볼 수 있는 방법은 현재 두 가지 정도가 떠오른다.

4. 위 테스트 코드에서 한 줄을 삭제하여 update 쿼리가 콘솔에 찍히게 해보자.

5. 위 코드에 ed.flush()를 어디에 추가하면 update문을 볼 수 있을까?

정답은 비공개.. 영원히..
top

  1. Favicon of http://igooo.org/tc BlogIcon igooo 2009.06.24 23:37 PERM. MOD/DEL REPLY

    emp.setName("tb";); 위에서 flush시키면 update가 나오기

    assertThat(ed.get(emp.getId()).getName(), is("ks";)); 지우면 update가 나타나지 않을까요?


    이클립스 설치하느라 실행은 못시켜봤습니다 .ㅎ

    Favicon of http://whiteship.me BlogIcon 기선 2009.06.25 08:33 PERM MOD/DEL

    안타깝게도.. 둘 다 땡입니다.

  2. koasu 2009.06.25 10:39 PERM. MOD/DEL REPLY

    4번: @Transactional 을 지우면 update 가 나타날꺼 같아요.
    5번: ed.update(emp); ed.flush(); 순으로 하면 update가 나타날꺼 같나요.

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2009.06.25 11:02 신고 PERM MOD/DEL

    4번은 땡. 그거 없이 dao 테스트하기는 좀 그르치요. 당장엔 트랜잭션이 없다고 에러가 날 겁니다. 트랜잭션 설정을 롤백=false로 바꾼다 하더라도 결과는 마찬가지 입니다.

    5번은 맞추셨습니다.

  3. crystal 2009.06.25 11:34 PERM. MOD/DEL REPLY

    4. 위 테스트 코드에서 한 줄을 삭제하여 update 쿼리가 콘솔에 찍히게 해보자.
    // ed.delete(emp);


    5. 위 코드에 ed.flush()를 어디에 추가하면 update문을 볼 수 있을까?
    update()아래, delete()전

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2009.06.25 11:41 신고 PERM MOD/DEL

    딩동댕!

  4. crystal 2009.06.25 11:38 PERM. MOD/DEL REPLY

    1. DB에 update가 되지도 않았는데 테스트는 어떻게 통과한 것일까?
    update전 emp객체가 컨택스트에 관리되는 객체, 즉 persistant 상태에 있기 때문이다.

    2. 왜 update 문은 날아가지 않은 것일까?
    아래 해당 객체의 delete가 있기 때문이다.

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2009.06.25 11:41 신고 PERM MOD/DEL

    딩동댕~

Write a comment.


하이버네이트 사용시 Association Fake Object라는 기술을 사용해 보세요.

Hibernate/etc : 2008.11.19 11:28


이 기술은 사부 토비님이 알려준 기술입니다.
 
Fake Object라는 이름으로 배운 기술인데 보다 적당한 이름이 생각나서 붙여봤습니다. 유즈케이스를 보여드리자면 Post -> Board 관계에서 Post를 추가하려고 할 때 Post에 Board 객체를 어디선가 setBoard로 묶어줘야 합니다.

그걸 폼을 보여주기 전에 하거나 아니면 폼을 받아서 처리할 때 하거나.. 언젠간 해야되죠. 그럴 때 보통 다음과 같은 코드를 작성하게 될 겁니다.

폼 보여줄 때 세팅할 경우를 생각해보죠.

    @RequestMapping(method=RequestMethod.GET)
    public void add(int boardId, ModelMap model){
        Post post = new Post();
        post.setBoard(boardService.getById(boardId));
        model.addAttribute(post);
    }

결국 저 문장은 서비스 타고 트랜잭션 내에서 DAO로 가서 DB로 가서 board 하나를 select하는 쿼리가 날아가게 합니다. 하지만  Association Fake Object 기술을 사용하면 그 쿼리를 보내지 않고도 아주 잘~ 연관을 맺은 상태로 객체를 저장할 수 있습니다.

Association Fake Object 라는 이름이 이미 사용 중인 용어인지는 모르겠는데, 연관 맺을 때 사용하는 가짜 객체 라는 뜻입니다.

어떻게?? 비밀입니다. 아.. 사실 비밀도 아닙니다. OSAF에서 아주 잘 활용하고 있거든요, 후후훗.
top

  1. 권남 2008.11.19 15:35 PERM. MOD/DEL REPLY

    ID 필드값만 저장된 Board객체를 new 로 직접 생성해서 setBoard하는건가요?
    DAO 단위테스트만 들때 내가 이런식으로 작성했던거 같긴한데 말이죠.. 그걸 Association Fake Object라고 하나요?
    OSAF 어느 부분인지 힌트라도 좀 주시지... ㅜㅜ

    Favicon of http://whiteship.me BlogIcon 기선 2008.11.19 16:43 PERM MOD/DEL

    맞추셨는데요. 와우 *_*

    일반적으로 사용하는 용어는 아니구요. 그냥 개인적으로 생각해 본 용어에요

    OSAF에 GenericPropertyEditor가 두 개가 있는데, 하나는 DB에서 실제 도메인 객체 가져오는 것하고 하나는 위처럼 AFO를 사용하는 것이 있습니다.

    도메인 객체 연관을 맺을 때 스프링의 프로퍼티에디터와 하이버네이트 특성을 활용한 AFO를 멋지게 조합한 부분이죠. ^^

Write a comment.


스프링에서 하이버네이트와 JDBC 같이 사용할 때 트랜잭션 처리는?

Hibernate/etc : 2008.10.08 16:23


별로 할 일이 없습니다.

PlatformManager는 하이버네이트가 사용하는
org.springframework.orm.hibernate3.HibernateTransactionManager

이걸 그대로 사용하면 되고, JDBC 코딩을 할 때는 그냥 JdbcTemplate을 사용하면 알아서 트랜잭션이 적용됩니다.

그런데 만약에 JdbcTemplate을 사용하지 못하고, DataSource를 직접 사용해야 할 경우에는 다음과 같이 TransactionAwareDataSourceProxy를 사용하면 된다고 합니다.

<bean id="rawDataSource" class="whatover youuse"/>

<bean id="dataSource" class="TransactionAwareDataSourceProxy">
  <constructor-arg ref="rawDataSource" />
</bean>

DataSource를 직접 사용하는 코드가 엄청나게 많아서 손을 못댈 경우에는 저렇게 dataSource를 스프링이 관리하는 트랜잭션을 알고 있는 데이터소스로 바꾸면 된다고 하는데, 해보진 않았습니다. 제가 해보고 싶었던 건 하이버네이트가 flush()를 하지 않은 데이터에 대한 JDBC쿼리로 인한 예외 상황인데...

테스트를 잘못 짠건지.. 잘 안 되더군요.

@Repository
public class MemberDao {

    @Autowired
    SessionFactory sessionFactory;
   
    SimpleJdbcTemplate jdbcTemplate;
   
    @Autowired
    public MemberDao(DataSource dataSource) {
        jdbcTemplate = new SimpleJdbcTemplate(dataSource);
    }
   
    public void add(Member member){
        sessionFactory.getCurrentSession().save(member);
    }
   
    public int update(Member member){
        return jdbcTemplate.update(
                "UPDATE Member SET age = ? WHERE name = ?", member.getAge(), member.getName());
    }

}

이런 DAO를 만들었습니다. add()는 하이버네이트로하고 update()는 JdbcTemplate으로 했습니다.

@Service
@Transactional
public class MemberService {

    @Autowired
    MemberDao memberDao;
   
    public void foo(){
        Member member = new Member();
        member.setName("keesun");
        memberDao.add(member);
       
        member.setAge(20);
        memberDao.update(member);
    }
   
}


그리고 서비스 코드는 저렇게 트랜잭션 처리를 하고, keesun이라는 객체를 하나 만들어서 저장하고, 나이를 추가한다음에 JDBC로 update문을 날립니다.

제가 원했던 결과는..

에러가 나는 겁니다.

그러나..

Hibernate: select nextval ('hibernate_sequence')
Hibernate: insert into Member (age, name, id) values (?, ?, ?)
Hibernate: update Member set age=?, name=? where id=?

에러가 나질 않고, 너무도 자연스럽게 동작해버려서 당황했습니다. 특히 마지막 줄의 쿼리는 제가 JdbcTemplate으로 날린 쿼리랑은 완전 다른 하이버네이트가 만든 쿼리가 날아갔습니다. 이게 대체;;; 무슨 일인지..  흠..

결과적으로는 아~무 걱정없이 하이버네이트랑 JdbcTemplate을 같이 사용할 수 있다는 것이지만, 제가 원했던 상황이 발생하지 않아서 좀 우울합니다.
top

Write a comment.


Cascade.DELETE를 적용하려면 Session.delete(Object)를 사용하세요.

Hibernate/Chapter 6 : 2008.09.22 13:48


기본적으로 이 문제는 Session API를 사용할 것이냐, 아니면 HQL이나 네이티브 쿼리를 날릴 것이냐에 해당하는데, Cascade와 같은 옵션을 적용하려면 세션 컨텍스트랑은 전혀 관련없이 바로 바로 DB로 쿼리를 날려버리는 HQL이나 네이티브 쿼리등을 사용하면 안 됩니다. Session에 있는 add(Obejct), reattach(Object), delete(Object) 등을 이용해야.. 원하는 Cascade 옵션을 적용할 수 있습니다.

@Entity
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    private String name; // Text field
    private String loginId;
    private String password; // Password
    private int sex; // Radio button
    @CollectionOfElements
    @Cascade(CascadeType.DELETE)
    private List<Integer> hobbies; // check boxes
    private String location; // Select
    private String memo; // Textarea
...

위와 같이 Cascade 설정을 해서 Emp를 지울 때 이 녀석을 참조하고 있는 value type 데이터도 지워지게 해뒀습니다. 자. 이게 제대로 동작하나 확인을 해봐야겠죠.

    @Test
    public void delete(){
        Employee employee = new Employee();
        employeeDao.add(employee);
        employee.addHobby(HobbyType.MOVIE);
        employee.addHobby(HobbyType.CODING);
       
        employeeDao.flushAndClear();
       
        Employee emp = employeeDao.get(employee.getId());
        System.out.println(emp.getHobbies());
       
        employeeDao.deleteById(emp.getId());
        employeeDao.flushAndClear();
    }

이 테스트 코드는 에러가 납니다. 왜 에러가 날까요? emp의 주키를 emp_hobby 테이블에서 참조하고 있는 레코드들이 아직 남아있는데, delete from emp wherer id = ? 이런 쿼리가 날아가기 때문입니다. 다음과 같은 에러를 볼 수 있습니다.

org.hibernate.exception.ConstraintViolationException: could not execute update query org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:71) org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43) org.hibernate.hql.ast.exec.BasicExecutor.execute(BasicExecutor.java:84) org.hibernate.hql.ast.QueryTranslatorImpl.executeUpdate(QueryTranslatorImpl.java:396) org.hibernate.engine.query.HQLQueryPlan.performExecuteUpdate(HQLQueryPlan.java:259) org.hibernate.impl.SessionImpl.executeUpdate(SessionImpl.java:1141) org.hibernate.impl.QueryImpl.executeUpdate(QueryImpl.java:94)
...

이런 식의 에러 메시지를 만나게 됩니다. 그러나 위 코드에서 한 줄만 바꾸면 테스트는 통과하죠.

Session API를 사용하는 쪽이 더 유연한 애플리케이션을 만드는데 도움이 되는 것으로 보입니다. 설정만 변경하면 위와 같이 Cascade.Delete를 처리할 수 있는데, 반해 SQL을 직접 날리는 DAO로 구성되어 있다면, 일일히 emp가 물고 있는 것들부터 다 지우고나서 지워야 할테니.. 이것참.. 번거로운 코딩일 뿐더러, 애플리케이션이 유연하지도 못한거 아닌가 싶습니다. 기능하나 변경하는데 사방을 건들테니 말이죠.

Hibernate 좋아 좋아.

2008/02/24 - [Hibernate/Chapter 9] - 객체 상태
2008/02/07 - [Hibernate/Chapter 6] - 객체 상태 전이하기

top

Write a comment.


@CollectionOfElements 애노테이션

Hibernate/Annotation : 2008.09.19 11:31


참조
http://www.hibernate.org/hib_docs/annotations/api/org/hibernate/annotations/CollectionOfElements.html

Entity 타입 콜렉션 말고 Value 타입 콜렉션을 맵핑할 때 사용하는 애노테이션 입니다.

    @CollectionOfElements
    private List<Integer> hobbies;

간단하죠. JPA는 아니고 하이버네이트 애노테이션입니다. fetch와 targetClass 속성을 가지고 있는데, targetClass는 콜렉션 타입을 명시하지 않았을 때 사용하면 됩니다.

'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

Write a comment.


하이버네이트 VS JPA

Hibernate/Chapter 10 : 2008.09.09 22:28


 하이버네이트
JPA
 Transaction API를 사용하여 JDBC와 JTA를 사용할 수 있다.  EntityTransaction API는 resource-local 트랜잭션을 사용할 때에만 유용하다.
 하이버네이트 JTA랑 EJB의 CMT를 연동할 때 사용할 수 있다.  Java SE와 Java EE 에서 DB 커넥션 이름이 바뀌는 것 빼고 추가적인 설정은 필요없다.
 하이버네이트 automatic versioning을 사용해서 optimistic concurrency control을 제일 효율적으로 제공한다.  자동 버전관리로 낙천적인 동시성 관리하는 것을 표준화 했다.


top

Write a comment.


하이버네이트를 이용한 DAO 코드에서 int 타입의 리턴값과 SQLException은 무슨 의미가 있을까...

Hibernate/study : 2008.09.08 11:12


JDBC로만 코딩을 해오신 분에게 하이버네이트를 사용하여 만들 DAO에 필요한 메소드들을 정의해서 커밋해달라고 부탁했습니다. 그리고 다음과 같은 코드를 받았습니다.

public int update(final Task task) throws SQLException {
..
}

public int delete(final Task task) throws SQLException {
...
}

객체와 레코드가 맵핑되어 있는데 그 수를 셀 필요가 있을까요?

이 코드는 하이버네이트가 그 동안 저에게 얼마나 많은 도움을 줬는지 알게 해주는 코드였습니다. 상대방과 위에서 정의한 리턴값의 용도와 의도를 물어보고, 하이버네이트의 update와 JDBC update 개념이 다르다는 것과 하이버네이트 최신 버전은 모두 RuntimeException을 던진다는 이야기를 해줬습니다. 자세한 내용은 생략하겠습니다. 이미 이전에 하이버네이트의 update에 대해 장문의 글을 쓴적도 있고, RuntimeException에 대해서는 말할 필요도 없을테니까요.

성실한 누군가를 교육한다는건 정말 재밌는 일입니다. 기회가 되면 스크린캐스팅을 해서 올리고 싶은데, 실제 나가는 진도에 비해 공백이 너무 커서 스크랜캐스팅 편집도 해야 되는데 도무지 그럴 짬은 안나네요 ㅋ
top

Write a comment.


하이버네이트 3.3.0 GA 릴리즈~

Hibernate/etc : 2008.08.16 11:04


http://in.relation.to/Bloggers/HibernateCore330GoesGA

주요 변경 사항
1. 빌드를 Maven기반으로 변경.
2. 여러 모듈 jar로 세분화 함.
3. 2차 캐시 SPI 재설계.
4. JBossCache 2.x를 2차 캐시 프로바이더로 통합.

http://www.infoq.com/news/2008/08/hibernate-33

대충 읽었는데, OSGi가 유명해 지면서, OSGi에서 발생하는 하이버네이트 문제들(SessionFactory의 동적인 변경, 클래스로딩 이슈)에 대해 알고는 있는데, 이번 배포판에서는 아직 해결이 안 됐다고 합니다. 하지만, 적극적으로 해당 이슈들을 모두 해결할 의사는 있다고 합니다. 시간 문제라는거죠. 캬~ 좋아 좋아.
top

Write a comment.


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

Write a comment.


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

  1. Favicon of https://ecogeo.tistory.com BlogIcon 에코지오 2008.04.30 12:23 신고 PERM. MOD/DEL REPLY

    상세한 설명 감사드립니다.

    스프링 샘플 소스 보다가 '엥. 이거 뭐냐. 내가 모르는 또다른 비밀이 있나' 싶어서 최범균님 책을 다시 읽어봐도 이유를 잘 몰랐었는데, 기선님이 명쾌하게 상황정리(?)해 주셨네요.

    근데 스프링 샘플에서 저런 코드가 있으니 많은 개발자들이 c&p하면서 오용된 코드를 양산할 가능성이 있겠는걸요.

    "merge는 merge일뿐 오용하지 말자." ^^

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.04.30 13:35 PERM MOD/DEL

    넹 잘 읽어주셔서 감사합니다. 상당히 글이 길어져서 누가 읽을까 싶었는데 쓴 보람이 있네요. :)

Write a comment.


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

Write a comment.


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

Write a comment.


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

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

    잘 봤습니다.
    락모드에 대해서는 공부하지 않아서 좀 이해하지 못한 부분이 있는데..
    UPGRADE와 UPGRADE_NOWAIT 는 객체를 가져오는 시점에서 락을 걸어버리는 pessimitic-lock 이고 그 외의 모드들은 optimistic-lock 과 관련된 부분인가요?

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

    네. 그렇습니다.

    해당 row에 접근할 때 롹을 하는 pessimistic lock을 사용하는 것이 UPGRADE와 UPGRADE_NOWAIT이구요.

    나머지 중에서 NONE은 optimistic한 동시성 처리를 하지 않겠다는 것입니다. 이 말은 다시 말하자면, first-win이 아니라 last-win 전략을 사용하겠다는 것이고, 사용자에게 불편함을 끼칠 우려가 많은 전략입니다.

    그래서 READ를 사용해서 데이터의 변경사항을 DB에 반영하기 전에 혹시 그 변경사항과 관련된 객체들에 대응하는 레코드가 이미 다른 트랜잭션에 의해 변경되지 않았는지 확인해서 first-win 전략을 사용할 수 있습니다.(automatic optimistic locking)

    그런데, READ와 같이 데이터를 먼저 쓰는 시점에 우선권을 주는것이 아니라 데이터에 먼저 접근하는 트랜잭션에게 우선권을 주는 pessimistic locking을 하려면 UPGRADE와 UPGRADE_NOWIAT을 사용할 수 있는 것 입니다. 그런데 이 두 개의 롹 모드는 사용하는 DB에서 SELECT ... FOR UPDATE를 지원할 때만 그렇게 동작이 되고, pessimistic locking을 지원하지 않는 DB에다가 대고 위와 같은 락모드를 설정하면 결국 READ와 동일하게 동작합니다.

    따라서 락모드를 설정하는 두 가지 방법이 있는데, optimistic locking을 할 때는 session.lock()을 사용하고, pessimistic locking을 사용할 때는 session.load()나 get()을 할 때 롹모드를 주는 것이 직관적이고 이해하기 쉬운 코드가 될 것으로 생각합니다.

Write a comment.


낙천적인 동시접근 제어

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

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

    저도 빨리 애노테이션으로 작업하고 싶어요~

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

    네.ㅋㅋ 애노테이션이 편해요.

Write a comment.


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

  1. Favicon of http://bumjin.egloos.com BlogIcon 지니랜드 2008.03.28 14:31 PERM. MOD/DEL REPLY

    그림이 저만 깨져보이는건가요?

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.03.28 19:29 PERM MOD/DEL

    수정했습니다. :)

  2. Favicon of http://bumjin.egloos.com BlogIcon 지니랜드 2008.03.28 21:00 PERM. MOD/DEL REPLY

    네 잘보입니다. ^^;

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

    넹. 좋은 주말 되세요. :)

Write a comment.


동시접근 제어하기

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

Write a comment.


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

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

Write a comment.


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

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

Write a comment.


트랜잭션 기초

Hibernate/Chapter 10 : 2008.03.27 07:49


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

Write a comment.


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

  1. seeyoung 2008.03.22 21:23 PERM. MOD/DEL REPLY

    지난번 조언 감사합니다. 이번에는 하이버네이트 활용 사이트들을 찾고 있습니다.

    hibernate을 성공적으로 활용한 커머스(쇼핑몰) 사이트 또는 규모가 큰 프로젝트에서 활용한 사이트를 알고 있다면, 알려주시면 감사하겠습니다. 이곳은 자주 찾고 있어서 reply로 남기셔도 되구요. seeyoungATgmail.com으로 메일 주셔도 됩니다.

    이번에도 좋은 답변을 기대하겠습니다.

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

    http://www.hibernate.org/113.html

    여기보시면 잘 나와있습니다.

  2. seeyoung 2008.03.24 10:46 PERM. MOD/DEL REPLY

    국내에서 hibernate 사용하는 site의 정보는 없을까요?

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

    글쎄요.

Write a comment.


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

Write a comment.




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