сохраняющий объект со связанными зависимыми объектами, использующими спящий режим

Мы используем Hibernate как уровень стойкости и имеем сложную объектную модель. Не подвергая реальную модель данных, я хочу объяснить проблему, используя следующий простой пример.

class Person {
    private Integer id; //PK
    private String name;
    private Account account;
    // other data, setters, getters
}


class Account {
    private Integer id; //PK
    // other data, setters, getters
}

Отображение БД определяется с помощью HBM следующим образом:

 <class name="Person" table="PERSON">
    <id name="id" column="ID">
        <generator class="native"/>
    </id>
    <version name="version" type="java.lang.Long"/>
    <property name="name" type="java.lang.String" length="50" column="NAME"/>
    <many-to-one name="account" column="ACCOUNT_ID"
                class="com.mycompany.model.Account"/>

</class>

Мне нужно сохранить новый заполненный экземпляр Person связанный с существующей Account. Вызов инициируется веб-клиентом, поэтому на моем уровне я получаю экземпляр Person, на который ссылается экземпляр Account, содержащий только его идентификатор.

Если я попытаюсь вызвать saveOrUpdate(person) следующее исключение:

org.hibernate.TransientObjectException: 
object references an unsaved transient instance - save the transient instance before flushing: 
com.mycompany.model.Account

Чтобы этого избежать, мне нужно найти сохраненный объект Account by ID, а затем вызвать person.setAccount(persistedAccount). В этом случае все работает нормально.

Но в реальной жизни я имею дело с десятками сущностей, на которые ссылаются друг на друга. Я не хочу писать специальный код для каждой ссылки.

Интересно, существует ли какое-то общее решение этой проблемы.

Ответ 1

Чтобы сохранить одну сущность, вам просто нужно иметь ссылки на ее прямые зависимости. Тот факт, что эти другие сущности ссылаются на другие объекты, не имеет значения.

Лучший способ сделать это - получить прокси-сервер для ссылочного объекта, даже не попав в базу данных, используя session.load(Account.class, accountId).

То, что вы делаете, - это правильная вещь: получить ссылку на постоянную учетную запись и установить эту ссылку во вновь созданную учетную запись.

Ответ 2

Hibernate позволяет использовать только ссылочные классы с идентификатором, вам не нужно делать session.load().

Важно только то, что если ваш ссылочный объект имеет VERSION, то должна быть установлена версия. В вашем случае вы должны указать версию объекта Account.

Ответ 3

Использовать cascade="all" на карте * -to- *

Ответ 4

Вы пробовали cascade="save-update" во many-to-one? Hibernate по умолчанию - cascade="none"...

Ответ 5

Спасибо за помощь. Я уже реализовал свое собственное универсальное решение, но хотел узнать, существуют ли другие решения.

Я хочу поделиться с вами идеей. Я называю ссылки сущностей, которые не содержат ничего, кроме ID (или другого поля, которые могут быть использованы для идентификации сущности однозначно) placeholder.

Итак, я создал аннотацию @Placeholder и разместил ее на всех ссылочных полях. В нашем примере это поле учетной записи класса Person. У нас уже есть класс с именем GenericDao который обертывает Hibernate API и имеет метод save(). Я добавил еще один метод saveWithPlacehodlers() который делает следующее. Он обнаруживает класс данного объекта путем отражения, находит все поля, помеченные аннотацией @Placeholder, находит объекты в БД и вызывает соответствующий сеттер в основной сущности для замены ссылочного заполнителя на постоянную сущность.

Annotation @Placeholder позволяет определить поле, которое будет использоваться для идентификации объекта. По умолчанию используется id.

Как вы думаете, ребята об этом решении?

Ответ 6

Существует еще одно решение проблемы - с использованием конструктора по умолчанию для объекта, для которого вы хотите ссылаться, и для установки идентификатора и версии (если это версия). У нас есть метод dao:

public <S extends T> S materialize(EId<S> entityId, Class<S> entityClass) {
        Constructor<S> c = entityClass.getDeclaredConstructor();
        c.setAccessible(true);

        S instance = c.newInstance();
        Fields.set(instance, "id", entityId.getId());
        Fields.set(instance, "version", entityId.getVersion());
        return instance; // Try catch omitted for brevity.
}

Мы можем использовать такой подход, потому что мы не используем ленивую загрузку, а вместо этого имеем "представления" объектов, которые используются в графическом интерфейсе. Это позволяет нам уйти от всех объединений, которые использует Hibernate для заполнения всех нетерпеливых отношений. В представлениях всегда есть id и версия объекта. Следовательно, мы можем заполнить ссылку, создав объект, который, казалось бы, Hibernate не был бы временным.

Я пробовал использовать этот подход, а другой - с session.load(). Оба работали нормально. Я вижу некоторое преимущество в моем подходе, поскольку Hibernate не будет течь со своими прокси-серверами в другом месте кода. Если не использовать должным образом, я просто получаю NPE вместо исключения no session bound to thread.