Как определять сущности и связи между ними я уже писал. Пора рассказать, как сущностями управлять. Хотя в сущности 🙂 большую часть управления сущностями я описал в вводном примере.
Фабрика
Управление сущностями начинается с создания EntityManagerFactory, которая отвечает за отображение объектов в базу, поддержку соединений, кэш состояний и всякие такие вещи. Создание EntityManagerFactory довольно дорогая операция, поэтому обычно её создают один раз и на всё приложение. А чаще всего не создают сами, а делегируют это фреймворку, такому как Spring, например.
У меня пример легковесный, поэтому создавать её буду руками:
1
|
entityManagerFactory = Persistence.createEntityManagerFactory("ru.easyjava.data.jpa.hibernate");
|
Строка, передаваемая в createEntityManagerFactory, является именем persistence unit, определённого в META-INF/persistence.xml
1
2
3
4
5
6
7
|
<persistence-unit name="ru.easyjava.data.jpa.hibernate">
<properties>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<property name="hibernate.connection.url" value="jdbc:h2:mem:test"/>
</properties>
</persistence-unit>
|
Разумеется, можно определить несколько persistence units и создать несколько фабрик, если это требуется.
Менеджер
Фабрика может создавать объекты EntityManager, вызывая методы которого можно управлять сущностями. EntityManager, в отличие от фабрики, достаточно легковесен и поэтому зачастую создаётся по месту использования и в больших количествах. Если проводить аналогию с обычным JDBC, то EntityManagerFactory будет аналогом DataSource, а EntityManager аналогом Connection.
Каждый экземпляр EntityManager связан с экземпляром EntityTransaction, что позволяет управлять транзакциями:
1
2
3
4
5
6
7
|
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
//Some actions
em.getTransaction().commit();
em.getTransaction().begin();
//Some actions
em.getTransaction().rollback();
|
Один или несколько EntityManager образуют или могут образовать persistence context. Я не буду переводить этот термин, попробую лучше его объяснить. Наличие persistence context означает, что для каждой существующей на данный момент сущности существует EntityManager, который следит за её состоянием. Что это значит? Смотри ниже.
Сущности и состояния
С точки зрения JPA у каждая управляемая EntityManager сущность имеет строго определённое состояние и строго определённые правила перехода из состояния в состояние (если углубляться в теорию, то речь идёт о конечном автомате). Эти состояния и правила отображены на рисунке вначале статьи.
Когда будущая сущность создаётся оператором new, она чиста и невинна и никакой EntityManager про неё не знает. Такая свежесозданная сущность считается «новой» (new).
1
2
3
4
5
6
7
8
9
|
//New op
Operation op = new Operation();
op.setId(1L);
op.setAccountId(100500);
op.setAmount(BigDecimal.TEN);
op.setTimestamp(ZonedDateTime.now());
op.setDescription("Test operation");
op.setOpCode(9000);
|
Но в состоянии «новая» сущность для нас бесполезна, и поэтому мы отдаём её под управление EntityManager
1
|
em.persist(op); // op is MANAGED now
|
Метод persist() делает сразу огромное количество вещей:
- Сохраняет данные сущности в базу. Или, возможно, запоминает, что надо бы их сохранить в базу потом.
- Делает всё тоже самое для связанных сущностей, у которых выставлено каскадирование CascadeType.PERSIST или CascadeType.ALL.
- Самое главное — берёт сущность под свою опеку и переводит её в состояние «управляемая» (managed)
С управляемой сущностью можно делать много полезных вещей. В первую очередь — менять значения полей, изменения в которых будут автоматически сохраняться в базе данных, пока сущность управляема.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//New op
Operation op = new Operation();
op.setId(1L);
op.setAccountId(100500);
op.setAmount(BigDecimal.TEN);
op.setTimestamp(ZonedDateTime.now());
op.setDescription("Test operation");
op.setOpCode(9000);
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
em.persist(op); // op is MANAGED now
op.setDescription("New operation name.");
em.getTransaction().commit();
|
В случае, когда возможны два источника изменений, то есть данные могут измениться в базе уже после того, как сущность была из неё загружена, помогает метод refresh()
1
|
em.refresh(op)
|
refresh() перечитывает дынные управляемой сущности из базы, оставляя её при этом управляемой. Эта операция выполняется так же и для связанных сущностей, у которых выставлено каскадирование CascadeType.REFRESH или CascadeType.ALL.
Какие ещё могут быть состояния? В первую очередь «удалённая» (deleted) сущность. Удаляются сущность методом remove(), который переводит её в состояние «удалённая» и запоминает, что записи в базе данных надо бы удалить. Эта операция выполняется так же и для связанных сущностей, у которых выставлено каскадирование CascadeType.REMOVE или CascadeType.ALL.
Удалённую сущность можно восстановить вызовом persist(), которая опять вернёт сущность в управляемые и уберёт отметки об удалении из базы.
Наконец самое интересное состояние — «отделённая» (detached). Обычно сущность впадает в это состояние внезапно и теряет связь с управляющим ей EntityManager. Переход в это состояние может произойти при:
- Сериализации/десериализации сущности, передаче из jvm в jvm и т.д.
- Окончании транзакции
- Закрытии EntityManager методом close() или просто сборщиком мусора.
- Сброса persistence context EntityManager методом clear().
Отделённая сущность теряет связь с базой данных и изменения в ней в базу сами не попадают. И что хуже всего, лениво загружаемые поля тоже не загружаются. И удалить её нельзя и из базы её тоже не обновить.
Выход из этой ситуации только один — метод merge() у EntityManager. merge(), применённый на отсоединённой сущности, либо копирует значение переданной сущности в уже существующую сущность с тем же значением поля @id, либо создаёт новую сущность этого типа и копирует данные в неё. В обоих случаях результат сохраняется в базу и из метода возвращается новая управляемая сущность. Старая при этом остаётся отсоединённой.
merge() можно использовать и с новыми сущностями. В отличие от persist() он создаст ещё одну сущность того же типа, скопирует все данные в неё и сделает её управляемой. persist() же, как написано выше, копий не делает и переводит в управляемые то, что ему передали.Persisted
Последнее состояние, в котором может пребывать сущность, это «сохранённая» (persisted). В этом состоянии сущность существует только в базе данных, а в программе её нет. Перевести сущность в состояние управляемой, то есть попросту загрузить её из базы данных, можно методами EntityManager find() или семейством методов createQuery(). Эти методы позволяют строить достаточно сложные запросы для любой потребности, но сейчас я расскажу только о базовом использовании: загрузка сущности по её идентификатору и загрузка всех сущностей определённого типа.
1
2
3
4
5
|
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
System.out.println(em.find(Operation.class, 1L).toString());
em.getTransaction().commit();
em.close();
|
В метод find() передаётся тип требуемой сущности и её идентификатор, то есть объект, эквивалентный объекту в поле @Id требуемой сущности. В случае, если такой не найдётся в базе, вернётся null. А если вот найдётся, то вернётся управляемая сущность.
Код примера доступен на github.