如何在辅助表中的非主键列上连接表?
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 映射还是多对一通过复合键)。
呃,除非我说过,如果我在主表中使用列,它就可以工作。以上是关于如何在辅助表中的非主键列上连接表?的主要内容,如果未能解决你的问题,请参考以下文章