2

I have 2 entities. An Appointment and an Item. They are both independent and we can have multiple items, in multiple appointments.

The Appointment class:

@Entity(name = "Appointment")
@Table(name = "appointment")
public class Appointment
{
    @Id
    @JsonProperty("appointment_id")
    @Column(name = "appointment_id", nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @JsonProperty("date")
    private Date startingTimestamp;

    @ManyToMany(mappedBy = "appointment",
        cascade = CascadeType.ALL)
    private List<Item> collectionOfItems;

    @JsonProperty("duration")
    private int duration;

    @JsonProperty("state")
    private AppoitmentState state;

    @ManyToOne
    @JsonBackReference
    @JoinColumn(name="user_appointment_owner_id", nullable = false)
    @JsonProperty("user_owner")
    private User user;

and the Item class :

@Entity
@Table(name = "item")
public class Item
{
    @Id
    @Column(name = "item_id", nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long Id;

    @JsonProperty("name")
    private String name;

    @JsonProperty("duration")
    private int duration;

    @ManyToMany(targetEntity = Appointment.class,
        cascade = CascadeType.ALL)
    @JoinTable(name = "appointment_item", joinColumns = { @JoinColumn(name = "item_id") },
        inverseJoinColumns = { @JoinColumn(name = "appointment_id") })
    private List<Appointment> appointment;

    @ManyToMany
    @JsonProperty("set_of_professionals")
    @JsonIgnore
    private Set<Professional> professional;

    @JsonProperty("business_owning")
    private Long business_id;

Constructors, getters and setters are omitted here.

There is also a patch method inside the Appointment controller.

@RequestMapping(value = "appointment/{itemID}", method = RequestMethod.PATCH,  consumes = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Appointment> addItem(@PathVariable Long itemID, @RequestBody ObjectNode appointment_id_str)
{
    Long appointment_id = appointment_id_str.get("appoitment_id").asLong();
    Optional<Appointment> targetAppoitment = appointmentService.findById(appointment_id);
    Optional<Item> addedItem = itemService.findById(itemID);
    if (targetAppoitment.isPresent() && addedItem.isPresent())
    {
        Appointment appoitmentInDB = targetAppoitment.get();
        appoitmentInDB.addItem(addedItem.get());
        Appointment savedAppointment = appointmentService.save(appoitmentInDB);
        return new ResponseEntity<>(savedAppointment, HttpStatus.CREATED);
    }
    return new ResponseEntity("", HttpStatus.INTERNAL_SERVER_ERROR);
}

Now although as seen in the debugger, an item has been added in appoitment's list, save will not flash the change into the database.

enter image description here

This is the database :

Database

Any idea what I am missing?

halfer
  • 19,824
  • 17
  • 99
  • 186

1 Answers1

3

It is because the Item class own's the relationship.

If you have described the relationship inside the Appointment class and used mappedBy inside the Item class you wouldn't have this issue. This happens because Hibernate uses the class in which the relationship was defined in order to maintain it's associations.

To fix this problem you should adjust you entities in the following way:

class Appointment {

...
    
@ManyToMany(targetEntity = Item.class,
        cascade = CascadeType.ALL)
    @JoinTable(name = "appointment_item", joinColumns = { @JoinColumn(name = "appointment_id") },
        inverseJoinColumns = { @JoinColumn(name = "item_id") })
    List<Item> collectionOfItems;

...

}

and

class Item {

...

    @ManyToMany(mappedBy = "collectionOfItems",
        cascade = CascadeType.ALL)
    private List<Appointment> appoitment;

...

}

This question has already been answered on stackoverflow, link

S.Tushinov
  • 548
  • 5
  • 13
  • So, I did the opposite right? Any idea why I getting circular Circular References? – Tsakiroglou Fotis Feb 04 '22 at 23:01
  • 1
    Well circular references usually happen when you `@Autowire` classes A and B within each other. So check if you have something like `class A { @Autowire private B b; }` and `class B { @Autowire private A a; }` – S.Tushinov Feb 04 '22 at 23:03
  • Apologies, my bad. I mean I get the Item in n infinity loop. – Tsakiroglou Fotis Feb 04 '22 at 23:05
  • 1
    Can you be a bit more specific ? Do you receive any specific exception or you application fails to start ? I don't think I got what you meant. – S.Tushinov Feb 04 '22 at 23:07
  • 1
    Do you mean that when you do a request to `/appointment/{itemID}` you're getting this infinity loop. Because the Appointment has an Item and the Item has an Appointment :D – S.Tushinov Feb 04 '22 at 23:11
  • I am sorry for the delay. Yes exactly. I remember fixing this easily, but it's been a few years since then. – Tsakiroglou Fotis Feb 04 '22 at 23:20
  • 1
    As far as I see from your code you're returning an `ResponseEntity` and the `Appointment` class is an entity. Since your `Appointment` contains a `List` and each `Item` inside it contains an `Appointment` this is the reason why you're getting the circular reference. When Spring serializes your appointment class to a JSON object it uses getters to do so. Your `Appointment` contains a method `getCollectionOfItems()` and in this collection each item has a method `getAppointment()`. – S.Tushinov Feb 04 '22 at 23:24
  • 1
    To fix this i would advise you to create a new POJO's like `AppointmentDTO` and `ItemDTO` and only have the `AppointmentDTO` contain an `ItemDTO`. After you're done with your db operations you will map the returned `Appointment` to an `AppointmentDTO` and return `ResponseEntity` back to the caller of the endpoint. – S.Tushinov Feb 04 '22 at 23:24
  • I remember how you fix it. You need to say @JsonIgnoreProperties("appointment") above the collectionOfItems. With this it will ignore what is beneath inner appointment value. – Tsakiroglou Fotis Feb 04 '22 at 23:47
  • 1
    Actually yes, I didn't think of that :D. Well, you've got yourself covered! Happy coding :) – S.Tushinov Feb 04 '22 at 23:50
  • I've migrate away from Java 5 years ago. I am rusty. Thanks a lot mate. – Tsakiroglou Fotis Feb 04 '22 at 23:52