Глава 11. Работа с объектами

Оглавление
  1. 11.1. Состояния объектов Hibernate
  2. 11.2. Делаем объекты постоянными (persistent)
  3. 11.3. Загрузка объекта
  4. 11.4. Запросы
    1. 11.4.1. Выполнение запросов
    2. 11.4.2. Фильтрация коллекций
    3. 11.4.3. Запросы Criteria
    4. 11.4.4. Запросы на нативном SQL
  5. 11.5. Изменение постоянных объектов
  6. 11.6. Изменение отсоединённых (detached) объектов
  7. 11.7. Автоматическое обнаружение состояния
  8. 11.8. Удаление постоянных объектов
  9. 11.9. Репликация объекта между двумя разными хранилищами данных
  10. 11.10. Сброс (flush) сессии
  11. 11.11. Переходное постоянство (Transitive persistence)
  12. 11.12. Использование метаданных

Hibernate — это полное объектно-реляционное решение для отображений, которое не только скрывает от разработчика детали системы управления базами данных, но также предлагает управление состояниями объектов. Это, вместо управления инструкциями SQL на низком уровне, даёт возможность работать с JDBC/SQL на высоком уровне в естественном для Java-приложений объектно-ориентированным стиле.

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

11.1. Состояния объектов Hibernate

Hibernate определяет и поддерживает следующие состояния объектов:

Рассмотрим более подробно состояния и переход состояний (и методы Hibernate, которые инициируют переход).

11.2. Делаем объекты постоянными (persistent)

Вновь сохданные экземпляры постоянного класса считаются переходными по Hibernate. Мы можем сделать переходный экземпляр постоянным, связав его с сессией:

DomesticCat fritz = new DomesticCat();
fritz.setColor(Color.GINGER);
fritz.setSex('M');
fritz.setName("Fritz");
Long generatedId = (Long) sess.save(fritz);

Если у Cat есть сгенерированный идентификатор, идентификатор генерируется и назначается Cat вызовом save(). Если у Cat есть присвоенный (assigned) идентификатор или составной ключ, идентификатор должен быть присвоен экземпляру cat перед вызовом save(). Вы также можете использовать persist() вместо save(), с семантикой, определённой в раннем черновике EJB3.

В качестве альтернативы вы можете назначить идентификатор, используя перегруженную версию save().

DomesticCat pk = new DomesticCat();
pk.setColor(Color.TABBY);
pk.setSex('F');
pk.setName("PK");
pk.setKittens( new HashSet() );
pk.addKitten(fritz);
sess.save( pk, new Long(1234) );

Если объект, который вы делаете постоянным, имеет связанные объекты (например, коллекция kittens в предыдущем примере), эти объекты могут быть сделаны постоянными в любом порядке, если вы не имеете ограничения NOT NULL для столбца внешнего ключа. Риск нарушить ограничения внешнего ключа никогда не возникнет. Однако вы можете нарушить ограничение NOT NULL, если сохраните объекты вызовом save() в неправильном порядке.

Обычно вы не беспокоитесь об этой детали, так как обычно используете функцию переходного постоянства (transitive persistence) Hibernate для автоматического сохранения связанных объектов. Тогда даже нарушения ограничений NOT NULL не встретятся — Hibernate позаботится обо всём. Переходное постоянство (Transitive persistence) обсуждается далее в этой главе.

11.3. Загрузка объекта

Методы load() из Session предоставляют способ получения постоянного экземпляра, если вы знаете его идентификатор. load() принимает объект класса и загружает состояние в только что созданный экземпляр этого класса в постоянном состоянии.

Cat fritz = (Cat) sess.load(Cat.class, generatedId);
// вам нужно обернуть примитивные идентификаторы
long id = 1234;
DomesticCat pk = (DomesticCat) sess.load( DomesticCat.class, new Long(id) );

Кроме того, вы можете загрузить состояние в данный экземпляр:

Cat cat = new DomesticCat();
// загрузить состояние в cat
sess.load( cat, new Long(pkId) );
Set kittens = cat.getKittens();

Имейте в виду, что load() выдает невосстанавливаемое исключение, если нет соответствующей записи в базе данных. Если класс отображён на прокси, load() просто возвращает неинициализированный прокси и фактически не попадает в базу данных до тех пор, пока вы не вызовете метод прокси. Это полезно, если вы хотите создать связь с объектом, не загружая его из базы данных. Он также позволяет загружать несколько экземпляров в виде пакета, если batch-size определён для отображения классов.

Если вы не уверены, что существует подходящая запись, вы должны использовать метод get(), который немедленно обратится к базе данных и возвратит null, если нет соответствующей записи.

Cat cat = (Cat) sess.get(Cat.class, id);
if (cat == null) {
    cat = new Cat();
    sess.save(cat, id);
}
return cat;

Вы даже можете загрузить объект, с помощью SQL SELECT ... FOR UPDATE, используя LockMode. Дополнительную информацию см. в документации по API.

Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);

Любые связанные экземпляры или содержащиеся коллекции не будут выбраны FOR UPDATE, если вы не решите указать lock или all как каскадный стиль для ассоциации.

Можно повторно загрузить объект и все его коллекции в любое время, используя метод refresh(). Это полезно, когда триггеры базы данных используются для инициализации некоторых свойств объекта.

sess.save(cat);
sess.flush(); //принудительно выполнить SQL INSERT
sess.refresh(cat); //перечитать состояние (после запуска триггера)

Как много загрузит Hibernate из базы данных и сколько SQL SELECT будут использовано? Это зависит от стратегии выборки (fetching strategy) и объясняется в разделе 20.1 «Стратегии выборки (fetching strategy)».

11.4. Запросы

Если вы не знаете идентификаторы объектов, которые вы ищете, вам нужен запрос. Hibernate поддерживает простой, но мощный объектно-ориентированный язык запросов (HQL). Для создания программных запросов Hibernate поддерживает сложные функциональные возможности: Criteria и Example (QBC и QBE). Вы также можете выразить свой запрос в нативном SQL вашей базы данных, с дополнительной поддержкой Hibernate для преобразования результатов в объекты.

11.4.1. Выполнение запросов

HQL и нативные SQL-запросы представлены экземпляром org.hibernate.Query. Этот интерфейс предлагает методы привязки (binding) параметров, обработки набора результатов и выполнения фактического запроса. Вы всегда получаете Query с использованием текущего Session:

List cats = session.createQuery(
    "from Cat as cat where cat.birthdate < ?")
    .setDate(0, date)
    .list();
List mothers = session.createQuery( "select mother from Cat as cat join cat.mother as mother where cat.name = ?") .setString(0, name) .list();
List kittens = session.createQuery( "from Cat as cat where cat.mother = ?") .setEntity(0, pk) .list();
Cat mother = (Cat) session.createQuery( "select cat.mother from Cat as cat where cat = ?") .setEntity(0, izi) .uniqueResult();]]
Query mothersWithKittens = (Cat) session.createQuery( "select mother from Cat as mother left join fetch mother.kittens"); Set uniqueMothers = new HashSet(mothersWithKittens.list());

Запрос обычно выполняется путем вызова list(). Результат запроса будет полностью загружен в коллекцию в памяти. Экземпляры сущностей, полученные запросом, находятся в постоянном (persistent) состоянии. Метод uniqueResult() предлагает ярлык (shortcut), если вы знаете, что ваш запрос возвратит только один объект. Запросы, которые используют целевую выборку коллекций, обычно возвращают дубликаты корневых объектов, но с их инициализацией коллекций. Вы можете фильтровать эти дубликаты с помощью Set.

11.4.1.1. Итерирование результатов

Иногда вы можете добиться большей производительности, выполнив запрос с помощью метода iterate(). Это будет уместно, если вы ожидаете, что фактические экземпляры объекта, возвращаемые запросом, уже будут находиться в кэше сессии или кэше второго уровня. Если они еще не кэшированы, iterate() будет медленнее, чем list(), и может потребоваться много запросов к базе данных для простого запроса, обычно 1 для начального select, который возвращает только идентификаторы, и N дополнительных select для инициализации фактических экземпляров.

// выьрать идентификаторы
Iterator iter = sess.createQuery("from eg.Qux q order by q.likeliness").iterate();
while ( iter.hasNext() ) {
    Qux qux = (Qux) iter.next();  // fetch the object
    // то, что мы не могли выразить в запросе
    if ( qux.calculateComplicatedAlgorithm() ) {
        // удалить текущий экземпляр
        iter.remove();
        // не нужно обрабатывать остальные
        break;
    }
}

11.4.1.2. Запросы, возвращающие кортежи (tuples)

Иногда запросы Hibernate возвращают кортежи объектов. Каждый кортеж возвращается как массив:

Iterator kittensAndMothers = sess.createQuery(
            "select kitten, mother from Cat kitten join kitten.mother mother")
            .list()
            .iterator();
while ( kittensAndMothers.hasNext() ) { Object[] tuple = (Object[]) kittensAndMothers.next(); Cat kitten = (Cat) tuple[0]; Cat mother = (Cat) tuple[1]; .... }

11.4.1.3. Скалярные результаты

Запросы могут указывать свойство класса в предложении select. Они могут даже вызывать функции агрегации SQL. Свойства или агрегаты считаются «скалярными» результатами, а не сущностями в постоянном состоянии.

Iterator results = sess.createQuery(
        "select cat.color, min(cat.birthdate), count(cat) from Cat cat " +
        "group by cat.color")
        .list()
        .iterator();
while ( results.hasNext() ) { Object[] row = (Object[]) results.next(); Color type = (Color) row[0]; Date oldest = (Date) row[1]; Integer count = (Integer) row[2]; ..... }

11.4.1.4. Привязка (bind) параметров

Методы Query обеспечивают привязку значений к именованным параметрам или JDBC-стилю ? параметров. В отличие от JDBC, параметры Hibernate нумеруются с нуля. Именованные параметры — это идентификаторы формы: :name в строке запроса. Преимущества названных параметров следующие:

//именованный параметр (предпочтительно)
Query q = sess.createQuery("from DomesticCat cat where cat.name = :name");
q.setString("name", "Fritz");
Iterator cats = q.iterate();
//позиционный параметр
Query q = sess.createQuery("from DomesticCat cat where cat.name = ?");
q.setString(0, "Izi");
Iterator cats = q.iterate();
//список именованных параметров
List names = new ArrayList();
names.add("Izi");
names.add("Fritz");
Query q = sess.createQuery("from DomesticCat cat where cat.name in (:namesList)");
q.setParameterList("namesList", names);
List cats = q.list();

11.4.1.5. Пагинация

Если вам нужно указать границы вашего результирующего набора, то есть максимальное количество записей, которые вы хотите получить, и/или первую запись, которую вы хотите получить, вы можете использовать методы интерфейса Query:

Query q = sess.createQuery("from DomesticCat cat");
q.setFirstResult(20);
q.setMaxResults(10);
List cats = q.list();

Hibernate знает, как перевести этот ограниченный запрос в нативный SQL вашей СУБД.

11.4.1.6. Прокручиваемая итерация

Если ваш драйвер JDBC поддерживает прокручиваемые ResultSet, интерфейс Query можно использовать для получения объекта ScrollableResults, который позволяет гибко осуществлять навигацию по результатам запроса.

Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +
                            "order by cat.name");
ScrollableResults cats = q.scroll();
if ( cats.first() ) {
// find the first name on each page of an alphabetical list of cats by name firstNamesOfPages = new ArrayList(); do { String name = cats.getString(0); firstNamesOfPages.add(name); } while ( cats.scroll(PAGE_SIZE) );
// Now get the first page of cats pageOfCats = new ArrayList(); cats.beforeFirst(); int i=0; while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) ); } cats.close()

Обратите внимание, что для этой функции требуется открытое соединение с базой данных и курсор (cursor). Используйте setMaxResult()/setFirstResult(), если вам нужна автономная (offline) функция пагинации.

11.4.1.7. Внешние именованные запросы

С использованием аннотаций или документов отображения Hibernate запросы можно настроить так, что они станут именованными запросами. @NamedQuery и @NamedQueries могут быть определены на уровне класса, как показано в  примере 11.1, «Определение именованного запроса с помощью @NamedQuery» . Однако их определения глобальны для области видомости фабрики сессии и фабрики управления сущностями. Именованный запрос определяется его именем и строкой запроса.

Пример 11.1. Определение именованного запроса с помощью @NamedQuery.

@Entity
@NamedQuery(name="night.moreRecentThan", query="select n from Night n where n.date >= :date")
public class Night {
    ...
}
public class MyDao { doStuff() { Query q = s.getNamedQuery("night.moreRecentThan"); q.setDate( "date", aMonthAgo ); List results = q.list(); ... } ... }

Использование документа отображения можно настроить с помощью элемента <query>. Не забудьте использовать раздел CDATA, если ваш запрос содержит символы, которые могут быть интерпретированы как разметка.

Пример 11.2. Определение именованного запроса с помощью <query>

<query name="ByNameAndMaximumWeight"><![CDATA[
    from eg.DomesticCat as cat
        where cat.name = ?
        and cat.weight > ?
] ]></query>

Привязка и выполнение параметров происходит программно, как показано в примере 11.3 «Привязка параметров именованного запроса».

Пример 11.3. Привязка параметров именованного запроса

Query q = sess.getNamedQuery("ByNameAndMaximumWeight");
q.setString(0, name);
q.setInt(1, minWeight);
List cats = q.list();

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

Также обратите внимание, что для объявления запроса внутри элемента <hibernate-mapping> требуется глобальное уникальное имя, тогда как объявление запроса внутри элемента <class> делает имя запроса уникальным автоматически, добавляя полное имя класса. Например, eg.Cat.ByNameAndMaximumWeight.

11.4.2. Фильтрация коллекций

Фильтр коллекции — это специальный тип запроса, который может применяться к постоянному набору или массиву. Строка запроса может ссылаться на this, то есть на текущий элемент коллекции.

Collection blackKittens = session.createFilter(
    pk.getKittens(), 
    "where this.color = ?")
    .setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) )
    .list()
);

Возвращённая коллекция считается сумкой (bag), которая является копией данной коллекции. Оригинальная коллекция не изменяется. Это противоречит импликации имени «filter», но соответствует ожидаемому поведению.

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

Collection blackKittenMates = session.createFilter(
    pk.getKittens(), 
    "select this.mate where this.color = eg.Color.BLACK.intValue")
    .list();

Даже пустой запрос фильтра полезен, например для загрузки подмножества элементов в большой коллекции:

Collection tenKittens = session.createFilter(
    mother.getKittens(), "")
    .setFirstResult(0).setMaxResults(10)
    .list();

11.4.3. Запросы Criteria

HQL чрезвычайно эффективен, но некоторые разработчики предпочитают строить запросы динамически, используя объектно-ориентированный API. Для этих случаев Hibernate предоставляет интуитивный API запросов Criteria:

Criteria crit = session.createCriteria(Cat.class);
crit.add( Restrictions.eq( "color", eg.Color.BLACK ) );
crit.setMaxResults(10);
List cats = crit.list();

API Criteria и связанный с ним Example обсуждаются более подробно в главе 17 «Запросы Criteria».

11.4.4. Запросы на нативном SQL

Вы можете выразить запрос в SQL, используя createSQLQuery() и позволить Hibernate управлять отображением из наборов результатов в объекты. Вы можете в любое время вызвать session.connection() и напрямую использовать JDBC Connection. Если вы решите использовать Hibernate API, вы должны заключить SQL-псевдонимы (aliases) в фигурные скобки:

List cats = session.createSQLQuery("SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10")
    .addEntity("cat", Cat.class)
.list();
List cats = session.createSQLQuery(
    "SELECT {cat}.ID AS {cat.id}, {cat}.SEX AS {cat.sex}, " +
           "{cat}.MATE AS {cat.mate}, {cat}.SUBCLASS AS {cat.class}, ... " +
    "FROM CAT {cat} WHERE ROWNUM<10")
    .addEntity("cat", Cat.class)
.list()

SQL-запросы могут содержать именованные и позиционные параметры, подобно запросам Hibernate. Более подробную информацию о нативных SQL-запросах в Hibernate можно найти в главе 18. «Нативный SQL».

11.5. Изменение постоянных объектов

Транзакционные постоянные экземпляры (т.е. объекты, загруженные, сохранённые, созданные или запрошенные Session) могут быть обработаны приложением, и любые изменения в постоянном состоянии будут сохраняться при очистке (flush) Session. Это обсуждается далее в этой главе. Нет необходимости вызывать конкретный метод (например, update(), который имеет другое назначение), чтобы ваши модификации сохранялись. Самый простой способ обновить состояние объекта — загрузить его вызовом load(), а затем манипулировать им напрямую, когда Session открыт:

DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) );
cat.setName("PK");
sess.flush();  // изменения в Cat автоматически обнаруживаются и сохраняются

Иногда эта модель программирования неэффективна, так как в одной сессии требуется как SQL SELECT для загрузки объекта, так и SQL UPDATE для сохранения его обновлённого состояния. Hibernate предлагает альтернативный подход, используя отсоединённые (detached) экземпляры.

11.6. Изменение отсоединённых (detached) объектов

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

Hibernate поддерживает эту модель, обеспечивая повторное присоединение отсоединённых экземпляров с использованием методов Session.update() или Session.merge():

// в первом Session
Cat cat = (Cat) firstSession.load(Cat.class, catId);
Cat potentialMate = new Cat();
firstSession.save(potentialMate);
// в более высоком слое приложения cat.setMate(potentialMate);
// позже, в новом Session secondSession.update(cat); // обновить Cat secondSession.update(mate); // обновить Mate

Если Cat с идентификатором catId уже был загружен secondSession, когда приложение пыталось его повторно присоединенить, было бы выброшено исключение.

Используйте update(), если вы уверены, что сессия не содержит постоянный экземпляр с тем же идентификатором. Используйте merge(), если вы хотите объединить свои изменения в любое время без учета состояния сессии. Другими словами, update() обычно является первым методом, который вы вызывали бы в новой сессии, гарантируя, что повторное присоединение ваших отсоединённых экземпляров является первой выполняемой операцией.

Приложение должно индивидуально вызывать update() для отсоединённых экземпляров, которые доступны из данного отсоединёного экземпляра, только если оно хочет, чтобы их состояние было обновлено. Это может быть автоматизировано с использованием переходного постоянства (Transitive persistence). Дополнительную информацию см. в разделе 11.11 «Переходное постоянство (Transitive persistence)».

Метод lock() также позволяет приложению повторно ассоциировать объект с новой сессией. Однако отделённый экземпляр должен быть немодифицирован.

//просто повторно ассоциировать:
sess.lock(fritz, LockMode.NONE);
//выполнить проверку версии, затем повторно ассоциировать:
sess.lock(izi, LockMode.READ);
//выполните проверку версии, используя SELECT ... FOR UPDATE, затем повторно ассоциировать:
sess.lock(pk, LockMode.UPGRADE);

Обратите внимание, что lock() может использоваться с различными LockMode. Дополнительную информацию см. в документации по API и главе по обработке транзакций. Повторная ассоциация не является единственной вариантом для lock().

Другие модели для длинных единиц работы обсуждаются в разделе 13.3 «Оптимистический контроль параллельного выполнения».

11.7. Автоматическое обнаружение состояния

Пользователи Hibernate запросили метод общего назначения, который либо сохраняет переходный экземпляр, генерируя новый идентификатор, либо обновляет/повторно присоединяет отсоединённый экземпляр, связанный с его текущим идентификатором. Метод saveOrUpdate() реализует этот функционал.

// в первой сессии
Cat cat = (Cat) firstSession.load(Cat.class, catID);
// в более высоком уровне приложения
Cat mate = new Cat();
cat.setMate(mate);
// позже, в новой сессии
secondSession.saveOrUpdate(cat);   // обновить существующее состояние (Cat имеет ненулевой идентификатор)
secondSession.saveOrUpdate(mate);  // сохранить новый экземпляр (Mate имеет нулевой идентификатор)

Использование и семантика saveOrUpdate() кажется запутанной для новых пользователей. Во-первых, если вы не пытаетесь использовать экземпляры из одной сессии в другой новой сессии, вам не нужно использовать update(), saveOrUpdate() или merge(). Бывает так, что во всём приложении нет вызова ни одого из этих методов.

Обычно update() или saveOrUpdate() используются в следующем сценарии:

saveOrUpdate() выполняет следующие действия:

а merge() очень отличается:

11.8. Удаление постоянных объектов

Session.delete() удалит состояние объекта из базы данных. Однако ваше приложение может содержать ссылку на удалённый объект. Лучше думать о delete() как о методе, делающем постоянный экземпляр переходным.

sess.delete(cat);

Вы можете удалять объекты в любом порядке, без риска нарушения внешнего ключа. По-прежнему можно нарушить ограничение NOT NULL в столбце внешнего ключа, удалив объекты в неправильном порядке. Например, если вы удалите родителя, но забудьте удалить детей.

11.9. Репликация объекта между двумя разными хранилищами данных

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

//получить Cat из одной базы данных
Session session1 = factory1.openSession();
Transaction tx1 = session1.beginTransaction();
Cat cat = session1.get(Cat.class, catId);
tx1.commit();
session1.close();
//согласовать со второй базой данных Session session2 = factory2.openSession(); Transaction tx2 = session2.beginTransaction(); session2.replicate(cat, ReplicationMode.LATEST_VERSION); tx2.commit(); session2.close();

ReplicationMode определяет, как replicate() будет иметь дело с конфликтами с существующими записями в базе данных:

Случаи использования для этой функции включают сверку данных, введённых в разные экземпляры базы данных, обновление информации о конфигурации системы во время обновления продукта, откатывание изменений, сделанных во время транзакций, отличных от ACID, и многое другое.

11.10. Сброс (flush) сессии

Иногда Session выполняет инструкции SQL, необходимые для синхронизации состояния соединения JDBC с состоянием объектов, хранящихся в памяти. Этот процесс, называемый сброс (flush), происходит по умолчанию в следующих точках:

Операторы SQL выдаются в следующем порядке:

  1. все вставки сущности в том же порядке, что и соответствующие объекты были сохранены с помощью Session.save()
  2. все обновления сущности
  3. все удаления коллекции
  4. все удаления, обновления и вставки элементов коллекции
  5. все вставки коллекции
  6. все удаления сущности в том же порядке, что и соответствующие объекты были удалены с помощью Session.delete()

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

За исключением случаев, когда вы явно вызываете flush(), нет абсолютно никаких гарантий о том, когда Session выполняет вызовы JDBC, — только тот порядок, в котором они выполняются. Однако Hibernate гарантирует, что Query.list(..) никогда не вернет устаревшие или неверные данные.

Можно изменить поведение по умолчанию, так чтобы сброс (flush) происходил реже. Класс FlushMode определяет три разных режима: только сброс (flush) в момент фиксации (commit), когда используется Hibernate Transaction API, сброс (flush) автоматически использует процедуру или никогда не сбрасывается, если явно не вызывается функция flush(). Последний режим полезен для длительных единиц работы, когда Session остается открытой и отключается через длительное временя (см. раздел 13.3.2, «Расширенная сессия и автоматическое ведение версий»).

sess = sf.openSession();
Transaction tx = sess.beginTransaction();
sess.setFlushMode(FlushMode.COMMIT); // разрешить запросам возвращать устаревшее состояние
Cat izi = (Cat) sess.load(Cat.class, id);
izi.setName(iznizi);
// может вернуть устаревшие данные
sess.find("from Cat as cat left outer join cat.kittens kitten");
// замена на izi не сброшена (not flushed)!
...
tx.commit(); // происходит сброс (flush)
sess.close();

Во время сброса (flush) может возникнуть исключение (например, если операция DML нарушает ограничение). Поскольку обработка исключений предполагает некоторое понимание поведения транзакционний Hibernate, мы обсуждаем его в главе 13 «Транзакции и параллельное выполнение».

11.11. Переходное постоянство (Transitive persistence)

Довольно обременительно сохранять, удалять или повторно присоединять отсоединённые объекты, особенно если вы имеете дело с графом связанных объектов. Частным случаем является отношение родитель/ребёнок. Рассмотрим следующий пример:

Если детям в отношении родитель/ребёнок будут введены значения (например, набор адресов или строк), их жизненный цикл будет зависеть от родителя, и для удобного «каскадирования» изменений состояния не потребуется никаких дополнительных действий. Когда родитель будет сохранён, дочерние объекты с типом значений (value-typed) сохраняются, а когда родитель будет удалён, дети будут удалены и т.д. Это работает для таких операций, как удаление дочернего объекта из коллекции. Поскольку объекты с типом значений (value-typed) не могут иметь общие ссылки, Hibernate обнаружит это и удалит дочерний объект из базы данных.

Теперь рассмотрим один и тот же сценарий с родительскими и дочерними объектами, являющимися сущностями, а не типами значений (например, категориями и объектами, или родительскими и дочерними кошками). У сущностей есть свой жизненный цикл и поддержка общих ссылок. Удаление сущности из коллекции не означает, что она может быть удалена), и по умолчанию нет каскадирования состояния от одной сущности к любым другим связанным сущностям. Hibernate по умолчанию не реализует постоянство по повторному присоединению.

Для каждой базовой операции сессии Hibernate, включая persist(), merge(), saveOrUpdate(), delete(), lock(), refresh(), evict(), replicate() - существует соответствующий каскадный стиль. Соответственно, каскадные стили называются create, merge, save-update, delete, lock, refresh, evict, replicate. Если вы хотите, чтобы операция была каскадирована по ассоциации, вы должны указать это в документе отображения. Например:

<one-to-one name="person" cascade="persist" />

Каскадные стили можно сочетать:

<one-to-one name="person" cascade="persist,delete,lock" />

Вы даже можете использовать cascade=«all», чтобы указать, что все операции должны быть каскадированы вдоль ассоциации. По умолчанию cascade=«none» указывает, что никакие операции не должны каскадироваться.

Если вы используете аннотации, вы, вероятно, заметили, что атрибут cascade принимает массив CascadeType в качестве значения. Каскадная концепция в JPA очень похожа на переходное постоянство (transitive persistence) и каскадирование операций, как описано выше, но с немного отличающейся семантикой и каскадированием типов:

Заметка

CascadeType.ALL также охватывает операции Hibernate, такие как save-update, lock и т.д. ...

Специальный каскадный стиль, delete-orphan, применяется только к ассоциациям «один-ко-многим» и указывает, что операция delete() должна применяться к любому дочернему объекту, который удален из ассоциации. При использовании аннотаций эквивалента CascadeType.DELETE-ORPHAN не существует. Вместо этого вы можете использовать атрибут orphanRemoval, как показано в примере 11.4, «@OneToMany с orphanRemoval». Если сущность удаляется из коллекции @OneToMany или ассоциированная с ним сущность разыменовывается из ассоциации @OneToOne, этот ассоциациированния сущность может быть помечена для удаления, если для orphanRemoval установлено значение true.

Пример 11.4. @OneToMany с orphanRemoval

@Entity 
public class Customer {
   private Set orders;
@OneToMany(cascade=CascadeType.ALL, orphanRemoval=true) public Set getOrders() { return orders; }
public void setOrders(Set orders) { this.orders = orders; }
[...] }
@Entity public class Order { ... }
Customer customer = em.find(Customer.class, 1l); Order order = em.find(Order.class, 1l); customer.getOrders().remove(order); //order will be deleted by cascade

Рекомендации:

Отображение ассоциации (либо однозначной ассоциации, либо коллекции) с cascade=«all» помечает ассоциацию как отношение в стиле родитель/ребёнок, где сохранение, обновление или удаление родительского объекта приводит к сохранению, обновлению или удалению дочернего объектов.

Кроме того, простая ссылка на ребенка из постоянного родителя приведет к сохранению/обновлению дочернего объекта. Однако эта метафора неполна. Ребенок, который стал разыменованным от своего родителя, автоматически не удаляется, за исключением случая ассоциации «один-ко-многим», отображаемой с помощью cascade=«delete-orphan». Точная семантика каскадных операций для отношения родитель/ребёнок выглядит следующим образом:

Наконец, обратите внимание, что каскадирование операций может быть применено к графу объектов во время вызова или во время сборса. Все операции, если они включены, каскадируются на ассоциированные сущности, доступные при выполнении операции. Однако save-update и delete-orphan являются транзитивными для всех ассоциированных сущностей, доступных во время сессии.

11.12. Использование метаданных

Hibernate требует богатой метауровневой модели всех типов сущностей и значений. Эта модель может быть полезна для самого приложения. Например, приложение может использовать метаданные Hibernate для реализации «умного» алгоритма «глубокого копирования», который понимает, какие объекты следует копировать (например, изменяемые типы значений) и какие нет (например, неизменные типы значений и, возможно, ассоциированные объекты).

Hibernate предоставляет метаданные через интерфейсы ClassMetadata и CollectionMetadata и иерархию Type. Экземпляры интерфейсов метаданных можно получить из SessionFactory.

Cat fritz = ......;
ClassMetadata catMeta = sessionfactory.getClassMetadata(Cat.class);
Object[] propertyValues = catMeta.getPropertyValues(fritz); String[] propertyNames = catMeta.getPropertyNames(); Type[] propertyTypes = catMeta.getPropertyTypes();
// получить карту всех свойств, которые не являются коллекциями или ассоциациями Map namedValues = new HashMap(); for ( int i=0; i<propertyNames.length; i++ ) { if ( !propertyTypes[i].isEntityType() && !propertyTypes[i].isCollectionType() ) { namedValues.put( propertyNames[i], propertyValues[i] ); } }