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


我有一种情况,我需要在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 )
)

这里是映射(为了简洁而消除了吸气剂和固定剂

@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。

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

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个名称的集合相对),许多似乎有效的解决方案将返回空集合


8276
2017-09-29 19:06


起源



答案:


我能够找到解决方案。如果您像这样映射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 类。


6
2017-10-01 22:08



barId 仍然标有 @Id, 有什么不同? - deathangel908


答案:


我能够找到解决方案。如果您像这样映射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 类。


6
2017-10-01 22:08



barId 仍然标有 @Id, 有什么不同? - deathangel908


不知道如何使用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>

2
2017-09-29 19:15



我相信这相当于@JoinTable(name =“BAR_NAMES”,joinColumns = @JoinColumn(name =“BAR_ID”)),这不起作用,因为它使用FOO_ID的值来加入BAR_NAMES表。 - Jherico
对不起,我误解了你在说什么。我无法将BAR_ID映射为Bar类的主键,因为它是Foo对象的子节点,它使用单表继承与辅助表或使用连接的子类继承。 - Jherico


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

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

可能的替代方案是使用 @OneToOne Foo和Bar之间的映射而不是继承。这是你能够映射的唯一方式 bar_names 至 bar_id 和表格结构最合适的映射(但也许不适用于您的域模型)。


2
2017-09-29 20:21



在实时环境中,Foo是具有不同类型id的许多子类的基类。我们使用了连接子类模型和带有辅助表的单类模型,发现它们都有自己的优点和缺点。更改层次结构不是一种选择。 - Jherico
说实话,一旦你用二级表增加“每层次表一次”方法,你就会失去它所具有的唯一优势(性能稍快),而不是“每类表”方法,但这与你的问题无关。正如我所说,如果不改变层次结构映射或数据库结构(例如放弃),您将无法按照自己的方式映射集合。 bar_id 和使用 foo_id 作为贯穿整个层次结构的PK)。 - ChssPly76
referencedColumnName专门用于解决此类问题。如果我创建一个表连接在FOO表的TYPE_ID上的表TYPE_NAMES,它按预期工作。无法允许在辅助表中的列上进行映射是错误或设计限制。我试图确定哪个。 - Jherico
referencedColumnName 解决了一个完全不同的问题 - 它允许您在“到一个”关联的另一端指定列的名称(无论是实际的@OneToOne映射,@ SecondaryTable映射还是通过复合键的ManyToOne)。 - ChssPly76
呃,除了像我说的那样,如果我在主表中使用了一个列,它就可以工作了。 - Jherico