error.log
javax.persistence.PersistenceException: org.hibernate.PropertyAccessException: Null value was assigned to a property of primitive type setter of com.comas.solme.framework.servicelib.board.Board.orderNo
    org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1389)
    org.hibernate.ejb.AbstractEntityManagerImpl.find(AbstractEntityManagerImpl.java:802)
    org.hibernate.ejb.AbstractEntityManagerImpl.find(AbstractEntityManagerImpl.java:756)

위와 같은 오류는 null 일 수 없는 컬럼을 가진 레코드가 있을 경우 발생한다.

대개, 이미 레코드가 존재하는 상황에서 boolean 등의 속성이 추가로 생성되어(컬럼이 생성되어) 기존 데이터 값을 null 로 초기화 하므로 발생한다.

이럴 경우에는 해당 객체의 속성에 해당하는 컬럼의 값을 적절한 값으로 초기화 하는 작업을 해줘야한다.

위 오류와 같은 경우에는 다음과 같이 해결할 수 있다.

fix.sql
update BOARD set ORDER_NO = 0 where ORDER_NO is null;
None  Edit Labels


특정 엔티티의 갯수가 많고, 그것을 한번에 처리하려고 하는 경우에 Query 나 Criteria 를 이용해 .list() 를 할 경우에, OutOfMemoryError(OOME) 가 나기 쉽상이다.

BadExample.java
Query q = getSession().createQuery("FROM "+Contents.class.getName())
                    .setReadOnly(true);
List<Contents> contentsList = q.list();
for (Contents contents : contentsList) {
    // TODO work here
};

이것을 처리하기 위해서는 scroll 을 이용하여 특정 데이터씩만 조회하여 처리한 후, 세션을 클리어해주면 된다.

GoodExample.java
ScrollableResults results = getSession().createQuery("FROM "+Contents.class.getName())
    .setReadOnly(true).setCacheable(false)
    .setCacheMode(CacheMode.IGNORE)
    .setFetchSize(10)
    .scroll(ScrollMode.FORWARD_ONLY);
while (results.next()) {
    Contents contents = (Contents)results.get()[0];
    // TODO work here  
    getSession().clear();
}

중요한 부분은 다음 포인트다.

  • list 대신 Cursor 를 이용하는 scroll 메서드를 이용한다.
  • Fetch 사이즈를 적절히 설정해서 한번에 처리할 레코드 수를 설정한다.
  • 작업 처리 후에는 Session 을 clear 한다.

이렇게 하면 메모리 부족 현상에서 해결된다.

하이버네이트를 혐오하는 사람들 중의 생각 하나가, "메모리에 다 올려놓으니깐 못 써먹는다" 인데 그럴리가 있니?

org.hibernate.HibernateException: Found shared references to a collection

이 오류는 특정 영속 객체에서 참조하고 있는 객체를 다른 영속 객체에 주입시킬 경우에 사용된다.

하이버네이트에서는 영속 객체에서 컬렉션 프로퍼티 같은 것들을 프락시 패턴을 이용하여 다르게 구현하고 있으므로, 이러한 프락시 객체에 대한 참조를 가져다가 쓰면 안된다.

Caused by: org.hibernate.HibernateException: Found shared references to a collection: com.vine.cm.contents.revision.Revision.metaDatas
    at org.hibernate.engine.Collections.processReachableCollection(Collections.java:186)
    at org.hibernate.event.def.FlushVisitor.processCollection(FlushVisitor.java:60)
    at org.hibernate.event.def.AbstractVisitor.processValue(AbstractVisitor.java:124)
    at org.hibernate.event.def.AbstractVisitor.processValue(AbstractVisitor.java:84)
    at org.hibernate.event.def.AbstractVisitor.processEntityPropertyValues(AbstractVisitor.java:78)
    at org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:161)
    at org.hibernate.event.def.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:219)
    at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:99)
    at org.hibernate.event.def.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:58)
    at org.hibernate.impl.SessionImpl.autoFlushIfRequired(SessionImpl.java:997)
    at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1590)
    at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:306)
    at org.springframework.orm.hibernate3.HibernateTemplate$36.doInHibernate(HibernateTemplate.java:1065)
    at org.springframework.orm.hibernate3.HibernateTemplate.doExecute(HibernateTemplate.java:419)
    ... 64 more

 예를 들어보자. 다음과 같은 영속 객체가 있고, 객체의 컬렉션 형태의 데이타를 다른 객체로 복사하려고 할 경우

public class Revision {
    ...
    private Map<Meta, string> metaDatas = new HashMap<Meta, string>();
    ...
}

public void testCopy() {
    // 영속 객체 가져오기
    Revision revision1 = dao.getRevision(1);
    Revision revision2 = dao.getRevision(2);
    copy(revision1, revision2);
}
 

다음과 같이 하면  org.hibernate.HibernateException: Found shared references to a collection: .. 와 같은 예외가 발생한다.

public copy(Revision revision1, Revision revision2) {
    revision2.setMetaDatas(revision1.getMetaDatas()); // ERROR

따라서 다음과 같은 방법으로 deep copy 를 해야한다.

public copy(Revision revision1, Revision revision2) {
    for(Map.Entry<Meta, String> entry : revision1.getMetaDatas.entrySet()) {
        revision2.getMetaDatas().put(entry.getKey(), entry.getValue());
    }
}

 

이 글은 스프링노트에서 작성되었습니다.

드디어 손에 넣었습니다. 오늘 새벽에 주문해서 당일 수령! 인터파크에게 감사합니다.
그동안 하이버네이트의 성능적인 부분에서 고생을 너무 많이 했었는데..OTL
오늘 오자마자 13장 페치와 캐싱 부분을 다 읽었습니다. 그동안 애매모호하고 비슷비슷하던 문제들이 모두 해결이 되는군요. 감격 ㅠ_ㅠ
proxy, no-proxy, lazy=ture, lazy=false, eager, select, join
나중에 야근 안하게 되면 한번 정리해서 올려봐야겠습니다.
지금 당장  n+1 문제와 카르테시안문제에 대해서 캐싱으로 해결할라고 그러고 있는데 내일 출근하자마자 다시 한번 최적화에 대해서 도전을 해봐야겠습니다.

3년전에 하이버네이트 원서가 나왔을 때 사서 보다가 빨랑빨랑 안익혀서 곧 뒤따라 나온 최범균님의 빨간 책에 물들어 있었는데. 역시 해답은 가빈킹님이시었군요.

이제 하이버네이트 가지고 실랑이 붙으면 말빨로 안 질겁니다. !!p(-_-)
내일 만납시다 ㅋㅋㅋ

  1. BlogIcon 기선 2010.07.02 17:48

    멋지십니다!!

hibernate 와 apache cxf 를 사용하려던 차에 asm 라이브러리 버젼 충돌이 발생했다.
원인은 hibernate 의 cglib 에서 사용하던 asm 라이브러리 버젼과 apache cxf 에서 사용하는 asm 버젼의 충돌이었다.

hibernate 3.3.2.GA 버젼에서의 pom.xml 을 보면

<!-- javassist is optional, but if defined it should be version 3.9.0 -->
<dependency>
    <groupId>javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.9.0.GA</version>
</dependency>

<!-- cglib is optional, but if defined it should be version 2.2 -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
<version>2.2</version>
 </dependency>

와 같이 정의되어있다. 
javaassist 는 옵션이지만 사용할거면 3.9.0 을 써라. cglib 또한 옵션이지만 사용한다면 2.2 를 써라.

이것의 해결방법은 hibernate 에서 사용하는  cglib 을 의존성이 없는 버젼. 즉 cglib-nodep 버젼을 사용하는 것이다.

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib-nodep</artifactId>
    <version>2.2</version>
</dependency>


하이버네이트에서 map 으로 연관된 데이타에 대하여 질의하는 방법이다.
하이버네이트 유저포럼에서 가빈킹을 비롯하여 전문가들이 잘 답변해준 좋은 글이다.

https://forums.hibernate.org/viewtopic.php?p=2256100&sid=a42bb1920e24eb0dd69158a5da7b2d6a

criteria 방식으로 테스트를 해보지는 않았지만, query 방식으로는 성공적으로 동작하였다.
하이버네이트 플러그인을 설치해보자

1. hibernate 플러그인은 jboss 에서 지원하며, 구글에서 "hibernate plugin" 으로 검색하여 쉽게 찾을 수 있다.
다운로드 페이지에 접속하여 릴리즈된 파일을 내려받는다.




2. 다운 받은 파일을 압축을 풀고, 이클립스 폴더에 복사해넣는다. 폴더명이 동일하기 때문에, 메세지를 띄우는데 물론 오버라이트 하면 된다.

3. 이클립스를 시작한다.

하이버네이트의 플러그인 대한 설명은 해당 사이트에서 별도로 레퍼런스 가이드를 제공하니, 해당 매뉴얼을 참조하면 된다.


머리가 나쁘긴 한가보다. 한번 경함한 에러가 아니었는데, 기억이 안나서 한동안 삽질 했다.
이제 별걸 다 기록해둬야겠다. 벌써 별게 아닌게 수두룩하긴 하다만;;

오래전에 가빈킹은 이런 경우 첨이라서 어쩌구 하는걸 구글링해서 봤다만, 만약 하이버네이트를 사용하고 오라클을 사용하고 있는데, 오류코드(ORA-01453:SET TRANSACTION 사용 시에는 트랜잭션의 최초문장 이어야 합니다) 가 나타났다면 다음 상황인지 아닌지 체크해볼만하다.

만하이버네이트는 ddl 자동 생성 기능이 있는데, 이걸 활성화시켰을 경우 ddl 코드 생성 후 트랜잭션을 시작하는 경우가 있다. 그러할 때에 이런 오류가 발생한다.

즉, 세션 팩토리 설정 중

<prop key="hibernate.hbm2ddl.auto">update</prop>

에서 update 를 validate 로 변경하면 문제없이 작동할 것이다. 하이버네이트가 좋긴 하다만, ddl 까지 맡기기엔 당신의 실력이 너무 좋지 않을까나? ^^

기참조 자체는 매우 쉽다.

그저 many-to-one 을 자기 클래스로 등록하기만 하면된다.

(참조 : 아래 코드에서 parentNode 속성)


그러나 다른 Table 을 통하여 many-to-many 관계를 형성하고 싶다면 주의할 점이 있다.


1. Set - Set 을 통한 자기참조

set 태그 중 한곳에만 inverse="true" 옵션을 지정해준다, cascade 는 지정하지 않아도 된다.


<class name="com.comas.proguide.category.CategoryNode" table="CATEGORY">

    ....

    <set name="linkCategory" table="LINK_CATEGORY">

        <key column="STAND_CATEGORY_SEQ" not-null="true" />

        <list-index column="LINK_INDEX" />

        <many-to-many column="LINK_CATEGORY_SEQ" class="com.comas.proguide.category.CategoryNode" />

    </set>

    <set name="refCategories" table="LINK_CATEGORY" inverse="true">

        <key column="LINK_CATEGORY_SEQ" not-null="true" />

        <many-to-many column="STAND_CATEGORY_SEQ" class="com.comas.proguide.category.CategoryNode" />

    </set>

</class>




2. List - Set 을 통한 자기참조

List 방식을 사용할 땐 더욱 주의해야한다. list 태그가 아닌 참조당하는 reference 객체에inverse="true" 옵션을 주고 list 태그는 cascade="save-update" 옵션을 걸어야한다.

list 태그에 inverse 옵션을 주게되면 list-index 값이 들어가지 않는다.


아래에 list-set 방식의 설정이 있다.


<class name="com.comas.proguide.category.CategoryNode" table="CATEGORY">

    <id name="seq" type="long" column="CATEGORY_SEQ">

        <generator class="native" />

    </id>

    <many-to-one name="parentNode" class="com.comas.proguide.category.CategoryNode" column="PARENT_SEQ" />

    <property name="name" type="string" column="NAME" />

    <property name="depth" type="int" column="DEPTH" />

    <property name="path" type="string" column="TREE_PATH" />

    <property name="sibling" type="int" column="SIBLING" />

            

    <list name="linkCategory" table="LINK_CATEGORY" cascade="save-update">

        <key column="STAND_CATEGORY_SEQ" not-null="true" />

        <list-index column="LINK_INDEX" />

        <many-to-many column="LINK_CATEGORY_SEQ" class="com.comas.proguide.category.CategoryNode" />

    </list>

    <set name="refCategories" table="LINK_CATEGORY" inverse="true">

        <key column="LINK_CATEGORY_SEQ" not-null="true" />

        <many-to-many column="STAND_CATEGORY_SEQ" class="com.comas.proguide.category.CategoryNode" />

    </set>

</class>



앞으로 레퍼런스나 책에 없는 설정들을 많이 적어봐야겠다.

외로운 하이버네이트 사용자들이여. Good Luck;

하이버네이트에서 Criteria 검색 방법을 사용하여 조인을 할 경우, 레코드가 많아지는것은 필연적이다. 따라서 이것을 distinct 하여 걸러내는 것이 필요한데, 여기에 페이징 기법을 적용하려면 오류가 발생한다.
그것에 대해서는 다음에 자세히 쓰도록 하고..
여튼, 그것에 대해서 이 친구가 명쾌한 답변을 주었다.

http://floledermann.blogspot.com/2007/10/solving-hibernate-criterias-distinct.html

Solving Hibernate Criteria's "Distinct Root Entity" limitation once and for all - including pagination

The Hibernate Criteria API, which I learned to at least partially like over the last months working on Maptales server code, has a few limitations which repeatedly make me wonder whether Hibernate really has been the right choice for a DB abstraction layer from time to time. It's not so much the limitations themselves, but rather the total lack of documentation of these limitations in the official Hibernate docs and examples and the generally rather arrogant tone on the Hibernate mailing Lists and FAQs — if you get an answer at all, that is — that annoy me and I guess a lot of other Hibernate users. The examples provided — and therefore copied blindly into nearly every Tutorial or Book about Hibernate out there — always avoid to show these limitations because of the triviality of the scenarios they cover. The purpose of a proper documentation would — for me — be to document especially such limitations and border cases where an API fails and provide solutions for these cases.

The problem is even amplified by the fact that googling for such limitiations rarely brings up a solution, since, as mentioned above, the hibernate documentation and inherited tutorials all do a good job in concealing the problems, and few coders out there seem to really use Hibernate in more sophisticated ways and publish their experiences. Which makes me wonder, because for these trivial cases too often good ol' SQL would have been perfectly sufficient — and advantageous to those lamer wannabee DB coders who would actually be forced to learn something about databases instead of replicating stupid Hibernate tutorial examples.

</rant>

OK now the specific limitation I want to talk about is: Hibernate does not return distinct results for a query with outer join fetching enabled for a collection. At least that's what they call it in the Hibernate advanced problems FAQ, failing to discuss this problem in deep and to give a general solution. Quote:

One day Hibernate might be smart enough to know that if you call setFirstResult() or setMaxResults() it should not use a join, but a second SQL SELECT. Try it, your version of Hibernate might already be smart enough. If not, write two queries, one for limiting stuff, the other for eager fetching.
And here they are again cutting of a large part of the problem. First, I checked an my version of hibernate is not "smart" enough, and it does not look like it will get any smarter soon. Second, the approach proposed there did not work for me when querying on properties of objects inside a collection - the collection has to always be JOINed for the query to work, therefore always producing results useless for pagination after transforming to distinct root entities. Generally, my problem was harder in a number of ways than the ususal examples:
  • I am nearly always using criterias on collection members, therefore I cannot revert to SELECT fetching.
  • I always have to use pagination (setFirstResult() and setMaxResults()), because in a web application it is not nice to return thousands of results on one page.
  • I am using PostGreSQL which adds a few oddities above it all (see below)
After digging through a lot of useless to semi-enlightening blog posts (thanks to Rick Hightower for providing two thirds of the solution) I could finally come up with the ultimate solution that works for me now:

First you have to accept that you have to live with 2 queries instead of one. You simply cannot do pagination and restrictions on collection members in hibernate in a single criteria query. Once you have accepted that fact, you can go ahead and construct your base query.

public List queryCats(Date queryDate) throws Exception {
Criteria criteria = getSession().createCriteria(Cat.class);
criteria.setFirstResult(offset).setMaxResults(num);
criteria.addOrder(Order.asc("age"));
if (queryDate != null) {
Criteria subCrit = criteria.createCriteria("kittens", "kitten");
subCrit.add(Expression.ge("birthDate", queryDate));
}

What you need to do then is to project the result to a collection of ids of the objects that match the query, eliminating duplicates. Here also the mentioned speciality of Postgres (at a second look it seems to be a general limitation) comes in: you have to include fields that are used for ordering in the projection clause. The nice thing is we can do the projection on demand and only if collection memebers are actually queried — this comes in handy for programatically constructed queries which can use the simple, single query way if collection properties are not used in the query:

  if (queryDate != null) {
// project result to distinct ids,
// including columns used for sorting
criteria.setProjection(Projections.distinct(
Projections.projectionList()
.add(Projections.id())
.add(Projections.property("birthDate"))
));
List list = criteria.list();

// unfortunately we have to copy the ids out of the
// resulting Object[] List
List idlist = new ArrayList<long>();

for (Iterator iditer = list.iterator(); iditer.hasNext();) {
Object[] record = (Object[]) iditer.next();
idlist.add((Long)record[0]);
}

// another Hibernate stupidity: empty Lists cause
// Expression.in to throw an error
if (idlist.size() > 0) {
criteria = getSession().createCriteria(Story.class);
criteria.add(Expression.in("id", idlist));
}
else {
return new ArrayList();
}
}

You can then transparently use the id-query in the complex case or issue the main query in the simple case:

  return criteria.list();
}

The presented approach has worked for me in a number of cases, and I am very happy now!
  1. peter 2008.04.15 22:50

    But if you use Expresion.in you can't be sure the order of Objects is the same as was in idList. Or is this documented?

프로퍼티명으로 실제 아이디가 아닌 id 라는 필드를 만들지 말것
예를 들면 Member.class 같은 경우, 실제 멤버일련번호를 생성하며, 사용자가 로그인시 사용하는 아이디를 만드는 경우가 많다.
그럴 때

private Integer no;
private String id;

에 대해 get/set 규칙을 활용을 활용할 경우 하이버네이트는 런타임 오류를 발생시킨다.
따라서 id필드가 실제 아이디(일련번호)가 아닐 경우 userId 등으로 변경해야함을 알고 있어야 할것이다.

<class name="Parent" table="PARENT">
<list name="childrens" table="CHILD" cascade="save-update,delete,delete-orphan">
   <key column="pid"/>
   <list-index column="POSITION" base="0" />
         <one-to-many class="Children"/>
  </list>
</class>
<class name="Children" table="CHILD" >
  <id name="ingredientId" column="cid">
   <generator class="increment" />
  </id>
 <many-to-one name="parent" class="Parent" column="pid" />
</class>

작성시 주의할 점
  1. key 엘리먼트의 속성으로 not-null="true" 를 명시하지 않는다.
  2. list 속성으로 inverse="true"를 명시하지 않는다.

+ Recent posts