类型 - 非性别或种族 - 歧视是指我们在从 SQL 查询结果集中读取一行,并确定应该实例化哪个 Java 类来保存该行数据时的行为。类型歧视是任何支持多态查询或关联的 ORM 解决方案或手动编写的持久化层所需要的。
可视化类型歧视的最简单方法是考虑以下结果集
( 123, '1234-4567-8910-1234', 'Foo Bar', 'VISA' ) ( 111, '4321-7654-0198-0987', 'Baz Qux', 'MCD' )
此结果集包含两张信用卡的详细信息。假设我们的应用程序有不同的 Java 类来表示每种类型的信用卡。我们有两个子类,VisaCard 和 MasterCard,它们都是 CreditCard 类的子类。然后我们可以检查结果集的最后一列,以决定为每一行实例化哪个类。这一列是所谓的 鉴别器列
。
你可能想知道为什么我要谈论 结果集
而不是表。好吧,有各种方法可以将 Java 类层次映射到关系数据库模式:每层一个表、每具体类一个表、每类一个表。因此,实际的表结构可能相当复杂。但是,从数据库中有效地获取数据的唯一方法是将其规范化为一个大的方形结果集。实际上,我通常认为 SQL 查询的工作是将每具体类一个表或每类一个表映射的数据转换为一个中间的每层一个表规范化形式。(这也是为什么每层一个表映射策略提供了三种映射策略中最好的性能 - 它已经处于方便的形式,并且不需要在数据库上进行并或连接。)无论我们选择哪种策略,我们都需要对这种扁平化的结果集执行类型歧视。
现在,我最感兴趣的是结果集区分器列。关于继承映射的主题的写作并不多,而且大多数ORM解决方案(实际上是我所知道的每一个)都假设区分器列是超类根表的物理列。确实如此,通常还假设从区分器值到类的映射是一对一的。但情况不必如此。在我的信用卡示例中,这当然是有意义的。但现在让我们考虑一个不同的案例。我们在PERSON表中存储与特定个人的数据;我们的SQL查询可能如下所示
SELECT ID, NAME, SPECIES FROM PERSON
而结果集可能如下
( 12345, 'Zxychg Ycjzy', 'Martian' ) ( 52778, 'Glooble Queghm', 'Venusian' ) ( 98876, 'Gavin King', 'Human' )
现在,在这个世界上,我们认为人类非常特别,值得比其他智能外星生命物种更多的关注。所以我们可能有一个特定的Human类和一个通用的Alien类。那么,从区分器列值到类的映射肯定不是一对一的。确实,有一个针对Human的特定
值,以及一个针对Alien的通配符
值。
实际上,在HUMAN表中包含一些额外的、针对Human的数据是相当合理的。为了获取所有必要的数据,让我们使用以下连接
SELECT ID, NAME, SPECIES, COUNTRY FROM PERSON NATURAL JOIN HUMAN ( 12345, 'Zxychg Ycjzy', 'Martian', null ) ( 52778, 'Glooble Queghm', 'Venusian', null ) ( 98876, 'Arnold Schwarzenegger', 'Human', 'US' )
在这个结果集中,我们有两个潜在的区分器列。可以是COUNTRY或SPECIES列来决定个人是否为人类。而且COUNTRY列不是根PERSON表的列。现在想象我们引入了进一步的细化,并包含我们组织员工的具体数据
SELECT ID, NAME, SPECIES, COUNTRY, JOB FROM PERSON NATURAL JOIN HUMAN NATURAL JOIN EMPLOYEE ( 12345, 'Zxychg Ycjzy', 'Martian', null, null ) ( 52778, 'Glooble Queghm', 'Venusian', null, null ) ( 98876, 'Arnold Schwarzenegger', 'Human', 'US', null ) ( 34556, 'Gavin King', 'Human', 'AU', 'Java Developer' )
现在我们不能再仅使用单个列来执行类型区分。哎呀!真乱。让我们稍微修改一下查询
SELECT ID, NAME, SPECIES, COUNTRY, JOB, CASE WHEN LOC IS NULL THEN 'ALIEN' WHEN JOB IS NULL THEN 'HUMAN' ELSE 'EMPLOYEE' END FROM PERSON NATURAL JOIN HUMAN NATURAL JOIN EMPLOYEE ( 12345, 'Zxychg Ycjzy', 'Martian', null, null, 'ALIEN' ) ( 52778, 'Glooble Queghm', 'Venusian', null, null, 'ALIEN' ) ( 98876, 'Arnold Schwarzenegger', 'Human', 'US', null, 'HUMAN' ) ( 34556, 'Gavin King', 'Human', 'AU', 'Java Developer', 'EMPLOYEE' )
耶,我们又得到了干净漂亮的区分器列!但这个列绝对不对应任何表的物理列。它只包含纯派生值。
这是基于内容的区分
。我们的示例使用的是按类映射策略的表,但上述结果集也可以来自其他映射策略。
这是基于内容的区分的第二例
SELECT TX_ID, ACCOUNT_ID, AMOUNT, CASE WHEN AMOUNT>0 THEN 'CREDIT' ELSE 'DEBIT' END FROM TRANSACTIONS ( 12875467987, 98798723, 56.99, 'CREDIT' ) ( 09808343123, 87558345, 123.25, 'DEBIT' )
在这里,我们使用基于数学表达式(AMOUNT>0)的列来区分DebitTransaction和CreditTransaction。原则上,可以存在更复杂的表达式。(在实践中,它们可能仍然相当简单。)
Hibernate 2.x中的按类映射策略始终使用基于内容的区分,而按层次映射策略始终使用基于列的区分。由于某种原因——现在对我来说有点模糊——这感觉相当自然。在Hibernate 3中,您可以为按层次映射策略使用基于内容的区分
<class name="Person" table="PERSON" discriminator-value="ALIEN"> ... <discriminator type="string"> <formula> CASE WHEN LOC IS NULL THEN 'ALIEN' WHEN JOB IS NULL THEN 'HUMAN' ELSE 'EMPLOYEE' END </formula> </discriminator> ... <subclass name="Human" discriminator-value="HUMAN"> ... <subclass name="Employee" discriminator-value="EMPLOYEE"> ... </subclass> </subclass> </class>
并且您可以为按类映射使用基于列的区分
<class name="Person" table="PERSON" discriminator-value="ALIEN"> ... <discriminator type="string" column="TYPE"/> ... <subclass name="Human" discriminator-value="HUMAN"> <join table="HUMAN"> ... <subclass name="Employee" discriminator-value="EMPLOYEE"> <join table="EMPLOYEE"> ... </join> </subclass> </join> </subclass> </class>
对于按具体类映射策略(<union-subclass>映射),只有基于内容的区分才有意义。