7

Problem: A Hibernate bi-directional One-to-Many relationship does not map easily to JSON. If default (Jackson) mapping is used there is an infinite recursion issue as the parent contains the children each of which contains a reference to the parent. The easiest way I've found to fix this is to mark the Many end attribute as @JsonIgnore. This means that the JSON gets generated as expected. However, when the objects are sent over a REST POST (for example) this means the ManyToOne references get lost. At present I just have the REST service reconstruct the references before persisting the data, but I'm wondering if there is a recommended way to do this automatically (using the Spring REST architecture).

To illustrate, here are some code snippets:

The parent Entity:

@Entity
@Table(name = "wine_case")
public class WineCase {
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "wineCase")
    private List<CaseContent> caseContents;
}

The child Entity:

@Entity
@Table(name = "case_content")
public class CaseContent {
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "wine_case_id", nullable = false)
    @JsonIgnore
    private WineCase wineCase;
}

The method in the REST controller which receives the request, and needs to rebuild the relationships back from CaseContent to WineCase:

@RequestMapping(method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public @ResponseBody
WineCase createWineCase(@RequestBody WineCase wineCase, HttpServletResponse response) {
    wineCase.setId(null);
    wineCase.getCaseContents().forEach((cc) -> cc.setWineCase(wineCase));
    entityManager.persist(wineCase);
    response.setHeader("Location", "/winecases/" + wineCase.getId());
    return wineCase;
}

3 Answers 3

6

Since you're using jackson you should take a look at this : https://github.com/FasterXML/jackson-datatype-hibernate

With this module, jackson will not serialize LAZY fields if you didn't initialize them, so you could have LAZY fields on both sides to not have any problems.

If this solution does not suit to your needs (maybe you want some eager). Then instead of use @JsonIgnore use @JsonIgnoreProperties like this :

@Entity
@Table(name = "wine_case")
public class WineCase {
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "wineCase")
    @JsonIngoreProperties("wineCase", allowSetters=true)
    private List<CaseContent> caseContents;
}

@Entity
@Table(name = "case_content")
public class CaseContent {
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "wine_case_id", nullable = false)
    @JsonIgnoreProperties("caseContents", allowSetters=true)
    private WineCase wineCase;
}

The properties exclusion will make sure that you don't serialize cycles whereever you start (ie from child or from parent). The allowSetters will allow deserialisation of the properties, only serialisation will ignore the defined properties in the annotation ( the allowSetters/Getters exists since 2.6).

Be aware that there is currently a bug with jsonignoreproperties and lazy objects : https://github.com/FasterXML/jackson-datatype-hibernate/issues/78. Let's hope it'll be fixed for 2.8.

0

Have a look at your JSON library's options - there should be a way to make it identify objects that it has already serialised and emit a back-reference instead of serializing the object again. This is almost certainly the simplest solution to your problem.

0

Instead of directly trying to return all the data, why not just serialize the children as just identifiers? This is how document databases recommend (or at least MongoDB).

Then you can alos include the complete entity when it is asked for. The jsonapi.org specification describes this behaviour.

So by default just return the WineCase entity with an array of identifiers to CaseContent, and only if the client request to include the CaseContent, include the whole entity where the wineCase field is serialized as just an identifier.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.