Efficent updating entity in Hibernate - java

I guess it may be like a newbie question, but still I'd like to know some answers.
Let's say there are entities: Hospital and Doctor (Many-To-Many). Suppose in my controller class I have to fetch all existing doctors and hospitals, and then hire one doctor in a specific hospital
#Controller
public class HospitalController {
...
#RequestMapping("/hireDoctor")
public String (HttpServletRequest request, Model model) {
List<Hospital> hospitals = hospitalService.findAllHospitals();
List<Doctor> doctors = doctorService.findAllDoctors();
//some logic, in the end we choose just one doctor and one hospital
Doctor doctor = doctors.get(0);
Hospital hospital = hospitals.get(0);
hospitalService.hireDoctor(hospital, doctor);
}
...
#Service
public class HospitalService {
..
#Transactional
public void hireDoctor(Hospital hospital, Doctor doctor) {
//ideally
List<Doctor> doctors = hospital.getDoctors();
doctors.add(doctor);
this.em.merge(hospital);
}
..
}
It of course doesn't work, because - as I understand - I've fetched all doctors and hospitals in my controller and then in hireDoctor method we're opening trasaction passing regular Java objects, which are not in a session.
I know, that I can just again fetch Hospital with a speicfic ID, and Doctor with a specific ID and then save it
public void hireDoctor(Hospital hospital, Doctor doctor) {
Hospital h = hospitalRepo.getHospitalById(hospital.getId());
Doctor d = hospitalRepo.getDoctorById(doctor.getId());
List<Doctor> doctors = h.getDoctors();
doctors.add(d);
}
But it just looks rubbish.
So - how should such update look like to be most efficient?

There is a nice and elegant way to do this. It relies on using Hibernate proxies combined with extracting the many-to-many relationship to a separate entity, for example:
#Entity
public class HospitalToDoctor implements Serializable {
#Id
#ManyToOne
private Hospital hospital;
#Id
#ManyToOne
private Doctor doctor;
}
#Entity
public class Hospital {
#OneToMany(mappedBy = "hospital")
private Collection<HospitalToDoctor> doctors;
}
#Entity
public class Doctor {
#OneToMany(mappedBy = "doctor")
private Collection<HospitalToDoctor> hospitals;
}
Now, to asscociate a Doctor and a Hospital with only one insert statement without any additional database round-trips:
HospitalToDoctor hospitalToDoctor = new HospitalToDoctor();
hospitalToDoctor.setHospital(entityManager.getReference(Hospital.class, hospitalId));
hospitalToDoctor.setDoctor(entityManager.getReference(Doctor.class, doctorId));
entityManager.persist(hospitalToDoctor);
The key point here is to use EntityManager.getReference:
Get an instance, whose state may be lazily fetched.
Hibernate will just create the proxy based on the provided id, without fetching the entity from the database.
In other use cases you can encapsulate the HospitalToDoctor entity, so that the association is still used as many-to-many. For example, you can add to the Hopsital something like this:
public Collection<Doctor> getDoctors() {
Collection<Doctor> result = new ArrayList<>(doctors.size());
for (HospitalToDoctor hospitalToDoctor : doctors) {
result.add(hospitalToDoctor.getDoctor());
}
return result;
}
The additional benefit of introducing the HospitalToDoctor is that you can easily store additional attributes in it if the need arises (like when a doctor started to work in a hospital, etc).
However, if you still don't want to introduce a separate entity but want to use a clean Hibernate many-to-many, you can still benefit from the proxies. You can add a Doctor proxy to the loaded Hospital (or vice versa). You may also want to look at Hibernate extra lazy collections to avoid loading the doctors collection when adding a Doctor to a Hospital or vice versa (the main concern of your question, I assume).

Related

Spring Data JPA - Deleting a child in one-to-many relationship

I'm implementing a Spring boot application and using Spring Data JPA in it. As you know you don't have to implement the repository interface for just CRUD methods, because Spring Data JPA creates an implementation on the fly. So I have just this:
public interface PersonRepository extends JpaRepository<Person, Long> {}
I'm working with one-to-many relationship, this is in my Person domain:
#OneToMany(cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY,
mappedBy = "person")
private Set<Contact> contacts = new HashSet<>();
I decided to write an integration test for child removal from the parent:
#Test
public void removeFromContacts() {
// given
Person person = new Person ("test person");
Contact contact = new Contact("test#gmail.com", "+123456789");
contact.setPerson(person);
person.getContacts().add(contact);
personRepository.save(person);
Person savedPerson = personRepository.findOne(person.getId());
Contact persistedContact = savedPerson.getContacts().stream().findFirst().orElse(null);
// when
savedPerson.getContacts().remove(persistedContact);
persistedContact.setPerson(null);
Person edited = personRepository.save(savedPerson);
// then
Assert.assertTrue(edited.getContacts().isEmpty());
}
This test fails. The reason is savedPerson.getContacts().remove(persistedContact) line doesn't change anything, remove method returns false. It's pretty strange, because I'm trying to remove an object from a hash set which has only one object with exact same hash code (equals() method returns true as well). According to this answer the contact object could've been altered somehow after adding it to the hash set. The only thing I can think of is it happened after this line: personRepository.save(person).
If I'm right then I'm really confused: how should I remove the contact from a person, and even if I find a way, is it okay for personRepository.save method to cause a set to malfunction? And if I'm wrong I would love to know the right answer.
Thanks in advance.
Class Compte and Class User joind to one-to-one relationship
public void delete(Integer integer){
User user = userRepository.findOne(integer);
Compte compte = user.getCompte();
compte.setUser(null);
compteRepository.save(compte);
user.setCompte(null);
userRepository.save(user);
compteRepository.delete(compte);
userRepository.delete(user);
}

Spring Data JPA - bidirectional relation with infinite recursion

First, here are my entities.
Player :
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Player {
// other fields
#ManyToOne
#JoinColumn(name = "pla_fk_n_teamId")
private Team team;
// methods
}
Team :
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Team {
// other fields
#OneToMany(mappedBy = "team")
private List<Player> members;
// methods
}
As many topics already stated, you can avoid the StackOverflowExeption in your WebService in many ways with Jackson.
That's cool and all but JPA still constructs an entity with infinite recursion to another entity before the serialization. This is just ugly ans the request takes much longer. Check this screenshot : IntelliJ debugger
Is there a way to fix it ? Knowing that I want different results depending on the endpoint. Examples :
endpoint /teams/{id} => Team={id..., members=[Player={id..., team=null}]}
endpoint /members/{id} => Player={id..., team={id..., members=null}}
Thank you!
EDIT : maybe the question isn't very clear giving the answers I get so I'll try to be more precise.
I know that it is possible to prevent the infinite recursion either with Jackson (#JSONIgnore, #JsonManagedReference/#JSONBackReference etc.) or by doing some mapping into DTO. The problem I still see is this : both of the above are post-query processing. The object that Spring JPA returns will still be (for example) a Team, containing a list of players, containing a team, containing a list of players, etc. etc.
I would like to know if there is a way to tell JPA or the repository (or anything) to not bind entities within entities over and over again?
Here is how I handle this problem in my projects.
I used the concept of data transfer objects, implemented in two version: a full object and a light object.
I define a object containing the referenced entities as List as Dto (data transfer object that only holds serializable values) and I define a object without the referenced entities as Info.
A Info object only hold information about the very entity itself and not about relations.
Now when I deliver a Dto object over a REST API, I simply put Info objects for the references.
Let's assume I deliever a PlayerDto over GET /players/1:
public class PlayerDto{
private String playerName;
private String playercountry;
private TeamInfo;
}
Whereas the TeamInfo object looks like
public class TeamInfo {
private String teamName;
private String teamColor;
}
compared to a TeamDto
public class TeamDto{
private String teamName;
private String teamColor;
private List<PlayerInfo> players;
}
This avoids an endless serialization and also makes a logical end for your rest resources as other wise you should be able to GET /player/1/team/player/1/team
Additionally, the concept clearly separates the data layer from the client layer (in this case the REST API), as you don't pass the actually entity object to the interface. For this, you convert the actual entity inside your service layer to a Dto or Info. I use http://modelmapper.org/ for this, as it's super easy (one short method call).
Also I fetch all referenced entities lazily. My service method which gets the entity and converts it to the Dto there for runs inside of a transaction scope, which is good practice anyway.
Lazy fetching
To tell JPA to fetch a entity lazily, simply modify your relationship annotation by defining the fetch type. The default value for this is fetch = FetchType.EAGER which in your situation is problematic. That is why you should change it to fetch = FetchType.LAZY
public class TeamEntity {
#OneToMany(mappedBy = "team",fetch = FetchType.LAZY)
private List<PlayerEntity> members;
}
Likewise the Player
public class PlayerEntity {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "pla_fk_n_teamId")
private TeamEntity team;
}
When calling your repository method from your service layer, it is important, that this is happening within a #Transactional scope, otherwise, you won't be able to get the lazily referenced entity. Which would look like this:
#Transactional(readOnly = true)
public TeamDto getTeamByName(String teamName){
TeamEntity entity= teamRepository.getTeamByName(teamName);
return modelMapper.map(entity,TeamDto.class);
}
In my case I realized I did not need a bidirectional (One To Many-Many To One) relationship.
This fixed my issue:
// Team Class:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Player> members = new HashSet<Player>();
// Player Class - These three lines removed:
// #ManyToOne
// #JoinColumn(name = "pla_fk_n_teamId")
// private Team team;
Project Lombok might also produce this issue. Try adding #ToString and #EqualsAndHashCode if you are using Lombok.
#Data
#Entity
#EqualsAndHashCode(exclude = { "members"}) // This,
#ToString(exclude = { "members"}) // and this
public class Team implements Serializable {
// ...
This is a nice guide on infinite recursion annotations https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion
You can use #JsonIgnoreProperties annotation to avoid infinite loop, like this:
#JsonIgnoreProperties("members")
private Team team;
or like this:
#JsonIgnoreProperties("team")
private List<Player> members;
or both.

Hibernate throws "org.hibernate.HibernateException: Found two representations of same collection" on entitymanager.flush()

I am currently working on a medium sized Java project with Hibernate and I have come across what seems to be a rare but quite persistent error. The situation is as follows: I have a Student entity who has a bidirectional many-to-many relation to an Education entity (implemented as a join table on the database) and an Admin entity who is a subclass of Student. My code allows for a Student to be "upgraded" to an Admin by removing the Student from the database, creating a new Admin based on the Student and persisting this Admin. However, whenever this happens, Hibernate throws the following error on EntityManager.flush():
org.hibernate.HibernateException: Found two representations of same collection: domain.Student.enrolledEducations
Below you can find the relevant code:
Education class
#Entity
public class Education {
...
#ManyToMany
#JoinColumn(name = "education_id")
private Set<Course> courses = new HashSet<>();
Student class
#Entity
public class Student {
....
#ManyToMany
#JoinColumn(name = "student_id")
private Set<Education> enrolledEducations = new HashSet<>();
Admin class
#Entity
public class Admin extends Student {
...
public Admin(Student student) {
this.setId(student.getId());
this.setFirstName(student.getFirstName());
this.setLastName(student.getLastName());
this.setEmail(student.getEmail());
this.setSalt(student.getSalt());
this.setSuperAdmin(false);
this.setEnrolledEducations(student.getEnrolledEducations());
this.setSessions(student.getSessions());
this.setManagedEducations(new HashSet<Education>());
}
Database methods
public Admin upgrade(Person person) {
Admin admin;
if (person instanceof Student){
removeStudent((Student) person);
admin = new Admin((Student) person);
}
else{
removePerson(person);
admin = new Admin(person);
}
addAdmin(admin); //exception happens here
return admin;
}
public void addAdmin(Admin admin) {
manager.getTransaction().begin();
if(manager.contains(admin)){
manager.merge(admin);
}
else{
manager.persist(admin);
}
manager.flush(); //exception happens here
manager.getTransaction().commit();
}
Test method
#Test
public void getEducationsForAdmin_and_upgrade_to_admin_work_correctly(){
educationSetup();
Admin admin1 = facade.upgrade(student1); //exception happens here
Admin admin2 = facade.upgrade(student2);
admin1.addNewEducation(education1);
admin1.addNewEducation(education2);
admin2.addNewEducation(education1);
facade.updateAdmin(admin1);
facade.updateAdmin(admin2);
Set<Education> educations1 = new HashSet<>(facade.getEducationsForStudent(admin1));
Set<Education> educations2 = new HashSet<>(facade.getEducationsForStudent(admin2));
assertTrue("admin 1 does not have exactly 1 education", educations1.size()==1);
assertTrue("admin 2 does not have exactly 2 educations", educations2.size()==2);
assertTrue("admin 1 does not have the IT education",educations1.contains(education1));
assertTrue("admin 2 does not have the IT education",educations2.contains(education1));
assertTrue("admin 2 does not have the M education",educations2.contains(education2));
}
It seems that you have a problem that both Admin and Student have the same identifier.
Since the Admin is created by calling the new function, it is not in the persistent state, the code
manager.contains(admin)
will always return false, so it will always go to the manager.persist statement.
Since Admin is a different object with the same identifier, you will get the exception
Found two representations of same collection
All you need to do is to add
manager.delete(person)
in your
removePerson
function. It should solve this problem.

How to beautifully update a JPA entity in Spring Data?

So I have looked at various tutorials about JPA with Spring Data and this has been done different on many occasions and I am no quite sure what the correct approach is.
Assume there is the follwing entity:
package stackoverflowTest.dao;
import javax.persistence.*;
#Entity
#Table(name = "customers")
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
public Customer(String name) {
this.name = name;
}
public Customer() {
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
We also have a DTO which is retrieved in the service layer and then handed to the controller/client side.
package stackoverflowTest.dto;
public class CustomerDto {
private long id;
private String name;
public CustomerDto(long id, String name) {
this.id = id;
this.name = name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
So now assume the Customer wants to change his name in the webui - then there will be some controller action, where there will be the updated DTO with the old ID and the new name.
Now I have to save this updated DTO to the database.
Unluckily currently there is no way to update an existing customer (except than deleting the entry in the DB and creating a new Cusomter with a new auto-generated id)
However as this is not feasible (especially considering such an entity could have hundreds of relations potentially) - so there come 2 straight forward solutions to my mind:
make a setter for the id in the Customer class - and thus allow setting of the id and then save the Customer object via the corresponding repository.
or
add the id field to the constructor and whenever you want to update a customer you always create a new object with the old id, but the new values for the other fields (in this case only the name)
So my question is wether there is a general rule how to do this?
Any maybe what the drawbacks of the 2 methods I explained are?
Even better then #Tanjim Rahman answer you can using Spring Data JPA use the method T getOne(ID id)
Customer customerToUpdate = customerRepository.getOne(id);
customerToUpdate.setName(customerDto.getName);
customerRepository.save(customerToUpdate);
Is's better because getOne(ID id) gets you only a reference (proxy) object and does not fetch it from the DB. On this reference you can set what you want and on save() it will do just an SQL UPDATE statement like you expect it. In comparsion when you call find() like in #Tanjim Rahmans answer spring data JPA will do an SQL SELECT to physically fetch the entity from the DB, which you dont need, when you are just updating.
In Spring Data you simply define an update query if you have the ID
#Repository
public interface CustomerRepository extends JpaRepository<Customer , Long> {
#Query("update Customer c set c.name = :name WHERE c.id = :customerId")
void setCustomerName(#Param("customerId") Long id, #Param("name") String name);
}
Some solutions claim to use Spring data and do JPA oldschool (even in a manner with lost updates) instead.
Simple JPA update..
Customer customer = em.find(id, Customer.class); //Consider em as JPA EntityManager
customer.setName(customerDto.getName);
em.merge(customer);
This is more an object initialzation question more than a jpa question, both methods work and you can have both of them at the same time , usually if the data member value is ready before the instantiation you use the constructor parameters, if this value could be updated after the instantiation you should have a setter.
If you need to work with DTOs rather than entities directly then you should retrieve the existing Customer instance and map the updated fields from the DTO to that.
Customer entity = //load from DB
//map fields from DTO to entity
So now assume the Customer wants to change his name in the webui -
then there will be some controller action, where there will be the
updated DTO with the old ID and the new name.
Normally, you have the following workflow:
User requests his data from server and obtains them in UI;
User corrects his data and sends it back to server with already present ID;
On server you obtain DTO with updated data by user, find it in DB by ID (otherwise throw exception) and transform DTO -> Entity with all given data, foreign keys, etc...
Then you just merge it, or if using Spring Data invoke save(), which in turn will merge it (see this thread);
P.S. This operation will inevitably issue 2 queries: select and update. Again, 2 queries, even if you wanna update a single field. However, if you utilize Hibernate's proprietary #DynamicUpdate annotation on top of entity class, it will help you not to include into update statement all the fields, but only those that actually changed.
P.S. If you do not wanna pay for first select statement and prefer to use Spring Data's #Modifying query, be prepared to lose L2C cache region related to modifiable entity; even worse situation with native update queries (see this thread) and also of course be prepared to write those queries manually, test them and support them in the future.
I have encountered this issue!
Luckily, I determine 2 ways and understand some things but the rest is not clear.
Hope someone discuss or support if you know.
Use RepositoryExtendJPA.save(entity). Example:
List<Person> person = this.PersonRepository.findById(0)
person.setName("Neo");
This.PersonReository.save(person);
this block code updated new name for record which has id = 0;
Use #Transactional from javax or spring framework. Let put #Transactional upon your class or specified function, both are ok. I read at somewhere that this annotation do a "commit" action at the end your function flow. So, every things you modified at entity would be updated to database.
There is a method in JpaRepository
getOne
It is deprecated at the moment in favor of
getById
So correct approach would be
Customer customerToUpdate = customerRepository.getById(id);
customerToUpdate.setName(customerDto.getName);
customerRepository.save(customerToUpdate);

Spring MVC Transactional Best Practices for this

I have a controller method which retrieves an User, then I've got mapped their UserConfig, and then with that UserConfig I retrieve the MainBrands (lazy collection of UserConfiguration).
Let me clarify this:
User Entity:
#Entity
#Table(name = "app_user")
public class User extends BaseEntity {
private UserConfig userConfig;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
public UserConfig getUserConfig() {
return userConfig;
}
//more props..
}
UserConfig Entity:
#Entity
#Table(name = "user_config")
public class UserConfig extends BaseEntity {
private Set<MainBrand> mainBrands;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(...)
public Set<MainBrand> getMainBrands() {
return mainBrands;
}
//more props..
}
And my UserService:
public interface UserService {
public User getById(Long id);
}
So my question is about "best practices" of transactional annotations. I have read more than once, that put #Transactional at Controller level, is bad practice. But in this case I wanna do at Controller:
#RequestMapping(method = RequestMethod.GET, value = "/")
public ModelAndView getMainPage(Long userId) {
ModelAndView = new ModelAndView("/home");
//do stuff
User user = userService.getById(userId);
//some stuff with user
modelAndView.addObject("username", user.getUsername());
//...
List<String> brandsNames = new ArrayList<>();
for(MainBrand mainBrand : user.getUserConfig().getMainBrands()){
brandsNames.add(mainBrand.getName());
}
}
That will fail if don't put the #Transactional annotation at Controller level, because of LazyInitializationException.
So, that's the choices that I've thinked out:
1) With the user make a call to an "UserConfigService" (it's not created now) like userConfigService.getUserConfigByUserId(userId): that's make me think that if I already have the binding at User class, why I would call it again? And I am just creating a new service only for this method.
2) Put the #Transactional annotation at controller level: which makes another problem for my, but it doesn't care in this post.
3) Call the getUserConfig() & getUserConfig().getMainBrands() at UserService so then the collection get initialized: don't like because whenever I use the getById it will initialize the collection even if I do not need it.
So what it would be a good practice for this case? On internet there are always perfect and beautiful examples, but when we start to give some business logic to the project, it turns hard to have a clean code.
Thanks, and sorry for my english.
LazyInitializationException is not related to transactional , it is related to relationship between objects, if your object has a lazy relation,you must fetch your MainBrands objects in your userService.getById(userId) query method before you return your user.
Transactional annotation must be in service class, you can create as many service classes as you need.

Categories

Resources