Внутренняя кухня hibernate.
Всем новичкам кажется что ORM Hibernate делает разработку проще и код эфективней, но если не знать, некоторых особенностей, то программирование с использованием ORM Hibernate преврашается в кромешный ад. И так что же нужно знать новичку о Hibernate - первое это способ применения, есть стандартный патерн Session per View, который предполагает последовательность правильных действий: Открыть сессию, начать транзакцию, выполнить действия над "pojo-объектом", построить представление на основе "pojo-объекта", закрыть транзакцию, закрыть сессию:
// create a event... Session session = sessionFactory.openSession(); session.beginTransaction(); Event event = new Event( "Our very first event!", new Date() ); session.save( event ); session.getTransaction().commit(); System.out.println( "Event create" ); session.close();
после закрытия сессии все данные не должны использоваться больше и участвовать в работе приложения. При этом использовать "pojo-объекты" можно только в одном потоке, который открыл сессию, иначе в работе Hibernate возможны ошибки. Почему нельзя использовать "pojo-объект" после закрытия сессии это связано с особенностью работы Hibernate со сложными "pojo-объектами" которые имеют связи один-к-одному(One-to-one), один-ко-многим(One-to-many) и т.д. при создании связи по умолчанию Hibernate применяет стратегию ленивой загрузки, т.е. за место реальной связи создается прокси-объект без его реального заполнения данными и после завершения сессии, при запросе к прокси-объекту возникает рантаймовое исключение LazyInitializationException. Если вы все-таки хотите работать с "pojo-объектом" после закрытия сессии нужно отключить стратегию ленивой загрузки и переопределить методы equals() и hashCode(). Поясню все на примере, пусть у нас связь person(one) -> address(one):
@Entity @Table(name="PERSON") public class Person implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name="personId") private int id; @OneToOne @PrimaryKeyJoinColumn private Address address; public Address getAddress() { return address; } } @Entity @Table(name = "ADDRESS") public class Address { @Id @Column(name = "personId") private int id; } .... session.close(); person.getAddress(); --> здесь возникает исключение LazyInitializationException.
я знаю 5 или 6 методов как отключить стратегию ленивой загрузки вот один из них, нужно просто добавить аннотацию для нашего примера @Proxy(lazy=false) и все:
@Entity @Table(name="PERSON") @Proxy(lazy=false) public class Person { .... session.close(); person.getAddress();
здесь уже не возникает исключение LazyInitializationException, так как тут уже подгружен реальный объект/коллекция объектов, но нужно быть осторожными с подгружаемыми зависимостями так как это может навредить быстродействию программы и ухудшить её работу, просто нужно найти золотую середину, посредством тестирования кода. Небольшое отступление от темы, Hibernate нужны интерфейсы для всех сущностей если вы хотите чтобы проксировались подгружаемые объекты через Java Proxy, иначе работа идет через CGLIB, ему не нужны интерфейсы сущностей и при помощи ASM'а Hibernate на лету генерирует байт код прокси-объектов, выбор метода проксирования остается за вами, по умолчанию Hibernate использует CGLIB и интерфейсы ему не нужны. Конечно мы не ищем легких путей и хотим пользоваться "pojo-объектом" и после закрытия сессии изменять их и сохранять сделанные нами изменения с помощью Hibernate, для этого как я упоминал раньше нужно переопределить методы equals() и hashCode(). Вообще я практически всегда эти важные методы наследую и периопределяю, плюс к этому всегда стараюсь наследовать итерфейс Serializable, но пропускаю конструктор "pojo-объекта" по умолчанию, т.к. при создании любого объекта как new Person() - java автоматически за нас создает и вызывает конструктор по умолчанию. Почему я так настаиваю на переопределении методов equals() и hashCode() я постараюсь объяснить на примере, пусть у нас есть "pojo-объект" без переопределенных методов equals() и hashCode():
public class Entity { private int id = 0; .... }
наша программа создает объект Entity и добавляет его в HashSet, далее идут проверки содержит ли HashSet точную копию объекта Entity.
import java.util.HashSet; import java.util.Set; public class Main { public static void main(String[] argv) { Entity slimy = new Entity(); //1 SetfailSet = new HashSet (); System.out.println(failSet.contains(slimy)); // false //2 failSet.add(slimy); System.out.println(failSet.contains(slimy)); // true // 3 ID was assigned by an ORM Hibernate slimy.setId(100); System.out.println(failSet.contains(slimy)); // true } }
- создаем объект и проверяем существует ли объект slimy в HashSet, его там нет // false
- заносим объект slimy в HashSet и проверяем существует ли объект slimy в HashSet - он там присутствует(содержит точную копию) // true
- уподобляемся ORM Hibernate и меняем slimy, производим проверку и опять точная копия содержится в HashSet // true, - ошибка!!!
переопределим методы equals() и hashCode() класса Entity:
public class Entity { private int id = 0; .... @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Entity other = (Entity) obj; if (id != other.id) return false; return true; } }
заново выполняем программу и на 3-м шаге получаем отрицательный ответ // false объект slimy в HashSet не совпадает с измененным объектом slimy что есть правильно! Плюс я всегда стараюсь переопределить метод toString() для правильной визуализации тестовых данных!
Еще новичкам обязательно нужно понять и знать как работает Hibernate cache, без его понимания не стоит и пытаться использовать Hibernate, особенности работы подробно описаны на Хабрахабр'е вот 2 сылки Hibernate cache и Hibernate Cache Практика. Ну вот и все! здесь я дал 30% теории которую вы должны усвоить прежде чем начать работу с ORM Hibernate, 70% придет с практикой!
Еще новичкам обязательно нужно понять и знать как работает Hibernate cache, без его понимания не стоит и пытаться использовать Hibernate, особенности работы подробно описаны на Хабрахабр'е вот 2 сылки Hibernate cache и Hibernate Cache Практика. Ну вот и все! здесь я дал 30% теории которую вы должны усвоить прежде чем начать работу с ORM Hibernate, 70% придет с практикой!
slimy который находится в HashSet и slimy у которого вызывается setId(100) - это есть один и тот же объект поэтому failSet.contains(slimy) (где slimy уже с переопределенными методами equals() и hashCode()) должен вернуть true ;
ОтветитьУдалить