Глава 12. Сущности «только для чтения»

Оглавление
  1. 12.1. Создание постоянных сущностей «только для чтения»
    1. 12.1.1. Сущности неизменяемых классов
    2. 12.1.2. Загрузка постоянных сущностей как «только для чтения»
    3. 12.1.3. Загрузка сущностей «только для чтения» из Query/Criteria HQL
    4. 12.1.4. Создание постоянной сущности «только для чтения»
  2. 12.2. Влияние «только для чтения» на тип свойства
    1. 12.2.1. Простые свойства
    2. 12.2.2. Однонаправленные ассоциации
    3. 12.2.3. Двунаправленные ассоциации

Важно

Использование Hibernate сущностей «только для чтения» может отличаться от того, что вы могли встретить в другом месте. Неправильное использование может привести к неожиданным результатам.

Когда сущность является «только для чтения»:

В некотором смысле, Hibernate рассматривает сущности, доступные только для чтения, как сущности, которые доступны не «только для чтения».

Даже если сущность не доступена только для чтения, её ассоциация коллекции может быть затронута, если она содержит сущность, доступную только для чтения.

Подробнее о влиянии сущностей, доступных только для чтения, на разные типы свойств и ассоциаций, см. Раздел 12.2 «Влияние «только для чтения» на тип свойства».

Подробнее о том, как сделать сущности доступными только для чтения см. в разделе 12.1 «Создание постоянных сущностей «только для чтения»

Hibernate выполняет некоторую оптимизацию для сущностей только для чтения:

12.1. Создание постоянных сущностей «только для чтения»

Только постоянные сущности могут быть доступны только для чтения. Переходные и отсоединённые сущности должны быть переведены в постоянное состояние, прежде чем их можно будет сделать доступными только для чтения.

Hibernate предоставляет следующие способы создания постоянных сущностей только для чтения:

12.1.1. Сущности неизменяемых (immutable) классов

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

Сущность неизменяемого класса может быть создана и удалена так же, как сущность изменяемого класса.

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

12.1.2. Загрузка постоянных сущностей как «только для чтения»

Заметка

Сущности неизменяемых классов автоматически загружаются как «только для чтения».

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

Session.setDefaultReadOnly( true );

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

Session.setDefaultReadOnly( false );

Вы можете определить текущую настройку, вызвав:

Session.isDefaultReadOnly();

Если isDefaultReadOnly() возвращает true, сущности, загруженные одним из следующим методов, автоматически становятся доступными только для чтения:

Изменение этого значения по умолчанию не влияет на:

12.1.3. Загрузка сущностей «только для чтения» из Query/Criteria HQL

Заметка

Сущности неизменяемых классов автоматически загружаются как «только для чтения».

Если Session.isDefaultReadOnly() возвращает false (по умолчанию), когда выполняется Query или Criteria HQL, сущности и прокси изменяемых классов, загружаемых запросом, не будут доступны только для чтения.

Вы можете переопределить это поведение, чтобы сущности и прокси, загруженные Query или Criteria HQL, автоматически делались только для чтения.

Для Query HQL вызовите:

Query.setReadOnly( true );

Query.setReadOnly(true) необходимо вызывать перед Query.list(), Query.uniqueResult(), Query.scroll() или Query.iterate().

Для Criteria HQL вызовите:

Criteria.setReadOnly( true );

Criteria.setReadOnly(true) необходимо вызвать до Criteria.list(), Criteria.uniqueResult() или Criteria.scroll().

Сущности и прокси, которые существуют в сессии перед возвратом Query или Criteria HQL, не затрагиваются.

Неинициализированные постоянные коллекции, возвращаемые запросом, не затрагиваются. Позже, когда коллекция будет инициализирована, сущности, загруженные в сессию, будут доступны только для чтения, если Session.isDefaultReadOnly() вернет значение true.

Использование Query.setReadOnly(true) или Criteria.setReadOnly(true) хорошо работает, когда один Query или Criteria HQL загружает все сущности и инициализирует все прокси и коллекции, которые в приложении должны быть доступны только для чтения.

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

Session session = factory.openSession();
Transaction tx = session.beginTransaction();
setDefaultReadOnly( true ); Contract contract = ( Contract ) session.createQuery( "from Contract where customerName = 'Sherman'" ) .uniqueResult(); Hibernate.initialize( contract.getPlan() ); Hibernate.initialize( contract.getVariations() ); Hibernate.initialize( contract.getNotes() ); setDefaultReadOnly( false ); ... tx.commit(); session.close();

Если Session.isDefaultReadOnly() возвращает true, вы можете использовать Query.setReadOnly(false) и Criteria.setReadOnly(false), чтобы переопределить этот параметр сессии и загрузить сущности, которые не «только для чтения».

12.1.4. Создание постоянной сущности «только для чтения»

Заметка

Постоянные сущности неизменяемых классов автоматически делаются как «только для чтения».

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

Session.setReadOnly(entityOrProxy, true)

Чтобы изменить сущность только для чтения или прокси изменяемого класса, чтобы она больше не была доступной только для чтения, вызовите:

Session.setReadOnly(entityOrProxy, false)

Важно

Когда сущность или прокси, доступные только для чтения, изменяются, они перестают быть «только для чтения». Hibernate предполагает, что текущее состояние сущности, доступной только для чтения, соответствует представлению базы данных. Если это не так, то любые не сброшенные (non-flushed) изменения, сделанные до или в то время, когда сущность была доступна только для чтения, будут проигнорированы.

Чтобы отбросить любые несброшенные (non-flushed) изменения и сделать постоянную сущность совместимой с своим представлением в базе данных, вызовите:

session.refresh( entity );

Чтобы сбросить (flush) изменения, сделанные до или в то время, когда сущность была доступна только для чтения и сделать представление базы данных согласованным с текущим состоянием постоянной сущности, вызовите:

// вытеснить сущность, доступную только для чтения, так, чтобы она стала отсоединённой
session.evict( entity );
// сделать отсоединённую сущность (с несброшенными (non-flushed) изменениями) постоянной
session.update( entity );
// теперь сущность больше не доступна для чтения и её изменения могут быть сброшены (flush)
s.flush();

12.2. Влияние «только для чтения» на тип свойства

В следующей таблице показано, как влияют различные типы свойств, делая сущность доступной только для чтения.

Тип свойства/ассоциации Изменения, сброшены (flush) в БД?
Простой (Простые свойства) нет*
Однонаправленная «один-к-одному»
Однонаправленная «многие-к-одному»
(Раздел Однонаправленные «один-к-одному» и «многие-к-одному» )
нет*
Однонаправленная «один-ко-многим»
Однонаправленная «многие-ко-многим»
(Раздел Однонаправленные «один-ко-многим» и «многие-ко-многим» )
да
Двунаправленная «один-к-одному»
(Раздел Двунаправленная «один-к-одному»)
только если владеющая сущность «только для чтения»*
Двунаправленная «один-ко-многим»
Двунаправленная «многие-к-одному»
обратная (inverse) коллекция
необратная (non-inverse) коллекция
(Раздел Двунаправленные «один-ко-многим» и «многие-к-одному» )
только добавленные/удалённые сущности, которые не доступны для чтения*
Двунаправленная «многие-ко-многим»
(Раздел Двунаправленная «многие-ко-многим»)
да

*Поведение отличается, когда сущность, обладающий свойством/ассоциацией, доступна только для чтения, по сравнению с тем, когда она доступна не «только для чтения».

12.2.1. Простые свойства

Когда постоянный объект доступен только для чтения, Hibernate не делает «грязную» проверку простых свойств.

Hibernate не будет синхронизировать изменение состояния простых свойств с базой данных. Если у вас есть автоматическое управление версиями, Hibernate не будет увеличивать версию, если будут изменены какие-либо простые свойства.

Session session = factory.openSession();
Transaction tx = session.beginTransaction();
// получить контракт и сделать его доступным только для чтения Contract contract = ( Contract ) session.get( Contract.class, contractId ); session.setReadOnly( contract, true );
// contract.getCustomerName() сейчас "Sherman" contract.setCustomerName( "Yogi" ); tx.commit();
tx = session.beginTransaction();
contract = ( Contract ) session.get( Contract.class, contractId ); // contract.getCustomerName() всё ещё "Sherman" ... tx.commit(); session.close();

12.2.2. Однонаправленные ассоциации

12.2.2.1. Однонаправленные «один-к-одному» и «многие-к-одному»

Hibernate обрабатывает однонаправленные ассоциации «один-к-одному» и «многие-к-одному» таким же образом, когда владеющая сущность доступна только для чтения.

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

Hibernate не делает «грязную» проверку однонаправленных односторонних (single-ended) ассоциаций, когда владеющая сущность доступна только для чтения.

Если вы измените ссылку сущности только для чтения на однонаправленную одностороннюю ассоциацию на null или ссылку на другую сущность, это изменение не будет сброшено (flush) в базу данных.

Заметка

Если сущность имеет неизменяемый класс, то его ссылки на однонаправленные односторонние ассоциации должны быть назначены, когда эта сущность впервые создается. Поскольку сущность автоматически создается только для чтения, эти ссылки не могут быть обновлены.

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

В следующих примерах Contract имеет однонаправленную связь «многие-ко-многим» с Plan. Contract выполняет каскадом операции сохранения и обновления в ассоциации.

Ниже показано, что изменение ссылки на связь «один-к-одному» для сущности «только для чтения» на значение null не влияет на представление сущности в базе данных.

// получить контракт с существующим планом
tx = session.beginTransaction();
Contract contract = ( Contract ) session.get( Contract.class, contractId );
// сделать контракт доступным только для чтения.
session.setReadOnly( contract, true );
// установить его план в null.
contract.setPlan( null );
tx.commit();
// получить тот же контракт.
tx = session.beginTransaction();
contract = ( Contract ) session.get( Contract.class, contractId );
// contract.getPlan () по-прежнему ссылается на исходный план.
tx.commit();
session.close();

Следующий пример показывает, что, несмотря на то, что обновление ассоциации «многие-к-одному» у сущности, доступной только для чтения, не влияет на представление сущности в базе данных, flush все еще каскадирует операцию сохранения-обновления в локально измененную ассоциацию.

// получить контракт с существующим планом;
tx = session.beginTransaction();
Contract contract = ( Contract ) session.get( Contract.class, contractId );
// сделать договор доступным только для чтения.
session.setReadOnly( contract, true );
// перейти на новый план.
Plan newPlan = new Plan( "new plan"
contract.setPlan( newPlan);
tx.commit();
// получить тот же контракт.
tx = session.beginTransaction();
contract = ( Contract ) session.get( Contract.class, contractId );
newPlan = ( Contract ) session.get( Plan.class, newPlan.getId() ); 
// contract.getPlan () по-прежнему ссылается на исходный план.
// newPlan не имеет значения null, поскольку он сохранялся, когда
// предыдущая транзакция была сохранена (commit);
tx.commit();
session.close();

12.2.2.2. Однонаправленные «один-ко-многим» и «многие-ко-многим»

Hibernate обрабатывает однонаправленные ассоциации «один-ко-многим» и «многие-ко-многим», принадлежащие сущности, доступной только для чтения, так же, как и принадлежащие сущности, которая доступна не «только для чтения».

Hibernate делает «грязную» проверку однонаправленных ассоциаций «один-ко-многим» и «многие-ко-многим».

Коллекция может содержать сущности, которые доступны только для чтения, а также сущности, которые доступны не «только для чтения».

Сущности могут быть добавлены и удалены из коллекции; изменения сбрасываются (flush) в базу данных.

Если используется автоматическое управление версиями, Hibernate будет обновлять версию из-за изменений в коллекции, если они «загрязняют» владеющую сущность.

12.2.3. Двунаправленные ассоциации

12.2.3.1. Двунаправленная «один-к-одному»

Если сущность, доступная только для чтения, имеет двунаправленную ассоциацию «один-к-одному»:

Заметка

Если сущность имеет неизменяемый класс и ему принадлежит двунаправленная ассоциация «один-к-одному», тогда её ссылка должна быть присвоена, когда эта сущность впервые создается. Поскольку сущность автоматически создается только для чтения, эти ссылки не могут быть обновлены.

Когда владелец доступен не «только для чтения», Hibernate рассматривает ассоциацию с сущностью только для чтения так же, как если ассоциация связана с сущностью, которая доступна не «только для чтения».

12.2.3.2. Двунаправленные «один-ко-многим» и «многие-к-одному»

Сущность, доступная только для чтения, не влияет на двунаправленную связь «один-ко-многим»/«многие-к-одному», если:

Когда сторона «один-ко-многим» использует обратную коллекцию:

12.2.3.3. Двунаправленная «многие-ко-многим»

Hibernate обрабатывает двунаправленные ассоциации «многие-ко-многим», принадлежащие сущности, доступной только для чтения, так же, как когда она принадлежит сущности, который доступна не «только для чтения».

Hibernate выполняет «грязные» проверки двунаправленных ассоциаций «многие-ко-многим».

Коллекция по обе стороны ассоциации может содержать сущности, которые доступны только для чтения, а также сущности, которые доступны не «только для чтения».

Сущности добавляются и удаляются с обеих сторон коллекции; изменения сбрасываются (flush) в базу данных.

Если используется автоматическое ведение версий, Hibernate обновит версию из-за изменений в обеих сторонах коллекции, если они загрязняют сущность, владеющую соответствующими коллекциями.