0

I'm having trouble figuring out why exactly the owning side of a relationship isn't getting persisted on the other side when I POST a JSON object to my REST API (using Spring and Hibernate).

Mapped superclass with id field:

@MappedSuperClass
public class BaseEntity implements Serializable {
    private static final long serialVersionUID = 11538918560302121L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    ....
    }

Owning class (extends NamedEntity which in turn extends BaseEntity):

@Entity
@DynamicUpdate
@SelectBeforeUpdate
@NamedQuery(name = "Chain.byId", query = "from Chain where id=:id")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope=Chain.class)
public class Chain extends NamedEntity {
    private static final long serialVersionUID = 4727994683438452454L;
    @OneToMany(mappedBy = "chain", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private List<Campaign> campaigns = new ArrayList<Campaign>();
    ....
}

Owned class:

@Entity
@DynamicUpdate
@SelectBeforeUpdate
@NamedQuery(name = "Campaign.byId", query = "from Campaign where id=:id")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope=Campaign.class)
public class Campaign extends NamedEntity {
    @ManyToOne
    @JoinColumn(name = "chain_id")
    private Chain chain;
    ....
}

The relevant part of my RestController:

@RequestMapping(value = "new", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
@ResponseBody
public Chain saveChain(@RequestBody Chain chain) {
    chainDAO.saveChain(chain);
    return chain;
}

JSON request body:

{  
  "name": "mcdonald's",
  "campaigns": [
      {
          "name": "summer1"
      }
  ]
}

Relevant part of JSON response body:

{
  "id": 1,
  "name": "mcdonald's",
  "campaigns": [
    {
      "id": 1,
      "name": "summer1",
      "rewards": [],
      "startDate": null,
      "endDate": null,
      "chain": null,
      "surveys": [],
      "achievements": []
    }
  ],
  "rewards": []
}

I suspect that this is actually the expected behaviour when using the @JsonIdentityInfo annotation to break infinite recursion? However, when I then try to request the Chain that I just created with its id field, I don't see the nested object (Campaign) anymore.

GET method I used to retrieve the Chain object I just created:

@RequestMapping(value = "{id}", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
public Chain getChain(Model model, @PathVariable int id) {
    Chain chain = chainDAO.getChainById(id).get(0);
    return chain;
}

JSON response body for GET method:

{
  "id": 1,
  "name": "mcdonald's",
  "campaigns": [],
  "rewards": [],
  "managers": [],
  "locations": []
}

As you can see, the campaigns array in this Chain object is now empty.

Daniel Vu
  • 25
  • 1
  • 6

1 Answers1

0

Turns out that I just wasn't managing the bidirectional relationship properly. The relationship must be explicitly set on both ends.

@RequestMapping(value = "{id}/campaigns/new", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
    @ResponseBody
    public Campaign saveCampaignToChain(@RequestBody Campaign campaign, @PathVariable int id) {
        Chain chain = chainDAO.getChainById(id).get(0);
        chain.getCampaigns().add(campaign);
        campaign.setChain(chain);
        chainDAO.saveChain(chain);
        campaignDAO.saveCampaign(campaign);
        return campaign;
    }
Daniel Vu
  • 25
  • 1
  • 6
  • but that means before you save your new record, you have to query your database first. Isn't that a bit of an overhead? – Francis Zabala Oct 23 '15 at 06:15
  • 1
    Yes, it is. I've since moved on from using an ORM altogether for this project as I found setting database relationships programmatically to be more trouble than it was worth compared to simply writing the SQL queries by hand. – Daniel Vu Oct 23 '15 at 23:47
  • I see. So what did you end up using? – Francis Zabala Oct 24 '15 at 03:04
  • I found a project named PostgREST that implements a REST API for any PostgreSQL database, and deployed both the API and database as Docker containers. I then switched over to Go from Java for writing whatever middleware I need to implement my client's business logic. Go is a smaller language and lets me focus on the problem at hand instead of worrying about the abstractions that frameworks like Spring and Hibernate introduce. It also handles JSON encoding/decoding like a champ, its built-in library has been enough for me and it also made implementing parallel processing of for-loops a breeze. – Daniel Vu Oct 24 '15 at 06:18
  • Thanks for the info. I got my issue working. The foreign key I am using to link Campaign to a Chain is not annotated with [@]Id. Without it, hibernate doesn't look for it in the Chain table and depending on the cascade rules, it creates a new one. tl:dr; foreign keys will work if the it is annotated [@]Id in the other side. – Francis Zabala Oct 25 '15 at 03:09
  • I use [@]Id anywhere that I use the [@]Entity annotation as well. The only time that I don't is for embeddables and element collections. – Daniel Vu Oct 25 '15 at 06:24