Возник тут у меня вопросик. Кода сейчас под рукой нет, но помощь нужна срочно, так что сначала постараюсь словами обьяснить.
Итак есть сущности A, B, C. Ссылаются они друг на друга вот так: A -> B -> C. Хранятся соответственно в трех таблицах.
Есть метод обьекта, который где-то внутри открытой транзакции пытается загрузить обьект A (и соответственно связанные с ним B и C) по его идентификатору. В штатном режиме все работает отлично. Но вот возникла ситуация, когда в цепочке A -> B -> C отсутствует последний элемент, то есть в таблице С нет записи с идентификатором указанным в таблице B. При этом бросается "org.hibernate.ObjectNotFoundException", что есть правильно. Однако с точки зрения логики моей программы эта ошибка не должна приводить к фатальным последствиям, то есть я ее ловлю, пишу в лог и продолжаю работать дальше. И вот здесь возникает проблема. При попытке закоммитить транзакцию бросается новая ошибка типа "org.hibernate.HibernateException: Found two representations of same...". Анализ стектрейса показывает, что происходит эта беда при session.flush() что случается перед коммитом транзакции. Причем в сессии я вижу висящим
обьект A загрузка которого вызвала ошибку №1 (и попытка сохранить который приводит к ошибке №2).
Вопрос такой — почему вообще обьект А оказался в сессии — ведь он не был до конца загружен, то есть по идее находится в невалидном состоянии? Как с этим бороться?
Здравствуйте, Jeremy, Вы писали:
J>Но вот возникла ситуация, когда в цепочке A -> B -> C отсутствует последний элемент, то есть в таблице С нет записи с идентификатором указанным в таблице B. При этом бросается "org.hibernate.ObjectNotFoundException", что есть правильно. Однако с точки зрения логики моей программы эта ошибка не должна приводить к фатальным последствиям,
У вас целостность базы нарушена и это не должно приводить к фатальным последствиям??? Почему констрейнта на FK нет?
J>то есть я ее ловлю, пишу в лог и продолжаю работать дальше. И вот здесь возникает проблема. При попытке закоммитить транзакцию бросается новая ошибка типа "org.hibernate.HibernateException: Found two representations of same...". Анализ стектрейса показывает, что происходит эта беда при session.flush() что случается перед коммитом транзакции. Причем в сессии я вижу висящим J>обьект A загрузка которого вызвала ошибку №1 (и попытка сохранить который приводит к ошибке №2). J>Вопрос такой — почему вообще обьект А оказался в сессии — ведь он не был до конца загружен, то есть по идее находится в невалидном состоянии? Как с этим бороться?
Хотелось бы все же увидеть полный текст ошибки, stacktrace и немного кода. Ибо ошибка Found two representations of same collection у меня с вашим A-B-C что-то не вяжется.
Здравствуйте, Jeremy, Вы писали:
J>Вопрос такой — почему вообще обьект А оказался в сессии — ведь он не был до конца загружен, то есть по идее находится в невалидном состоянии? Как с этим бороться?
Потому, что никто не обещал, что сессия хибернейта будет находится в рабочем состоянии после исключения.
Здравствуйте, Blazkowicz, Вы писали:
B>У вас целостность базы нарушена и это не должно приводить к фатальным последствиям??? Почему констрейнта на FK нет?
Констрейнта нет потому что в эту базу пишет внешнее приложение, контроля над которым нет и которое может записать невалидные (с точки зрения моей программы) данные. Короче говоря, таково, увы, условие задачи.
B>Хотелось бы все же увидеть полный текст ошибки, stacktrace и немного кода. Ибо ошибка Found two representations of same collection у меня с вашим A-B-C что-то не вяжется.
Код (синтетический пример), сорри за обьем:
1. Описание сущностей:
@Entity
@Table(name="A")
public static class A {
private String id;
private List<B> children;
public A(){}
@Column(name = "id")
@Id
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@OneToMany(mappedBy="parentId", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public List<B> getChildren() {
return children;
}
public void setChildren(List<B> children) {
this.children = children;
}
}
@Entity
@Table(name="A1")
public static class B {
private String id;
private String parentId;
private C child;
public B(){
}
@Column(name = "id")
@Id
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Column(name = "parentid")
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
@OneToOne(fetch = FetchType.EAGER)
@JoinColumn(name="ID", referencedColumnName="ID", insertable = false, updatable = false)
public C getChild() {
return child;
}
public void setChild(C child) {
this.child = child;
}
}
@Entity
@Table(name = "A2")
public static class C {
private String id;
public C() {
}
@Column(name = "id")
@Id
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
2. Считывание экземпляра A:
public A getA(String id){
return (A) factory.getCurrentSession()
.createCriteria(A.class)
.add(Restrictions.eq("id", id))
.uniqueResult();
}
3. Фрагмент кода, бросающий исключение:
Transaction t = factory.getCurrentSession().beginTransaction();
try {
try{
getA("1");
} catch (Exception ex){
// Здесь ловим org.hibernate.ObjectNotFoundException: No row with the given identifier exists
}
t.commit();
} catch(Exception e){
// Здесь ловим исключение, описанное в п.4
}
4. Стектрейс:
org.hibernate.HibernateException: Found two representations of same collection: A.children
at org.hibernate.engine.Collections.processReachableCollection(Collections.java:153)
at org.hibernate.event.def.FlushVisitor.processCollection(FlushVisitor.java:37)
at org.hibernate.event.def.AbstractVisitor.processValue(AbstractVisitor.java:101)
at org.hibernate.event.def.AbstractVisitor.processValue(AbstractVisitor.java:61)
at org.hibernate.event.def.AbstractVisitor.processEntityPropertyValues(AbstractVisitor.java:55)
at org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:138)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:196)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:76)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:26)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
Здравствуйте, Blazkowicz, Вы писали:
B>@org.hibernate.annotations.NotFound(action=NotFoundAction.IGNORE)
Да, я в курсе . Скорее всего, по этому пути и придется идти.
Два момента:
1. Изначально так не сделал поскольку мне все-таки хотелось отслеживать случаи с несуществующим экземпляром C и, как минимум, логировать их появления. В случае с ловлей исключения это было сделать проще. В случае использования @NotFound придется вводить дополнительную проверку на NULL, руками фильтровать результаты, что кмк сделает код менее понятным.
2. Все-таки любопытно разобраться в причинах такого поведения Hibernate. Мне оно кажется крайне нелогичным. Не смог загрузить обьект (ObjectNotFound) — зачем при этом портить всю сессию, делая невозможной дальнейшую работу?
Здравствуйте, Jeremy, Вы писали:
J>2. Все-таки любопытно разобраться в причинах такого поведения Hibernate. Мне оно кажется крайне нелогичным. Не смог загрузить обьект (ObjectNotFound) — зачем при этом портить всю сессию, делая невозможной дальнейшую работу?
Откуда все же берется вторая коллекция? Посмотрите содержимое сессии в дебаге.
Здравствуйте, Jeremy, Вы писали:
J>2. Все-таки любопытно разобраться в причинах такого поведения Hibernate. Мне оно кажется крайне нелогичным. Не смог загрузить обьект (ObjectNotFound) — зачем при этом портить всю сессию, делая невозможной дальнейшую работу?
Может попробовать на самой последней версии Hibernate?