Remove in #OneToOne unidirectionnal relation JPA - spring

#Data
#Entity
public class Phone {
#Id
#Column(name="brand_phone")
private String brand;
#Enumerated(EnumType.STRING)
private Color color = Color.BLACK;
private int capacity;
private double price;
private boolean guaranteed;
}
#Data
#Entity
public class Characteristic {
#Id
#Column(name="brand_phone")
private String brandPhone;
private String description;
private double note;
private boolean isAvailable;
#MapsId("brand_phone")
#OneToOne
#JoinColumn(name="brand_phone", insertable=false, updatable=false)
private Phone phone;
}
The characteristic entity has foreign key (which is also the primary key) of a phone. The relation is unidirectional, only the characteristic know his phone. The problem is the following, when i remove a phone i got a "Referential integrity constraint violation", cause the phone is still exist in Characteristic entity. Is there way to remove a Phone and his Characteristic at the same time , avoiding use 2 request deletes (one for Phone and other for Characteristic) or add a bidirectional relationship. I was thinking using orphanRemoval = true and CascaceType. All but it doesn't work. I also wan't to avoid change the unidirectionnal relation by adding a characteristic reference in Phone entity.

Related

Spring data jpa hibernate one to may duplicate issue

I have one to many relation ship between a User and UserRole.
public class User {
#Id
#GeneratedValue
private long id;
#Column(unique = true)
private String username;
private String password;
#OneToMany(fetch=FetchType.EAGER)
#JoinTable(
name = "user_roles",
joinColumns = {#JoinColumn(name="userId")},
inverseJoinColumns = {#JoinColumn(name="roleId")}
)
private Collection<UserRole> roles;
}
and
public class UserRole {
#Id
#GeneratedValue
private long id;
#Column(unique = true)
private String roleName;
}
I am able to save a user at first. But when I try to save another user it rejects with an exception of duplicated entry on the junction table
One thing I noticed is when we have One-to-Many association the many side foreign key will be a primary key on the junction table. So, I need to make the relation Many-to-Many. So that the combination of both foreign keys will serve as a composite key.

Spring JPA bidirectional relation on multiple nested entities

I know there has been multiple questions on bidirectional relations using spring jpa in the past but my case is a little bit different because i am using 3 entities with 2 relationships to implement a medical system
I have 3 entities : doctor/patient/appointment
here is the code for the 3 entities
please note all setters , getters and constructors implemented but ommited here for clarity
Patient class
#Entity
public class resPatient {
#Id
#GeneratedValue( strategy= GenerationType.IDENTITY )
private long code;
private String name;
private String gender;
private String email;
private String mobile;
private int age;
private String notes;
#OneToMany(mappedBy = "patient")
List<resPackageMembership> memberships;
#OneToMany(mappedBy = "patient")
List<resAppointment> appointments;
#OneToMany(fetch = FetchType.LAZY,mappedBy = "patient")
List<resMedImage> medImages;
Doctor class
#Entity
public class resDoctor {
#Id
#GeneratedValue( strategy= GenerationType.IDENTITY )
private long code;
private String name;
private String mobile;
private String email;
private String gender;
private int age;
private String speciality;
#OneToMany(mappedBy = "doctor")
List<resAppointment> appointments;
Appointment class
#Entity
public class resAppointment {
#Id
#GeneratedValue( strategy= GenerationType.IDENTITY )
private long code;
private String speciality;
#Basic
#Temporal(TemporalType.TIMESTAMP)
private Date dateCreated;
#Basic
#Temporal(TemporalType.TIMESTAMP)
private Date dateToVisit;
private String status;
private String notes;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "doctorCode")
private resDoctor doctor;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "patientCode")
private resPatient patient;
the way my medical system should work is that when i get a patient using my restful controller i want all the patient data including his appointments but this leads to an infinite loop as the appointment has the doctor which also has appointments and so on.
i cannot user #JSONIGNORE as there are 2 relationships i want to get the patient with his appointments which should have the doctor without the appointments array and should not have any patient data as i already am in the patient object
As a general best-practice, it's recommended to separate the entities from the data transfer objects used for the rest controllers. With DTO's in place, you have more control on which data to include and serialize within them to avoid the circlular references.
If you like check out https://bootify.io, it generates the DTOs from your database schema, but the custom endpoint you still need to define/build.
I develop an annotation processor called beanknife recently, it support generate DTO from any class. You need config by annotation. But you don't need change the original class. This library support configuring on a separate class. Of course you can choose which property you want and which you not need. And you can add new property by the static method in the config class. For your question:
// this will generate a DTO class named "resPatientView".
// You can change this name using genName attribute.
#ViewOf(value=resPatient.class, includePattern = ".*")
public class PatientViewConfigure {
// here tell the processor to automatically convert the property appointments from List<resAppointment> to List<resAppointmentWithoutPatient>.
// resAppointmentWithoutPatient is the generated class configured at the following.
// Note, although at this moment it not exists and your idea think it is an error.
// this code really can be compiled, and after compiled, all will ok.
#OverrideViewProperty("appointments")
private List<resAppointmentWithoutPatient> appointments;
}
// here generated a class named resAppointmentWithoutPatient whick has all properties of resAppointment except patient
#ViewOf(value=resAppointment.class, genName="resAppointmentWithoutPatient", includePattern = ".*", excludes={"patient"})
public class AppointmentWithoutPatientViewConfigure {
// the doctor property will be converted to its dto version which defined by the configure class DoctorWithoutAppointmentsViewConfigure.
#OverrideViewProperty("doctor")
private resDoctorWithoutAppointments doctor;
}
// here we generate a class which has all properties of resDoctor except appointments
#ViewOf(value=resDoctor.class, genName="resDoctorWithoutAppointments", includePattern = ".*", excludes={"appointments"})
public class DoctorWithoutAppointmentsViewConfigure {}
// in you rest controller. return the dto instead of the entities.
resPatient patient = ...
resPatientView dto = resPatientView.read(patient);
List<resPatient> patients = ...
List<resPatientView> dto = resPatientView.read(patients);
At the end, the class resPatientView will has the same shap with resPatient except its appointments not having patient property and its doctor property is replaced with a version without appointments property.
Here are more examples.
The version 1.10 is ready. Will fix some bug and support the configure bean to be managed by spring.

How to make sure country is not added twice in mysql database in hibernate using spring mvc one to many relationship?

I have two entity first one is Student and the other one is an address. It is one too many relations ie one address can have many students. Now I have is a registration page. When I first register a student say with country name united states, it is saved in database giving primary id as 1 to united states and correspondingly gives correct id in student's database. But when I again try to register the next student with different information but the same country in my case united states it gives me a new primary key for the same country. But as this one is, one to many relationships I am thinking if there is anything in hibernate that maps to the same id in address database, therefore, I will only have one value of the united states in the address database. I need to have only a single entry of the united states a database. What is the appropraite way of doing need? Thank you
This one is Address table
#Entity
#Table(name = "tbl_address")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "address_id")
private int addressId;
private String country;
#OneToMany(targetEntity = Student.class, mappedBy = "address")
private List<Student> student;
This one is Student table
#Entity
#Table(name = "tbl_student")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "student_id")
private int studentId;
#Column(name = "first_Name")
private String firstName;
#Column(name = "second_Name")
private String secondName;
private String email;
#Column(name = "mobile_no")
private float mobileNo;
#DateTimeFormat(pattern = "yyyy-MM-dd")
#Temporal(TemporalType.DATE)
private Date dob;
private String gender;
#ManyToOne(cascade = {CascadeType.MERGE , CascadeType.ALL} )
#JoinColumn(name = "address_id")
private Address address;
}
This one is just the implementation in StudentRepositoryImpl class
#Override
public void saveUserInfo(Student user) {
Session session = HibernateUtil.getSession(sessionFactory);
session.save(user);
}
#Override
public void saveAddressInfo(Address address) {
// TODO Auto-generated method stub
Session session = HibernateUtil.getSession(sessionFactory);
session.save(address);
}
First option. On UI you should have a list of available countries (so I expect you already have populated country table in database). So UI will display to users available country names, but on backend side you will operate with countryId.
Second option. In case on UI you want users insert any string as country name, and if you want to register country on any new provided country name. For that you need to have unique index in country table on country name (it's up to you to decide whether it will be case insensitive or not).
Before saving student entity, you should fetch available country by it's name. if such one exist - use countryId with saving studend. if not exist - create a new country entity, and use generated countryId in studend entity.
Second option is risky in that way users could provide any values for country names, and probably with typo, so the first option is preferable.
Have a separate table for Country (That would be called master data). Then the Address entity would be something like this:
#Entity
#Table(name = "tbl_address")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "address_id")
private int addressId;
#ManyToOne
private Country country;
#OneToMany(targetEntity = Student.class, mappedBy = "address")
private List<Student> student;
You can get the list of students doing a join query with Country and Address tables.
Hope this is helpful

Saving entity with composite key get ConversionNotSupportedException

I use spring boot 2 and some of my entities have composite key
When I try to save an entity, I get this error
Failed to convert request element:
org.springframework.beans.ConversionNotSupportedException: Failed to
convert property value of type 'java.lang.Integer' to required type
'com.lcm.model.SamplingsPK' for property 'sampling'; nested exception
is java.lang.IllegalStateException: Cannot convert value of type
'java.lang.Integer' to required type 'com.lcm.model.SamplingsPK' for
property 'sampling': no matching editors or conversion strategy found
I get my entity with that method
public Samples findById(Integer id, int year, String sampleLetter) {
Optional<Samples> optSamples = samplesRepository.findById(new SamplesPK(new SamplingsPK(year, id), sampleLetter));
if (optSamples.isPresent()) {
return optSamples.get();
}
return null;
}
Samples samples = samplesService.findById(idSeq, year, samplesLetter);
Compressions compressionTest = null;
if (samples.getTestSamples().getAbsorptionTest() != null) {
compressionTest = samples.getTestSamples().getCompressionTest();
} else {
compressionTest = new Compressions();
}
samplesService.save(samples);
My entity
#Entity
#IdClass(SamplesPK.class)
public class Samples extends BaseEntity{
#Id
private String sampleLetter;
#Embedded
private TestSamples testSamples;
#Id
#ManyToOne(optional=false)
#JoinColumns({
#JoinColumn(name = "sampling_id", referencedColumnName = "id"),
#JoinColumn(name = "sampling_year", referencedColumnName = "year")})
private Samplings sampling;
}
#Entity
#IdClass(SamplingsPK.class)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Samplings {
#Id
private Integer year;
#Id
#GeneratedValue
private Integer id;
#OneToMany(mappedBy = "sampling", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Samples> samples = new ArrayList<>();
}
public class SamplingsPK implements Serializable {
private int year;
private Integer id;
public SamplingsPK(int year, Integer id) {
this.id = id;
this.year = year;
}
}
public class SamplesPK implements Serializable {
private SamplingsPK sampling;
private String sampleLetter;
public SamplesPK(SamplingsPK sampling, String sampleLetter) {
this.sampling = sampling;
this.sampleLetter = sampleLetter;
}
}
edit
no problem to save sample, when I pass from sampling
The problem is that since the IDs are set manually and there's no #Version property on these entities then Spring Data has no good way of knowing if the entity is a brand new one or an existing one. In this case it decides it is an existing entity and attempts a merge instead of a persist. This is obviously a wrong conclusion.
You can read more about how Spring Data decides if an entity is new or not here.
The best solution I've found is to always let entity classes with manually set IDs implement Persistable interface. This solves the problem. I make this a rule for myself for any such case. Most of the time I do not have to implement Persistable because my entity either has an auto-generated key or my entity uses a "#Version" annotation. But this is special case.
So, as per the recommendation in the Spring official documentation, for example the Samplings class would become:
#Entity
#IdClass(SamplingsPK.class)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Samplings implements Persistable<SamplingsPK> {
#Transient
private boolean isNew = true;
#Id
private Integer year;
#Id
#GeneratedValue
private Integer id;
#OneToMany(mappedBy = "sampling", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Samples> samples = new ArrayList<>();
#Override
public boolean isNew() {
return isNew;
}
#PrePersist
#PostLoad
void markNotNew() {
this.isNew = false;
}
#Override
public SamplingsPK getId() {
return new SamplingsPK(year, id);
}
}
This issue is tracked at https://jira.spring.io/browse/DATAJPA-1391 and has to do with the use of #Id #ManyToOne inside of Samples. As a workaround, you can try creating a constructor for Samplings that takes in its two primary keys, or maybe one that takes a java.lang.Integer? That's what worked for a single level of composite primary keys, but it might not work if you have multiple levels.
You also have year in SamplingsPK typed as an int rather than an Integer. This may cause problems with PK recognition, since special consideration is needed to handle autobox-able primitive classes and I doubt it was considered.
I noticed this too. It does not happen on my IDE on Windows but it happens on the Azure build server
I was on org.springframework.data:spring-data-jpa:jar:2.4.5:compile.
I upgraded the BOM to <spring-data-bom.version>2020.0.15</spring-data-bom.version> so I have org.springframework.data:spring-data-jpa:jar:2.4.15:compile
Once I did that it started working correctly.

Delete object in OneToOne relationship ConstraintViolationException

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.

Resources