Spring 使用 hibernate 和 jackson-hibernate-mapper 的 mvc 集成测试:非法尝试将一个集合与两个会话相关联
Spring mvc integration test with hibernate and jackson-hibernate-mapper: illegal attempt to associate a collection with two sessions
异常 "illegal attempt to associate a collection with two sessions" 是众所周知的,当在两个不同的会话中引用同一实体时会发生这种情况。我有一个 spring mvc 集成测试发生的地方,需要有关如何使其正常工作的建议。
另外需要注意的是,我使用的是 https://github.com/FasterXML/jackson-datatype-hibernate,这有助于在将域对象转换为 json 时避免 LazyInitializationException。我的猜测是它在内部打开 Hibernate 会话以获取惰性关系。
这是一个测试:
@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>
我的例外是:
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
那么,除了避免使用 jackson-hibernate-mapper 进行测试外,有什么办法可以避免这个问题吗?使用 DTO 和 Dozer 代替域对象不被视为解决方案(将来会完成,但不是现在)。
提前致谢
感谢 M. Deinum,我决定采用最简单的解决方案并创建一个 test-servlet-context.xml:
<beans>
<import resource="classpath:servlet.xml">
<bean id="jsonConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</beans>
我还更改了转换器的定义 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>
现在我的测试没有使用任何 jackson-hibernate 魔法。这是无用的,因为所有测试本身都是事务性的。
此处报告了缺陷:https://github.com/FasterXML/jackson-datatype-hibernate/issues/74
异常 "illegal attempt to associate a collection with two sessions" 是众所周知的,当在两个不同的会话中引用同一实体时会发生这种情况。我有一个 spring mvc 集成测试发生的地方,需要有关如何使其正常工作的建议。
另外需要注意的是,我使用的是 https://github.com/FasterXML/jackson-datatype-hibernate,这有助于在将域对象转换为 json 时避免 LazyInitializationException。我的猜测是它在内部打开 Hibernate 会话以获取惰性关系。
这是一个测试:
@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>
我的例外是:
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
那么,除了避免使用 jackson-hibernate-mapper 进行测试外,有什么办法可以避免这个问题吗?使用 DTO 和 Dozer 代替域对象不被视为解决方案(将来会完成,但不是现在)。
提前致谢
感谢 M. Deinum,我决定采用最简单的解决方案并创建一个 test-servlet-context.xml:
<beans>
<import resource="classpath:servlet.xml">
<bean id="jsonConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</beans>
我还更改了转换器的定义 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>
现在我的测试没有使用任何 jackson-hibernate 魔法。这是无用的,因为所有测试本身都是事务性的。
此处报告了缺陷:https://github.com/FasterXML/jackson-datatype-hibernate/issues/74