Как присоединиться к таблицам в столбцах непервичного ключа во вторичных таблицах?

У меня есть ситуация, когда мне нужно присоединить таблицы к объекту в иерархии классов ORM, где столбец join НЕ является основным ключом базового класса. Вот пример дизайна таблицы:

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>();

Это не удается, потому что SQL для извлечения объекта Bar пытается получить значение BAR_ID из таблицы FOO. Я также попытался заменить аннотацию 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');

Многие решения, которые работают, возвращают пустую коллекцию при получении объекта Foo для ID 10 (в отличие от коллекции, содержащей 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.

Ответ 2

Не знаю, как это сделать с JPA/Annotations, но с файлами сопоставления XML файлов Hibernate это будет примерно так:

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

Ответ 3

Вы не сможете делать то, что хотите. @CollectionOfElements (и @OneToMany, если на то пошло) являются всегда, отображаемыми через первичный ключ сущности владельца.

То, как вы наследуете наследование Foo/Bar, также довольно странно - они явно не в одной таблице; похоже, использование RegisteredSubclass было бы лучшим подходом. Имейте в виду, что все равно не поможет вам сопоставить bar_names с bar_id, потому что значение первичного ключа является общим для иерархии (даже если имя столбца может быть разным для подкласса).

Возможной альтернативой является использование @OneToOne сопоставление между Foo и Bar вместо наследования. Это единственный способ сопоставления bar_names с bar_id и наиболее подходящего отображения для вашей структуры таблицы (хотя, возможно, не для вашей модели домена).