hibernate 映射_Hibernate简化了继承映射

总览

Hibernate是一个纯Java对象关系映射和持久性框架,它允许您使用XML配置文件将普通的旧Java对象映射到关系数据库表。 使用Hibernate可以节省一个项目的大量开发时间,因为整个JDBC层都由框架管理。 这意味着您的应用程序的数据访问层将位于Hibernate之上,并完全从基础数据模型中抽象出来。

与其他类似的对象关系映射方法(JDO,实体bean,内部开发等)相比,Hibernate具有许多优点:它是免费和开源的,已经达到了良好的成熟度,被广泛使用,并且它有一个非常活跃的社区论坛。

要将Hibernate集成到现有Java项目中,您将需要完成以下步骤:

  1. 从Hibernate网站下载最新版本的Hibernate框架。 (有关链接,请参见下面的“ 相关主题”部分。)
  2. 将必要的Hibernate库(JAR文件)复制到应用程序的CLASSPATH中。
  3. 创建XML配置文件,该文件将用于将Java对象映射到数据库表。 (我们将在本文中描述该过程。)
  4. 将XML配置文件复制到应用程序的CLASSPATH。

您会注意到,不必修改任何Java对象即可支持该框架。 例如,想象一下,您需要以某种方式更改Java应用程序使用的数据库表,例如,通过重命名列。 一旦更改了表,更新Java应用程序所需要做的就是更新适当的XML配置文件。 您不需要重新编译任何Java代码。

Hibernate查询语言(HQL)

Hibernate提供了一种称为Hibernate Query Language(HQL)的查询语言,它与SQL非常相似。 对于那些更喜欢老式SQL查询的人来说,Hibernate仍然为您提供使用它们的机会。 但是我们的支持示例将仅使用HQL。

HQL的使用非常简单。 您会找到所有从SQL知道的熟悉的关键字,例如SELECTFROMWHERE 。 HQL与SQL的不同之处在于,您不直接在数据模型上(即在表,列等上)写查询,而是在Java对象上使用它们的属性和关系来写查询。

清单1展示了一个基本示例。 此HQL代码检索其firstName为“ John”的所有Individual

清单1.基本的HQL查询
SELECT * FROM eg.hibernate.mapping.dataobject.Individual WHERE firstName = "John"

您可以参考HQL的参考材料对Hibernate的网站,如果你想了解更多有关HQL语法(见相关信息中的链接)。

XML配置文件

Hibernate功能的核心位于XML配置文件中。 这些文件必须位于应用程序的CLASSPATH中。 我们将它们放在示例代码包的config目录中(可以从参考资料中下载)。

我们将检查的第一个文件是hibernate.cfg.xml。 它包含有关您的数据源的信息(数据库URL,架构名称,用户名,密码等),以及对将包含映射信息的其他配置文件的引用。

其余的XML文件使您可以将Java类与数据库表进行映射。 稍后我们将仔细研究这些文件,但是重要的是要知道它们的文件名遵循ClassName.hbm.xml模式。

我们的支持例子

在本文中,我们将研究一个基本示例,该示例说明Hibernate是如何工作的,并充分利用了三种不同的策略,您可以在这些策略下使用Hibernate进行对象关系映射。 我们的示例应用程序将由一家保险公司使用,该公司必须保留为其客户购买保险的所有财产的合法记录。 我们已经在本文中提供了完整的源代码(请参阅参考资料 )。 此代码提供了基本功能,您可以从中构建完整的应用程序,例如Web或Swing应用程序。

我们的示例假定了这种应用程序的经典用例。 用户将为任何类型的客户(个人,公司,政府机构等)提供搜索条件,然后向用户显示符合指定条件的所有客户的列表-即使这些客户是不同类型的客户。 用户可以从同一列表访问特定客户的更详细视图。

在我们的应用程序中,产权由Right类表示。 Right可以是Lease也可以是PropertyRight归客户所有。 为了代表我们的客户,我们将使用通用类Person 。 一个Person可以是IndividualCorporation 。 当然,保险公司必须知道这些Right所分配到的Estate 。 您会同意, Estate是一个非常通用的术语。 因此,我们将使用LandBuilding类为开发人员提供更全面的对象。

从这个摘要中,我们可以开发如图1所示的类模型:

图1.完整的类模型
完整的课堂模型

我们的数据库模型旨在涵盖我们将在本文中讨论的三种不同策略。 对于Right层次结构,我们将使用一个表( TB_RIGHT ),并使用DISCRIMINATOR列映射到正确的类。 对于Person层次结构,我们将使用称为超级表 ( TB_PERSON )的超级表 ,该表将与其他两个表( TB_CORPORATIONTB_INDIVIDUAL )共享相同的ID 。 第三层次结构( Estate )使用两个不同的表( TB_BUILDINGTB_LAND ),该表由外键链接,该外键由两列( REF_ESTATE_IDREF_ESTATE_TYPE )的组合定义。

图2显示了数据模型:

图2.完整的数据模型
完整的数据模型

设置数据库

Hibernate支持各种RDBMS,它们中的任何一个都可以与我们的示例一起使用。 但是,示例代码和这篇文章的文本已专为HSQLDB(请参阅相关信息中的链接),完全用Java语言编写一个功能齐全的关系型数据库。 在示例代码包的sql目录中,您将找到一个名为datamodel.sql的文件。 该SQL脚本将创建在我们的示例中使用的数据模型。

设置Java项目

尽管您始终可以使用命令行来构建和执行示例代码,但是您可能需要考虑在IDE中设置项目以实现更好的集成。 在示例代码包中,您将找到以下目录:

  • config ,其中包含示例的所有XML配置文件(映射,Log4J等)
  • data ,其中包含HSQLDB使用的配置文件。 您还将找到一个名为startHSQLDB.bat的批处理文件,可用于启动数据库。
  • src ,包含所有示例的源代码。

确保将所需的Java库和XML配置文件复制到应用程序的CLASSPATH中。 该代码仅需要Hibernate和HSQLDB库即可正确编译和运行。 您可以从“ 相关主题”部分下载这些软件包。

策略1:每个子类一张桌子(人)

在第一个策略中,我们将研究如何映射Person层次结构。 您会注意到数据模型与我们的类模型非常接近。 结果,我们将为层次结构中的每个类使用不同的表,但是所有这些表必须共享相同的主键(我们将在稍后详细说明)。 Hibernate在将新记录插入数据库时​​将使用此主键。 访问数据库时,它还将利用相同的主键执行JOIN操作。

现在,我们需要将对象层次结构映射到表模型。 我们有三个表( TB_PERSONTB_INDIVIDUALTB_CORPORATION )。 如上所述,它们都有一个名为ID的列作为主键。 这样的共享列名不是强制性的,但被认为是一种很好的做法,它使读取生成SQL查询变得容易得多。

在清单2所示的XML映射文件中,您会注意到在Person映射定义中这两个具体的类被声明为<joined-subclass> 。 XML元素<id>映射到顶级表TB_PERSON的主键,而<key>元素(来自每个子类)映射到TB_INDIVIDUALTB_CORPORATION表的匹配主键。

清单2. Person.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
	"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping>
  <class name="eg.hibernate.mapping.dataobject.Person" table="TB_PERSON" polymorphism="implicit">
    <id name="id" column="ID">
      <generator class="assigned"/>
    </id>
    <set name="rights" lazy="false">
      <key column="REF_PERSON_ID"/>
      <one-to-many class="eg.hibernate.mapping.dataobject.Right" />
    </set>
    <joined-subclass name="eg.hibernate.mapping.dataobject.Individual" 
  table="TB_INDIVIDUAL">
      <key column="id"/>
      <property name="firstName" column="FIRST_NAME" type="java.lang.String" />
      <property name="lastName" column="LAST_NAME" type="java.lang.String" />
    </joined-subclass>
    <joined-subclass name="eg.hibernate.mapping.dataobject.Corporation" 
  table="TB_CORPORATION">
      <key column="id"/>
      <property name="name" column="NAME" type="string" />
      <property name="registrationNumber" column="REGISTRATION_NUMBER" type="string" />
    </joined-subclass>
  </class>
</hibernate-mapping>

使用Hibernate将新的Individual实例保存在我们的Java代码中非常简单,如清单3所示:

清单3.保存一个新的Individual实例
public Object create(Object object) {
  Session session = null;
  try {
    session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    session.save(object);
    session.flush();
    tx.commit();
    ...
}

反过来,Hibernate生成清单4中所示的两个SQL INSERT请求。这两个请求只需要一个save()

清单4. SQL插入查询
insert into TB_PERSON (ID) values (?)
insert into TB_INDIVIDUAL (FIRST_NAME, LAST_NAME, id) values (?, ?, ?)

要从数据库访问Individual ,只需在HQL查询中指定类名,如清单5所示。

清单5.调用一个HQL查询
public Person findIndividual(Integer id) {
  ...
  session.find("select p from " + Individual.class.getName() + " as p where p.id = ?",
    new Object[] { id },
    new Type[] { Hibernate.INTEGER });	
  ...
}

然后,Hibernate将自动执行SQL JOIN以从两个表中检索所有必需的信息,如清单6所示:

清单6.个人SQL SELECT查询
select individual0_.id as ID, individual0_.FIRST_NAME as FIRST_NAME55_, 
  individual0_.LAST_NAME as LAST_NAME55_ 
  from TB_INDIVIDUAL individual0_ 
  inner join TB_PERSON individual0__1_ on individual0_.id=individual0__1_.ID 
  where (individual0_.id=? )

但是,如果未指定具体的类,则Hibernate需要执行SQL JOIN ,因为它不知道要通过哪个表。 在从HQL查询返回的所有检索到的表列中,还将返回一个额外的动态列。 Hibernate使用clazz列来实例化并填充返回的对象。 与我们将在第二种策略中使用的方法相反,我们将该类确定称为动态的 。

清单7显示了如何查询具有给定id属性的抽象Person ,而清单8显示了Hibernate自动生成SQL查询,包括表联结:

清单7. find()方法调用中的HQL查询
public Person find(Integer id) {
  ...
  session.find("select p from " + Person.class.getName() + " as p where p.id = ?",
    new Object[] { id },
    new Type[] { Hibernate.INTEGER });	
  ...
}
清单8.任何类型的PersonSQL SELECT查询
select person0_.ID as ID0_,
  casewhen(person0__1_.id is not null, 1,  
  casewhen(person0__2_.id is not null, 2,  
  casewhen(person0_.ID is not null, 0, -1))) as clazz_0_, 
  person0__1_.FIRST_NAME as FIRST_NAME61_0_, 
  person0__1_.LAST_NAME as LAST_NAME61_0_, 
  person0__2_.NAME as NAME62_0_, 
  person0__2_.REGISTRATION_NUMBER as REGISTRA3_62_0_ 
  from TB_PERSON person0_ 
  left outer join TB_INDIVIDUAL person0__1_ on person0_.ID=person0__1_.id 
  left outer join TB_CORPORATION person0__2_ on person0_.ID=person0__2_.id 
  where person0_.ID=?

策略2:每个阶层的君主制拥有一张桌子(权利)

对于我们的Right层次结构,我们将使用一个表( TB_RIGHT )存储整个类层次结构。 您会注意到TB_RIGHT表拥有存储Right类层次结构的每个属性所需的所有列。 然后,已保存实例的值将被保存在表中,而每个未使用的列均填充有NULL值。 (由于到处都是“Kong”,因此我们常称其为瑞士奶酪桌。 )

在图3中,您将注意到TB_RIGHT表包含一个附加列DISCRIMINATOR 。 Hibernate使用此列自动实例化适当的类并相应地填充它。 此列是使用映射文件中的<discriminator> XML元素映射的。

图3. TB_RIGHT表的内容
TB_RIGHT表内容

就像清单2中的Person映射文件一样,在清单9中,我们映射了抽象类( Right )及其所有属性。 为了映射我们的两个具体类( LeaseProperty ),我们将使用<subclass> XML标签。 这个标签非常简单。 它需要name属性,就像class标签需要discriminator-value属性一样。 Hibernate将使用last属性来标识应使用的类。

图1的类图中您会注意到, discriminator不是任何Java类的属性。 实际上,它甚至都没有映射。 它只是Hibernate和数据库之间共享的技术专栏。

清单9. Right.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC 
  "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
  "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
  <class name="eg.hibernate.mapping.dataobject.Right" table="TB_RIGHT" polymorphism="implicit">
    <id name="id" column="ID">
      <generator class="assigned"/>
    </id>
    <discriminator>
      <column name="DISCRIMINATOR"/>
    </discriminator>
    <property name="date" column="DATE" type="java.sql.Date" />
    <many-to-one name="person" class="eg.hibernate.mapping.dataobject.Person" column="REF_PERSON_ID"/>
    <any name="estate"
         meta-type="string"
         id-type="java.lang.Integer">
      <meta-value value="LND" class="eg.hibernate.mapping.dataobject.Land"/>
      <meta-value value="BLD" class="eg.hibernate.mapping.dataobject.Building"/>
      <column name="REF_ESTATE_TYPE"/>
      <column name="REF_ESTATE_ID"/>          
    </any>
    
    <subclass name="eg.hibernate.mapping.dataobject.Property" discriminator-value="PRO"/>
    
    <subclass name="eg.hibernate.mapping.dataobject.Lease" discriminator-value="LEA">
      <property name="duration" column="DURATION" type="java.lang.Integer" />
     </subclass>
  </class>
</hibernate-mapping>

在清单9的映射文件中,您会注意到RightPerson层次结构之间是多对一的关系,(自然地)与Person层次结构(一对多)中的关系相反。 还请注意, RightEstate层次结构之间的关系; 我们将在本文后面介绍这种关系。

与第一种策略一样,Hibernate在访问数据库时会产生非常有效SQL语句。 当我们查询具体的类时,如清单10所示,Hibernate会自动对discriminator符值进行过滤,这是一件好事,因为这意味着Hibernate仅读取指定类的适当列。

清单10.对具体类SQL查询
select property0_.ID as ID, property0_.DATE as DATE, 
	property0_.REF_PERSON_ID as REF_PERS4_, property0_.REF_ESTATE_TYPE as REF_ESTA5_, 
	property0_.REF_ESTATE_ID as REF_ESTA6_ 
	from TB_RIGHT property0_ where property0_.DISCRIMINATOR='PRO'

当我们查询抽象类时,事情变得有些棘手。 由于Hibernate不知道您要使用的特定类,因此它必须读取每一列(包括discriminator列),然后确定要实例化的类,最后填充它。 然后,判别器与我们的第一个策略的clazz列扮演相同的角色。 但是,这种方法被证明是更静态的,因为类名是直接从区分符的值派生的。

清单11.(抽象)Right类SQL查询
select right0_.ID as ID, 
	right0_.DISCRIMINATOR as DISCRIMI2_, 
	right0_.DATE as DATE, right0_.REF_PERSON_ID as REF_PERS4_, 
	right0_.REF_ESTATE_TYPE as REF_ESTA5_, right0_.REF_ESTATE_ID as REF_ESTA6_, 
	right0_.DURATION as DURATION from TB_RIGHT right0_

数据库模型完整性

关于第二种策略,有一个主要的问题:为了使其起作用,必须将所有非共享列设置为NULLABLE 。 结果表可能非常难以使用,因为开发人员通常依赖于数据库约束。 (毕竟,将持续时间设置为NULLLease没有多大意义!)

一种解决方案是使用数据库级别的检查约束。 根据DISCRIMINATOR值,您可以定义一组要实现的规则,如清单12所示。当然,数据库引擎必须支持此功能。 此外,由于必须针对所有具体类并同时在单个有效表达式中表达这些约束,因此随着层次结构的增长,可能变得难以维护。

清单12.数据完整性约束
alter table TB_RIGHT 
	add constraint CHK_RIGHT check(	
		(discriminant ='DPP' and date is null and duration is null)
	or	(discriminant ='DLM' and date is not null and duration is not null));

策略3:每个具体班级一张桌子(房地产)

我们的第三个也是最后一个策略可能是最有想象力的:每个具体类有一张桌子,而Estate抽象超类没有一张桌子。 我们将依靠Hibernate为多态提供支持。 在清单13所示的XML映射文件中,您会注意到,只有两个具体的类( BuildingLand )被映射:

清单13. Estate.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC 
  "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
  "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping>
  <class name="eg.hibernate.mapping.dataobject.Land" table="TB_LAND" polymorphism="implicit">
    <id name="id" column="ID">
      <generator class="assigned"/>
    </id>
    <property name="description" column="DESCRIPTION" type="java.lang.String" />
    <property name="squareFeet" column="SQUARE_FEET" type="java.lang.Double"/>
  </class>

  <class name="eg.hibernate.mapping.dataobject.Building" table="TB_BUILDING" polymorphism="implicit">
    <id name="id" column="ID">
      <generator class="assigned"/>
    </id>
    <property name="description" column="DESCRIPTION" type="java.lang.String" />
    <property name="address" column="ADDRESS" type="java.lang.String"/>
  </class>
</hibernate-mapping>

当您查看清单13中的映射文件时,您的第一个React可能是说:“嘿!此映射与我每天使用的映射没有什么不同。这里没有什么大不了的!” 而且您应该这样认为。 实际上,我们的第三个策略只需要一个条件:我们需要将polymorphism属性专门设置为implicit

即使在映射文件中找不到Estate类,它也仍然存在于我们的类层次结构中。 并且,由于我们的两个映射类( BuildingLand )继承自Estate ,因此我们可以在HQL查询中使用此抽象超类,如清单14所示。Hibernate随后将使用自省功能来标识扩展该抽象类的类,以便它可以连续地为每个子类执行适当SQL查询。

清单14. find()方法调用中的HQL查询
public Estate find(Integer id) {
  ...
  List objects =
    session.find(
      "select e from " + Estate.class.getName() + " as e where e.id = ?",
        new Object[] { id },
        new Type[] { Hibernate.INTEGER });
  ...
}

为了找到给定ID的匹配Estate ,Hibernate必须将清单15中的两个查询提交到数据库。

清单15. SQL查询
select land0_.ID as ID, land0_.DESCRIPTION as DESCRIPT2_, land0_.SQUARE_FEET as SQUARE_F3_ 
from TB_LAND land0_ where (land0_.ID=? )

select building0_.ID as ID, building0_.DESCRIPTION as DESCRIPT2_, building0_.ADDRESS as ADDRESS 
from TB_BUILDING building0_ where (building0_.ID=? )

正如我们在第二种策略中看到的那样, Right阶级和Estate阶级之间存在多对一的关系。 用简单的英语来说,它是这样的:“一个Estate可以指许多 Right 。但是每个Right只能指一个 Estate 。” 但是从数据模型的角度来看,没有可以用来创建外键约束的唯一表,例如TB_RIGHTTB_PERSON之间的TB_PERSON 。 这实际上使我们无法创建外键。 幸运的是,Hibernate为我们提供了一个非常强大的XML映射元素, <any>标记,清单16展示了其用法。

清单16.任何关系的XML映射
<any name="estate"
	meta-type="string"
	id-type="java.lang.Integer">
  <meta-value value="LND" class="eg.hibernate.mapping.dataobject.Land"/>
  <meta-value value="BLD" class="eg.hibernate.mapping.dataobject.Building"/>
  <column name="REF_ESTATE_TYPE"/>
  <column name="REF_ESTATE_ID"/>          
</any>

让我们仔细看看我们的新映射。 我们的虚拟外键基于TB_RIGHT表中的两列。 第一个( REF_ESTATE_TYPE )包含将用于映射适当的类名称的鉴别字符串 。 第二个( REF_ESTATE_ID )是另一个表的主键中的列名。 使用默认设置,Hibernate将尝试将映射的类名存储在第一列中,这可能会占用空间且效率低下(尤其是如果在代码重构期间更改了类名)。 幸运的是,Hibernate提供了一种使用<meta-value> XML元素将类名与字符串常量关联的方法。 这些常量的作用与第二种策略中讨论的鉴别器相同。 再次,此功能仅涉及Hibernate和数据库,因此不会更改类层次结构。

数据库模型完整性

尽管标准SQL不允许给定列同时具有多个表的引用约束,但仍然可以添加触发器,该触发器将根据给定读取的鉴别符值检查目标表中是否存在数据。 但是,这种完整性强制方法可能非常难以维护,并且还可能降低数据库的整体性能。

多态-到极点!

使用Hibernate的内置多态性时,要牢记一件事:如果不小心,则可以检索比讨价还价更多的信息,只要所有类都已将polymorphism属性设置为implicit 。 清单17展示了一种使用两个单词的HQL查询检索整个数据库的方法。

清单17. HQL查询
public List all() {
  ...
  List objects = session.find("from Object");
  ...
}

很强大,你不觉得吗? 当然,我们中很少有人需要(或为此)通过单个HQL查询来检索整个数据库。 这个(废话)示例的目标仅旨在显示隐式多态性的功能。 您可以利用此功能来避免将无用且消耗资源SQL查询发送到数据库。

结论

在本文中,我们尝试为您提供Hibernate提供的三种映射策略的相当简单的实现示例。 回顾一下,每种策略都有其优点和缺点:

  • 对于我们的第一个策略(每个子类一个表),每次实例化和填充对象时,Hibernate都会读取多个表。 如果您的索引定义正确并且层次结构不太深,那么该操作将产生良好的结果。 但是,如果不是这种情况,您可能会遇到整体性能问题。
  • 对于第二种策略(每个类层次结构一个表),您必须使用检查约束来定义完整性。 随着列数的增加,此策略可能难以维护。 另一方面,您可以选择根本不使用此类约束,而是依靠应用程序的代码来管理其自身的数据完整性。
  • 我们的第三个策略(每个具体类一个表)具有一些映射限制,并且基础数据模型不能使用参照完整性,这意味着您没有充分利用关系数据库引擎。 从好的方面来说,这种策略可以很容易地与其他两种策略结合使用。

无论选择哪种策略,请始终记住,您无需在流程中更改Java类,这意味着业务对象与持久性框架之间绝对没有链接。 正是这种灵活性使Hibernate在对象关系Java项目中如此流行。


翻译自: https://www.ibm.com/developerworks/java/library/j-hibernate/index.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值