[하이버네이트] 빌드 타임 인스트루먼트로 OneToOne Lazy Fetching하기

어제 작성한 글에서 이어집니다. 그 해결책 세개 중에서 그나마 쓸만한 해결책은 이것이고, 그 효과도 똑같고, 그 단점도 똑같은 방법입니다.

@OneToOne 맵핑에 @LazyToOne(LazyToOneOption.NO_PROXY)를 추가합니다.

[java]
@Entity
public class Product {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private BigDecimal price;

private String name;

@OneToOne(fetch = FetchType.LAZY, optional = true, mappedBy = "product")
@LazyToOne(LazyToOneOption.NO_PROXY)
private ProductDetails productDetails;

@OneToOne(fetch = FetchType.LAZY, optional = true, mappedBy = "product")
@LazyToOne(LazyToOneOption.NO_PROXY)
private ProductInfo productInfo;

}
[/java]

이렇게요. 그리고 빌드 타임에 코드를 조작하도록 메이븐 플러그인을 설정합니다.
[xml]
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>process-classes</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
<configuration>
<tasks>
<taskdef name="instrument"
classname="org.hibernate.tool.instrument.javassist.InstrumentTask">
<classpath>
<path refid="maven.runtime.classpath" />
<path refid="maven.plugin.classpath" />
</classpath>
</taskdef>
<instrument verbose="false">
<fileset dir="${project.build.outputDirectory}">
<include name="/usecase/snapshot/domain/*.class" />
</fileset>
</instrument>
</tasks>
</configuration>
</plugin>
[/xml]

여기서 include에 사용한 경로는 자신의 프로젝트에 맞게 잘 고쳐야겠죠.

그리고 빌드를 합니다. antrun의 run 골을 실행하는게 주요 목적이겠죠.

[java]
//Test
Product product = productRepository.findOne(1l);
assertThat(product, is(notNullValue()));
assertThat(product.getName(), is("keesun"));

System.out.println("========LAZY LOADING…=========");
product.getProductDetails().getDetails();
[/java]

이제 다시 테스트를 실행하면 다음과 같은 로그를 볼 수 있습니다.

18:51:54.224 [main] DEBUG org.hibernate.loader.Loader – loading entity: [usecase.snapshot.domain.Product#1]
18:51:54.226 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
18:51:54.227 [main] DEBUG org.hibernate.SQL –
select
product0_.id as id1_0_,
product0_.name as name1_0_,
product0_.price as price1_0_
from
Product product0_
where
product0_.id=?
Hibernate:
select
product0_.id as id1_0_,
product0_.name as name1_0_,
product0_.price as price1_0_
from
Product product0_
where
product0_.id=?
18:51:54.231 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to open ResultSet (open ResultSets: 0, globally: 0)
18:51:54.231 [main] DEBUG org.hibernate.loader.Loader – result row: EntityKey[usecase.snapshot.domain.Product#1]
18:51:54.238 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to close ResultSet (open ResultSets: 1, globally: 1)
18:51:54.238 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
18:51:54.239 [main] DEBUG org.hibernate.engine.TwoPhaseLoad – resolving associations for [usecase.snapshot.domain.Product#1]
18:51:54.244 [main] DEBUG org.hibernate.engine.TwoPhaseLoad – done materializing entity [usecase.snapshot.domain.Product#1]
18:51:54.245 [main] DEBUG o.h.e.StatefulPersistenceContext – initializing non-lazy collections
18:51:54.245 [main] DEBUG org.hibernate.loader.Loader – done entity load
========LAZY LOADING…=========
18:51:54.246 [main] DEBUG org.hibernate.loader.Loader – loading entity: [usecase.snapshot.domain.ProductDetails#1]
18:51:54.247 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
18:51:54.247 [main] DEBUG org.hibernate.SQL –
    select
productdet0_.id as id2_0_,
productdet0_.details as details2_0_,
productdet0_.product_id as product3_2_0_
from
ProductDetails productdet0_
where
productdet0_.product_id=?

Hibernate:
select
productdet0_.id as id2_0_,
productdet0_.details as details2_0_,
productdet0_.product_id as product3_2_0_
from
ProductDetails productdet0_
where
productdet0_.product_id=?
18:51:54.249 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to open ResultSet (open ResultSets: 0, globally: 0)
18:51:54.250 [main] DEBUG org.hibernate.loader.Loader – result row: EntityKey[usecase.snapshot.domain.ProductDetails#1]
18:51:54.251 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to close ResultSet (open ResultSets: 1, globally: 1)
18:51:54.251 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
18:51:54.251 [main] DEBUG org.hibernate.engine.TwoPhaseLoad – resolving associations for [usecase.snapshot.domain.ProductDetails#1]
18:51:54.252 [main] DEBUG org.hibernate.engine.TwoPhaseLoad – done materializing entity [usecase.snapshot.domain.ProductDetails#1]
18:51:54.253 [main] DEBUG o.h.e.StatefulPersistenceContext – initializing non-lazy collections
18:51:54.253 [main] DEBUG org.hibernate.loader.Loader – done entity load
18:51:54.253 [main] DEBUG org.hibernate.loader.Loader – loading entity: [usecase.snapshot.domain.ProductInfo#1]
18:51:54.254 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
18:51:54.254 [main] DEBUG org.hibernate.SQL –
    select
productinf0_.id as id0_0_,
productinf0_.info as info0_0_,
productinf0_.product_id as product3_0_0_
from
ProductInfo productinf0_
where
productinf0_.product_id=?

Hibernate:
select
productinf0_.id as id0_0_,
productinf0_.info as info0_0_,
productinf0_.product_id as product3_0_0_
from
ProductInfo productinf0_
where
productinf0_.product_id=?
18:51:54.256 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to open ResultSet (open ResultSets: 0, globally: 0)
18:51:54.256 [main] DEBUG org.hibernate.loader.Loader – result row: EntityKey[usecase.snapshot.domain.ProductInfo#1]

ㅎㅎ충격적이지 않나요? 일단 Lazy Fetching이 되긴 했습니다. 그런데 문제는 OneToOne 관계이면서, 아직 로딩하지 않았던 다른 객체도 같이 가져왔네요? 이게 바로 제가 OneToOne Lazy Fetching을 파보게 된 계기입니다. ㅎㅎ;;

이것 때문에 동료분과 하이버네이트와 javassist 소스 코드를 까보았는데… 아주… 그냥.. 죽여주네요. OP코드도 필드 추가하고, 세터 게터 메서드 추가하고, 하이버 Session 가지고 initialize도 시키고.. @_@;;; 아오.. 이 방법 밖에 없는것인가? 필드를 개별적으로 Lazy Fetching 할 순 없는 것인가?….

[하이버네이트] OneToOne 연관 관계 Lazy Fetching이 안 먹어!?

OneToOne 관계를 맵핑 했을 때, Lazy Fetching이 제대로 적용되는 경우가 있고, 그렇지 않은 경우가 있다. 우선 제대로 동작하는 경우부터 볼까나…

Product 1 –> 1 ProductDetails

Product 1 –> 1 ProductInfo

이렇게 OneToOne 관계가 두 개 있다고 가정하고, 다음과 같이 맵핑했다.

[java]
@Entity
public class Product {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private BigDecimal price;

private String name;

@OneToOne(fetch = FetchType.LAZY)
private ProductDetails productDetails;

@OneToOne(fetch = FetchType.LAZY)
private ProductInfo productInfo;

//나머지 생략

}

@Entity
public class ProductDetails {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String details;

}

@Entity
public class ProductInfo {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String info;

}
[/java]

각 엔티티에 선언한 속성이 의미가 있는건 아니니까 주의깊게 살펴보지 마시고, 연관 관계를 유심히 살펴보는게 좋겠다. 단방향 관계다. 그리고 FetchMode.LAZY다.

Repository는 Spring-Data-JPA 1.0.1.RELEASE를 사용해서 만들었고, 테스트 코드는 다음과 같다.

[java]
DatabaseOperation.CLEAN_INSERT.execute(connection, dataSet);

//Test
Product product = productRepository.findOne(1l);
assertThat(product, is(notNullValue()));
assertThat(product.getName(), is("keesun"));

System.out.println("========LAZY LOADING…=========");
product.getProductDetails().getDetails();
[/java]

이보다 더 많은 테스트 코드가 있지만, 나머지는 DBUnit으로 데이터를 넣는 부분과 스프링 TestContext 설정이라서 생략했다. 테스트 데이터를 DBUnit으로 넣지 않고, JDBC로 넣어도 된다. JPA만 아니면 된다. 왜 그래야 하냐면… 흠… 갑자기 설명하기가 귀찮네;; 퀴즈로 남기자. 왜 그래야 할까요?

아무튼, 다시 돌아가서… 저 테스트를 실행했을 때 콘솔을 살펴보자.

Hibernate:
select
product0_.id as id0_0_,
product0_.name as name0_0_,
product0_.price as price0_0_,
product0_.productDetails_id as productD4_0_0_,
product0_.productInfo_id as productI5_0_0_
from
Product product0_
where
product0_.id=?
18:24:53.609 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to open ResultSet (open ResultSets: 0, globally: 0)
18:24:53.609 [main] DEBUG org.hibernate.loader.Loader – result row: EntityKey[usecase.snapshot.domain.Product#1]
18:24:53.615 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to close ResultSet (open ResultSets: 1, globally: 1)
18:24:53.616 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
18:24:53.618 [main] DEBUG org.hibernate.engine.TwoPhaseLoad – resolving associations for [usecase.snapshot.domain.Product#1]
18:24:53.621 [main] DEBUG org.hibernate.engine.TwoPhaseLoad – done materializing entity [usecase.snapshot.domain.Product#1]
18:24:53.622 [main] DEBUG o.h.e.StatefulPersistenceContext – initializing non-lazy collections
18:24:53.623 [main] DEBUG org.hibernate.loader.Loader – done entity load
========LAZY LOADING…=========
18:24:53.623 [main] DEBUG org.hibernate.impl.SessionImpl – initializing proxy: [usecase.snapshot.domain.ProductDetails#1]
18:24:53.624 [main] DEBUG org.hibernate.loader.Loader – loading entity: [usecase.snapshot.domain.ProductDetails#1]
18:24:53.624 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
18:24:53.625 [main] DEBUG org.hibernate.SQL –
select
productdet0_.id as id1_0_,
productdet0_.details as details1_0_
from
ProductDetails productdet0_
where
productdet0_.id=?

됐다. Lazy Loading이 되었다!! 그럼 이걸 이제 양방향 관계로 바꿔보면 어떻게 될까?

우선 맵핑을 다음과 같이 변경하자.

[java]
@Entity
public class Product {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private BigDecimal price;

private String name;

@OneToOne(fetch = FetchType.LAZY, <strong>mappedBy = "product"</strong>)
private ProductDetails productDetails;

@OneToOne(fetch = FetchType.LAZY, <strong>mappedBy = "product"</strong>)
private ProductInfo productInfo;

}

@Entity
public class ProductDetails {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String details;

<strong>    @OneToOne(fetch = FetchType.LAZY)
private Product product;</strong>

}

@Entity
public class ProductInfo {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String info;

<strong>@OneToOne(fetch = FetchType.LAZY)
private Product product;</strong>

}
[/java]

나는 사실 이 부분이 조금 의문이다. 왜 양방향이어야 할까… ProductInfo와 ProductDetails를 개별적으로 사용하게 될 일이 있을까? 그러다가 ProductInfo –> Product 방향으로 참조할 일이 있을까? 이런 의문을 가지기 시작하면 ProductInfo와 ProductDetails에 id가 있는 것도 의문이 간다. 이거 엔티티가 맞는건가? Value Object는 아닌가? 뭐 내가 모델링한것도 아니고 도메인 전문가도 아니기 때문에 그렇다치고 넘어가자.

이제 다시 테스트 해보자.

18:35:37.430 [main] DEBUG org.hibernate.SQL –
select
product0_.id as id0_0_,
product0_.name as name0_0_,
product0_.price as price0_0_
from
Product product0_
where
product0_.id=?
Hibernate:
select
product0_.id as id0_0_,
product0_.name as name0_0_,
product0_.price as price0_0_
from
Product product0_
where
product0_.id=?
18:35:37.433 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to open ResultSet (open ResultSets: 0, globally: 0)
18:35:37.435 [main] DEBUG org.hibernate.loader.Loader – result row: EntityKey[usecase.snapshot.domain.Product#1]
18:35:37.441 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to close ResultSet (open ResultSets: 1, globally: 1)
18:35:37.441 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
18:35:37.443 [main] DEBUG org.hibernate.engine.TwoPhaseLoad – resolving associations for [usecase.snapshot.domain.Product#1]
18:35:37.445 [main] DEBUG org.hibernate.loader.Loader – loading entity: [usecase.snapshot.domain.ProductDetails#1]
18:35:37.446 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
18:35:37.446 [main] DEBUG org.hibernate.SQL –
select
productdet0_.id as id1_0_,
productdet0_.details as details1_0_,
productdet0_.product_id as product3_1_0_
from
ProductDetails productdet0_
where
productdet0_.product_id=?
Hibernate:
select
productdet0_.id as id1_0_,
productdet0_.details as details1_0_,
productdet0_.product_id as product3_1_0_
from
ProductDetails productdet0_
where
productdet0_.product_id=?
18:35:37.449 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to open ResultSet (open ResultSets: 0, globally: 0)
18:35:37.450 [main] DEBUG org.hibernate.loader.Loader – result row: EntityKey[usecase.snapshot.domain.ProductDetails#1]
18:35:37.451 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to close ResultSet (open ResultSets: 1, globally: 1)
18:35:37.451 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
18:35:37.451 [main] DEBUG org.hibernate.engine.TwoPhaseLoad – resolving associations for [usecase.snapshot.domain.ProductDetails#1]
18:35:37.453 [main] DEBUG org.hibernate.engine.TwoPhaseLoad – done materializing entity [usecase.snapshot.domain.ProductDetails#1]
18:35:37.454 [main] DEBUG org.hibernate.loader.Loader – done entity load
18:35:37.455 [main] DEBUG org.hibernate.loader.Loader – loading entity: [usecase.snapshot.domain.ProductInfo#1]
18:35:37.455 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
18:35:37.455 [main] DEBUG org.hibernate.SQL –
select
productinf0_.id as id2_0_,
productinf0_.info as info2_0_,
productinf0_.product_id as product3_2_0_
from
ProductInfo productinf0_
where
productinf0_.product_id=?
Hibernate:
select
productinf0_.id as id2_0_,
productinf0_.info as info2_0_,
productinf0_.product_id as product3_2_0_
from
ProductInfo productinf0_
where
productinf0_.product_id=?
18:35:37.460 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to open ResultSet (open ResultSets: 0, globally: 0)
18:35:37.460 [main] DEBUG org.hibernate.loader.Loader – result row: EntityKey[usecase.snapshot.domain.ProductInfo#1]
18:35:37.461 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to close ResultSet (open ResultSets: 1, globally: 1)
18:35:37.461 [main] DEBUG org.hibernate.jdbc.AbstractBatcher – about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
18:35:37.462 [main] DEBUG org.hibernate.engine.TwoPhaseLoad – resolving associations for [usecase.snapshot.domain.ProductInfo#1]
18:35:37.462 [main] DEBUG org.hibernate.engine.TwoPhaseLoad – done materializing entity [usecase.snapshot.domain.ProductInfo#1]
18:35:37.462 [main] DEBUG org.hibernate.loader.Loader – done entity load
18:35:37.462 [main] DEBUG org.hibernate.engine.TwoPhaseLoad – done materializing entity [usecase.snapshot.domain.Product#1]
18:35:37.463 [main] DEBUG o.h.e.StatefulPersistenceContext – initializing non-lazy collections
18:35:37.463 [main] DEBUG org.hibernate.loader.Loader – done entity load
========LAZY LOADING…=========
18:35:37.464 [main] DEBUG o.s.t.c.t.TransactionalTestExecutionListener – No method-level @Rollback override: using default rollback [true] for test context [[TestContext@2c84d9 testClass = SnapshotTest, testInstance = usecase.snapshot.SnapshotTest@c5c3ac, testMethod = di@SnapshotTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@1b16e52 testClass = SnapshotTest, locations = ‘{classpath:/applicationContext.xml}’, classes = ‘{}’, activeProfiles = ‘{}’, contextLoader = org.springframework.test.context.support.GenericXmlContextLoader@1c1ea29]]]
18:35:37.466 [main] DEBUG o.s.orm.jpa.JpaTransactionManager – Initiating transaction rollback
18:35:37.466 [main] DEBUG o.s.orm.jpa.JpaTransactionManager – Rolling back JPA transaction on EntityManager [org.hibernate.ejb.EntityManagerImpl@514f7f]

 

이게 뭐시얏. Lazy Fetching이 안 된다. 사실, 나도 왜 단방향일 때는 되고, 양방향일 때는 안 되는지 모르겠다.

http://community.jboss.org/wiki/SomeExplanationsOnLazyLoadingone-to-one

실마리가 될만한 글은 찾았지만… 단방향 일때와 양방향 일때의 생기는 테이블 스키마가 다른것과 뭔가 연관이 있지 않을까 싶다.

흠;; 생각해볼까? 단방향 일때는 프록시를 사용할 수 있다. 이때의 스키마는 Product 테이블에 ProductDetail_Id와 ProductInfo_Id 컬럼이 생긴다. 그리고 Product만 읽어올 때는 ProductDetail_Id와 ProductInfo_Id 컬럼의 값으로 Product.getProductDetail()과 Product.getProductInfo()의 프록시 객체로 채울 수 있겠다. 위 글에서는 optional 여부와 관계지어 설명하지만 optional 여부와는 관계 없다.

양방향일 때는 스키마가 좀 다르다. Product에 있던 연관 관계 컬럼은 사라지고, ProductDetail 테이블과 ProductInfo 테이블에 각각 product_id 컬럼이 생긴다. 이때 Product를 읽어올 때 productDetail과 productInfo 객체에 필요한 정보를 각각 DB에서 읽어온다. 첫번째 쿼리를 보면 이렇다.

select
product0_.id as id3_0_,
product0_.name as name3_0_,
product0_.price as price3_0_
from
Product product0_
where
product0_.id=?

 

이것만 날아간다는 것은… productInfo와 productDetail이 null인지 아닌지 Product 테이블만 보고서는 알 수 없다는게 된다. 그럼 결국 Product가 참조할 ProductInfo와 ProductDetail의 프록시 객체를 만들어야되는가 아닌가가 고민되게 된다. 실제로는 null일수도 있는데 무조건 프록시 객체를 만들어 넣어버리면 null이 안되니깐;; 그렇다고 optional 속성을 false로 해서 NOT NULL로 하면 Proxy 객체를 만들 수 있지 않을까? 확인해 봤지만.. optional 속성의 값은 결과에 아무런 영향을 주지 않았다;

어찌됐든… 이 문제를 우회하는 방법은 세가지 정도 된다.

  1. 빌드 타임 인스트루먼트
  2. FieldHandler
  3. @OneToMany로 변경

각각은 다음에 살펴보겠다.

[하이버네이트 3.5] Fetch Profile

참조: http://blog.frankel.ch/hibernate-hard-facts-part-6

기본으로 Xxx-To-Many 관계에서 Many 쪽 연관에 별다른 설정을 하지 않으면 lazy loading이 적요됩니다. 즉..

[java]
class Member {
private Set<Role>;
roles;

}
[/java]

이런 코드가 있을 때 만약 하이버네이트로 Member를 읽어오면 Member의 Roles까지 읽어오진 않습니다. 그러다 세션이 열려있는 상태에서 getRoles()를 호출하는 등으로 필요해지는 순간이 오면 그때 DB에 쿼리를 보내서 가져오게 되죠. 이렇게 필요해지면 가져오는걸 lazy loading이라고 하고 이렇게 하는 이유는 굳이 당장 필요없는 객체로 메모리를 잡아먹을 필요도 없기 때문입니다.

그런데 Roles를 Member 객체를 가져온 뒤에 자주 참조한다면 애초에 Member를 가져올 때 미리 가져다 놓고 캐싱을 해버리는게 더 좋습니다. 그럴때 사용하는 기능이 하이버네이트의 Fetch라는 것인데 FetchMode라고 해서 도메인 클래스에 애노테이션으로 또는 XML로 매핑을 할 때 이 모드를 설정해 둘 수 있습니다. LAZY는 위에서 설명했고 EAGER가 바로 LAZY와는 반대로 Member를 가져갈때 Roles도 전부 가져가라는 뜻이 됩니다.

그런데 이런 설정을 매핑에서만 설정할 수 있는게 아니라 Criteria나 HQL을 만들 때도 설정할 수 있죠. Critera.setFatchMode()를 사용하거나 FETCH JOIN이라는 HQL을 사용하면 됩니다. 그런데.. 매번 쿼리를 만들 때마다 이렇게 FetchMode를 설정하는게 귀찮을 수 있으니 하이버네이트 3.5에서는 미리 특정 관계에 대한 FetchMode를 정의해 놓고(펫치 프로파일) 쿼리를 만들 시점에 해당 그 팻치 프로파일을 가져다 쓸 수 있는 기능을 제공합니다.

[java]
@Entity
@FetchProfile(name = "customer-with-orders", fetchOverrides = {
@FetchProfile.FetchOverride(entity = Customer.class, association = "orders", mode = FetchMode.JOIN)
})
public class Customer {
@Id
@GeneratedValue
private long id;

private String name;

private long customerNumber;

@OneToMany
private Set<Order> orders;

// standard getter/setter

}
[/java]

from: http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#d0e3524

이렇게 orders는 기본으로 lazy loading이 적용되서 사실상 FetchMode=Lazy로 설정한 것과 동일하지만.. 쿼리를 만들 때 @FetchProfile에 정의한 걸 적용해서 마치 Eager로 설정한 것처럼 orders를 미리 읽어가는 쿼리를 만들 수도 있습니다.

[java]
Session session = …;
session.enableFetchProfile( "customer-with-orders" );  // name matches @FetchProfile name
Customer customer = (Customer) session.get( Customer.class, customerId );
session.disableFetchProfile( "customer-with-orders" ); // or just close the session

[/java]

from: http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#d0e3524

흠.. 이걸 처음 보고. 어차피 fetchMode 설정하는게도 한줄 필요하고 저기서도 fetch profile 때문에 한줄 필요하면 똑같은거 아닌가 생각했지만 fetchMode를 조정할 곳이 한군대로 축소된다는 장점이 있더군요. 여러군데에서 fetchMode를 조작하는 코드가 있다면 fetch profile을 써먹으면 괜찮겠네요.

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