How to load a full graph of 2 entities that are in relationship #OneToMany each other with a Join Table - spring-boot

I'm using Spring Boot and Spring Data and I have a problem when trying to load entities using JPA and EntityGraph.
I have a Patient and Insurance entities. Each Patient can have many Insurances and each Insurance can be assigned to many patients. I decided to use a Join Table PatientInsurance because I need to store extra fields like 'active', and also the relation code (a Patient can be a Member, Spouse, or Child for that specific insurance).
Using Spring Data repositories I annotated the method to find a patient, with an EntityGraph, to have ready the list of PatientInsurances (and Insurances) for that patient in one query.
This is the code (I removed the non-necessary parts in the scope)
Patient class
#Entity
#Table(name = "patient")
public class Patient {
#NotNull
#NotEmpty
#Column(length = 60, nullable = false)
private String patientFirstName;
#NotNull
#NotEmpty
#Column(length = 60, nullable = false)
private String patientLastName;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "patient", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
List<PatientInsurance> patientsInsurances = new ArrayList<>();
public void addPatientInsurance(PatientInsurance patientIns) {
if (!patientsInsurances.contains(patientIns)) {
patientsInsurances.add(patientIns);
}
}
//other properties...
Insurance class
#Entity
#Table(name = "insurance")
public class Insurance {
#Column(name = "policy_id", length = 20)
private String policyId;
#OneToMany(mappedBy = "insurance", fetch = FetchType.LAZY,cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
private List<PatientInsurance> patientsInsurances = new ArrayList<PatientInsurance>();
public void addPatientInsurance(PatientInsurance patientIns) {
if (!patientsInsurances.contains(patientIns)) {
patientsInsurances.add(patientIns);
}
}
//other properties
Entity for the join table between patient and insurance (needed a join table for extra field in this entity like active and relCode
#Entity
#IdClass(PatientInsurance.PatientInsurancePK.class)
#Table(name = "patient_insurance")
public class PatientInsurance implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "patient_id")
private Patient patient;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "insurance_id")
private Insurance insurance;
#Column(name = "active")
private boolean active;
#Column(length = 1)
private String relCode;
public PatientInsurance() {
insurance = new Insurance();
patient = new Patient();
}
public PatientInsurance(Patient p, Insurance i, boolean active, String relCode) {
this.patient = p;
this.insurance = i;
this.active = active;
this.relCode = relCode;
p.addPatientInsurance(this);
i.addPatientInsurance(this);
}
public Patient getPatient() {
return patient;
}
public Insurance getInsurance() {
return insurance;
}
public void setInsurance(Insurance insurance) {
this.insurance = insurance;
insurance.addPatientInsurance(this);
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
public void setPatient(Patient patient) {
this.patient = patient;
patient.addPatientInsurance(this);
}
public String getRelCode() {
return relCode;
}
public void setRelCode(String relCode) {
this.relCode = relCode;
}
static public class PatientInsurancePK implements Serializable {
protected Patient patient;
protected Insurance insurance;
public PatientInsurancePK() {
}
public PatientInsurancePK(Patient patient, Insurance insurance) {
this.patient = patient;
this.insurance = insurance;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PatientInsurancePK)) return false;
PatientInsurancePK that = (PatientInsurancePK) o;
if (!patient.equals(that.patient)) return false;
return insurance.equals(that.insurance);
}
#Override
public int hashCode() {
int result = (patient != null) ? patient.hashCode() : 0;
result = 31 * result + ((insurance != null) ? insurance.hashCode() : 0);
return result;
}
}
}
Implementation of the PatientService
#Transactional
#Service("patientService")
public class PatientServiceImpl implements PatientService {
#Autowired
PatientRepository patientRepository;
#Override
public Optional<Patient> findByIdFull(Long id) {
Optional<Patient> patient = patientRepository.findById(id);
return patient;
}
//other methods...
Patient Repository
public interface PatientRepository extends JpaRepository<Patient, Long> {
#EntityGraph(
attributePaths = {
"patientsInsurances",
"patientsInsurances.patient",
"patientsInsurances.insurance"},
type = EntityGraph.EntityGraphType.LOAD)
Optional<Patient> findById(Long id);
A snippet of code that calls the method in PatientService
Optional<Patient> patientOptional = patientService.findByIdFull(p.getId());
if (patientOptional.isPresent()) {
Patient patient1 = patientOptional.get();
List<PatientInsurance> patientInsurances = patient1.getPatientInsurances();
PatientInsurances patientInsurance = patientInsurances.get(0);
Patient patient2 = patientInsurance.getPatient(); //and this is same istance of patient1, it's ok
Insurance insurance = patientInsurance.getInsurance();
//here is the problem!!!
insurance.getPatientInsurances();
//Unable to evaluate the expression Method threw 'org.hibernate.LazyInitializationException' exception.
So the problem seems that when I go inside the patient side, I can loop into his Insurances without problems, but when I try to do the same starting from the Insurance instance, I cannot loop into its patients cause they are lazily loaded.
So how to make jpa download the full graph in the correct way?

Related

Spring JPA hibernate how to persist children (remove, add, or update) from #OneToMany parent column?

I'm trying to solve this problem since a while and I haven't achieved a 100% solution.
First of all I have to describe my problem. I'm developping a restaurant application, and amoung the Entities, I have the Entity Ingredient and as you know Ingredient can consist of other Ingredient with a specific quantity. So I created an Entity SubIngredient with an Embedded Id.
And to persist subIngredients list I tried a combinations of Cascade and orphanRemoval, each combination worked for some operation but not for the others.
I started by using CascadeType.ALL and the new subIngredient persisted successfuly from the #OneToMany propertiy, But if I try to remove an subIngredient from the subIngredients list and save this error appear.
java.lang.StackOverflowError: null
at com.mysql.cj.NativeSession.execSQL(NativeSession.java:1109) ~[mysql-connector-java-8.0.23.jar:8.0.23]......
I loked in the net for a solution and I find the I have to use orphanremoval = true I tried it but it didn't work until I changed cascade from CascadeType.ALL to CascadeType.PERSIST. But this one make the persistance of new SubIngredient this error aprear
Caused by: javax.persistence.EntityNotFoundException: Unable to find com.example.Resto.domain.SubIngredient with id com.example.Resto.domain.SubIngredientKey#51b11186........
These are my Enities:
#Entity
public class Ingredient {
#Id
#GeneratedValue( strategy = GenerationType.IDENTITY)
#Column(name="ID")
private long id;
#NotNull
#Column(unique=true)
private String name;
private String photoContentType;
#Lob
private byte[] photo;
#JsonIgnoreProperties({"photoContentType","photo"})
#ManyToOne
private IngredientType ingredientType;
#OneToMany(mappedBy = "embId.ingredientId", fetch = FetchType.EAGER,
cascade = CascadeType.ALL /*or orphanRemoval = true, cascade = CascadeType.PERSIST*/ )
private Set<SubIngredient> subIngredients = new HashSet<SubIngredient>();
getters and setters.....
And
#Entity
#AssociationOverrides({
#AssociationOverride(name = "embId.ingredientId",
joinColumns = #JoinColumn(name = "ING_ID")),
#AssociationOverride(name = "embId.subIngredientId",
joinColumns = #JoinColumn(name = "SUB_ING_ID")) })
public class SubIngredient {
#EmbeddedId
private SubIngredientKey embId = new SubIngredientKey();
private double quantity;
getters and setters....
And
#Embeddable
public class SubIngredientKey implements Serializable{
#ManyToOne(cascade = CascadeType.ALL)
private Ingredient ingredientId;
#ManyToOne(cascade = CascadeType.ALL)
private Ingredient subIngredientId;
getters and setters...
The stackoverflow happen because you use a Set<> with Hibernate. When Hibernate retrieves the entities from your DB, it will fill up the Set<> with each entities. In order to that, hashode/equals will be used to determine wether or not the entitie is already present in the Set<>. By default, when you call the hashcode of Ingredient, this happen:
hashcode Ingredient -> hashcode SubIngredient -> hashcode Ingredient
which will result in an infinite call of hashcode method. That's why you have a stackoverflow error.
The same thing will happen with equals/toString.
So to avoid such an issue, it's best to override hashcode, equals and toString.
I have solved the problem by making some changes to may Entities and override equals/hashcode methods thanks Pilpo.
#Embeddable
public class SubIngredientKey implements Serializable{
private Long ingredientId;
private Long subIngredientId;
/**
* #return the ingredientId
*/
#Override
public int hashCode() {
return Objects.hash(ingredientId, subIngredientId);
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof SubIngredientKey)) {
return false;
}
SubIngredientKey other = (SubIngredientKey) obj;
return Objects.equals(ingredientId, other.ingredientId)
&& Objects.equals(subIngredientId, other.subIngredientId);
}
}
#Entity
public class SubIngredient {
#EmbeddedId
private SubIngredientKey embId = new SubIngredientKey();
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("ingredientId")
private Ingredient ingredient;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("subIngredientId")
private Ingredient subIngredient;
private double quantity;
#JsonIgnore
public SubIngredientKey getId() {
return embId;
}
public void setId(SubIngredientKey id) {
this.embId = id;
}
#JsonIgnoreProperties({"subIngredients","photo","photoContentType","ingredientType"})
public Ingredient getIngredient() {
return ingredient;
}
public void setIngredient(Ingredient ingredient) {
this.ingredient = ingredient;
}
#JsonIgnoreProperties({"subIngredients","photo","photoContentType","ingredientType"})
public Ingredient getSubIngredient() {
return subIngredient;
}
public void setSubIngredient(Ingredient subIngredient) {
this.subIngredient = subIngredient;
}
public double getQuantity() {
return quantity;
}
public void setQuantity(double quantity) {
this.quantity = quantity;
}
#Override
public String toString() {
return "subIngredient= " + getSubIngredient().getName() + " , quantity= " + getQuantity();
}
#Override
public int hashCode() {
return Objects.hash(ingredient,subIngredient);
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof SubIngredient)) {
return false;
}
SubIngredient other = (SubIngredient) obj;
return Objects.equals(ingredient, other.ingredient) && Objects.equals(subIngredient, other.subIngredient);
}
}
#Entity
public class Ingredient {
#Id
#GeneratedValue( strategy = GenerationType.IDENTITY)
#Column(name="ID")
private long id;
#NotNull
#Column(unique=true)
private String name;
private String photoContentType;
#Lob
private byte[] photo;
#JsonIgnoreProperties({"photoContentType","photo"})
#ManyToOne
private IngredientType ingredientType;
#OneToMany(mappedBy = "embId.ingredientId", fetch = FetchType.EAGER, cascade =
CascadeType.ALL, orphanRemoval = true)
private Set<SubIngredient> subIngredients = new HashSet<SubIngredient>();
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getPhotoContentType() {
return photoContentType;
}
public void setPhotoContentType(String photoContentType) {
this.photoContentType = photoContentType;
}
public byte[] getPhoto() {
return photo;
}
public void setPhoto(byte[] photo) {
this.photo = photo;
}
public IngredientType getIngredientType() {
return this.ingredientType;
}
public void setIngredientType(IngredientType ingredientType) {
this.ingredientType = ingredientType;
}
public Set<SubIngredient> getSubIngredients() {
return subIngredients;
}
public void setSubIngredients(Set<SubIngredient> subIngredients) {
this.subIngredients = subIngredients;
}
public void addSubIngredient(SubIngredient subIngredient) {
this.subIngredients.add(subIngredient);
}
#Override
public String toString() {
String subIngsText = "";
for(var subIngredient:this.subIngredients) {
subIngsText = subIngsText + ", " + subIngredient.toString();
}
return "{id= "+id+",name=" + name +", ingredients="+subIngsText+"}";
}
#Override
public int hashCode() {
return Objects.hash(name);
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Ingredient)) {
return false;
}
Ingredient other = (Ingredient) obj;
return Objects.equals(name, other.name);
}
}

Many to One Relationship with #IdClass

Using Spring Data JPA & Hibernate, I am saving an object Company, that has 0 to Many AccountMapping. The AccountMappings Primary Key is a composite of a String accountNumber and the Company Primary Key. When I save a new company the COMP_NUM from the Company Object is not set into the AccountMapping object. When I use long companyNumber it is zero, and Long it is NUM. Hibernate is executing the insert statement first, but how to get it to set the primary key from company into child object ?
#Entity
#Table(name = "COMPANY")
public class Company implements Serializable {
#Id
#Column(name = "COMP_NUM")
#SequenceGenerator(name = "comp_num_seq", sequenceName = "comp_num_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "comp_num_seq")
private long number;
#OneToMany(mappedBy = "companyNumber", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<AccountMapping> accountMappings;
public Company() {
super();
}
public long getNumber() {
return this.number;
}
public void setNumber(long id) {
this.number = id;
}
public List<AccountMapping> getAccountMappings() {
return accountMappings;
}
public void setAccountMappings(List<AccountMapping> accountMappings) {
this.accountMappings = accountMappings;
}
}
#Entity
#IdClass(value = AccountMappingPK.class)
#Table(name = "ACCOUNT_MAPPING")
public class AccountMapping implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ACCNT_NUM")
private String accountNumber;
#Id
#Column(name = "COMP_NUM")
private Long companyNumber;
#Column(name = "IS_PRIMARY")
private Boolean isPrimary;
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
public Long getCompanyNumber() {
return companyNumber;
}
public void setCompanyNumber(Long companyNumber) {
this.companyNumber = companyNumber;
}
public Boolean getIsPrimary() {
return isPrimary;
}
public void setIsPrimary(Boolean isPrimary) {
this.isPrimary = isPrimary;
}
}
public class AccountMapping implements Serializable {
#Column(name = "EA_ACCNT_NUM", nullable = false)
private String accountNumber;
#Column(name = "COMP_NUM", nullable = false)
private Long companyNumber;
public AccountMapping() {
// default constructor
}
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNum(String accountNumber) {
this.accountNumber = accountNumber;
}
public Long getCompanyNumber() {
return companyNumber;
}
public void setCompanyNumber(Long companyNumber) {
this.companyNumber = companyNumber;
}
#Override
public boolean equals(Object obj) {
if (obj instanceof AccountMappingPK) {
AccountMappingPK accntPk = (AccountMappingPK) obj;
if (!(accountNumber.equals(accntPk.getAccountNumber()))) {
return false;
}
if (!(accntPk.getCompanyNumber() == (companyNumber))) {
return false;
}
return true;
}
return false;
}
#Override
public int hashCode() {
int hash = (accountNumber == null ? 1 : accountNumber.hashCode());
return (int) (hash * companyNumber);
}
}
#Entity
#IdClass(value = AccountMappingPK.class)
#Table(name = "ACCOUNT_MAPPING")
public class AccountMapping implements Serializable {
#Id
#Column(name = "ACCNT_NUM")
private String accountNumber;
#Id
#ManyToOne
#JoinColumn(name = "COMP_NUM")
private Company company;
...
}
// No annotations in this class
public class AccountMappingPK implements Serializable {
private String accountNumber;
private Company company;
...
// All the getter/setter, constructors, and so on ...
}
The Hibernate ORM documentation has more details about mapping with #IdClass: See Example 134. IdClass with #ManyToOne

org.hibernate.PropertyAccessException: Could not set field value [1] value by reflection

Hi guys I am new to Spring and I am getting this error in my project:
org.hibernate.PropertyAccessException: Could not set field value [1] value by
reflection : [class com.**.domain.identities.NurseAgencyIdentity.agencyId]
setter of com.**.domain.identities.NurseAgencyIdentity.agencyId
There are some classes involved in this process: Nurse , Agency, Named(abstract), NurseAgency and NurseAgencyIdentity. There is a many-to-many relationship between Nurse--Agency with an extra column nurse record. The Named class is an abstract class that contains the fields id and name and is being used by many tables in my design being id the identifier of the descendant table. To implement the many-to-many I had to use the #Embeddable annotation in the last class NurseAgencyIdentity which is the id of my NurseAgency join table. Here is the code:
NurseAgencyIdentity
#Embeddable
#Data
public class NurseAgencyIdentity implements Serializable {
#Column(name="nurse_id")
private Long nurseId;
#Column(name="agency_id")
private Long agencyId;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NurseAgencyIdentity that = (NurseAgencyIdentity) o;
return Objects.equals(nurseId, that.nurseId) &&
Objects.equals(agencyId, that.agencyId);
}
#Override
public int hashCode() {
return Objects.hash(nurseId, agencyId);
}
}
NurseAgency
#Entity
#Data
public class NurseAgency {
#EmbeddedId
private NurseAgencyIdentity id;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("nurseId")
private Nurse nurse;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("agencyId")
private Agency agency;
private String nurseRecord;
}
Nurse
#Entity
#Data
public class Nurse {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
#Enumerated(EnumType.STRING)
private License license;
#OneToMany(mappedBy = "nurse", cascade = CascadeType.ALL, orphanRemoval = true)
private List<NurseAgency> agencies = new ArrayList<>();
// need the extra column
public void addAgency(Agency agency) {//, String nurseRecord) {
NurseAgency nurseAgency = new NurseAgency();
nurseAgency.setAgency(agency);
nurseAgency.setNurse(this);
//nurseAgency.setNurseRecord(nurseRecord);
agency.getNurses().add(nurseAgency);
}
public void removeAgency(Agency agency) {
for (Iterator<NurseAgency> iterator = agencies.iterator(); iterator.hasNext(); ) {
NurseAgency nurseAgency = iterator.next();
if (nurseAgency.getNurse().equals(this) && nurseAgency.getAgency().equals(agency)){
iterator.remove();
nurseAgency.getAgency().getNurses().remove(nurseAgency);
nurseAgency.setNurse(null);
nurseAgency.setAgency(null);
}
}
}
#Override
public String toString() {
return id + " " + firstName + " " + middleName + " " + lastName;
}
}
Named
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
#Data
public abstract class Named implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Agency
#Entity
#Data
public class Agency extends Named {
private String description;
#OneToMany(mappedBy = "agency", cascade = CascadeType.ALL, orphanRemoval = true)
private List<NurseAgency> nurses = new ArrayList<>();
}
And I am having this error when trying to seed the join table:
BootStrapData
#Component
public class BootStrapData implements CommandLineRunner {
#Autowired
private final NurseRepository nurseRepository;
#Autowired
private final AgencyRepository agencyRepository;
private final NurseAgencyRepository nurseAgencyRepository;
public BootStrapData(NurseRepository nurseRepository, AgencyRepository agencyRepository, NurseAgencyRepository nurseAgencyRepository) {
this.nurseRepository = nurseRepository;
this.agencyRepository = agencyRepository;
this.nurseAgencyRepository = nurseAgencyRepository;
}
#Override
public void run(String... args) throws Exception {
System.out.println("Loading agencies ");
ArrayList<Agency> agencies = GetAgencies();
System.out.println("Loading Nurses ");
ArrayList<Nurse> nurses = GetNurses(agencies);
nurses.stream().forEach( n -> nurseRepository.save(n));
agencies.stream().forEach( a -> agencyRepository.save(a));
//Nurses Agencies
ArrayList<NurseAgency> nurseAgencies = new ArrayList<>(1);
nurseAgencies.addAll(SetNurseAndAgencies(nurses.get(0), new Agency[]{agencies.get(0), agencies.get(1), agencies.get(2)}));
nurseAgencies.addAll(SetNurseAndAgencies(nurses.get(1), new Agency[]{agencies.get(0), agencies.get(1)}));
nurseAgencies.addAll(SetNurseAndAgencies(nurses.get(2), new Agency[]{agencies.get(1), agencies.get(2)}));
for (int i=0; i<nurseAgencies.size();i++){
nurseAgencyRepository.save(nurseAgencies.get(i)); // I've got the error in first iteration in this line
}
}
private ArrayList<Agency> GetAgencies() {
ArrayList<Agency> agencies = new ArrayList<>(3);
Agency a1 = new Agency();
a1.setName("Agency 1");
agencies.add(a1);
Agency a2 = new Agency();
a2.setName("Agency 2");
agencies.add(a2);
Agency a3 = new Agency();
a3.setName("Agency 3");
agencies.add(a3);
return agencies;
}
private ArrayList<Nurse> GetNurses(ArrayList<Agency> agencies) {
ArrayList<Nurse> nurses = new ArrayList<>(3);
Nurse n1 = new Nurse();
n1.setFirstName("Mario");
n1.setLastName("Perez");
nurses.add(n1);
Nurse n2 = new Nurse();
n2.setFirstName("Luis");
n2.setLastName("Ruiz");
nurses.add(n2);
Nurse n3 = new Nurse();
n3.setFirstName("Maria");
n3.setLastName("Crez");
nurses.add(n3);
return nurses;
}
private ArrayList<NurseAgency> SetNurseAndAgencies(Nurse nurse, Agency[] agencies) {
ArrayList<NurseAgency> nurseagencies = new ArrayList<>(agencies.length);
for (int i=0; i<agencies.length; i++){
NurseAgency na = new NurseAgency();
na.setNurse(nurse);
na.setAgency(agencies[i]);
na.setNurseRecord(nurse.getFirstName() + agencies[i].getName());
nurseagencies.add(na);
}
return nurseagencies;
}
}
Where is the problem?
Try changing the NurseAgencyIdentity declaration on NurseAgency from:
#EmbeddedId
private NurseAgencyIdentity id;
to:
#EmbeddedId
private NurseAgencyIdentity id = new NurseAgencyIdentity();
I didn't see the full stack trace but the root cause can be a NullPointerException when hibernate tries to set fields (generated agencyId [ 1 ] in your case) via reflection on NurseAgencyIdentity and it's null.
See org.hibernate.tuple.entity.AbstractEntityTuplizer#getIdentifier

Hibernate transaction and session with multiple save

Thanks, let me completely change it.
Using:
Spring Boot, Hibernate JPA
I have created a link table with a composite primary key across all 3 columns(event_attendee_link_program)
I used the JPA tools in STS IDE to generate Entities from my tables and it came up with the below code. I removed some of the columns to save space.
EventAttendee.java
#Entity
#Table(name="event_attendee")
#NamedQuery(name="EventAttendee.findAll", query="SELECT e FROM EventAttendee e")
public class EventAttendee implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="attendee_id")
private long attendeeId;
//bi-directional many-to-one association to EventAttendeeLinkProgram
#OneToMany(mappedBy="eventAttendee")
private List<EventAttendeeLinkProgram> eventAttendeeLinkPrograms;
public List<EventAttendeeLinkProgram> getEventAttendeeLinkPrograms() {
return this.eventAttendeeLinkPrograms;
}
public void setEventAttendeeLinkPrograms(List<EventAttendeeLinkProgram> eventAttendeeLinkPrograms) {
this.eventAttendeeLinkPrograms = eventAttendeeLinkPrograms;
}
public EventAttendeeLinkProgram addEventAttendeeLinkProgram(EventAttendeeLinkProgram eventAttendeeLinkProgram) {
getEventAttendeeLinkPrograms().add(eventAttendeeLinkProgram);
eventAttendeeLinkProgram.setEventAttendee(this);
return eventAttendeeLinkProgram;
}
public EventAttendeeLinkProgram removeEventAttendeeLinkProgram(EventAttendeeLinkProgram eventAttendeeLinkProgram) {
getEventAttendeeLinkPrograms().remove(eventAttendeeLinkProgram);
eventAttendeeLinkProgram.setEventAttendee(null);
return eventAttendeeLinkProgram;
}
}
EventAttendeeLinkProgram.java
#Entity
#Table(name="event_attendee_link_program")
#NamedQuery(name="EventAttendeeLinkProgram.findAll", query="SELECT e FROM EventAttendeeLinkProgram e")
public class EventAttendeeLinkProgram implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private EventAttendeeLinkProgramPK id;
//bi-directional many-to-one association to EventAttendee
#ManyToOne
#JoinColumn(name="attendee_id", insertable=false, updatable=false)
private EventAttendee eventAttendee;
//bi-directional many-to-one association to EventOptionsAttendeeType
#ManyToOne
#JoinColumn(name="attendee_type_id", insertable=false, updatable=false)
private EventOptionsAttendeeType eventOptionsAttendeeType;
//bi-directional many-to-one association to EventProgram
#ManyToOne
#JoinColumn(name="program_id", insertable=false, updatable=false)
private EventProgram eventProgram;
public EventAttendeeLinkProgram() {
}
public EventAttendeeLinkProgramPK getId() {
return this.id;
}
public void setId(EventAttendeeLinkProgramPK id) {
this.id = id;
}
public EventAttendee getEventAttendee() {
return this.eventAttendee;
}
public void setEventAttendee(EventAttendee eventAttendee) {
this.eventAttendee = eventAttendee;
}
public EventOptionsAttendeeType getEventOptionsAttendeeType() {
return this.eventOptionsAttendeeType;
}
public void setEventOptionsAttendeeType(EventOptionsAttendeeType eventOptionsAttendeeType) {
this.eventOptionsAttendeeType = eventOptionsAttendeeType;
}
public EventProgram getEventProgram() {
return this.eventProgram;
}
public void setEventProgram(EventProgram eventProgram) {
this.eventProgram = eventProgram;
}
}
EventAttendeeLinkProgramPK.java
#Embeddable
public class EventAttendeeLinkProgramPK implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#Column(name="attendee_id", insertable=false, updatable=false)
private int attendeeId;
#Column(name="attendee_type_id", insertable=false, updatable=false)
private int attendeeTypeId;
#Column(name="program_id", insertable=false, updatable=false)
private int programId;
public EventAttendeeLinkProgramPK() {
}
public int getAttendeeId() {
return this.attendeeId;
}
public void setAttendeeId(int attendeeId) {
this.attendeeId = attendeeId;
}
public int getAttendeeTypeId() {
return this.attendeeTypeId;
}
public void setAttendeeTypeId(int attendeeTypeId) {
this.attendeeTypeId = attendeeTypeId;
}
public int getProgramId() {
return this.programId;
}
public void setProgramId(int programId) {
this.programId = programId;
}
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof EventAttendeeLinkProgramPK)) {
return false;
}
EventAttendeeLinkProgramPK castOther = (EventAttendeeLinkProgramPK)other;
return
(this.attendeeId == castOther.attendeeId)
&& (this.attendeeTypeId == castOther.attendeeTypeId)
&& (this.programId == castOther.programId);
}
public int hashCode() {
final int prime = 31;
int hash = 17;
hash = hash * prime + this.attendeeId;
hash = hash * prime + this.attendeeTypeId;
hash = hash * prime + this.programId;
return hash;
}
}
EventAttendeeServiceImpl.java
#Service
#Primary
public class EventAttendeeServiceImpl implements EventAttendeeService {
#Autowired
private EventAttendeeRepository eventAttendeeRepository;
#Autowired
private EventOptionsAttendeeTypeRepository eventOptionsAttendeeTypeRepository;
#Autowired
private EventProgramRepository eventProgramRepository;
#Override
#Transactional
public String addEventAttendee(EventAttendee eventAttendee) {
EventAttendeeLinkProgram ep = new EventAttendeeLinkProgram();
ep.setEventOptionsAttendeeType(eventOptionsAttendeeTypeRepository.findOne(2L));
ep.setEventProgram(eventProgramRepository.findOne(2L));
eventAttendee.setEventAttendeeLinkPrograms(new ArrayList<>());
eventAttendee.getEventAttendeeLinkPrograms().add(ep);
eventAttendeeRepository.save(eventAttendee);
return "";
}
With this in place, my code is not throwing any errors. It is saving the EventAttendee, but nothing is being saved to the EventAttendeeLinkProgram. Please Note: I am trying so save both EventAttendee and EventAttendeeLinkProgram entities. So I think hibernate should be smart enought to forst save EventAttendee and generating the Id for it, then use that Id to store in EventAttendeeLinkProgram.
Why don't you let spring do the heavy lifting:
First create a JPA repository in spring:
public interface UserRepository extends CrudRepository<User, Long>{
}
Then create your 2 entities with the relationship
#Entity
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name = "name")
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true, fetch = FetchType.EAGER)
private List<UserType> userTypes;
And :
#Entity
public class UserType {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
My test looks like this:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
public class UserRepositoryTest extends AbstractTest {
#Autowired
private UserRepository userRepository;
#Test
#Transactional
public void test1() throws SQLException {
showTables();
User user1 = makeUser("Greg");
userRepository.save(user1);
System.out.println(user1);
userRepository.save(makeUser("George"));
assertEquals(2, userRepository.count());
User user = userRepository.findOne(1l);
}
User makeUser(String name) {
User user = new User();
user.setName(name);
user.setUserTypes(new ArrayList<>());
user.getUserTypes().add(makeUserType("admin"));
user.getUserTypes().add(makeUserType("head chef"));
return user;
}
UserType makeUserType(String description) {
UserType userType = new UserType();
userType.setDescription(description);
return userType;
}
}
First of all, user save return the identifier directly
Long insertId = (Long) session.save(user);
Then you'd better call the rollback on the txtransaction itself instead of retrieving again the transaction from the session.
Finally, when using spring you should consider to let spring manage the transaction itself (container managed transaction)using #Transactional annotation instead of using user managed transaction. It's logical as you let spring manage the session for you (sessionFactory.getCurrentSession()) and both session and transaction should have the same scope (e.g. the unit of work).
Consider reading some literature on Session (e.g. JPA entityManager) and transaction management.

Basic CRUD operation with composite Id (spring + hibernate)

Im trying to make a basic create operation with hibernate and spring, but i keep getting the message that a id is empty when it is not.So im thinking that it might be because, the entity uses a composite id, fun fact at least for me is that i don't have any problem deleting the entities.
The method im using
#RequestMapping(value="addPatientFamilyRelative",method = RequestMethod.POST)
public #ResponseBody String addPatientFamilyRelative(#RequestParam(value="idPatient")int idPatient,
#RequestParam(value="idRelative")int idRelative,
#RequestParam(value="idRelationship")int idRelationship)
{
Patient_Relative patientRelative = new Patient_Relative();
patientRelative.setIdRelationship(relationshipService.getById(idRelationship));
patientRelative.setPatient(patientService.getById(idPatient));
patientRelative.setRelative(relativeService.getRelative(idRelative));
prService.create(patientRelative);
return "$('#tblPatientFamilyPatientRelatives').ajax.reload();$('#tblPatientRelativesList').ajax.reload()";
}
Patient_Relative class
#Entity
#Table(name="Patient_Relative")
public class Patient_Relative implements Serializable{
/**
*
*/
private static final long serialVersionUID = -2670460334767266076L;
#EmbeddedId
#JoinColumn(name = "idRelative", referencedColumnName = "idRelative", insertable = false, updatable = false)
#ManyToOne(optional = false)
#JsonIgnore
private Relative relative;
#JoinColumn(name = "idRelationship", referencedColumnName = "idRelationship")
#ManyToOne
private Relationship idRelationship;
#JoinColumn(name = "idPatient", referencedColumnName = "idPatient", insertable = false, updatable = false)
#ManyToOne(optional = false)
#JsonIgnore
private Patient patient;
public Relative getRelative() {
return relative;
}
public void setRelative(Relative relative) {
this.relative = relative;
}
public Relationship getIdRelationship() {
return idRelationship;
}
public void setIdRelationship(Relationship idRelationship) {
this.idRelationship = idRelationship;
}
public Patient getPatient() {
return patient;
}
public void setPatient(Patient patient) {
this.patient = patient;
}
}
PatientRelativeId
#Embeddable
public class PatientRelativeId implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 2719758608242901070L;
#Column(name = "idPatient")
private int patientId;
#Column(name = "idRelative")
private int relativeId;
public PatientRelativeId() {
}
public PatientRelativeId(int patientId, int relativeId) {
this.patientId = patientId;
this.relativeId = relativeId;
}
public int getPatientId() {
return patientId;
}
public void setPatientId(int patientId) {
this.patientId = patientId;
}
public int getRelativeId() {
return relativeId;
}
public void setRelativeId(int relativeId) {
this.relativeId = relativeId;
}
}
i hope this is enough to get some ideas, i would have liked to add a column just for the id but i think im not able to do that anymore.
Thanks in advance
I hope this helps someone.
First , my Patient_Relative class was short 1 variable , the one that would store the composite id, so i added the variable PatientRelativeId compositeId.
Second, at the controller method all i had to do was set the values of the composite id , the patient and the relative , and then call the service to create the object.
#RequestMapping(value="addPatientFamilyRelative",method = RequestMethod.POST)
public #ResponseBody String addPatientFamilyRelative(#RequestParam(value="idPatient")int idPatient,
#RequestParam(value="idRelative")int idRelative,
#RequestParam(value="idRelationship")int idRelationship)
{
Patient_Relative patientRelative = new Patient_Relative();
PatientRelativeId id = new PatientRelativeId(idPatient, idRelative);
patientRelative.setPatienRelativeId(id);
patientRelative.setIdRelationship(relationshipService.getById(idRelationship));
patientRelative.setPatient(patientService.getById(idPatient));
patientRelative.setRelative(relativeService.getRelative(idRelative));
prService.create(patientRelative);
return "addRelative";
}

Resources