Java – Best way to deal with Hibernate 1->Many relationship over REST/JSON service

hibernatejavajsonrest

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;
}

Best Answer

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.