【发布时间】:2015-08-21 18:31:03
【问题描述】:
这是我的情况:我想使用 Jackson 和 Hibernate 构建一个简单的 CRUD Web 服务。似乎是 Spring Boot 的完美工作。所以我们有以下内容:
(请注意,我正在压缩代码,因此无法编译)
class Doctor {
@Id
long id;
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinTable(name = "doctor_service", joinColumns = { @JoinColumn(name = "doctor_id", nullable = false) }, inverseJoinColumns = { @JoinColumn(name = "service_id", nullable = false) })
Set<Service> services;
}
class Service {
@Id
long id;
@ManyToMany(fetch = FetchType.EAGER, mappedBy = "services")
Set<Doctor> doctors;
}
一个简单的数据模型。我们有一个简单的要求:在 web 服务上,当我们获取 Service 对象时,我们应该获取相关的 Doctors。当我们得到医生时,我们应该得到相关的服务。我们使用惰性是因为 [在此处插入理由]。
现在让我们为它服务吧:
@Path("/list")
@POST
@Produces(MediaType.APPLICATION_JSON)
@Transactional
public JsonResponse<List<Doctor>> list() {
return JsonResponse.success(doctorCrudRepo.findAll());
}
修饰 JsonResponse 对象(现在只是一个方便的黑盒)并假设 DoctorCrudRepo 是 CrudRepository 的有效实例。
风暴开始了:
failed to lazily initialize a collection of role: Doctor.services, could not initialize proxy - no Session (through reference chain: ...)
好吧,那么 Lazy 就不起作用了。很简单。让它变得渴望。
Caused by: java.lang.StackOverflowError: null
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:455)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:367)
at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:655)
... 1011 common frames omitted
那么让我们看看其他人怎么说:
Contestant #1:解决方案不相关,因为它们适用于一对多,而不是多对多,所以我仍然收到 StackOverflowError。
Contestant #2:和以前一样,仍然是一对多,仍然是 StackOverflow。
Contestant #3: 一样(没有人用过many-to-many???)
Contestant #4:我不能使用@JsonIgnore,因为这意味着它永远不会被序列化。所以不符合要求。
Contestant #5:乍一看,它似乎工作正常!但是,只有 Doctor 端点有效——它正在获取服务。服务端点不起作用 - 它没有得到医生(空集)。它可能基于哪个引用定义了连接表。这又不符合要求。
Contestant #6:没有。
其他一些错误但值得一提的解决方案:
为 json 序列化创建一组新的不被 hibernate 包装的对象,然后将属性复制到控制器中。这是很多额外的工作。在任何地方都强制使用这种模式会破坏使用 Hibernate 的目的。
加载 Doctor 后,循环遍历每个 Service 并将 service.doctors 设置为 null,以防止进一步延迟加载。我正在尝试建立一套最佳实践,而不是想出一些骇人听闻的变通办法。
那么...正确的解决方案是什么?我可以遵循什么样的模式,看起来很干净,让我为使用 Hibernate 和 Jackson 感到自豪?还是这种技术组合如此格格不入,无法提出新的范式?
【问题讨论】:
-
你试过 Hibernate.initialize(service.getDoctors()) 吗?
-
是的。这是为参赛者 #1 提出的解决方案之一。但是每个医生都再次引用了服务,所以我收到的错误消息略有不同,但根本原因相同。
-
发布实体的实际代码,而不是它的派生部分。通常你必须指定
mappedBy,如果你不休眠可能会混淆。您还提到堆栈溢出,请添加堆栈跟踪以查看哪里出错了。理论上,当您正确设置急切加载应该可以工作时,我怀疑您的@Transactional几乎没有用,因为它是 JAX-RS 而不是 spring bean。 -
查看我的编辑。如果它不是 spring bean 那么为什么 @Autowired 工作?
-
也许this
标签: java hibernate jackson jax-rs spring-boot