Whiteship's Note


[하이버네이트 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 버전 설명에 보면, 다이어그램까지 만들어 주는데.. 좀.. 멋진 툴인 듯 합니다.

01


문제는 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.


[하이버네이트 퀴즈] 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.


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