Обектни състояния в сесията на хибернацията

1. Въведение

Hibernate е удобна рамка за управление на постоянни данни, но понякога разбирането за това как работи вътрешно може да бъде сложно.

В този урок ще научим за състоянията на обектите и как да се движим между тях. Също така ще разгледаме проблемите, които можем да срещнем с отделени обекти и как да ги разрешим.

2. Сесия на хибернацията

Интерфейсът Session е основният инструмент, използван за комуникация с Hibernate. Той предоставя API, който ни позволява да създаваме, четем, актуализираме и изтриваме постоянни обекти. В сесията има прост жизнен цикъл. Отваряме го, извършваме някои операции и след това го затваряме.

Когато оперираме обектите по време на сесията , те се привързват към тази сесия . Промените, които правим, се откриват и запазват при затваряне. След затваряне Hibernate прекъсва връзките между обектите и сесията.

3. Обектни състояния

В контекста на сесията на хибернацията обектите могат да бъдат в едно от трите възможни състояния: преходни, постоянни или отделени.

3.1. Преходни

Обект, който не сме прикачили към никоя сесия, е в преходно състояние. Тъй като никога не е бил продължен, той няма никакво представяне в базата данни. Тъй като нито една сесия не знае, тя няма да бъде запазена автоматично.

Нека създадем потребителски обект с конструктора и потвърдим, че той не се управлява от сесията:

Session session = openSession(); UserEntity userEntity = new UserEntity("John"); assertThat(session.contains(userEntity)).isFalse();

3.2. Постоянни

Обект, който сме асоциирали със сесия, е в постоянно състояние. Или го запазихме, или го прочетохме от контекста на постоянство, така че той представлява някакъв ред в базата данни.

Нека създадем обект и след това използваме метода persist , за да го направим устойчив:

Session session = openSession(); UserEntity userEntity = new UserEntity("John"); session.persist(userEntity); assertThat(session.contains(userEntity)).isTrue();

Като алтернатива можем да използваме метода save . Разликата е, че методът persist просто ще запази обект и методът save ще генерира допълнително неговия идентификатор, ако това е необходимо.

3.3. Откъснати

Когато затворим сесията , всички обекти в нея се отделят. Въпреки че те все още представляват редове в базата данни, те вече не се управляват от никоя сесия :

session.persist(userEntity); session.close(); assertThat(session.isOpen()).isFalse(); assertThatThrownBy(() -> session.contains(userEntity));

След това ще научим как да запазваме преходни и отделени обекти.

4. Запазване и повторно свързване на обект

4.1. Запазване на преходен обект

Нека създадем нов обект и го запишем в базата данни. Когато за първи път конструираме обекта, той ще бъде в преходно състояние.

За да запазим новия си обект, ще използваме метода persist :

UserEntity userEntity = new UserEntity("John"); session.persist(userEntity);

Сега ще създадем друг обект със същия идентификатор като първия. Този втори обект е преходен, тъй като все още не се управлява от никоя сесия , но не можем да го направим постоянен, използвайки метода persist . Той вече е представен в базата данни, така че не е наистина нов в контекста на слоя за устойчивост.

Вместо това ще използваме метода на сливане , за да актуализираме базата данни и да направим обекта постоянен :

UserEntity onceAgainJohn = new UserEntity("John"); session.merge(onceAgainJohn);

4.2. Запазване на обособен обект

Ако затворим предишната сесия , обектите ни ще бъдат в отделно състояние. Подобно на предишния пример, те са представени в базата данни, но в момента не се управляват от никоя сесия . Можем да ги направим устойчиви отново, използвайки метода на сливане :

UserEntity userEntity = new UserEntity("John"); session.persist(userEntity); session.close(); session.merge(userEntity);

5. Вложени обекти

Нещата се усложняват, когато разгледаме вложени обекти. Да приемем, че нашата потребителска единица също ще съхранява информация за своя мениджър:

public class UserEntity { @Id private String name; @ManyToOne private UserEntity manager; }

Когато запазваме тази същност, трябва да мислим не само за състоянието на самата същност, но и за състоянието на вложената същност. Нека създадем постоянен потребителски обект и след това зададем неговия мениджър:

UserEntity userEntity = new UserEntity("John"); session.persist(userEntity); UserEntity manager = new UserEntity("Adam"); userEntity.setManager(manager);

Ако се опитаме да го актуализираме сега, ще получим изключение:

assertThatThrownBy(() -> { session.saveOrUpdate(userEntity); transaction.commit(); });
java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.baeldung.states.UserEntity.manager -> com.baeldung.states.UserEntity 

Това се случва, защото Hibernate не знае какво да прави с преходния вложен обект.

5.1. Персистиращи вложени обекти

Един от начините за решаване на този проблем е изрично да се запазят вложени обекти:

UserEntity manager = new UserEntity("Adam"); session.persist(manager); userEntity.setManager(manager);

След това, след извършване на транзакцията, ще можем да извлечем правилно запазения обект:

transaction.commit(); session.close(); Session otherSession = openSession(); UserEntity savedUser = otherSession.get(UserEntity.class, "John"); assertThat(savedUser.getManager().getName()).isEqualTo("Adam");

5.2. Каскадни операции

Преходните вложени обекти могат да се запазят автоматично, ако конфигурираме правилно каскадното свойство на връзката в класа на обекта:

@ManyToOne(cascade = CascadeType.PERSIST) private UserEntity manager;

Сега, когато запазим обекта, тази операция ще бъде каскадна за всички вложени обекти:

UserEntityWithCascade userEntity = new UserEntityWithCascade("John"); session.persist(userEntity); UserEntityWithCascade manager = new UserEntityWithCascade("Adam"); userEntity.setManager(manager); // add transient manager to persistent user session.saveOrUpdate(userEntity); transaction.commit(); session.close(); Session otherSession = openSession(); UserEntityWithCascade savedUser = otherSession.get(UserEntityWithCascade.class, "John"); assertThat(savedUser.getManager().getName()).isEqualTo("Adam");

6. Обобщение

В този урок разгледахме по-отблизо как работи Hibernate Session по отношение на състоянието на обекта. След това проверихме някои проблеми, които може да създаде, и как да ги разрешим.

Както винаги, изходният код е достъпен в GitHub.