MyBatis는 ORM이 아니다.

구 iBatis, 현재 MyBatis는 ORM(Object Relational Mapping이 아니다.

http://en.wikipedia.org/wiki/Object-relational_mapping

http://www.mybatis.org/

이 두개 링크만 봐도 알 수 있지만, MyBatis 홈페이지에도 ORM이라는 말은 전혀 나오지 않는다. 그런데도 일부 MyBatis와 JPA 류를 같은 ORM이 아닌가 혼동하는 분들이 있다.

ORM은 간단하게 말해서 Object와 Relataion 간의 불일치 문제(단위, 상속, 데이터 타입)등등 여기 나와있는 문제를 해결해주는 도구다.

MyBatis가 저기 나열된 문제 중에 뭘 해결해주는가? 없다. ORM이 아니다. 그냥 자기 홈피에 써있는데로 단순한 데이터 맵퍼일 뿐이다.

[하이버네이트 N+1 Select] Batch Patching으로 해결하기

간단하다. Member -> C 연관 관계 위에다가 @BatchSize라는 애노테이션을 붙여주면 된다. 애노테이션의 값으로는 해당 컬렉션 몇 개를 동시에 읽어올지 설정하면 된다.

[java]

@OneToMany(mappedBy = "owner")
@BatchSize(size=30)
private Set<Item> items;

[/java]

이렇게 설정해 두면 Member를 순회하면서 Item 컬렉션에 접근할 때 개별적으로 Item 컬렉션을 select 하는게 아니라 첫번째 Member의 Item 컬렉션에 접근할 때 미리 배치 사이즈 만큼의 컬렉션을 같이 가져오게 된다. 만약에 배치 사이즈를 30으로 했다면 n/30+1 로 문제 크기가 줄어든다. 실제로는 그렇게 간단한 로직은 아니였던 것 같은 기억이 난다. 하이버네이트 책에 보면 잘 설명되어 있지만 대충 저정도 쿼리가 발생하니까 그냥 저렇게 알고 있어도 될 것 같다.

이렇게 말이다. 테스트 데이타로 member 객체 100명을 만들어 놨으니 이전 같으면 100 + 1 = 101 번의 select 가 날아갔을 터인데.. 이번에는 4 + 1 번밖에 안날아 갔다.

하지만 이 방법은 필요없는 컬렉션까지도 미리 패치해오는 경우가 발생할 수 있으니 최선책은 아니라고 볼 수 있다. 물론 위와 같이 모든 Member 객체를 순회하며 모든 컬렉션에 접근하는 경우라면 이렇게 사용해도 괜찮겠지만 말이다… 그래서 이 방법 말고도 다른 해결책이 또 있다.

그건 다음에~


[하이버네이트 성능 튜닝] 패칭 전략

참조: http://www.javadev.org/files/Hibernate%20Performance%20Tuning.pdf

하이버네이트에서 패칭 전략은 애플리케이션에서 어떤 객체와 연관된 객체를 가져오는 방법이다. 즉 A->B 또는 A->Collection 이런 관계가 있을 때 A와 연관되어 있는 B 또는 Collection를 가져오는 방법이다. 쉽게 생각해보면 A를 가져갈때 B나 C도 같이 가져가는 방법이 있고, A를 가져갈땐 A만 가져가고 B는 나중에 a.b라고 접근하는 순간에 가져갈 수 있겠다. 그런데 이렇게 단순하지 많은 않다.

  • Join fetching: 연관된 인스턴스나 콜렉션을 동일한 SELECT 절에서 OUTER JOIN으로 가져온다.
  • Select fetching: 연관된 인스턴스나 콜렉션을 부가적인 SELECT 절을 사용해서 가져오기. lazy=”false”라고 명시하지 않는한 이 부가적인 쿼리는 실제 해당 컬렉션에 접근할 때 발생한다.
  • Subselect fetching: 이전 쿼리나 패치에서 가져온 모든 엔티티에 연관된 인스턴스나 콜렉션을 부가적인 SELECT 절을 사용해서 가져오기. lazy=”false”라고 명시하지 않는한 이 부가적인 쿼리는 실제 해당 컬렉션에 접근할 때 발생한다.
  • Batch fetching: select fetching의 최적화 전략. 하이버네이트는 인스턴스나 컬렉션을 주키나 외래키 목록을 사용해 하나의 SELECT 절로 묶어서 가져온다.

패칭 전략을 이렇게 나누기도 한다.

  • Immediate fetching: owner 쪽을 로딩할 때 컬렉션이나 속성을 그 즉시 가져온다.
  • Lazy collection fetching: 해당 컬렉션에 접근할 때 가져온다.(이게 컬렉션의 기본 전략)
  • “Extra-lazy” collection fetching: 컬렉션의 개별 요소에 접근할 때 데이터베이스에 접근한다. 컬렉션을 전부다 메모리로 가져오지 않는 방법인데 컬렉션이 매우 클때 적합하다.
  • Proxy fetching: 단일 값 관계에 있는 인스턴스는 id가 아닌 다른 속성에 접근할 때 가져온다.
  • “No-proxy” fetching: 인스턴스 변수에 접근하면 바로 가져온다. Proxy fetching에 비해 덜 lazy한 방식이지만 프록시를 사용하지 않으니 더 깔끔하다. 이 방법을 사용하려면 빌드시 바이트코드 조작을 해야 하다. 이 방법은 거의 사용하지 않는다.
  • Lazy attribute fetching: 어떤 속성이나 단일 값 관계에 접근할 때 인스턴스 변수를 접근한다. 이 방법도 빌드시 바이트코드 조작을 해야 하며 거의 사용하지 않는다.


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

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(); 
저렇게 변경하고 아무것도 실행하지 않는다. 왜일까? 퀴즈닷.

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

배치 작업이라는 것이 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 해야 합니다. 반대로 하면 음식 담긴 쟁반을 서빙도 안하고 설것이 해버리는거나 마찬가지..