Hibernate中的有效更新实体


问题内容

我想这可能像一个新手问题,但我仍然想知道一些答案。

假设有实体:医院和医生( 多对多 )。假设在我的控制者班级中,我必须先找所有现有的医生和医院,然后再在特定的医院雇用一名医生

@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);
    }
  ..
}

它当然不起作用,因为-据我了解-
我已经在控制器中获取了所有医生和医院,然后在hireDoctor方法中,我们通过常规Java对象(不在会话中)打开了事务处理。

我知道,我可以再次获取带有特定ID的医院,以及具有特定ID的Doctor,然后将其保存

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);
}

但这看起来只是垃圾。

那么-这样的更新看起来如何最有效?


问题答案:

有一个很好的方法可以做到这一点。它依赖于结合使用Hibernate代理和提取与一个单独实体的多对多关系,例如:

@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;
}

现在,仅使用一个insert语句来关联a Doctor和a Hospital,而无需进行任何其他数据库往返操作:

HospitalToDoctor hospitalToDoctor = new HospitalToDoctor();
hospitalToDoctor.setHospital(entityManager.getReference(Hospital.class, hospitalId));
hospitalToDoctor.setDoctor(entityManager.getReference(Doctor.class, doctorId));
entityManager.persist(hospitalToDoctor);

这里的关键点是使用EntityManager.getReference

获取一个实例,其状态可能会延迟获取。

Hibernate只会根据提供的ID创建代理,而无需从数据库中获取实体。

在其他用例中,您可以封装HospitalToDoctor实体,以便仍然使用多对多关联。例如,您可以添加Hopsital如下内容:

public Collection<Doctor> getDoctors() {
    Collection<Doctor> result = new ArrayList<>(doctors.size());
    for (HospitalToDoctor hospitalToDoctor : doctors) {
        result.add(hospitalToDoctor.getDoctor());
    }
    return result;
}

引入的另一个好处HospitalToDoctor是,如果需要,您可以轻松地在其中存储其他属性(例如,当医生开始在医院工作时等)。

但是,如果您仍然不想引入单独的实体,而是想要使用干净的多对多Hibernate,仍然可以从代理中受益。您可以将Doctor代理添加到已加载的代理中Hospital(反之亦然)。您可能还希望查看Hibernate额外的惰性集合,以避免doctors在将a添加Doctor到a时Hospital反之亦然(反之亦然)(我想这是您问题的主要关注点),以避免加载该集合。