tenticon
tenticon

Reputation: 2913

spring boot - how to save many-to-one entities to database

In this spring boot app I have a one-to-many mapping from Cart to Item where the serialization cycle is solved by ignoring the Cart in Item's serialization. (So the Cart field in Item carries the @JsonBackReference annocation)

Spring boot initializes the database successfully such that GET-ing all Carts gives the json

[
    {
        "id": 1, <-- cart 1
        "items": [
            {
                "id": 0, <-- item 0
                "itemName": "tooth brush"
            },
            {
                "id": 1,
                "itemName": "shampoo"
            }
        ]
    },
    {
        "id": 2,
        "items": [
            {
                "id": 2,
                "itemName": "body wash"
            }
        ]
    },
    {
        "id": 3,
        "items": []
    }
]

However when I want to add an Item to an existing Cart the new Item is saved as null

@PostMapping("carts/{cartId}")
    public Cart addItemToCart(@PathVariable("cartId") long cartId, @RequestBody Item item)
    {
        LOG.info("Trying to add ITEM: id={}, name={}, cart={} to CART: {}", item.getId(), item.getItemName(), item.getCart(), cartId);

        Cart cart = dao.getById(cartId);
        LOG.info("I) CART: id={}, items={}", cart.getId(),cart.getItems().stream().map(i -> i.getItemName()).collect(Collectors.toList()));

        // also tried at this location: item.setCart(cart);
        cart.getItems().add(item);
        LOG.info("II) CART: id={}, items={}", cart.getId(),cart.getItems().stream().map(i -> i.getItemName()).collect(Collectors.toList()));

        Cart res = dao.save(cart); // HERE CART SAVES NEW ITEM AS NULL
        LOG.info("III) CART: id={}, items={}", res.getId(),res.getItems().stream().map(i -> i.getItemName()).collect(Collectors.toList()));

        Cart resCheck = dao.getById(res.getId());
        LOG.info("IV) CART: id={}, items={}", resCheck.getId(),resCheck.getItems().stream().map(i -> i.getItemName()).collect(Collectors.toList()));
        return res;
    }

Say I want to add an Item with name "beer" to Cart with cartid=1. In my opinion, I only need the itemName in the json because the Cart field is ignored (@JsonBackReference) and the itemid is autogenerated.

So, posting

{
    "itemName":"beer"
}

localhost:9200/demo/api/carts/1 gives the log (compare with above code)

Trying to add ITEM: id=0, name=beer, cart=null to CART: 1
I) CART: id=1, items=[tooth brush, shampoo]
II) CART: id=1, items=[tooth brush, beer, shampoo]
III) CART: id=1, items=[null, tooth brush, shampoo] <-- beer itemName is null after dao.save()
IV) CART: id=1, items=[null, tooth brush, shampoo]

And that's unsurprising because Carts and Items are joined on cartid but where should the beer Item get a cartid from when serializing an Item ignores any Cart info! What also doesnt work if I POST

{
    "itemName":"beer",
    "cart":{
        "id":1,
        "items":[]
    }
}

which also saves new item as null.

What I really want to avoid is switching JsonBackReference and JsonManagedReference.

How can I still add Items to Carts?

Here are the entity classes

@Entity
@Table(name="items")
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class Item
{
    @Id
    @GenericGenerator(name="itemIdGen" , strategy="increment")
    @GeneratedValue(generator="itemIdGen")
    @Column(name = "itemid", nullable=false)
    private long id;

    @Column(name="itemname")
    private String itemName;

    @ManyToOne
    @JoinColumn(name = "cartid", nullable=false)
    @JsonBackReference
    private Cart cart;
    /*...*/
}

@Entity
@Table(name="carts")
public class Cart 
{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "cartid", nullable=false)
    private long id;

    @Column(name="items")
    @OneToMany(mappedBy = "cart")
    @JsonManagedReference
    private Set<Item> items;
    /*..*/
}

Thanks for the help

P.S.: If you want to clone the repo linked in the beginning you also need to run the eureka server and clone the demo-commons project.

Upvotes: 1

Views: 5501

Answers (1)

tenticon
tenticon

Reputation: 2913

The solution is to forget about referencing the entire Cart object in Item but just keep the cartid (on which is merged)

@Entity
@Table(name="carts")
public class Cart 
{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "cartid", nullable=false)
    private long id;

//  @Column(name="items")
//    @OneToMany(mappedBy = "cart")
//  @JsonManagedReference
//    private Set<Item> items;
    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name="cartid", referencedColumnName="cartid")
    private Set<Item> items;
    /*..*/
}

@Entity
@Table(name="items")
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class Item
{
    @Id
    @GenericGenerator(name="itemIdGen" , strategy="increment")
    @GeneratedValue(generator="itemIdGen")
    @Column(name = "itemid", nullable=false)
    private long id;

    @Column(name="itemname")
    private String itemName;

//    @ManyToOne
//    @JoinColumn(name = "cartid", nullable=false)
//    @JsonBackReference
//    private Cart cart;
    @Column(name="cartid")
    private Long cartId;
    /*..*/
 }

Now I can just happily POST the Item

{
    "itemName":"beer",
    "cartid":1
}

Upvotes: 1

Related Questions