발생문제

org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: [entity.list_collection_a, entity.list_collection_b]
@ElementCollection(targetClass = EnumType.class, fetch = FetchType.EAGER)
@Enumerated(EnumType.STRING)

해당필드는 enum 타입의 목록을 가지는 필드였고, 프로젝트의 의존성 라이브러리 버전들을 업그레이드 하면서org.hibernate.loader.MultipleBagFetchException 이 발생했다.

  • 업그레이드

    • org.hibernate:hibernate-entitymanager:5.1.0.Final →org.hibernate:hibernate-entitymanager:5.2.4.Final

해결방법

이 문제를 해결하는 방법은 enum 타입 컬렉션 필드에 정의를 다음과 같이 변경했다.

@ElementCollection(targetClass = EnumType.class)
@Enumerated(EnumType.STRING)
@LazyCollection(LazyCollectionOption.FALSE)

@ElementCollection 는 기본적으로 LAZY 값을 가진다. 그러나 한단에 선언된@LazyCollection(LazyCollectionOption.FALSE) 을 통해서 EAGER 로 처리된다.@LazyCollection 는 컬렉션 타입에 대한 LAZY 여부를 결정하는 기능을 한다.


Caused by: org.hibernate.MappingException: property mapping has wrong number of columns: Parent.child type: Child
    at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:497)
    at org.hibernate.mapping.RootClass.validate(RootClass.java:270)
    at org.hibernate.cfg.Configuration.validate(Configuration.java:1360)
    at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1851)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:857)
    ... 23 more

1. 원인

2. 이유

연관관계의 주인(mappedBy)를 설정하지 않았기 때문이다.

양방향 매핑의 규칙: 연관관계의 주인
양방향 연관관계 매핑시 지켜야 할 규칙이 있는데 두 연관관계 중 하나를 연관관계의 주인으로 정해야 한다. 연관관게의 주인만이 데이터베이스 연관관계와 매핑되고 외래 키를 관리(등록, 수정, 삭제)할 수 있다. 반면이 주인이 아닌 쪽은 읽기만 할 수 있다. - 자바 ORM 표준 JPA 프로그래밍, 181p.

3. 해결책

mappedBy 를 선언해주어 연관관계의 주인을 지정해주면 된다.


문제발생지점

엔티티 클래스에 fetch=FetchType.LAZY로 선언해놓은 배열이나 객체를 사용하다 보면, 빈 배열로 선언되어 있는 경우,

Exception in thread "threadPoolTaskExecutor-5" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: ...{collection}..., could not initialize proxy - no Session
    at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:566)
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:186)
    at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:545)
    at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:124)
    at org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:266)
    ...{중략}...
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)

위와 같은 예외가 발생한다.


처리방법

public final class Hibernate {
...
    /**
     * Force initialization of a proxy or persistent collection.
     * <p/>
     * Note: This only ensures intialization of a proxy object or collection;
     * it is not guaranteed that the elements INSIDE the collection will be initialized/materialized.
     *
     * @param proxy a persistable object, proxy, persistent collection or <tt>null</tt>
     * @throws HibernateException if we can't initialize the proxy at this time, eg. the <tt>Session</tt> was closed
     */
    public static void initialize(Object proxy) throws HibernateException {
        if ( proxy == null ) {
            return;
        }
        else if ( proxy instanceof HibernateProxy ) {
            ( ( HibernateProxy ) proxy ).getHibernateLazyInitializer().initialize();
        }
        else if ( proxy instanceof PersistentCollection ) {
            ( (PersistentCollection) proxy ).forceInitialization();
        }
    }
...
}

위에 설명한 Hibernate.initialize(대상 오브젝트나 컬렉션) 으로 lazy 컬렉션을 지정해주면 proxy 처리를 하면서 lazy 객체들을 미리 호출해놓는 것으로 보인다.

### 개발환경

* Hibernate 4.2.0.Final


### 문제발생 원인

* @Entity 선언 객체를 정렬하려는 영도로 interface Compare<T>를 구현한 후

 int compareTo(Object o) 에서 사용하려는 비교값 필드로 order 을 정의했다.

@Entity

@Table(name="SAMPLE")

public class Sample implements Serialize, Compare<Sample> {

@Id

@GeneratedValue(strategy=GenerationType.AUTO)

private Long id;


     @Getter

private Long order;


@Override

public int compareTo(Sample o) {

return getOrder().compareTo(o.getOrder());

}

}


  이렇게 정의된 엔티티 객체의 테이블이 생성되지 않는 문제가 발생해서, 이를 해결하기 위해서 여러가지를 시도해봤다.  복사해서 새로운 엔티티 객체를 만들어보고, 객체의 참조관계를 변경해보고, 클래스명을 바꿔보고 하이버네이트 버전을 변경해봤지만 증상은 동일했다.

  이를 확인하기 위해서 구현하는 과정을 한단계한단계 되짚어보았다. ㅠㅅ-) 그러다가 발견했다.

  새로 만들어서 id 값을 넣었을 떄까지는 이상없이 생성이 되다가,

private Long order;

이 필드를 넣는 순간부터 구현되지 않는 것을 발견했다.


### 해결방법

* order -> seq 로 변경

필드명을 seq로 변경하고 나니... 정상적으로 테이블이 생성되는 것을 확인했다.

### 보충 설명

다른 분이 알려주신 것을 보고 생각했다.

'아, order가 hibernate와 관련된 예약어가 아니라 database와 관련된 예약어...였어.'

Entity 객체의 필드명을 지정할 때 Database의 예약어를 사용하지 않도록 하자. 반드시 도메인의 필드명으로 사용해야 한다면, @Column(name="")을 이용하여 테이블의 컬럼명을 다른 것으로 변경하자.



### 개발환경

* Spring MVC 3.2.0

* Hibernate 4.2.0.Final


### 문제발생

MVC 패턴에서 Controller 단에서 @PathVariable로 객체를 받아서 처리하려는 시도를 하면서 문제가 발생했다.


### 로그


HTTP Status 500 - No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: java.util.HashMap["result"]->com.sil.docsflow.web.common.Result["data"]->com.sil.docsflow.domain.company.User_$$_javassist_15["handler"])


type Exception report


message No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: java.util.HashMap["result"]->com.sil.docsflow.web.common.Result["data"]->com.sil.docsflow.domain.company.User_$$_javassist_15["handler"])


description The server encountered an internal error that prevented it from fulfilling this request.


exception


org.codehaus.jackson.map.JsonMappingException: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: java.util.HashMap["result"]->com.sil.docsflow.web.common.Result["data"]->com.sil.docsflow.domain.company.User_$$_javassist_15["handler"])

org.codehaus.jackson.map.ser.StdSerializerProvider$1.failForEmpty(StdSerializerProvider.java:89)

org.codehaus.jackson.map.ser.StdSerializerProvider$1.serialize(StdSerializerProvider.java:62)

org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:430)

org.codehaus.jackson.map.ser.BeanSerializer.serializeFields(BeanSerializer.java:175)

org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:142)

org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:430)

org.codehaus.jackson.map.ser.BeanSerializer.serializeFields(BeanSerializer.java:175)

org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:142)

org.codehaus.jackson.map.ser.MapSerializer.serializeFields(MapSerializer.java:287)

org.codehaus.jackson.map.ser.MapSerializer.serialize(MapSerializer.java:212)

org.codehaus.jackson.map.ser.MapSerializer.serialize(MapSerializer.java:23)

org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:600)

org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:280)

org.codehaus.jackson.map.ObjectMapper.writeValue(ObjectMapper.java:1345)

org.springframework.web.servlet.view.json.MappingJacksonJsonView.writeContent(MappingJacksonJsonView.java:292)

org.springframework.web.servlet.view.json.MappingJacksonJsonView.renderMergedOutputModel(MappingJacksonJsonView.java:247)

org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:264)

org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1208)

org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:992)

org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:939)

org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)

org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936)

org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:827)

javax.servlet.http.HttpServlet.service(HttpServlet.java:621)

org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812)

javax.servlet.http.HttpServlet.service(HttpServlet.java:728)

org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)

org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)

org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)

org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)

note The full stack trace of the root cause is available in the Apache Tomcat/7.0.34 logs.


Apache Tomcat/7.0.34


### 해결방법

* 참조페이지 : [Strange Jackson exception being thrown when serializing Hibernate object](http://stackoverflow.com/questions/4362104/strange-jackson-exception-being-thrown-when-serializing-hibernate-object)


### 나름 분석

하이버네이트에 의해 영속화된 객체에 hadler 와 hibernateLazyInitializer 의 필드가 생성되는 것으로 보인다. 이렇게 영속화된 객체를 JSON으로 직렬화 하는 과정에서 hadler 가 lazy로 처리되어 있어서 발생하는 문제가 아닌가 추측해본다.


+ Recent posts