关系处理:Hibernate 与 JDBC

Posted

技术标签:

【中文标题】关系处理:Hibernate 与 JDBC【英文标题】:Relationship Handling: Hibernate vs JDBC 【发布时间】:2017-01-31 21:25:36 【问题描述】:

假设我有一个 mysql 数据库,其中包含两个表 patientmedicine。我在下面显示了他们的列。

患者

idPatient (int) (primary key)
first_name (varchar)
last_name (varchar)

医药

idMedicine (int) (primary key)
idPatient (int) (foreign key)
drug_name (varchar)

请注意Medicine 表确实有Patient 表的外键。

现在,如果我使用纯 JDBC,我将执行以下操作来为 MedicinePatient 表创建一个 bean

PatientBean

public class PatientBean

    private int idPatient;
    private String first_name;
    private String last_name;

    public void setIdPatient(int idPatient)
    
        this.idPatient = idPatient;
    

    public int getIdPatient()
    
        return idPatient;
    

    public void setFirstName(String first_name)
    
        this.first_name = first_name;
    

    public String getFirstName()
    
        return first_name;
    

    public void setLastName(String last_name)
    
        this.last_name = last_name;
    

    public String getLastName()
    
        return last_name;
    



`MedicineBean` class

public class MedicineBean

    private int idMedicine;
    private int idPatient;
    private String drug_name;

    public void setIdMedicine(int idMedicine)
    
        this.idMedicine = idMedicine;
    

    public int getIdMedicine()
    
        return idMedicine;
    

    public void setIdPatient(int idPatient)
    
        this.idPatient = idPatient;
    

    public int getIdPatient()
    
        return idPatient;
    

    public void setDrugName(String drug_name)
    
        this.drug_name = drug_name;
    

    public String getDrugName()
    
        return drug_name;
    


但是,如果我使用 NetBeans 之类的工具对我的数据库进行反向工程以实现休眠,该工具将为休眠生成 POJO 文件、映射等,我可以期待如下所示。

PatientBean

public class PatientBean
    
        private int idPatient;
        private String first_name;
        private String last_name;
    private MedicineBean medicineBean;

        public void setIdPatient(int idPatient)
        
            this.idPatient = idPatient;
        

        public int getIdPatient()
        
            return idPatient;
        

        public void setFirstName(String first_name)
        
            this.first_name = first_name;
        

        public String getFirstName()
        
            return first_name;
        

        public void setLastName(String last_name)
        
            this.last_name = last_name;
        

        public String getLastName()
        
            return last_name;
        

    public void setMedicineBean(String medicineBean)
        
            this.medicineBean = medicineBean;
        

        public String getMedicineBean()
        
            return medicineBean;
        
    

MedicineBean

public class MedicineBean
    
        private int idMedicine;
        private int idPatient;
        private String drug_name;
    private Set<PatientBean> patients = new HashSet<PatientBean>(0);

        public void setIdMedicine(int idMedicine)
        
            this.idMedicine = idMedicine;
        

        public int getIdMedicine()
        
            return idMedicine;
        

        public void setIdPatient(int idPatient)
        
            this.idPatient = idPatient;
        

        public int getIdPatient()
        
            return idPatient;
        

        public void setDrugName(String drug_name)
        
            this.drug_name = drug_name;
        

        public String getDrugName()
        
            return drug_name;
        

    public void setPatients(Set<PatientBean>patients)
    
        this.patients = patients;
    

    public Set<PatientBean> getPatients()
    
        return patients;
    

    

不仅如此,Hibernate 还会在 xml 文件中映射关系类型(一对一、一对多、多对一)。但是在 JDBC 中我们根本不关心它们,它们只是以相同方式处理的外键。

所以我的问题是,为什么会有这种差异?我相信 Hibernate 所做的大部分操作都是无用的,只是使用 CPU。例如,当我们调用getAllMedicines() 方法时,尝试在Patient 表中检索patients 的列表。在 99% 的情况下,我们只需要所有药物而不是患者名单,如果我们需要,我们可以加入并获得它!

那么这背后的原因是什么?或者我们是否也应该为 JDBC 保持相同的行为?

【问题讨论】:

patient 列表通常会延迟加载,这意味着它只会在需要时读取数据。第二种方式包含您的数据,就像您通常在 OO 程序中编写它一样 @ScaryWombat:是的,我知道。我的问题是为什么我们不在 JDBC 中做同样的事情?我认为它根本没用。 您没有在 JDBC 中执行此操作的唯一原因是因为 没有在 JDBC 中执行此操作。 可以完全控制您想要的操作方式,因此您可以通过将 MedicineBean 的 int idPatient 替换为 PatientBean patient 并将 List&lt;MedicineBean&gt; medicines 添加到 PatientBean 来实现。这取决于它们的具体使用方式,这是另一种方法。 @Andreas: 好了.. 使用Hibernate 会导致我的REST API 出现严重问题,出现failed to lazily initialize collection 错误,因为这种不必要的加载其他内容的尝试。我在 Hibernate 的 JBOSS 站点建议中尝试了所有内容,完全没有用。我更喜欢在MedicineBean 中使用int idPatient 而不是PatientBean patient,因为如果需要,我总是可以使用连接。 您始终可以选择使用轻量级 O/RM 库而不是 Hibernate。这样,您仍然可以获得 O/RM 的强大功能和灵活性,而不会产生 Hibernate 的开销(以及其他开销)。 【参考方案1】:

我不认为使用 hibernate 你会因为害怕而失去完全控制。

主要区别在于hibernate会在你的代码和jdbc之间增加一个额外的层。这一层可以非常薄:您可以选择在休眠中随时使用 jdbc。所以你不会失去任何控制。

更难的部分是了解 hibernate 的工作原理,以便您可以使用其更高级别的 api 并了解 hibernate 如何将其转换为 jdbc。这是一个有点复杂的任务,因为 orm 映射是一个复杂的主题。多次阅读参考文档,以准确了解 hibernate 可以做什么,以及他们建议做什么和不做什么,是一个很好的起点。其余的将来自使用 hibernate 的经验。

对于您的示例,您说的是休眠映射关系,但事实并非如此:您的逆向工程工具做到了。您可以不映射关系而只映射外键基本类型(如果 id 是数字,则为 Long)。

至于装载的东西。如果您希望始终加载@OneToMany,只需使用FetchType.EAGER 对其进行注释。 @*ToMany 关联默认是惰性的(以避免加载太多数据),但另一方面,@*ToOne 关联默认为 EAGER

这可以在实体级别进行配置,使其成为查询的默认行为,但可以为每个查询重载。

你看到了吗?你并没有失去控制,你只需要了解hibernate api如何转换为jdbc。

除了向 hibernate 团队提出时已修复的 bug 外,hibernate 对性能的影响并没有那么大。在应用程序的性能关键部分,您始终可以选择使用 jdbc,其中休眠开销为 0。

使用 hibernate 有什么好处?根据我的经验,实体模型/数据库模型中的重构要容易得多,因为您更改了休眠映射,并且休眠生成的所有查询也会自动更改。您只需更新您手写的自定义查询(SQL / HQL / Criteria)。

根据我在数百个表(其中一些具有超过 10B 行)、数 TB 数据库上的经验(使用休眠 10 年),我不想回到普通 jdbc,这并不意味着我不想'当它是完美的工具时不会使用它,但它只是我编写的 orm 代码的 1% 或 2%。

希望对您有所帮助。

编辑:如果你使用的是带有 spring 的 hibernate,请查看 spring-jdbc,它在 jdbc 周围添加了一个很好的层。在那里,您几乎不需要阅读文档:您可以直接识别它将如何转换为 jdbc,但它带来了许多实用程序,从而大大减少了直接使用 jdbc 的样板(例如异常处理以关闭 ResultsetPreparedStatement,将ResultSet 转换为 DTO 列表等)。

当然hibernate和spring-jdbc可以在同一个应用中使用。只需将它们配置为使用相同的事务层,并且在相同的 tx 中使用时要小心。

【讨论】:

感谢您的回复。不,我不使用带有 Spring 的 Hibernate,我正在构建一个 REST API。我在这里不能同意你的看法 - If you wish to always have a @OneToMany loaded, just annotate it with FetchType.EAGER 这将是一个巨大的开销。` 无论如何,你的意思是我可以像在 Hibernate 中一样使用像 idPatient 这样的外键,而无需使其成为 SET 或完整的 bean?然后我必须更改关联的 XML 不是吗?我没有使用注释。 我所说的也代表 hbm 映射。你为什么说这是一个巨大的开销?为了加载父实体,hibernate会触发2个查询:一个是加载父实体(按id,一行,不应该很重),一个是你感兴趣的子集合。你不需要父实体吗?只需创建一个 Criteria / HQL / SQL 来加载 parent_id = parent.id 的子实体(并且不要忘记将 ManyToOne 标记为 LAZY)。 hbm 映射文件需要反映两件事:数据库模型,以及您希望如何从 java 代码访问实体。仅供参考,hbm 文件已被弃用。 我从高层次的角度回答了您的问题,因为在我看来,您想要更多的全局洞察力而不是 hibernate 的精确部分的答案。如果不是这种情况,请在您的问题中添加表的数据库模型、hbm 映射文件、您希望 hibernate 生成的 sql 查询以及您的 hibernate 加载代码,我们将看看如何修改映射和休眠代码以获得您想要的。 谢谢。我的问题与我同事在这里提出的问题非常相关,我们现在正在面对它 - ***.com/questions/39637121/… 如果他的帖子不够清楚,我可以重新发布。

以上是关于关系处理:Hibernate 与 JDBC的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate (开源对象关系映射框架)

ORM与hibernate概述

码农小汪-Hibernate学习8-hibernate关联关系注解表示@OneToMany mappedBy @ManyToMany @JoinTable

hibernate annotation注解方式来处理映射关系

hibernate annotation注解方式来处理映射关系

hibernate关联关系(多对多)