如何在辅助表中的非主键列上连接表?

Posted

技术标签:

【中文标题】如何在辅助表中的非主键列上连接表?【英文标题】:How do I join tables on non-primary key columns in secondary tables? 【发布时间】:2009-09-29 19:06:36 【问题描述】:

我有一种情况,我需要在 ORM 类层次结构中的对象上连接表,其中连接列不是基类的主键。以下是表格设计的示例:

CREATE TABLE APP.FOO
(
    FOO_ID INTEGER NOT NULL,
    TYPE_ID INTEGER NOT NULL,
    PRIMARY KEY( FOO_ID )
)

CREATE TABLE APP.BAR
(
    FOO_ID INTEGER NOT NULL,
    BAR_ID INTEGER NOT NULL,
    PRIMARY KEY( BAR_ID ),
    CONSTRAINT bar_fk FOREIGN KEY( FOO_ID ) REFERENCES APP.FOO( FOO_ID )
)

CREATE TABLE APP.BAR_NAMES
(
    BAR_ID INTEGER NOT NULL,
    BAR_NAME VARCHAR(128) NOT NULL,
    PRIMARY KEY( BAR_ID, BAR_NAME),
    CONSTRAINT bar_names_fk FOREIGN KEY( BAR_ID ) REFERENCES APP.BAR( BAR_ID )
)

这里是映射(为简洁起见,删除了 getter 和 setter

@Entity
@Table(name = "FOO")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "TYPE_ID", discriminatorType =    javax.persistence.DiscriminatorType.INTEGER)
public abstract class Foo 
    @Id
    @Column(name = "FOO_ID")
    private Long fooId;


@Entity
@DiscriminatorValue("1")
@SecondaryTable(name = "BAR", pkJoinColumns =  @PrimaryKeyJoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID") )
public class Bar extends Foo
    @Column(table = "BAR", name = "BAR_ID")
    Long barId;
     

如果BAR_NAMES 的连接列不是FOO_ID,而是BAR_ID,我该如何添加映射?

我尝试了以下方法:

@CollectionOfElements(fetch = FetchType.LAZY)
@Column(name = "BAR_NAME")
@JoinTable(name = "BAR_NAMES", joinColumns = @JoinColumn(table = "BAR", name = "BAR_ID", referencedColumnName="BAR_ID"))
List<String> names = new ArrayList<String>();

这会失败,因为用于检索 Bar 对象的 SQL 尝试从 FOO 表中获取 BAR_ID 值。我也尝试将 JoinTable 注释替换为

@JoinTable(name = "BAR_NAMES", joinColumns = @JoinColumn(name = "BAR_ID"))

这不会产生 SQL 错误,但也不会检索任何数据,因为针对 BAR_NAMES 的查询使用 FOO_ID 作为连接值而不是 BAR_ID。

出于测试目的,我使用以下命令填充了数据库

insert into FOO (FOO_ID, TYPE_ID) values (10, 1);
insert into BAR (FOO_ID, BAR_ID) values (10, 20);
insert into BAR_NAMES (BAR_ID, BAR_NAME) values (20, 'HELLO');

当获取 ID 为 10 的 Foo 对象时,许多看似有效的解决方案将返回一个空集合(而不是包含 1 个名称的集合)

【问题讨论】:

【参考方案1】:

我能够找到解决方案。如果你像这样映射 Bar 类

@Entity
@DiscriminatorValue("1")
@SecondaryTable(name = "BAR", pkJoinColumns =  @PrimaryKeyJoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID") )
public class Bar extends Foo 
    @OneToOne
    @JoinColumn(table = "BAR", name = "BAR_ID")
    MiniBar miniBar;

并添加以下类

@Entity
@SqlResultSetMapping(name = "compositekey", entities = @EntityResult(entityClass = MiniBar.class, fields =  @FieldResult(name = "miniBar", column = "BAR_ID"), ))
@NamedNativeQuery(name = "compositekey", query = "select BAR_ID from BAR", resultSetMapping = "compositekey")
@Table(name = "BAR")
public class MiniBar 
    @Id
    @Column(name = "BAR_ID")
    Long barId;
 

然后,您可以将所需的任何类型的映射添加到 MiniBar 类,就好像 barId 是主键一样,然后进一步使其在外部 Bar 类中可用。

【讨论】:

barId 还是标有@Id,有什么区别?【参考方案2】:

不确定如何使用 JPA/Annotations 执行此操作,但使用 Hibernate XML 映射文件会是这样的:

<class name="Bar" table="BAR">
    <id name="id" type="int">
        <column name="BAR_ID"/>
        <generator class="native"/>
    </id>
    <set name="barNames" table="BAR_NAMES">
        <!-- Key in BAR_NAMES table to map to this class's key -->
        <key column="BAR_ID"/> 
        <!-- The value in the BAR_NAMES table we want to populate this set with -->
        <element type="string" column="BAR_NAME"/>
    </set>
</class>

【讨论】:

我相信这等价于@JoinTable(name = "BAR_NAMES", joinColumns = @JoinColumn(name = "BAR_ID")),因为它使用FOO_ID的值来加入BAR_NAMES 表。 对不起,我误读了你的意思。我无法将 BAR_ID 映射为 Bar 类的主键,因为它是 Foo 对象的子对象,使用带有辅助表的单表继承或使用连接的子类继承。【参考方案3】:

你将无法做你想做的事。 @CollectionOfElements(和 @OneToMany,就此而言)总是通过所有者的实体主键映射。

映射 Foo / Bar 继承的方式也很奇怪——它们显然不在同一个表中;似乎使用JoinedSubclass 会是更好的方法。请记住,这仍然无法帮助您将 bar_names 映射到 bar_id,因为主键值在层次结构之间共享(即使子类的列名可能不同)。

一种可能的替代方法是在 Foo 和 Bar 之间使用 @OneToOne 映射而不是继承。这是您能够将 bar_names 映射到 bar_id 以及最适合您的表结构的映射的唯一方法(尽管可能不适用于您的域模型)。

【讨论】:

在 live 环境中,Foo 是许多具有不同类型 id 的子类的基类。我们已经使用了连接子类模型和带有辅助表的单类模型,并发现两者都有其优点和缺点。更改层次结构不是一种选择。 老实说,一旦你用辅助表增加了“table-per-hierarchy”方法,与“table-per-class”方法相比,你失去了它所具有的唯一优势(性能稍快),但那是与您的问题无关。正如我所说,如果不更改层次结构映射或数据库结构(例如,放弃 bar_id 并在整个层次结构中使用 foo_id 作为 PK),您将无法按照您想要的方式映射您的集合。 referencedColumnName 专门用于解决此类问题。如果我创建一个连接 FOO 表的 TYPE_ID 的表 TYPE_NAMES,则可以按预期工作。不允许映射辅助表中的列是错误或设计限制。我不想确定是哪个。 referencedColumnName 解决了一个完全不同的问题 - 它允许您在“to one”关联的另一端指定列的名称(无论是实际的 @OneToOne 映射、@SecondaryTable 映射还是多对一通过复合键)。 呃,除非我说过,如果我在主表中使用列,它就可以工作。

以上是关于如何在辅助表中的非主键列上连接表?的主要内容,如果未能解决你的问题,请参考以下文章

Mysql 6 业务设计(逻辑设计)+物理设计

使用 IN vs = 运算符时在非主键列上使用聚集索引

如何在Django中加入非主键和外键列的查询

MySQL面试精华提炼

oracle:物化视图中的主键列

表设计遵循的三大范式实例