Delete object in OneToOne relationship ConstraintViolationException - spring

I'm trying to delete entity which is the owner of the relationship, but I am getting an exception as follows:
org.h2.jdbc.JdbcSQLException: Referential integrity constraint
violation: "FKS59BBPCYQ1GUKBWWA61TYF8YF: PUBLIC.RESERVATIONS FOREIGN
KEY(CAR_LICENSE_ID) REFERENCES PUBLIC.CARS(LICENSE_ID) ('EPA13S')";
SQL statement:
I know that is because of trying to delete an object to which another one has a reference with fk_key. Here is my model:
public class Reservation
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#Enumerated(EnumType.STRING)
private DriverType driverType;
private LocalDateTime startTime;
private LocalDateTime stopTime;
#OneToOne(cascade = CascadeType.PERSIST)
private Car car;
private BigDecimal cost;
}
public class Car
{
#Id
#NonNull
#Size(min = 6, max = 6)
String licenseId;
#OneToOne(mappedBy = "car", cascade = CascadeType.REMOVE)
Reservation reservation;
}
How could I possibly deal with such scenario? I would like to delete car from parking when the reservation ends as I don't need it and having license id as pk_key make it vulnerable for trying to insert new car with upcoming reservation even though the previous one has ended.
Deleting car:
carRepository.deleteByLicenseId(reservation.getCarLicenseId());
#Query("DELETE FROM Car c where c.licenseId = :licenseId")
void deleteByLicenseId(#Param("licenseId") String licenseId);

I assume you are extending Spring CrudRepository<Reservation, Long>
The joinColumn is on the Reservation side and from what I can see what you want to do is to delete the Reservation as well. So why not delete it from the owning side, which looks like the reservation.
Change Reservation to.
#OneToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
Rather use the following for delete.
reservationRepository.delete(reservation);

This is a bidirectional relationship. Add this to Car class:
public void removeReservation() {
if (this.reservation != null) {
this.reservation.setCar(null);
this.reservation = null;
}
}
Call car.removeReservation().
Then delete car.

Related

Why Value is not getting assigned in JPA for insert statement

Hi I have couple of Entity classes as below, using lombok for getter and setters
Parent Entity Class have
#Table(name = "PARTY")
#Entity
public class Party {
#Id
#Column(name = "PARTY_ID")
private Long partyId;
#OneToMany(targetEntity = DVLoanParticipants.class,cascade = CascadeType.ALL)
#JoinColumn(name = "PARTY_ID")
#MapKey(name="dvpParticipantName")
#LazyCollection(LazyCollectionOption.FALSE)
private Map<String, DVLoanParticipants> dvLoanParticipantsMap;
}
Child Entity Class have
#Table(name = "DV_LOAN_PARTICIPANTS")
#Entity
public class DVLoanParticipants implements Serializable {
#Id
#Column(name = "PARTY_ID")
private Long partyId;
#Id
#Column(name = "DVP_PARTICIPANT_NAME")
private String dvpParticipantName;
#Column(name = "DVP_PARTICIPANT_TYPE")
private String dvpParticipantType;
}
In service class i am calling save operation as
repository.save(parentEntityObject);
I am able to execute update statements ,but when i try to insert new row for child entity class i am getting an error saying
cannot insert NULL into ("ABC"."DV_LOAN_PARTICIPANTS"."PARTY_ID")
But if i print the parentEntityObject just before the save operation i see the values like
(partyId=12345678, dvpParticipantName=XYZ, dvpParticipantType=VKP)
I see the query formed as
insert
into
DV_LOAN_PARTICIPANTS
(DVP_PARTICIPANT_TYPE, PARTY_ID, DVP_PARTICIPANT_NAME)
values
(?, ?, ?)
Just before te save i am seeing valules in the Object
Builder=DVLoanParticipants(partyId=123456, dvpParticipantName=Builder,
dvpParticipantType=Individual)
Update
This is the setting part for values
DVLoanParticipants dvLoanParticipants = new
DVLoanParticipants();
dvLoanParticipants.setPartyId(Long.valueOf(partyId));
dvLoanParticipants.setDvpParticipantName("Builder");
dvLoanParticipants.setDvpParticipantType("Individual");
Party party = new Party();
Map<String, DVLoanParticipants> dvLoanParticipantsMap = new
java.util.HashMap<>();
dvLoanParticipantsMap.put("Builder", dvLoanParticipants);
party.setPartyId(Long.valueOf(partyId));
party.setDvLoanParticipantsMap(dvLoanParticipantsMap);
repository.save(party);
What is the mistake i am doing ?
The root cause of your problem in this part:
#OneToMany(targetEntity = DVLoanParticipants.class,cascade = CascadeType.ALL)
#JoinColumn(name = "LOAN_ID")
#MapKey(name="dvpParticipantName")
private Map<String, DVLoanParticipants> dvLoanParticipantsMap;
actually for your case the column name in the #JoinColumn means:
If the join is for a unidirectional OneToMany mapping using a foreign key mapping strategy, the foreign key is in the table of the target entity.
So, assuming for the clarity that you want to map the following schema:
create table PARTY
(
PARTY_ID int,
-- ...
primary key (PARTY_ID)
);
create table DV_LOAN_PARTICIPANTS
(
PARTY_ID int,
DVP_PARTICIPANT_NAME varchar(50),
DVP_PARTICIPANT_TYPE varchar(10),
-- ...
primary key (PARTY_ID, DVP_PARTICIPANT_NAME),
foreign key (PARTY_ID) references PARTY(PARTY_ID)
);
You can use the following mapping:
#Entity
#Table(name = "PARTY")
public class Party
{
#Id
#Column(name = "PARTY_ID")
private Long partyId;
// I use fetch = FetchType.EAGER instead of deprecated #LazyCollection(LazyCollectionOption.FALSE)
// targetEntity = DVLoanParticipants.class is redundant here
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "PARTY_ID") // this is DV_LOAN_PARTICIPANTS.PARTY_ID column
#MapKey(name = "dvpParticipantName")
private Map<String, DVLoanParticipants> dvLoanParticipantsMap;
public Party()
{
dvLoanParticipantsMap = new HashMap<>();
}
// getters / setters
public void addParticipant(DVLoanParticipants p)
{
this.dvLoanParticipantsMap.put(p.getDvpParticipantName(), p);
p.setPartyId(getPartyId());
}
}
#Entity
#Table(name = "DV_LOAN_PARTICIPANTS")
public class DVLoanParticipants implements Serializable
{
#Id
#Column(name = "PARTY_ID")
private Long partyId;
#Id
#Column(name = "DVP_PARTICIPANT_NAME")
private String dvpParticipantName;
#Column(name = "DVP_PARTICIPANT_TYPE")
private String dvpParticipantType;
// getters / setters
}
and example how to save:
Party party = new Party();
party.setPartyId(2L);
// ...
DVLoanParticipants part1 = new DVLoanParticipants();
part1.setDvpParticipantName("Name 3");
part1.setDvpParticipantType("T1");
DVLoanParticipants part2 = new DVLoanParticipants();
part2.setDvpParticipantName("Name 4");
part2.setDvpParticipantType("T1");
party.addParticipant(part1);
party.addParticipant(part2);
repository.save(party);
and several notes:
The LazyCollectionOption.TRUE and LazyCollectionOption.FALSE values are deprecated since you should be using the JPA FetchType attribute of the #OneToMany association.
You use hibernate specific approach for mapping сomposite identifiers. As it's mentioned in the hibernate documentation:
The restriction that a composite identifier has to be represented by a primary key class (e.g. #EmbeddedId or #IdClass) is only JPA-specific.
Hibernate does allow composite identifiers to be defined without a primary key class via multiple #Id attributes.
But if you want to achieve more portability you should prefer one of the jpa allowed approaches.

spring data - how to make unique constraint with custom logic?

using spring data, I created User 1:N UserDog N:1 Dog relation. Both 1:N relations are unidirectional #ManyToOne with UserDog being the custom relation table.
User entity:
#Entity
public class User {
#Id
#GeneratedValue
private long id;
#Column(nullable = false)
private String name;
}
Dog entity:
#Entity
public class Dog {
#Id
#GeneratedValue
private long id;
#Column(nullable = false)
private String name;
}
User dog relation table:
#Entity
public class UserDog {
#Id
#GeneratedValue
private long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn
#OnDelete(action = OnDeleteAction.CASCADE)
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn
#OnDelete(action = OnDeleteAction.CASCADE)
private Dog dog;
#Column(nullable = false)
private Instant createdOn = Instant.now();
#Column
private Instant disabledOn;
}
Use case
Use case is to store history of User-Dog bindings, where the concrete Dog can be bound only to one User at the time. That's why I added createdOn and disabledOn columns to UserDog. disabledOn being null indicates that the relation is active and the Dog can't be assigned another User. If disabledOn is not null, then the record is stored only for evidence purposes and the Dog can be assigned to the same or another User again.
Question
How to ensure that the combination of Dog's id and disabledOn being null is unique in UserDog table?
In pseudo code I want something like this:
#Entity
#UniqueConstraint({#UniqueConstraint(this.dog.id), #NullConstraint(this.disabledOn)})
public class UserDog {...}
You can simply create a unique constraint for dogId and disabledOn.
It does add the limitation that no two relationships may end at the same time but this seems to fit your use case.

How to send POST request with Many-to-many relationship in Spring?

I'm trying to add a order with equipment list, here's my entities:
the order entity
#Entity #Table(name = "orders") public class Order extends Ticket{
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private Set<OrderEquipment> orderEquipments = new HashSet<>();}
the equipment entity
#Entity #Table(name = "equipments") public class Equipment extends DateAudit {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(max = 30)
private String name;
#NotNull
private Long nbr_piece ;
#OneToMany(mappedBy = "equipment", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<OrderEquipment> orderEquipments = new HashSet<>();}
and the order_equipment entity
#Entity #Table(name = "order_equipment") public class OrderEquipment extends DateAudit { #Id
#ManyToOne
#JoinColumn(name = "order_id")
private Order order;
#Id
#ManyToOne
#JoinColumn(name = "equipment_id")
private Equipment equipment;
#NotBlank
#Column(name = "quantity")
private Long quantity;}
here is the add function in the orderController
#PostMapping("/orders")
public Order createOrder(#Valid #RequestBody Order Order){
Order.setObservateurEmail(Order.getObservateurEmail());
Order.setObject(Order.getObject());
Order.setDescription(Order.getDescription());
return orderRepository.save(Order);
}
I have seen a mistakes there, lemme try to help you. Since you issue is not clear, please lemme know if it does/does not work:
You have two bidirectional mappings there:
Order (with ALL cascade) <-> OrderEquipment
Equipment (with ALL cascade) <-> OrderEquipment
You are using #JoinColumn for both of them, even though they are bidirectional. Please take a look at this. You should always use the mappedBy attribute when defining bidirectional relationships.
Now, you are receiving an Order object from a POST request, making changes to 3 attributes and then saving it. Since the mapping between the Order and OrderEquipment have the CascadeType.ALL attribute, any save on the Order object will save all OrderEquipment children associated. If the Order object you are receiving already have OrderEquipment children, your method will also save/update them.
Your POST mapping looks good to me, just take care with your table relationship definitions.
Take a look at this answer to check how a lits of entities should be formatted on a JSON POST.

How to correctly setup bi-directional one-to-many relationship in Hibernate

I have gone through several Q/As on stackoverflow and several other tutorials online to find what am I exactly missing for the problem described below.
Background:
I am learning to use restful APIs in my android application and for that reason, I have written a simple doctor-patient management app. There's a one to many relationship between a doctor and his patients. i.e. One doctor can have many patients.
Problem:
I am using one user table that is supposed to maintain all the user information, i.e. doctor and patients' basic info is maintained in this table and this table is also used for determining what type of user is trying to log in, so that appropriate screens can be presented. Here's how the entity for that table looks like:
#Entity
public class ConcreteUser implements User{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name= "USER_ID")
private long id;
private String name;
private String email;
private int age;
private SEX sex;
private String accessLevel;
public ConcreteUser() {
}
// gettersand setters here
}
This Entity has one to one relationship with tables that maintain doctors and patient entities. And as mentioned earlier, doctors and patient entities have one to one relationship. Here's how those two entities look like:
#Entity
public class PatientEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "PATIENT_RECORD_ID")
private long recordId;
// specify this as a foreign key from ConcreteUser entity
#OneToOne(cascade = CascadeType.ALL) /*CascadeType.ALL should not be required according to almost all the tutorials I have seen - But I always get unsaved transient object error if I don't do this and try to save a patient entity */
#JoinColumn(name="USER_ID")
private ConcreteUser patient;
#ManyToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "DOCTOR_RECORD_ID")
#JsonBackReference
private DoctorEntity doctor;
public PatientEntity() {
}
public void setDoctor(DoctorEntity doctor) {
this.doctor = doctor;
//if(!doctor.getPatients().contains(this)){
// doctor.addPatient(this);
//}
/* Commented out code always leads to stack overflow error */
/* although, according to tutorial in the link below, this code is necessary */
/* http://en.wikibooks.org/wiki/Java_Persistence/OneToMany */
}
// getters and setters are not shown
}
And lastly, here's my Doctor entity:
#Entity
public class DoctorEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "DOCTOR_RECORD_ID")
private long recordId;
// specify this as a foreign key from ConcreteUser entity
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="USER_ID")
private ConcreteUser doctor;
#OneToMany(mappedBy = "doctor", cascade = CascadeType.ALL)
#JsonManagedReference
private Collection<PatientEntity> patients = new ArrayList<PatientEntity>();
public DoctorEntity() {
}
public boolean addPatient(PatientEntity p) {
boolean status = false;
status = patients.add(p);
//if (p.getDoctor() != this) {
// p.setDoctor(this);
//}
return status;
}
public boolean removePatient(PatientEntity p) {
boolean status = false;
status = patients.remove(p);
//if (p.getDoctor() != this) {
// p.setDoctor(null);
//}
return status;
}
// getters and setters are not shown. Same problem with the commented out code as described above
}
Now to test the fact that, when a POJO object of PatientEntity can be saved and it retains the information, I am using the following test case:
#Test
public void TestPatientDoctorManyToOne() throws Exception{
PatientEntity p1 = TestData.getPatientEntity(patient1);
DoctorEntity d = TestData.getDoctorEntity(doctor);
p1.setDoctor(d);
p1 = patientService.addPatient(p1);
assertNotNull(p1);
PatientEntity p2 = patientService.getPatientById(p1.getRecordId());
assertNotNull(p2);
assertNotNull(p2.getDoctor());
assertEquals(p1.getRecordId(), p2.getRecordId());
assertEquals(p1.getDoctor().getRecordId(), p2.getDoctor().getRecordId());
assertEquals(p1.getDoctor().getDoctor().getEmail(), p2.getDoctor().getDoctor().getEmail());
}
In the above test case, assertNotNull(p2.getDoctor()); assertion fails, as the returned patient entity does not contain doctor object at all.
Here's the log:
Outgoing:
"{"recordId":0,"patient":{"id":0,"name":"Patient-0ee1407e-2d2b-4c6c-a57b-e2fad24fafa5","email":"0ee1407e-2d2b-4c6c-a57b-e2fad24fafa5","age":50,"sex":"MALE","accessLevel":"patient"},"doctor":{"recordId":0,"doctor":{"id":0,"name":"Doctor-f025c8ce-8c31-4681-b673-a9e322dccf5a","email":"f025c8ce-8c31-4681-b673-a9e322dccf5a","age":50,"sex":"MALE","accessLevel":"doctor"},"patients":[]}}"
Incoming:
{"recordId":16,"patient":{"id":33,"name":"Patient-0ee1407e-2d2b-4c6c-a57b-e2fad24fafa5","email":"0ee1407e-2d2b-4c6c-a57b-e2fad24fafa5","age":50,"sex":"MALE","accessLevel":"patient"}}
As you can see, the returned object doesn't have a Doctor entity at all.
However, when I try to save the doctor entity with patients in it, it is saved with no problem. i.e. the following test case passes:
#Test
public void testDoctorPatientOneToMany() throws Exception {
PatientEntity p1 = TestData.getPatientEntity(patient1);
PatientEntity p2 = TestData.getPatientEntity(patient2);
DoctorEntity d = TestData.getDoctorEntity(doctor);
d.addPatient(p1);
d.addPatient(p2);
d = doctorService.addDoctor(d);
DoctorEntity d2 = doctorService.getDoctorById(d.getRecordId());
assertNotNull(d2);
assertEquals(d2.getRecordId(), d.getRecordId());
assertEquals(d2.getDoctor().getEmail(), d.getDoctor().getEmail());
}
Transactions for the above test case:
Outgoing:
"{"recordId":17,"doctor":{"id":43,"name":"Doctor-e4baeee7-eaaa-443e-8845-e0b12d7be82f","email":"e4baeee7-eaaa-443e-8845-e0b12d7be82f","age":50,"sex":"MALE","accessLevel":"doctor"},"patients":[{"recordId":21,"patient":{"id":44,"name":"Patient-d8aab5ad-d3d9-4442-b8de-678de9e3b1ce","email":"d8aab5ad-d3d9-4442-b8de-678de9e3b1ce","age":50,"sex":"MALE","accessLevel":"patient"}},{"recordId":22,"patient":{"id":45,"name":"Patient-5c9cfa3c-ee79-4aea-a193-4d8762f58431","email":"5c9cfa3c-ee79-4aea-a193-4d8762f58431","age":50,"sex":"MALE","accessLevel":"patient"}}]}[\r][\n]"
Incoming:
{"recordId":17,"doctor":{"id":43,"name":"Doctor-e4baeee7-eaaa-443e-8845-e0b12d7be82f","email":"e4baeee7-eaaa-443e-8845-e0b12d7be82f","age":50,"sex":"MALE","accessLevel":"doctor"},"patients":[{"recordId":21,"patient":{"id":44,"name":"Patient-d8aab5ad-d3d9-4442-b8de-678de9e3b1ce","email":"d8aab5ad-d3d9-4442-b8de-678de9e3b1ce","age":50,"sex":"MALE","accessLevel":"patient"}},{"recordId":22,"patient":{"id":45,"name":"Patient-5c9cfa3c-ee79-4aea-a193-4d8762f58431","email":"5c9cfa3c-ee79-4aea-a193-4d8762f58431","age":50,"sex":"MALE","accessLevel":"patient"}}]}
I apologize for along post, but I think I have exhausted all my resources. I'd absolutely worship anyone who decides to take a look at it and points out where the problem is. At this point, I am not even sure if I am testing this thing right, or my expectations are correct.
This is because by specifying mappedBy="doctor" in the DoctorEntity class
#Entity
public class DoctorEntity {
#OneToMany(mappedBy = "doctor", cascade = CascadeType.ALL)
#JsonManagedReference
private Collection<PatientEntity> patients = new ArrayList<PatientEntity>();
public DoctorEntity() {
}
}
you are saying that DoctorEntity is no more the owner of the one-to-many relationship. PatientEntity is the owner. Hence during the save of PatientEntity (in the first test case) the foreign key of doctor entity is not updated in the Patient table.
mappedBy is equivalent to specifying inverse=true in an xml format.
Follow this link for a detailed explanation on what queries are executed when inverse=true or inverse=false is specified in the one-to-many mapping.

how to add #onetoone mapping for self entity in hibernate

how to add one-to-one mapping for the self entity. Like in this example. I want to have parent-child relationship for the Person itself.
#Entity
#Table(name="PERSON")
public class Person {
#Id
#Column(name="personId")
private int id;
#OneToOne
#JoinColumn()
private Person parentPerson;
}
Here is example of bidirectional self mapping #OneToOne (I change column names to SQL notation):
#Entity
#Table(name="PERSON")
public class Person {
#Id
#Column(name="person_id")
private int id;
#OneToOne
#JoinColumn(name = "parent_person_id")
private Person parentPerson;
#OneToOne(mappedBy = "parentPerson")
private Person childPerson;
}
But, I don't understand why you want to use #OneToOne in this case.
I am using it like this:
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "PARENT_ID", nullable = true)
private Person parent;
In order to add parent from your service layer, you need to already have at least one Person in the database.
Lets presume you do. Then create a new person. For e.g.:
#Transactional
public void createPerson() {
Person parent = //get your parent
Person child = new Person();
if (parent != null) {
child.setParent(parent);
}
}
If this is what you mean..

Resources