сохраняющий объект со связанными зависимыми объектами, использующими спящий режим
Мы используем 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.