one-to-many bidirection performance issue - performance

i have one doubt, that is when we create one-to-many as bidirectional. we will put one parent class reference in child class.
see the code.
Person.java
#Entity
#Table(name="PERSON")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="personId")
private int id;
#Column(name="personName")
private String name;
#OneToMany(cascade =CascadeType.ALL,fetch = FetchType.LAZY)
#JoinColumn(name="personId")
private Set <Address> addresses;
....
....
}
Address.java
#Entity
#Table(name = "ADDRESS")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "addressId")
private int id;
#Column(name = "address",nullable=false)
private String address;
#ManyToOne(cascade =CascadeType.ALL)
#JoinColumn(name="personId",nullable=false)
private Person person;
....
....
}
here Person.java is parent class and Address.java is child class.
when we fetch the parent object from data base it is loading child class. it is fine. no proble.
but in vice versa ie. if we are fetching child class it is also holding parent class (person).
eg: we fetch Address by address id. just assume retrieved 100 address from data base.
but in address class person variable hold the parent(Person) object aslo.
my doubt is here
is use different 100 memory of person. with same data. in Address class. ?
my dao is like this.
public List<Address> getAllAddressByPersonId(int personId) {
List<Address> addressList = null;
try {
DetachedCriteria criteria = DetachedCriteria.forClass(Address.class);
criteria.createCriteria("person").add(Restrictions.eq("id", personId));
addressList = getHibernateTemplate().findByCriteria(criteria);
} catch (HibernateException e) {
e.printStackTrace();
} catch (DataAccessException e) {
e.printStackTrace();
}
return addressList;
just assume the size of addrssList is 100.
like
per = addressList.get(0).getPerson() ,
per 1 = addressList.get(1).getPerson(),
per2 = addressList.get(2).getPerson(),
....
per99 = addressList.get(99).getPerson().
here per1, per2 .... and per 99 are using same memory or different.
if same it is ok.. else it may cause of any performing issue of more memory utilization.?
help pls...

Hibernate does identity check. One hibernate session contains only single instance of an object with the same ID. So, if per, per1,...per99 is one person, you will have one object for it. Otherwise - different objects.

Related

Automatic JPA refresh ManyToOne objects with #Version feature

I'm getting an exception:
org.hibernate.TransientPropertyValueException:
object references an unsaved transient instance
- save the transient instance before flushing :
com.example.jpamapstruct.entity.Member.club ->
com.example.jpamapstruct.entity.Club
while saving the member entity:
#Transactional
public MemberDto save(MemberDto memberDto){
Member entity = memberMapper.toEntity(memberDto);
return memberMapper.toDto(repository.save(entity));
}
How to fix this case in a proper way?
Possible solution:
I can get and set a club object before saving a member but is it only one and the best approach in such scenario?
Member entity = memberMapper.toEntity(memberDto);
clubRepository.getReferencedById(memberDto.getClubId()).ifPresent(entity::setClub);
return memberMapper.toDto(repository.save(entity));
Questions:
Should I put this getReferencedById code explicity? I mean what if we have several child objects (unidirectional ManyToOne), for each we need to get data from DB.
Is there any way to handle this by JPA (Spring Data/JPA) "automatically"?
Maybe it is possible to hit DB only one time with f.e join fetch somehow for all childs (with using custom #Query or querydsl or criteria/specification)?
Next, hoow to handle collections (unidirectional manyToMany)? In my case set of events in member object. Also need to loop thru and get all objects one by one before saving member?
Where should I put such logic in a service or maybe better in a mapstuct mapper?
If so, how to use repositories in such mapper?
#Mapper(componentModel = "spring")
public interface MemberMapper extends EntityMapper<MemberDto, Member> {
#AfterMapping
default void afterMemberMapping(#MappingTarget Member m, MemberDto dto) {
var club = clubRepo.findById(m.getClub().getId())
m.setClub(club)
}
Source code:
#Entity
public class Club extends AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
}
public class ClubDto extends AbstractDto {
private Long id;
}
#Entity
public class Member {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Long id;
// commented out as don't want to save child object as it should already exist
// #ManyToOne(cascade = CascadeType.ALL)
#ManyToOne
Club club;
#ManyToMany
#JoinTable(name = "member_events",
joinColumns = #JoinColumn(name = "member_id"),
inverseJoinColumns = #JoinColumn(name = "event_id")
)
List<Event> events = new ArrayList<>();
}
public class MemberDto {
private Long id;
private ClubDto club;
}
#MappedSuperclass
public abstract class AbstractEntity {
#Version
private Integer version;
}
public abstract class AbstractDto {
private Integer version;
}
//MemberMapper above

Spring Data JDBC many to many relationship management

I have a many-to-many relationship person -> person_address <- address and use a reference class. But in my Person aggregate root it seems only adding person_address works (addresses collection):
#MappedCollection(idColumn = "PERSON_ID")
private Set<PersonAddress> addresses;
public void addAddress(final Address address) {
Assert.notNull(getId(),"Person ID cannot be null");
Assert.notNull(address.getId(),"Address ID cannot be null");
addresses.add(new PersonAddress(address.getId()));
}
I want to be able to delete from addresses collection and then do a save, but this doesn't work. So instead I use:
#Modifying
#Query(value = "delete from person_address where person_id = :personId and address_id = :addressId")
void deletePersonAddressById(#Param("personId") final Long personId, final Long addressId);
Is this the best way to handle this?
#ToString
#EqualsAndHashCode
#Getter
#Setter
public class PersonAddress {
/**
* Timestamp generated by database.
*/
#ReadOnlyProperty
#Setter(AccessLevel.NONE)
private LocalDateTime created;
private Long addressId;
public PersonAddress(final Long addressId) {
this.addressId = addressId;
}
}
You should be able to just remove entries from Person.addresses and save the entity again.
I created a sample to demonstrate this on GitHub.
On pitfall I fell into in the past was to not properly implement equals and hashCode for PersonAddress, which is of course important, when you want to remove instances from a HashSet or similar.

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.

Spring/JPA: composite Key find returns empty elements [{}]

I have build my data model using JPA and am using Hibernate's EntityManager to access the data. I am using this configuration for other classes and have had no problems.
The issue is that I created an entity with a composite primary key (the two keys are foreign keys) , adding elements works perfectly I checked it in database but I am not able to retrieve the populated row from database.
For example if I query "FROM Referentiel" to return a list of all referentiels in the table, I get this [{},{}] my list.size() has the proper number of elements (2), but the elements are null.
The entity:
#Entity
#Table(name = "Et_referentiel")
public class Referentiel implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#ManyToOne
#JoinColumn(name = "id_projet")
private Projet projet;
#Id
#ManyToOne
#JoinColumn(name = "id_ressource")
private Ressource ressource;
#Column(name = "unite", nullable = false)
private String unite;
}
here is my controller getList method:
#PostMapping(value = "/list", consumes = { MediaType.APPLICATION_JSON_UTF8_VALUE })
public List<Referentiel> listReferentiel(#RequestBody Long idProjet) {
List<Referentiel> referentiel = referentielService.listReferentiel(idProjet);
return referentiel;
}
and here is my dao methods:
#Autowired
private EntityManager em;
#Override
public void ajouterReferentiel(Referentiel ref) {
em.persist(ref);
em.flush();
}
#SuppressWarnings("unchecked")
#Override
public List<Referentiel> listReferentiel(Long idProjet) {
Query query = em.createQuery("Select r from Referentiel r where r.projet.idProjet=:arg1");
query.setParameter("arg1", idProjet);
em.flush();
List<Referentiel> resultList = query.getResultList();
return resultList;
}
Any help is greatly appreciated.
Try creating a class representing your composite key:
public class ReferentielId implements Serializable {
private static final long serialVersionUID = 0L;
private Long projet; // Same type than idProjet, same name than inside Referentiel
private Long ressource; // Same type than idRessource (I guess), same name than inside Referentiel
// Constructors, getters, setters...
}
And assign it to your entity having that composite key.
#Entity
#IdClass(ReferentielId.class) // <- here
#Table(name = "Et_referentiel")
public class Referentiel implements Serializable {
// ...
}
Notice that it is required to have a class representing your composite keys, even if that does not help in your problem.

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.

Resources