简体   繁体   English

JPA ManyToMany关系和JSON序列化

[英]JPA ManyToMany relationship and JSON serialization

I'm implementing a RESTful service using Spring Framework v3.2.4 and JPA + Hibernate 4. I'm returning resources in JSON format (using Jackson Mapper), but now I'm stuck with this exception: 我正在使用Spring Framework v3.2.4和JPA + Hibernate 4实现RESTful服务。我以JSON格式返回资源(使用Jackson Mapper),但是现在我遇到了以下异常:

Could not write JSON: failed to lazily initialize a collection of role: it.teck.service.model.Canvas.params, could not initialize proxy - no Session (through reference chain: it.teck.service.model.Canvas["params"]);

I have a "many to many" relationship between Canvas and Param entities and I need to serialize also params list when a canvas is requested to the service. 我在CanvasParam实体之间存在“多对多”关系,当向服务请求画布时,我还需要序列化params列表。

In my classes I have: 在我的课程中,我有:

@Entity
public class Canvass {

    @ManyToMany
    @JoinTable(name = "canvas_params", joinColumns = { @JoinColumn(name = "id_canvas", referencedColumnName = "id_canvas") }, inverseJoinColumns = { @JoinColumn(name = "id_param", referencedColumnName = "id_param") })
        private List<Param> params;

    // ...
}

And: 和:

@Entity
public class Param {

    @ManyToMany(mappedBy = "params")
    private List<Canvas> canvasList;

    // ...
}

In several posts and SO answers is suggested to exclude ManyToMany fields from serialization, to break the serialization loop, but I need to serialize params linked to my canvas entities . 在几篇文章中,建议使用SO答案从序列化中排除ManyToMany字段,以打破序列化循环,但是我需要对链接到我的canvas实体的参数进行序列化 So, what should I do? 所以我该怎么做?

Because of the Hibernate lazy loading at the serialization time you end up with Hibernate proxy collections. 由于序列化时的Hibernate延迟加载,您最终将获得Hibernate代理集合。 At this time session it's already closed or not in scope. 目前会话已经关闭或不在范围内。 You can write a custom converter that has a JPA entity manager in scope (look for OpenEntityManagerInViewFilter examples) or if the performance is not an issue(the collections are fairly small) and you always need fully populated objects, you can specify a eager fetching strategy on the mapping as: 您可以编写一个在范围内具有JPA实体管理器的自定义转换器(查找OpenEntityManagerInViewFilter示例),或者如果性能不成问题(集合很小)并且您始终需要完全填充的对象,则可以指定一个急切的获取策略在映射上为:

@ManyToMany(fetch = FetchType.EAGER) 
@JoinTable(name = "canvas_params", joinColumns = { @JoinColumn(name = "id_canvas", referencedColumnName = "id_canvas") }, inverseJoinColumns = { @JoinColumn(name = "id_param", referencedColumnName = "id_param") })
    private List<Param> params;

This happens because Jackson is trying to access a property of your bean that is managed by Hibernate outside of the session. 发生这种情况是因为Jackson尝试在会话外部访问由Hibernate管理的bean的属性。 So the property is lazily loaded and when you try to access it outside the session, hibernate will not be able to fetch it from DB. 因此,该属性是延迟加载的,当您尝试在会话外访问该属性时,hibernate将无法从数据库中获取它。

You have three options: 您有三种选择:

  1. You do like Ion says, you set FetchType.EAGER on the entity. 您确实像Ion所说的那样,在实体上设置了FetchType.EAGER。 The downside is that everytime that you fetch one entity this way, it will fetch all the Canvases and Params linked to it. 不利之处在于,每次以这种方式获取一个实体时,它都会获取与之链接的所有画布和参数。 And you may not want that, because it may slow down your app. 而且您可能不希望这样做,因为这可能会降低您的应用程序运行速度。
  2. You serialize it when still in the session at DAO level or Service level. 在仍处于DAO级别或Service级别的会话中时,可以对其进行序列化。 This is the cleanest and most proper way 这是最干净,最正确的方法
  3. Finally, if (while still in the session) you do something like params.size() or canvasList.size(), this will automatically trigger the collection fetch from db. 最后,如果(仍然在会话中)执行诸如params.size()或canvasList.size()之类的操作,这将自动触发从db获取集合。 This is a bit of a hack, but it works and you don't have to modify your DAO signature or the fetch strategy for the entity. 这有点骇人听闻,但确实可行,您不必修改实体的DAO签名或获取策略。

There is another option, for Spring, that consists in making the controller method where the serialization is happening @Transactional, this will keep the session open! 对于Spring,还有另一种选择,其中包括在进行序列化的控制器方法中进行@Transactional,这将使会话保持打开状态! But it will make the method transactional so be careful with unwanted consequences. 但这会使该方法具有事务性,因此请小心不要产生不良后果。

Your issue do not seems to be linked to a serialization loop, but to serialization that is done outside to the transaction that retrieved the parent object. 您的问题似乎并没有链接到序列化循环,而是链接到在检索父对象的事务之外完成的序列化。

To fix this, can you do the serialization inside this tx ? 要解决此问题,可以在此TX内进行序列化吗?

You have several other options, described in hibernate "lazily init exception" threads like : failed to lazily initialize a collection of role 您还有其他几个选项,在休眠的“延迟初始化异常”线程中有描述,例如: 无法延迟初始化角色集合

The other answers supplied do cover the cause of the problem. 提供的其他答案确实涵盖了问题的原因。 For the solution I advise you look into something like Hexagonal Architecture or something like that. 对于该解决方案,我建议您研究类似“六角结构”之类的东西。

Essentially you want a repository layer where you get the data you are going to use, by forcing yourself to map from the Entity data objects into Domain data objects you will in fact circumnavigate this issue. 本质上,您需要一个存储库层,在其中可以获取要使用的数据,方法是强制您将自己从Entity数据对象映射到Domain数据对象,从而实际上可以避免此问题。 The reason is that you are separating out your concerns. 原因是您要分开关注点。 You are loading all the data you will use (not extra data you won't use) in a single place and then you are performing any logic in a different place. 您要在一个位置加载所有将要使用的数据(而不是多余的数据),然后在不同的位置执行任何逻辑。 This will have the effect of getting all your database stuff done first and release those resources so that you can then do your logic at your leisure. 这将具有先完成所有数据库工作并释放这些资源的效果,以便您随后可以随意执行逻辑。

A good link to use for this is: http://alistair.cockburn.us/Hexagonal+architecture 为此使用的一个很好的链接是: http : //alistair.cockburn.us/Hexagonal+architecture

Even if your implementation isn't strict, applying some of the disciplines it talks about will help massively. 即使您的实现不严格,应用它所谈论的某些学科也会极大地帮助您。

Hope this helps. 希望这可以帮助。

You can try as below to change the fetch strategy at run time to overcome this problem. 您可以尝试以下方法在运行时更改获取策略,以解决此问题。

User user = (User) session.createCriteria(User.class)
    .setFetchMode("permissions", FetchMode.JOIN)
    .add( Restrictions.idEq(userId) )
    .uniqueResult();

There are few more option in "FetchMode". “ FetchMode”中没有更多选项。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM