I am not an expert of Hibernate, so my question could be trivial.
I am writing a booking engine for an event. With the engine, it is possible to make registrations, each one of which has a certain number of participants. Each registration can have one or more payments associated.
For each registration, it is possible to make multiple reservations for rooms. The reservation can be for part of a room (e.g. a reservation for two participants that want to share a quadruple room) or for a whole room. Each room can have several room arrangements (e.g. quadruple, triple or double) corresponding to a certain room type. When a reservation is done for a room, the chosen room type and room are connected via a room assignment, that is connected to the reservation.
To code this in Spring, I've set up these model classes:
Registration.java
@Entity @Table(name = "registrations") @Data public class Registration { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") private Long id; // other fields } Participant.java
@Entity @Table(name = "participants") @Data public class Participant { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") private Long id; @ManyToOne(targetEntity = Registration.class) @JoinColumn(name = "registration_id") private Registration registration; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "reservation_id") private Reservation reservation; // other fields } Reservation.java
@Entity @Table(name = "reservations") @Data public class Reservation { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "room_assignment_id") private RoomAssignment roomAssignment; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "registration_id") private Registration registration; @OneToMany(fetch = FetchType.LAZY) @JoinColumn(name = "reservation_id") private List<Participant> participants; // other fields } RoomAssignment.java
@Entity @Table(name = "room_assignments") @Data public class RoomAssignment { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") private Long id; @OneToOne(fetch = FetchType.LAZY, mappedBy = "assignment", cascade = CascadeType.ALL) private Room room; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "room_type_id", nullable = false) private RoomType roomType; @OneToMany(fetch = FetchType.LAZY) @JoinColumn(name = "room_assignment_id") private List<Reservation> reservations; } Room.java
@Entity @Table(name = "rooms") @Data public class Room { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") private Long id; @Column(name = "room_name") private String roomName; @OneToMany(fetch = FetchType.LAZY ) @JoinColumn(name = "room_id") private List<RoomArrangement> roomArrangements; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "assignment_id") private RoomAssignment assignment; } And finally, RoomArrangement.java
@Entity @Table(name = "room_arrangements") @Data public class RoomArrangement { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") private Long id; @ManyToOne(targetEntity = RoomType.class) @JoinColumn(name = "room_type_id", nullable = false) private RoomType roomType; @ManyToOne(targetEntity = Room.class) @JoinColumn(name = "room_id", nullable = false) private Room room; @Column(name = "priority") private Integer priority; } All model classes have their corresponding Repository classes, which I will not add here as they just contain the method names, which you can see from the code.
I am currently writing the procedure to delete a registration. To do so, I eliminate all the participants and all the payments, then for each reservation, I see if it is the only one for the corresponding room assignment, in which case I eliminate the room assignment too, and I eliminate the reservation and finally the registration. I've coded this part in this method:
@Override public void deleteRegistration(Registration registration) { // Deleting payments paymentRepository.deleteAll(paymentRepository.findByRegistration(registration)); // Deleting participants participantRepository.deleteAll(participantRepository.findByRegistration(registration)); List<Reservation> reservations = reservationRepository.findByRegistration(registration); for (Reservation res : reservations) { RoomAssignment assignment = roomAssignmentRepository.findByReservationsContains(res); if (reservationRepository.findByRoomAssignment(assignment).size() == 1) { reservationRepository.delete(res); Room room = roomRepository.findByAssignment(assignment); room.setAssignment(null); roomRepository.save(room); roomAssignmentRepository.delete(assignment); } else { reservationRepository.delete(res); } } registrationRepository.delete(registration); } The outcome is quite weird: when the execution arrives to the roomAssignmentRepository.delete(assignment); call, it breaks with a DataIntegrityViolationException with message
Column 'room_id' cannot be null I've tried to log the hibernate calls, and one of them is definitely weird to my eyes.
Here I list them with the corresponding java call
paymentRepository.deleteAll (there were no payments for this registration)
select payment0_.id as id1_1_, payment0_.amount as amount2_1_, payment0_.paypal_order_id as paypal_o3_1_, payment0_.registration_id as registra5_1_, payment0_.type as type4_1_ from payments payment0_ where payment0_.registration_id=? participantRepository.deleteAll
select participan0_.id as id1_0_, participan0_.birth_date as birth_da2_0_, participan0_.birth_place as birth_pl3_0_, participan0_.email as email4_0_, participan0_.facebook_profile as facebook5_0_, participan0_.first_name as first_na6_0_, participan0_.first_time as first_ti7_0_, participan0_.food_intolerances as food_int8_0_, participan0_.food_preferences as food_pre9_0_, participan0_.gender as gender10_0_, participan0_.last_name as last_na11_0_, participan0_.other_food_notes as other_f12_0_, participan0_.registration_id as registr13_0_, participan0_.reservation_id as reserva14_0_ from participants participan0_ where participan0_.registration_id=? delete from participants where id=? reservationRepository.findByRegistration
select reservatio0_.id as id1_3_, reservatio0_.monday as monday2_3_, reservatio0_.registration_id as registra4_3_, reservatio0_.room_assignment_id as room_ass5_3_, reservatio0_.thursday as thursday3_3_ from reservations reservatio0_ where reservatio0_.registration_id=? roomAssignmentRepository.findByReservationsContains
select roomassign0_.id as id1_5_, roomassign0_.room_type_id as room_typ2_5_ from room_assignments roomassign0_ where ? in (select reservatio1_.id from reservations reservatio1_ where roomassign0_.id=reservatio1_.room_assignment_id) select room0_.id as id1_7_0_, room0_.assignment_id as assignme3_7_0_, room0_.room_name as room_nam2_7_0_ from rooms room0_ where room0_.assignment_id=? reservationRepository.delete
select reservatio0_.id as id1_3_, reservatio0_.monday as monday2_3_, reservatio0_.registration_id as registra4_3_, reservatio0_.room_assignment_id as room_ass5_3_, reservatio0_.thursday as thursday3_3_ from reservations reservatio0_ where reservatio0_.room_assignment_id=? update participants set reservation_id=null where reservation_id=? delete from reservations where id=? roomRepository.findByAssignment
select room0_.id as id1_7_, room0_.assignment_id as assignme3_7_, room0_.room_name as room_nam2_7_ from rooms room0_ where room0_.assignment_id=? roomRepository.save
update rooms set assignment_id=?, room_name=? where id=? roomAssignmentRepository.delete
update reservations set room_assignment_id=null where room_assignment_id=? update room_arrangements set room_id=null where room_id=? This last update room_arrangements set room_id=null where room_id=? is, of course, the SQL update breaking the non-null constraint.
After this long preparation, the question is: why Hibernate is doing this last update, as I do not modify any room_arrangement in any way? At the moment of that call, I am modifying the RoomAssignment class, which is not even connected to the RoomArrangement.