Search code examples
hibernatespring-mvcjacksonspring-transactionshibernate-session

Spring mvc integration test with hibernate and jackson-hibernate-mapper: illegal attempt to associate a collection with two sessions


The exception "illegal attempt to associate a collection with two sessions" is well known and happens when same entity is referenced in two different sessions. I have a spring mvc integration test where it happens and need an advice how to make it work correctly.

Also it is important to note that I am using https://github.com/FasterXML/jackson-datatype-hibernate which helps to avoid LazyInitializationException when converting domain objects to json. My guess is that it internally opens Hibernate session to fetch lazy relationships.

Here is a test:

@RunWith(SpringJunit4ClassRunner.class)
@WebAppConfiguration
@ContextHierarchy({
    @ContextConfiguration(name = "root", locations = "classpath:application.xml"),
    @ContextConfiguration(name = "servlet", locations = "classpath:servlet.xml")
})
@TransactionalConfiguration
public class IntegrationTest {
    private MockMvc mockMvc;

    //some initialization and autowiring

    @Test
    @Transactional // It is important to rollback all the db changes after the test. So, it's a common pattern to make all tests transactional.
    // So here we have HibernateSession#1
    public void testController() {
        // repository is spring-data-jpa repository
        // but you may think of it as a DAO which returns persisted 
        // hibernate entity
        ChildEntity child = new ChildEntity();
        child = childRepository.save();

        MyEntity entity = new MyEntity();
        entity.setChildEntities(Collections.singletoneList(child));
        entity = repository.save(entity);
        // entity and its child are still preserved in the HibernateSession#1

        mockMvc.perform(get("/entity/by_child_id/" + child.getId()).andExpect(status().is("200"));
    }

}

@RestController
@RequestMapping("/entity")
public class MyController {

    @RequestMapping("by_child_id/{id}")
    // My guess: Jackson hibernate mapper will open another session
    // and try to fetch children  
    // So here we have HibernateSession#2
    public MyEntity getByChildId(@PathVariable("id") childId) {
         return repository.findByChildrenId(childId);
    }

}

@Entity 
public class MyEntity {
    @OneToMany(fetch = FetchType.EAGER)
    private List<ChildEntity> children;
}

//and finally here is a piece of servlet.xml
<mvc:annotation-driven>
    <mvc:message-converters>
        <!-- Use the HibernateAware mapper instead of the default -->
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper">
                <bean class="path.to.your.HibernateAwareObjectMapper" />
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven> 

The exception I have is:

Caused by org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions: [MyEntity.children#1]
    at com.fasterxml.jackson.datatype.hibernate4.PersistentCollectionSerializer.inistializeCollection(PersistentCollectionSerializer.java:195)
    at com.fasterxml.jackson.datatype.hibernate4.PersistentCollectionSerializer.findLazyValue(PersistentCollectionSerializer.java:160)
    at com.fasterxml.jackson.datatype.hibernate4.PersistentCollectionSerializer.serialize(PersistentCollectionSerializer.java:115)
    at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:505)
    at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase:639)

jackson-databind-2.4.2.jar

So, is there any way to avoid the issue, except avoid using jackson-hibernate-mapper for tests? Using DTO and Dozer instead of domain object is not considered as a solution(it will be done in the future, but not now).

Thanks in advance


Solution

  • Thanks to the M. Deinum, I decided to go with the simplest solution and created a test-servlet-context.xml:

    <beans>
    
       <import resource="classpath:servlet.xml">   
    
       <bean id="jsonConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
    
    </beans>
    

    Also I changed the definition of converters in servlet.xml:

    <mvc:annotation-driven>
        <mvc:message-converters>
            <!-- Use the HibernateAware mapper instead of the default -->
            <ref bean="jsonConverter"/>
        </mvc:message-converters>
    </mvc:annotation-driven> 
    
    <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
        <property name="objectMapper">
            <bean class="path.to.your.HibernateAwareObjectMapper" />
        </property>
    </bean>
    

    Now my tests are not using any jackson-hibernate magic. It is useless since all the tests are transactional themselves.

    The defect was reported here: https://github.com/FasterXML/jackson-datatype-hibernate/issues/74