Reputation: 2119
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?
Upvotes: 2
Views: 1195
Reputation: 23562
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).
Upvotes: 3