Hibernate是一个纯Java对象关系映射和持久性框架,它允许您使用XML配置文件将普通的旧Java对象映射到关系数据库表。 使用Hibernate可以节省一个项目的大量开发时间,因为整个JDBC层都由框架管理。 这意味着您的应用程序的数据访问层将位于Hibernate之上,并完全从基础数据模型中抽象出来。
与其他类似的对象关系映射方法(JDO,实体bean,内部开发等)相比,Hibernate具有许多优点:它是免费和开源的,已经达到了良好的成熟度,被广泛使用,并且它有一个非常活跃的社区论坛。
要将Hibernate集成到现有Java项目中,您将需要完成以下步骤:
从Hibernate网站下载最新版本的Hibernate框架。 (有关链接,请参见下面的“ 相关主题”部分。) 将必要的Hibernate库(JAR文件)复制到应用程序的CLASSPATH中。 创建XML配置文件,该文件将用于将Java对象映射到数据库表。 (我们将在本文中描述该过程。) 将XML配置文件复制到应用程序的CLASSPATH。您会注意到,不必修改任何Java对象即可支持该框架。 例如,想象一下,您需要以某种方式更改Java应用程序使用的数据库表,例如,通过重命名列。 一旦更改了表,更新Java应用程序所需要做的就是更新适当的XML配置文件。 您不需要重新编译任何Java代码。
Hibernate提供了一种称为Hibernate Query Language(HQL)的查询语言,它与SQL非常相似。 对于那些更喜欢老式SQL查询的人来说,Hibernate仍然为您提供使用它们的机会。 但是我们的支持示例将仅使用HQL。
HQL的使用非常简单。 您会找到所有从SQL知道的熟悉的关键字,例如SELECT , FROM和WHERE 。 HQL与SQL的不同之处在于,您不直接在数据模型上(即在表,列等上)写查询,而是在Java对象上使用它们的属性和关系来写查询。
清单1展示了一个基本示例。 此HQL代码检索其firstName为“ John”的所有Individual 。
您可以参考HQL的参考材料对Hibernate的网站,如果你想了解更多有关HQL语法(见相关信息中的链接)。
Hibernate功能的核心位于XML配置文件中。 这些文件必须位于应用程序的CLASSPATH中。 我们将它们放在示例代码包的config目录中(可以从参考资料中下载)。
我们将检查的第一个文件是hibernate.cfg.xml。 它包含有关您的数据源的信息(数据库URL,架构名称,用户名,密码等),以及对将包含映射信息的其他配置文件的引用。
其余的XML文件使您可以将Java类与数据库表进行映射。 稍后我们将仔细研究这些文件,但是重要的是要知道它们的文件名遵循ClassName.hbm.xml模式。
在本文中,我们将研究一个基本示例,该示例说明Hibernate是如何工作的,并充分利用了三种不同的策略,您可以在这些策略下使用Hibernate进行对象关系映射。 我们的示例应用程序将由一家保险公司使用,该公司必须保留为其客户购买保险的所有财产的合法记录。 我们已经在本文中提供了完整的源代码(请参阅参考资料 )。 此代码提供了基本功能,您可以从中构建完整的应用程序,例如Web或Swing应用程序。
我们的示例假定了这种应用程序的经典用例。 用户将为任何类型的客户(个人,公司,政府机构等)提供搜索条件,然后向用户显示符合指定条件的所有客户的列表-即使这些客户是不同类型的客户。 用户可以从同一列表访问特定客户的更详细视图。
在我们的应用程序中,产权由Right类表示。 Right可以是Lease也可以是Property 。 Right归客户所有。 为了代表我们的客户,我们将使用通用类Person 。 一个Person可以是Individual或Corporation 。 当然,保险公司必须知道这些Right所分配到的Estate 。 您会同意, Estate是一个非常通用的术语。 因此,我们将使用Land和Building类为开发人员提供更全面的对象。
从这个摘要中,我们可以开发如图1所示的类模型:
我们的数据库模型旨在涵盖我们将在本文中讨论的三种不同策略。 对于Right层次结构,我们将使用一个表( TB_RIGHT ),并使用DISCRIMINATOR列映射到正确的类。 对于Person层次结构,我们将使用称为超级表 ( TB_PERSON )的超级表 ,该表将与其他两个表( TB_CORPORATION和TB_INDIVIDUAL )共享相同的ID 。 第三层次结构( Estate )使用两个不同的表( TB_BUILDING和TB_LAND ),该表由外键链接,该外键由两列( REF_ESTATE_ID和REF_ESTATE_TYPE )的组合定义。
图2显示了数据模型:
Hibernate支持各种RDBMS,它们中的任何一个都可以与我们的示例一起使用。 但是,示例代码和这篇文章的文本已专为HSQLDB(请参阅相关信息中的链接),完全用Java语言编写一个功能齐全的关系型数据库。 在示例代码包的sql目录中,您将找到一个名为datamodel.sql的文件。 该SQL脚本将创建在我们的示例中使用的数据模型。
尽管您始终可以使用命令行来构建和执行示例代码,但是您可能需要考虑在IDE中设置项目以实现更好的集成。 在示例代码包中,您将找到以下目录:
config ,其中包含示例的所有XML配置文件(映射,Log4J等) data ,其中包含HSQLDB使用的配置文件。 您还将找到一个名为startHSQLDB.bat的批处理文件,可用于启动数据库。 src ,包含所有示例的源代码。确保将所需的Java库和XML配置文件复制到应用程序的CLASSPATH中。 该代码仅需要Hibernate和HSQLDB库即可正确编译和运行。 您可以从“ 相关主题”部分下载这些软件包。
在第一个策略中,我们将研究如何映射Person层次结构。 您会注意到数据模型与我们的类模型非常接近。 结果,我们将为层次结构中的每个类使用不同的表,但是所有这些表必须共享相同的主键(我们将在稍后详细说明)。 Hibernate在将新记录插入数据库时将使用此主键。 访问数据库时,它还将利用相同的主键执行JOIN操作。
现在,我们需要将对象层次结构映射到表模型。 我们有三个表( TB_PERSON , TB_INDIVIDUAL和TB_CORPORATION )。 如上所述,它们都有一个名为ID的列作为主键。 这样的共享列名不是强制性的,但被认为是一种很好的做法,它使读取生成SQL查询变得容易得多。
在清单2所示的XML映射文件中,您会注意到在Person映射定义中这两个具体的类被声明为<joined-subclass> 。 XML元素<id>映射到顶级表TB_PERSON的主键,而<key>元素(来自每个子类)映射到TB_INDIVIDUAL和TB_CORPORATION表的匹配主键。
使用Hibernate将新的Individual实例保存在我们的Java代码中非常简单,如清单3所示:
反过来,Hibernate生成清单4中所示的两个SQL INSERT请求。这两个请求只需要一个save() 。
要从数据库访问Individual ,只需在HQL查询中指定类名,如清单5所示。
然后,Hibernate将自动执行SQL JOIN以从两个表中检索所有必需的信息,如清单6所示:
查询抽象类时,Hibernate自动返回匹配的异构具体子类的集合。 例如,如果我们从数据库中查询每个Person ,则Hibernate将返回“ Individual和“ Corporation对象的列表。
但是,如果未指定具体的类,则Hibernate需要执行SQL JOIN ,因为它不知道要通过哪个表。 在从HQL查询返回的所有检索到的表列中,还将返回一个额外的动态列。 Hibernate使用clazz列来实例化并填充返回的对象。 与我们将在第二种策略中使用的方法相反,我们将该类确定称为动态的 。
清单7显示了如何查询具有给定id属性的抽象Person ,而清单8显示了Hibernate自动生成SQL查询,包括表联结:
对于我们的Right层次结构,我们将使用一个表( TB_RIGHT )存储整个类层次结构。 您会注意到TB_RIGHT表拥有存储Right类层次结构的每个属性所需的所有列。 然后,已保存实例的值将被保存在表中,而每个未使用的列均填充有NULL值。 (由于到处都是“Kong”,因此我们常称其为瑞士奶酪桌。 )
在图3中,您将注意到TB_RIGHT表包含一个附加列DISCRIMINATOR 。 Hibernate使用此列自动实例化适当的类并相应地填充它。 此列是使用映射文件中的<discriminator> XML元素映射的。
在非常大的项目中,您将面临具有多个抽象类级别的复杂类层次结构。 幸运的是,您不必为抽象类指定discriminator-value 。 您只需要为Hibernate将使用的实际具体类指定它即可。
就像清单2中的Person映射文件一样,在清单9中,我们映射了抽象类( Right )及其所有属性。 为了映射我们的两个具体类( Lease和Property ),我们将使用<subclass> XML标签。 这个标签非常简单。 它需要name属性,就像class标签需要discriminator-value属性一样。 Hibernate将使用last属性来标识应使用的类。
从图1的类图中您会注意到, discriminator不是任何Java类的属性。 实际上,它甚至都没有映射。 它只是Hibernate和数据库之间共享的技术专栏。
在清单9的映射文件中,您会注意到Right和Person层次结构之间是多对一的关系,(自然地)与Person层次结构(一对多)中的关系相反。 还请注意, Right和Estate层次结构之间的关系; 我们将在本文后面介绍这种关系。
与第一种策略一样,Hibernate在访问数据库时会产生非常有效SQL语句。 当我们查询具体的类时,如清单10所示,Hibernate会自动对discriminator符值进行过滤,这是一件好事,因为这意味着Hibernate仅读取指定类的适当列。
当我们查询抽象类时,事情变得有些棘手。 由于Hibernate不知道您要使用的特定类,因此它必须读取每一列(包括discriminator列),然后确定要实例化的类,最后填充它。 然后,判别器与我们的第一个策略的clazz列扮演相同的角色。 但是,这种方法被证明是更静态的,因为类名是直接从区分符的值派生的。
正如在Hibernate映射的DTD中所定义的那样,本文中介绍的前两个策略是互斥的,这意味着它们无法组合在一起以映射单个层次结构。
关于第二种策略,有一个主要的问题:为了使其起作用,必须将所有非共享列设置为NULLABLE 。 结果表可能非常难以使用,因为开发人员通常依赖于数据库约束。 (毕竟,将持续时间设置为NULL的Lease没有多大意义!)
一种解决方案是使用数据库级别的检查约束。 根据DISCRIMINATOR值,您可以定义一组要实现的规则,如清单12所示。当然,数据库引擎必须支持此功能。 此外,由于必须针对所有具体类并同时在单个有效表达式中表达这些约束,因此随着层次结构的增长,可能变得难以维护。
我们的第三个也是最后一个策略可能是最有想象力的:每个具体类有一张桌子,而Estate抽象超类没有一张桌子。 我们将依靠Hibernate为多态提供支持。 在清单13所示的XML映射文件中,您会注意到,只有两个具体的类( Building和Land )被映射:
重要的是,不要在映射到同一类层次结构中的两个表之间共享相同的ID值。 如果这样做,Hibernate将为同一ID返回多个不同的对象。 对于Hibernate以及您来说,这可能会让您感到困惑。
当您查看清单13中的映射文件时,您的第一个React可能是说:“嘿!此映射与我每天使用的映射没有什么不同。这里没有什么大不了的!” 而且您应该这样认为。 实际上,我们的第三个策略只需要一个条件:我们需要将polymorphism属性专门设置为implicit 。
即使在映射文件中找不到Estate类,它也仍然存在于我们的类层次结构中。 并且,由于我们的两个映射类( Building和Land )继承自Estate ,因此我们可以在HQL查询中使用此抽象超类,如清单14所示。Hibernate随后将使用自省功能来标识扩展该抽象类的类,以便它可以连续地为每个子类执行适当SQL查询。
为了找到给定ID的匹配Estate ,Hibernate必须将清单15中的两个查询提交到数据库。
正如我们在第二种策略中看到的那样, Right阶级和Estate阶级之间存在多对一的关系。 用简单的英语来说,它是这样的:“一个Estate可以指许多 Right 。但是每个Right只能指一个 Estate 。” 但是从数据模型的角度来看,没有可以用来创建外键约束的唯一表,例如TB_RIGHT和TB_PERSON之间的TB_PERSON 。 这实际上使我们无法创建外键。 幸运的是,Hibernate为我们提供了一个非常强大的XML映射元素, <any>标记,清单16展示了其用法。
禁用了多态支持的<class...polymorphism="explicit"...> ( <class...polymorphism="explicit"...> )被排除在针对其任何超类的查询中。
让我们仔细看看我们的新映射。 我们的虚拟外键基于TB_RIGHT表中的两列。 第一个( REF_ESTATE_TYPE )包含将用于映射适当的类名称的鉴别字符串 。 第二个( REF_ESTATE_ID )是另一个表的主键中的列名。 使用默认设置,Hibernate将尝试将映射的类名存储在第一列中,这可能会占用空间且效率低下(尤其是如果在代码重构期间更改了类名)。 幸运的是,Hibernate提供了一种使用<meta-value> XML元素将类名与字符串常量关联的方法。 这些常量的作用与第二种策略中讨论的鉴别器相同。 再次,此功能仅涉及Hibernate和数据库,因此不会更改类层次结构。
尽管标准SQL不允许给定列同时具有多个表的引用约束,但仍然可以添加触发器,该触发器将根据给定读取的鉴别符值检查目标表中是否存在数据。 但是,这种完整性强制方法可能非常难以维护,并且还可能降低数据库的整体性能。
使用Hibernate的内置多态性时,要牢记一件事:如果不小心,则可以检索比讨价还价更多的信息,只要所有类都已将polymorphism属性设置为implicit 。 清单17展示了一种使用两个单词的HQL查询检索整个数据库的方法。
很强大,你不觉得吗? 当然,我们中很少有人需要(或为此)通过单个HQL查询来检索整个数据库。 这个(废话)示例的目标仅旨在显示隐式多态性的功能。 您可以利用此功能来避免将无用且消耗资源SQL查询发送到数据库。
在本文中,我们尝试为您提供Hibernate提供的三种映射策略的相当简单的实现示例。 回顾一下,每种策略都有其优点和缺点:
对于我们的第一个策略(每个子类一个表),每次实例化和填充对象时,Hibernate都会读取多个表。 如果您的索引定义正确并且层次结构不太深,那么该操作将产生良好的结果。 但是,如果不是这种情况,您可能会遇到整体性能问题。 对于第二种策略(每个类层次结构一个表),您必须使用检查约束来定义完整性。 随着列数的增加,此策略可能难以维护。 另一方面,您可以选择根本不使用此类约束,而是依靠应用程序的代码来管理其自身的数据完整性。 我们的第三个策略(每个具体类一个表)具有一些映射限制,并且基础数据模型不能使用参照完整性,这意味着您没有充分利用关系数据库引擎。 从好的方面来说,这种策略可以很容易地与其他两种策略结合使用。无论选择哪种策略,请始终记住,您无需在流程中更改Java类,这意味着业务对象与持久性框架之间绝对没有链接。 正是这种灵活性使Hibernate在对象关系Java项目中如此流行。
翻译自: https://www.ibm.com/developerworks/java/library/j-hibernate/index.html
相关资源:用Hibernate映射继承关系