Проблема отображения Hibernate с несвязанной коллекцией - PullRequest
3 голосов
/ 10 августа 2010

Добро пожаловать,

У меня есть проблемы с отображением Hibernate.

Структура базы данных:

TableA
 -ID_A --PK

TableB
 -ID_B --PK
 -ID_A -- FK -> TableA

TableC
 -ID_C -- PK
 -ID_A -- FK -> TableA

Структура POJO:

class TableA extends Pojo {

 /*Some Fields*/

}

class TableB extends Pojo {

  TableA tableA; 

 /*Some properties*/

}

class TableC extends Pojo {

 TableA tableA;

 Collection<tableB> tableBs;

}

То, что я хочу иметь, это набор элементов TableB в отображении для Pojo TableC, ключ отображения - это tableA.

Эта коллекция должна быть только для чтения.

Отображение должно быть hbm, а не аннотациями.

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

ОБНОВЛЕНИЕ: описание случая.

Вариант использования 1: загрузка одного объекта TableC

Session session = (Session) getHibernateTemplate().getSessionFactory().openSession();
SQLQuery sqlQuery = session.createSQLQuery("SELECT c.* FROM TableC c WHERE c.ID_C = 1"); //Oracle
  sqlQuery.addEntity("c", TableC.class);
return sqlQuery.list(); //Return list with sigle object of TableC

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

Вариант использования 2 Загрузка коллекций объектов

Session session = (Session) getHibernateTemplate().getSessionFactory().openSession();
SQLQuery sqlQuery = session.createSQLQuery("SELECT c.* FROM TableC c WHERE c.ID_C in (1,2)"); //Oracle
  sqlQuery.addEntity("c", TableC.class);
return sqlQuery.list(); // throws "collection is not associated with any session" 

В этом случае Hibernate генерирует исключение при получении следующего объекта.

* коды - только образцы, сессия все-таки закрыта.

Ответы [ 4 ]

9 голосов
/ 17 августа 2010

После некоторых исследований проблема в Hibernate известна как ошибка # HHH-2862

Это в основном вызвано тем, что ключколлекция не уникальна в результатах.

Когда Hibernate инициализирует коллекцию, используя 'persistenceContext.addUninitializedCollection ()', и это обнаружит, что коллекция с данным ключом уже была добавлена, затем устанавливает старый экземплярнулевым и текущим в коллекции.Однако эта коллекция уже была добавлена ​​в контекст постоянства с помощью более раннего вызова, и когда StatefulPersistenceContext.initializeNonLazyCollections() выполняет итерацию по всем коллекциям в постоянном контексте, вызывая forceInitialization() для ссылок, удаляется пустая ссылка, которая выдает «коллекция несвязан с любым сеансом «исключения». И это объясняет, почему в моем случае только последний объект имеет ссылку, и только с одним все работало нормально, и ваши предположения относительно проблемы отложенной инициализации.

Некоторые мысли о том, какобойти эту ошибку?

5 голосов
/ 14 августа 2010

Если у вас есть внешний ключ, который ссылается на неосновной ключ, вы должны использовать свойство-ref-атрибут

Его документация

Имя свойства связанного класса который присоединяется к этому внешнему ключу

И поскольку ваша Коллекция является неизменной , вы должны установить атрибут mutable в false

Здесь идет ваше отображение

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping 
          PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!--Set up your package right here-->
<hibernate-mapping package="br.com.ar">
     <class name="Aa" table="A">
         <id name="id">
             <generator class="native"/>
         </id>
    </class>
    <class name="Bb" table="B">
         <id name="id">
             <generator class="native"/>
         </id>
         <many-to-one name="aa" column="A_ID" class="Aa"/>
    </class>
    <class name="Cc" table="C">
         <id name="id">
             <generator class="native"/>
         </id>
         <many-to-one name="aa" column="A_ID" class="Aa"/>
         <bag name="bbList" table="B" mutable="false">
             <key column="A_ID" property-ref="aa"/>
             <one-to-many class="Bb"/>
         </bag>
    </class>
</hibernate-mapping>

Каждый класс описывается следующим образом

br.com.ar.Aa

public class Aa {

    private Integer id;

    // getter's and setter's

}

br.com.ar.Bb

public class Bb {

    private Integer id;

    private Aa aa;

    // getter's and setter's

}

br.com.ar.Cc

public class Cc {

    private Integer id;

    private Aa aa;

    private Collection<Bb> bbList = new ArrayList<Bb>();

    // getter's and setter's

}

ДОБАВЛЕНО В КАЧЕСТВО РАБОТЫ

Хорошо, давайте посмотрим первый вариант использования

query = new StringBuilder().append("SELECT ")
                               .append("{cc.*} ")
                           .append("from ")
                               .append("C cc ")
                           .append("where ")
                               .append("cc.id = 1 ")
                           .toString();

Вы сказали

Объект загружен всеми данными и объектами, связанными с Bb .С этим объектом мы можем работать, изменять его и обновлять модификации в базе данных

Как видите, ваш NATIVE SQL просто получает объекты Cc. Нет присоединиться.Но вы сказали, что он извлекает все связанные объекты, включая The bbList.Если это так, то это происходит потому, что у вас есть такое отображение (Notice fetch = "select" lazy = "false")

<class name="Cc" table="C">
     ...
     <bag name="bbList" table="B" mutable="false" fetch="select" lazy="false">
         <key column="A_ID" property-ref="aa"/>
         <one-to-many class="Bb"/>
     </bag>
</class>

fetch = "select" использует дополнительный выбор.Для отношения «один ко многим» и «многие ко многим» (bbList, верно ???) атрибут fetch работает в сочетании с с атрибутом lazy, чтобы определить, как и когда загружается связанная коллекция.Если lazy = "true", то коллекция загружается, когда к ней обращается приложение, но если lazy = "false", то Hibernate загружает коллекцию немедленно , используя отдельный оператор SQL SELECT.

Но если запустить

query = new StringBuilder().append("SELECT ")
                               .append("{cc.*} ")
                           .append("from ")
                               .append("C cc ")
                           .append("where ")
                               .append("cc.id in (1,2) ")
                           .toString();

Я получаю ожидаемое

org.hibernate.HibernateException: коллекция не связана ни с одним сеансом

Почему ???

Вы хотите присоединиться к Cc и связанному с ним bbList через столбец A_ID , что подразумевает использование атрибута property-ref , верно ???

<class name="Cc" table="C">
     ...
     <bag name="bbList" table="B" mutable="false" fetch="select" lazy="false">
         <key column="A_ID" property-ref="aa"/>
         <one-to-many class="Bb"/>
     </bag>
</class>

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

Обходной путь

Попробуйте получить одно за другим

List<Cc> resultList = new ArrayList<Cc>();
for (Integer id : new Integer[] {1, 2}) {
    query = new StringBuilder().append("SELECT ")
                                   .append("{cc.*} ")
                               .append("from ")
                                   .append("C cc ")
                               .append("where ")
                                   .append("cc.id = :id ")
                               .toString();

    resultList.addAll(
             session.createSQLQuery(query)
                    .addEntity("cc", Cc.class)
                    .setParameter("id", id)
                    .list());
}

Или использовать простые запросы JDBC

0 голосов
/ 17 августа 2010

ИМХО, вы не используете ORM, как это предполагается использовать.Ваша модель класса выглядит в точности как структура таблицы.Это не обязательно так.Таким образом, вы не извлекаете выгоду из ORM.

Поэтому я предлагаю поместить список B и C в таблицу A. Затем вы можете перемещаться в любом месте:

class A
{
 IList<B> Bs { get; private set; }
 IList<C> Cs { get; private set; }
}

class B
{
  A A { get; set; }
}

class C
{
  A A { get; set; }
}

Вы отображаете это так:

<class name="A">
  <bag name="As" class="A">
    <key column="ID_A"/>
  </bag>
  <bag name="Bs" class="B">
    <key column="ID_A"/>
  </bag>
</class>

<class name="B">
  <many-to-one name="A" class="A" column="ID_A"/>
</class>

<class name="C">
  <many-to-one name="A" class="A" column="ID_A"/>
</class>

, тогда вы можете просто перемещаться по нему следующим образом:

B b = session.Get<B>(id);
foreach(C c in b.A.Cs)
{
  // iterate C's
}

Запрос HQL:

SQLQuery sqlQuery = session.createQuery("FROM TableC c WHERE c.ID_C in (1,2)"); 
  • ДонНе знаю, почему вы используете собственный SQL здесь.
  • addEntity должен добавить параметры к запросу объекта типа (который фактически обрабатывается как его идентификатор).Насколько я вижу, в вашем запросе нет параметров.

Редактировать:

Запросить все B, которые имеют один и тот же Aкак определенный C:

    C c = session.Get<C>(cId);

    SQLQuery sqlQuery = session.createQuery(
@"select B
from B, C
where B.A = C.A
  and C = :c")
      .addEntity("c", c);

или короче:

    C c = session.Get<C>(cId);

    SQLQuery sqlQuery = session.createQuery(
@"select B
from B
where B.A = :a")
      .addEntity("a", c.A);
0 голосов
/ 10 августа 2010

Возможно, вы допустили ошибку в опубликованных кодах, но предполагали, что это правильно, но ваш класс TableC имеет список TableB, тогда как ваша таблица базы данных TableC имеет ссылку FK на таблицу A.

...