Подтвердить что ты не робот

Создание внешнего ключа в структуре "один-на-один" за один конкретный класс

TL; DR. Как я могу обеспечить создание схемы Hibernate для создания ограничения внешнего ключа в настройке таблицы за конкретный класс от AbstractProperty.ownerId до Owner.ownerId для структуры, отображаемой ниже, не добавляя свойство Owner к AbstractProperty?

Я работаю над проектом, где у меня есть следующая структура классов:

Class structure sample

Owner имеет взаимно однозначное сопоставление с AbstractProperty, которое расширяется классом ConcreteProperty (и другими, такими как AnotherProperty, но это не актуально для остальной части этого вопроса).

AbstractProperty действительно имеет только одно свойство, abstractPropertyId. Поэтому мы хотим использовать структуру table-per-concrete-class, в результате получим таблицы Owner, ConcreteProperty и таблицы для других расширяющих классов AbstractProperty (AnotherProperty).

С этой целью я создал следующее сопоставление для Owner:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.example">
    <class name="Owner">
        <id name="ownerId">
            <generator class="identity"/>
        </id>
        <property name="ownerProperty"/>
        <one-to-one name="abstractProperty"/>
    </class>
</hibernate-mapping>

И для AbstractProperty:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.example">
    <class name="AbstractProperty" abstract="true">
        <id name="ownerId">
            <generator class="foreign">
                <param name="property">ownerId</param>
            </generator>
        </id>
        <union-subclass name="ConcreteProperty">
            <property name="concreteProperty"/>
        </union-subclass>
        <union-subclass name="AnotherProperty">
            <property name="anotherProperty"/>
        </union-subclass>
    </class>
</hibernate-mapping>

Это работает.

Однако, и вот мой вопрос, используя это сопоставление и имея Hibernate, создаю схему для меня (<property name="hbm2ddl.auto">create</property>), он не создает ограничение внешнего ключа из поля базы данных ConcreteProperty.ownerId в поле Owner.ownerId. Это происходит, когда я создаю инверсное связанное одно-одно поле от AbstractProperty до Owner, используя это сопоставление для AbstractProperty (где поле Owner имеет тип Owner в классе AbstractProperty java ):

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.example">
    <class name="AbstractProperty" abstract="true">
        <id name="ownerId">
            <generator class="foreign">
                <param name="property">ownerId</param>
            </generator>
        </id>
        <one-to-one name="owner" constrained="true"/>
        <union-subclass name="ConcreteProperty">
            <property name="concreteProperty"/>
        </union-subclass>
        <union-subclass name="AnotherProperty">
            <property name="anotherProperty"/>
        </union-subclass>
    </class>
</hibernate-mapping>

Как я могу принудительно создать внешний ключ от AbstractProperty.ownerId до Owner.ownerId без этого поля Owner в моем AbstractProperty?

4b9b3361

Ответ 1

Простой ответ: никогда не позволяйте Hibernate создавать схему для реальных приложений.

Спящий режим является объектным реляционным картографом, и его следует рассматривать как это.

Этот Hibernate дополнительно создает схемы в первый раз. Но в средах после первого выпуска вы не хотите, чтобы Hibernate контролировал схему. В конце концов вы должны обрабатывать SQL, чтобы иметь сценарии миграции (вручную или поддержка инструмента). После первого выпуска у вас будут данные в базе данных. Чтобы обеспечить миграцию данных в производственной системе с меньшими проблемами, вы должны рассматривать схему и миграцию данных таким же образом в рабочей среде, как и в своей среде разработки.

Исключениями могут быть любое приложение, которое редко меняет данные, которые могут быть быстро восстановлены при потере данных.

Ответ 2

Мы используем стандартную JPA (без хаберовских хаков) и имели ту же проблему, и мы не нашли подходящего решения.

Предположения:

  • AbstractProperty - это класс в пакете, который многократно используется/используется в разных приложениях и что вы не хотите ссылаться на специфический для приложения класс Owner.
  • ConcreteProperty и AnotherProperty относится к конкретным приложениям.

В этом случае решением было бы поставить ссылки в ConcreteProperty на Owner (с внешними ключами), в конечном итоге оба с AnotherProperty, расширяющие один и тот же ApplicationProperty, и делая abstractPropertyId так что при настройке Владельца он устанавливается автоматически.

Ответ 3

Не будет ли он работать автоматически, если вы определяете атрибут Owner в Abstract Property как " переходный процесс?

Переменные могут быть помечены как переходные, чтобы указать, что они не являются частью постоянного состояния объекта.

И если вы реализуете собственную сериализацию вручную, вы можете проверить модификатор на поле и просто игнорировать его → проблема циклической сериализации исключена.

Единственным другим способом, который я вижу, является толкать атрибут Owner к каждому из конкретных классов свойств и изменять ваше сопоставление на

<class name="AbstractProperty" abstract="true">
    <id name="ownerId">
        <generator class="foreign">
            <param name="property">ownerId</param>
        </generator>
    </id>

    <union-subclass name="ConcreteProperty">
        <property name="concreteProperty"/>
        <one-to-one name="owner" constrained="true"/>
    </union-subclass>
    <union-subclass name="AnotherProperty">
        <property name="anotherProperty"/>
        <one-to-one name="owner" constrained="true"/>
    </union-subclass>
</class>

который создает следующий sql:

create table AnotherProperty (
    ownerId integer not null,
    anotherProperty varchar(255),
    primary key (ownerId)
)

create table ConcreteProperty (
    ownerId integer not null,
    concreteProperty varchar(255),
    primary key (ownerId)
)

create table Owner (
    ownerId integer generated by default as identity,
    ownerProperty varchar(255),
    primary key (ownerId)
)

alter table AnotherProperty 
    add constraint FK_ceq89n6x2i1ax18bb4gqpq4m5 
    foreign key (ownerId) 
    references Owner

alter table ConcreteProperty 
    add constraint FK_i41buhvtxxtpsim2cc0ur1gxr 
    foreign key (ownerId) 
    references Owner

Ответ 4

Во-первых: Hibernate/JPA способен обрабатывать множество сценариев - если действительно было много людей, которые пытались использовать тот же подход, как и вы, я бы подумал, что к нему уже обратились бы - это не цыпленок spring, ** что ключ;-) **

Во-вторых: наличие таблицы с именем "Владелец" с "ownerProperty" является еще одним ключом. Имена выводят встроенные отношения.

В-третьих: просто заявив, что вы не хотите собственности владельца в таблице AbstractProperty, это создает основу для логической ошибки, обычно называемой catch-22 (http://en.wikipedia.org/wiki/False_dilemma).

Моя точка → Это, по-видимому, больше связано с проблемой моделирования/дизайна, чем с технической/каркасной проблемой.

Мое предложение было бы сделать шаг назад от проблемы и пересмотреть ее. Например, если вы просто писали прямые запросы с помощью spring -jdbc, как бы вы ожидали взаимодействия с данными для операций SELECT, UPDATE и DELETE?... если вы будете работать с ними, ваши решения/потребности, скорее всего, будут проявляться более четко. Чтобы быть еще более заостренным, что бы вы ожидали, что поведение будет на каскадном удалении? Если я выдаю инструкцию DELETE для одной записи владельца, хотите ли вы, чтобы база данных автоматически удаляла записи из дочерних таблиц? рекурсивно? Как только у вас будет это изолированное, вы можете понять, как сообщить Hibernate, что делать - не позволяйте членам команды поставить корзину перед лошадью, преждевременно ограничивая это решение.

Например, (случай 1), если вы действительно имеете дело с "собственностью" владельца, было бы разумно предвидеть, что вам нужно будет хранить несколько свойств о владельце (aka: OneToMany).

В качестве альтернативы (случай 2), если вы имеете дело с "типом" владельца (как в поле дискриминатора), тогда ваша таблица "AbstractProperty" должна расширять владельца... в этом случае вы можете уменьшите ваше решение до трех таблиц (владелец с дискриминатором, Concrete1 w/ownerId, Concrete2 w/ownerId).

Предлагаемое решение 1: В любом из этих случаев для таблицы "AbstractProperty" было бы целесообразно иметь ссылку на него parent/Owner. И если это так, я думаю, что Cascading DELETES может работать так, как вам хотелось бы.

Предлагаемое решение 2: Если, однако, в каскадном сценарии удаления записи "Владелец" вы предпочтете, чтобы строки (-ы) в AbstractProperty привязывались как ссылочные данные, тогда можно было бы утверждать, что вы должны добавить дополнительную таблицу между Owner и AbstractProperty, чтобы защитить ваши ссылочные данные... как таблицу сопоставления, которая имеет уникальный составной ключ.

Сосредоточьтесь на потребностях бизнеса и пользовательских рассказах, и это, надеюсь, поможет вам выбрать доступные решения.