【问题标题】: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 个名称的集合)

【问题讨论】:

    标签: java hibernate orm


    【解决方案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 映射还是多对一通过复合键)。
    • 呃,除非我说过,如果我在主表中使用列,它就可以工作。
    猜你喜欢
    • 2011-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-15
    • 2018-03-29
    • 1970-01-01
    • 2020-09-22
    • 1970-01-01
    相关资源
    最近更新 更多