Hibernate интересные моменты.
Замапленый объект может находиться в четырех состояниях:
- transient-объект создаётся командой new, может быть заполнен данными но, ни когда не сохранялся в сессии т.е. не ассоциирован со строкой в таблице БД.
- persistent-объект, объект в данный момент связанный с некоторой сессией, hibernate сессия "работает" с экземпляром объекта в данный промежуток времени, т.е. объект ассоциирован со строкой в таблице БД. Получить persistent-объект можно двумя способами: 1-й способ запросить объект такими методами как get(), load(), list(), uniqueResult(), iterate(), scroll(), find(), getReference(). 2-й способ перевести наш transient-объект в persistent-объект путем вызова таких методов как save(), saveOrUpdate(), persist(), merge().
- detached-объект - это persistent-объект отсоединенный от сессии, это состояние объекта возникает после закрытия сессии close(), которая работала с объектом до этого или при вызовах методов evict(), clear() сессии. Переход из состояния detached в persistent объектом возможно при вызове методов сессии - update(), saveOrUpdate(), merge().
- removed - объект - это persistent-объект удаленный в сессии методом delete() или remove() в jpa.
Еще хотелось бы сказать пару слов о detached-объектах, их можно использовать дальше, при работе с новой сессией, если к ним применить такие команды как update(); saveOrUpdate() или
merge() (в jpa), то они переходят в состояние persistent - в Hibernate такие операции называются reattached mode или merging mode - в jpa.
merge() (в jpa), то они переходят в состояние persistent - в Hibernate такие операции называются reattached mode или merging mode - в jpa.
Замапленный объект должен удовлетворять требованиям, тогда и только тогда он будет правильно взаимодействовать с hibernate:
- Иметь конструктор по умолчанию без параметров.
- Переопределять методы toString(), Equals(), HashCode().
- Иметь поле "первичного ключа" (id) как минимум, как максимум сложный составной ключ.
- Класс сущности не должен быть объявлен как final класс.
- Класс сущности должен наследовать интерфейс Serializable.
Немножко отступим от темы и поговорим о Serializable. С каждым сериализуемым классом связан уникальный идентификационный номер. Если вы не указываете этот идентификатор явно, декларируя поле private static final long с названием serialVersionUID, система генерирует его автоматически, используя для класса сложную схему расчетов. При этом на автоматически генерируемое значение оказывают влияние название класса, названия реализуемых им интерфейсов, а также все открытые и защищенные члены. Если вы каким-то образом поменяете что-либо в этом наборе, например, добавите простой и удобный метод, изменится
и автоматически генерируемый serial version UID. Следовательно, если вы не будете явным образом декларировать этот идентификатор, совместимость с предыдущими версиями будет потеряна. Клиенты, которые пытаются сериализовать объект с помощью старой версии класса и десериализовать его уже с помощью новой версии, получат сбой программы. И так, как создать static final long serialVersionUID? Пусть у нас есть скомпилированный класс src/org/hibernate/tutorial/annotations/Person.class перейдем в папку src и запустим команду serialver которая входит в jdk:
и автоматически генерируемый serial version UID. Следовательно, если вы не будете явным образом декларировать этот идентификатор, совместимость с предыдущими версиями будет потеряна. Клиенты, которые пытаются сериализовать объект с помощью старой версии класса и десериализовать его уже с помощью новой версии, получат сбой программы. И так, как создать static final long serialVersionUID? Пусть у нас есть скомпилированный класс src/org/hibernate/tutorial/annotations/Person.class перейдем в папку src и запустим команду serialver которая входит в jdk:
C:\...\src>serialver org.hibernate.tutorial.annotations.Person org.hibernate.tutorial.annotations.Person: static final long serialVersionUID = -7593775012501239455L; C:\...\src>
копируем и вставляем в наш класс:
package org.hibernate.tutorial.annotations; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import lombok.ToString; import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollectionOption; import org.hibernate.annotations.Proxy; import javax.persistence.*; import javax.persistence.criteria.Fetch; import java.io.Serializable; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @Entity @Table(name = "Persons") @ToString @EqualsAndHashCode //@Proxy(lazy = true)//@Proxy(lazy = false) public class Person implements Serializable { static final long serialVersionUID = -7593775012501239455L; @Id @GeneratedValue() @Column(name = "personId") private Long id; @Column(name = "fName") private String firstName; @Column(name = "sName") private String sureName; /* @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.EAGER, mappedBy = "person1") @Getter @Setter private Listevents = new ArrayList (); */ public String getSureName() { return sureName; } public void setSureName(String sureName) { this.sureName = sureName; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } }
Различие между командами load() и get(), оба метода предназначены для получения объекта из базы данных, отличие в том что если метод get() не находит объект в БД то возвращает null, метод load() может вернуть прокси объект (если разрещена реалиализация lazy объектов - по умолчанию в Hibernate 3), вместо реального объекта. Заполнение данными proxy-объекта происходит, только после вызова любого метода прокси объекта исключение составляет запрос первичного ключа (getId()). Hibernate выполняет sql запрос только тогда, когда нужны реальные данные, если существует такая запись в БД то она заполняет данными proxy-объект. Все это называется ленивой загрузкой и её можно отменить выставив параметр @Proxy(lazy = false) у объекта. Если запись не найдена в БД, то происходит Exception (org.hibernate.ObjectNotFoundException.class). Смотрите коментарии в примерах:
@Test() public void loadProxyPersons(){ Session session = sessionFactory.openSession(); session.beginTransaction(); //в бд нет записи 10L load() возвращает proxy объект Person person2 = (Person) session.load(Person.class, 10L); //проверяем существование proxy объекта assertNotNull(person2); session.getTransaction().commit(); session.close(); } @Test(expected = org.hibernate.ObjectNotFoundException.class) public void loadProxyPersonsException(){ Session session = sessionFactory.openSession(); session.beginTransaction(); // в бд нет записи 10L load() возвращает proxy объект Person person2 = (Person) session.load(Person.class, 10L); //пытаемся заполнить проси объект данными, вызываем исключение org.hibernate.ObjectNotFoundException.class person2.getFirstName(); session.getTransaction().commit(); session.close(); } @Test() public void getNullPerson(){ Session session = sessionFactory.openSession(); session.beginTransaction(); //запращиваем объект Person person2 = (Person) session.get(Person.class, 10L); //проверяем что get возвращает null assertNull(person2); session.getTransaction().commit(); session.close(); }
По умолчанию Hibernate, всегда использует кеш первого уровня в транзакции,
его не возможно отключить. Т.е когда мы производим операции над
persistent-объектом изменения не сразу попадают в БД, это нужно
для уменьшения количества выполняемых sql запросов к базе данных.
Допустим объект модифицируется в одной сессии несколько раз,
все изменения происходят в памяти (aka кеше первого уровня), в итоге генерируется 1 update
sql запрос который скидывает только последние изменения по последнему состоянию,
все предыдущие состояния объекта не учитываються и не сохраняюстя в БД.
Расмотрим пример:
@Test public void testHibernateCacheLevelOne() { Person person = new Person(); person.setFirstName("Vit"); person.setSureName("Lopanov"); Statistics statistics = sessionFactory.getStatistics(); Session session = sessionFactory.openSession(); session.beginTransaction(); session.saveOrUpdate(person); session.getTransaction().commit(); session.close(); //включаем статистику для сбора информации. statistics.setStatisticsEnabled(true); session = sessionFactory.openSession(); session.beginTransaction(); // загружаем объект из БД - выполняется sql: //Hibernate: select person0_.personId as personId16_0_, person0_.fName as fName16_0_, person0_.sName as sName16_0_ from Persons person0_ where person0_.personId=? person = (Person)session.load(Person.class,1L); // проверяем сохранен ли наш объект в кеше - true System.out.println("contains in cache: "+session.contains(person)); person.setFirstName("Tom"); session.saveOrUpdate(person); //.... выполняем еще какие-то операции //запрашиваем заново наш объект, sql не выполняется, объект подсовывается из кеша person = (Person)session.load(Person.class,1L); // проверяем находиться ли наш объект в кеше - true System.out.println("contains in cache: "+session.contains(person)); person.setSureName("Aristov"); session.saveOrUpdate(person); // выгружаем объект из кеша if (session.contains(person)) session.evict(person); // объект не найден в кеше, выполняем sql запрос //Hibernate: select person0_.personId as personId16_0_, person0_.fName as fName16_0_, person0_.sName as sName16_0_ from Persons person0_ where person0_.personId=? person = (Person)session.load(Person.class,1L); person.setFirstName("Oleg"); session.saveOrUpdate(person); session.getTransaction().commit(); session.close(); System.out.println("InsertCount: "+statistics.getEntityInsertCount()+" " + "UpdateCount: "+statistics.getEntityUpdateCount()+" " + "FlushCount: "+statistics.getFlushCount()+" " + "TransactionCount: "+statistics.getTransactionCount()+" " + "SuccessfulTransactionCount: "+statistics.getSuccessfulTransactionCount()); }
этот пример даёт такой лог, обратите внимание что мы меняли состояние объекта 3
раза (setSureName("Aristov"), setFirstName("Oleg") ... ), а sql - update выполнился лишь один раз:
Hibernate: insert into Persons (personId, fName, sName) values (null, ?, ?) contains in cache: true Hibernate: select person0_.personId as personId16_0_, person0_.fName as fName16_0_, person0_.sName as sName16_0_ from Persons person0_ where person0_.personId=? contains in cache: true Hibernate: select person0_.personId as personId16_0_, person0_.fName as fName16_0_, person0_.sName as sName16_0_ from Persons person0_ where person0_.personId=? Hibernate: update Persons set fName=?, sName=? where personId=? InsertCount: 0 UpdateCount: 1 FlushCount: 1 TransactionCount: 1 SuccessfulTransactionCount: 1
Отсюда вывод что: При использовании методов save(), update(), saveOrUpdate(), load(), get(), list(), iterate(), scroll() всегда будет задействован кеш первого уровня,
для того что бы немедленно сохранить объект в БД, после вызовов методов save(), update(), saveOrUpdate(),
нужно выполнить команду session.flush(). А для непосредственной загрузки объекта
из БД sql-м, методами load(), get(), list(), iterate(), scroll(),
а не из кеша первого уровня, нужно вызвать команду session.evict(person)
- удаления объекта из кеша. Так же сушествует команда которая очищает полностью
кеш первого уровня session.clear();
Продолжая тему кеша первого уровня, нельзя не упомянуть про Batch Inserts,
загрузку множества объектов, для этого нужно контролировать кеш первого уровня
от разростания и вовремя скидывать данные в БД, очищая при этом кеш:
@Test public void batchInsert() { Person person; Session session = sessionFactory.openSession(); session.beginTransaction(); for (int i = 0; i < 10000; i++) { person = new Person(); person.setFirstName("Vit" + i); person.setSureName("Lopanov" + i); session.saveOrUpdate(person); if (i % 30 == 0) { //скидываем изменения из кеша первого уровня session.flush(); //очищаем кеш первого уровня session.clear(); } } session.getTransaction().commit(); session.close(); }
Не забудте установить параметр hibernate.jdbc.batch_size и отключить кеш второго уровня (second-level cache). И на последок вкладываю maven pom
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.hibernate.tutorials</groupId> <artifactId>hibernate-tutorials</artifactId> <version>4.1.7.Final</version> <packaging>pom</packaging> <name>Hibernate Getting Started Guide Tutorials</name> <description>Aggregator for the Hibernate tutorials presented in the Getting Started Guide</description> <properties> <!-- Skip artifact deployment --> <maven.deploy.skip>true</maven.deploy.skip> </properties> <modules> <module>annotations</module> <module>annotations0</module> <module>annotations1</module> <module>annotations2</module> <module>annotations3</module> <module>annotations4</module> <module>annotations5</module> <module>annotations6</module> <module>annotations7</module> <module>annotations8</module> </modules> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>4.1.7.Final</version> </dependency> <!-- Hibernate uses jboss-logging for logging, for the tutorials we will use the sl4fj-simple backend --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.6.1</version> </dependency> <!-- The tutorials use JUnit test cases to illustrate usage --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> </dependency> <!-- The tutorials use the H2 in-memory database --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.2.145</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-ehcache</artifactId> <version>4.1.7.Final</version> </dependency> <!--<dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.6.0</version> </dependency>--> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>1.6.6</version> </dependency> <!--<dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.4.3</version> </dependency> --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>0.11.0</version> <scope>provided</scope> </dependency> <!--<dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency>--> <!-- Hibernate c3p0 connection pool --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-c3p0</artifactId> <version>4.1.7.Final</version> </dependency> <!-- Hibernate jbosscache-core --> <dependency> <groupId>org.jboss.cache</groupId> <artifactId>jbosscache-core</artifactId> <version>3.2.5.GA</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> <testResources> <testResource> <filtering>false</filtering> <directory>src/test/java</directory> <includes> <include>**/*.xml</include> </includes> </testResource> <testResource> <directory>src/test/resources</directory> </testResource> </testResources> </build> <repositories> <repository> <id>sourceforge</id> <url>http://oss.sonatype.org/content/groups/sourceforge/</url> <releases> <enabled>true</enabled> </releases> </repository> <repository> <id>terracotta-releases1</id> <url>http://www.terracotta.org/download/reflector/releases</url> </repository> <repository> <id>terracotta-releases</id> <url>http://www.terracotta.org/download/reflector/releases</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>jboss-public-repository-group</id> <name>JBoss Public Maven Repository Group</name> <url>http://repository.jboss.org/nexus/content/groups/public-jboss/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> </project>
hibernate.cfg.xml
<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- Database connection settings --> <property name="connection.driver_class">org.h2.Driver</property> <property name="connection.url">jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE</property> <property name="connection.username">sa</property> <property name="connection.password"></property> <!-- JDBC connection pool (use the built-in) --> <property name="connection.pool_size">1</property> <!-- SQL dialect --> <property name="dialect">org.hibernate.dialect.H2Dialect</property> <!-- Disable the second-level cache --> <property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property> <!-- Echo all executed SQL to stdout --> <!--<property name="show_sql">true</property> --> <!--<property name="format_sql">true</property>--> <!-- Drop and re-create the database schema on startup --> <property name="hbm2ddl.auto">create</property> <property name="hibernate.jdbc.fetch_size">2</property> <property name="hibernate.jdbc.batch_size">30</property> <!-- Names the annotated entity class --> <mapping class="org.hibernate.tutorial.annotations.Person"/> <mapping class="org.hibernate.tutorial.annotations.Event"/> </session-factory> </hibernate-configuration>
AnnotationsIllustrationTest.java
package org.hibernate.tutorial.annotations; import java.util.*; import static junit.framework.Assert.*; import lombok.extern.slf4j.Slf4j; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; import org.hibernate.stat.Statistics; import org.junit.*; import org.junit.Test; @Slf4j public class AnnotationsIllustrationTest { private SessionFactory sessionFactory; @Before public void setUp() { // A SessionFactory is set up once for an application sessionFactory = new Configuration() .configure() // configures settings from hibernate.cfg.xml .buildSessionFactory(); } @After public void tearDown() { if (sessionFactory != null) { sessionFactory.close(); } } @Test @Ignore public void testBasicUsage() { //transient object Person person = new Person(); person.setFirstName("Vit"); person.setSureName("Lopanov"); /*Event event1 = new Event("Our very first event!", new Date()); Event event2 = new Event("A follow up event", new Date()); event1.setPerson1(person); event2.setPerson1(person); Listevents = new ArrayList (); events.add(event1); events.add(event2); person.setEvents(events); */ Session session = sessionFactory.openSession(); session.beginTransaction(); //persistent object session.saveOrUpdate(person); session.getTransaction().commit(); session.close(); //detached object объект person перешел в состояние detached /* session = sessionFactory.openSession(); session.beginTransaction(); List result = session.createQuery("from Person p").list(); for (Person person1 : (List ) result) { log.info("Event (" + person1.getFirstName() + ") : " + person1.getSureName() + " \n" + person1.getEvents().toString()); } Person person2 = (Person) session.get(Person.class, 1L); //removed - объект session.delete(person2); result = session.createQuery("from Event").list(); for (Event event : (List ) result) { log.info(event.toString()); event.getPerson1().toString(); } session.getTransaction().commit(); session.close();*/ } @Test() @Ignore public void loadProxyPersons() { Session session = sessionFactory.openSession(); session.beginTransaction(); // в бд нет записи 10L load() возвращает proxy объект Person person2 = (Person) session.load(Person.class, 10L); //проверяем существование proxy объекта assertNotNull(person2); session.getTransaction().commit(); session.close(); } @Ignore @Test(expected = org.hibernate.ObjectNotFoundException.class) public void loadProxyPersonsException() { Session session = sessionFactory.openSession(); session.beginTransaction(); // в бд нет записи 10L load() возвращает proxy объект Person person2 = (Person) session.load(Person.class, 10L); // вызываем исключение org.hibernate.ObjectNotFoundException.class person2.getFirstName(); session.getTransaction().commit(); session.close(); } @Test() @Ignore public void getNullPerson() { Session session = sessionFactory.openSession(); session.beginTransaction(); //запращиваем объект Person person2 = (Person) session.get(Person.class, 10L); //проверяем что get возвращает null assertNull(person2); session.getTransaction().commit(); session.close(); } @Test @Ignore public void testHibernateCacheLevelOne() { Person person = new Person(); person.setFirstName("Vit"); person.setSureName("Lopanov"); Statistics statistics = sessionFactory.getStatistics(); Session session = sessionFactory.openSession(); session.beginTransaction(); session.saveOrUpdate(person); session.getTransaction().commit(); session.close(); //включаем статистику для сбора информации. statistics.setStatisticsEnabled(true); session = sessionFactory.openSession(); session.beginTransaction(); // загружаем объект из БД - выполняется sql: //Hibernate: select person0_.personId as personId16_0_, person0_.fName as fName16_0_, person0_.sName as sName16_0_ from Persons person0_ where person0_.personId=? person = (Person) session.load(Person.class, 1L); // проверяем сохранен ли наш объект в кеше - true System.out.println("contains in cache: " + session.contains(person)); person.setFirstName("Tom"); session.saveOrUpdate(person); //.... выполняем еще какие-то операции //запрашиваем заново наш объект, sql не выполняется, объект подсовывается из кеша person = (Person) session.load(Person.class, 1L); // проверяем находиться ли наш объект в кеше - true System.out.println("contains in cache: " + session.contains(person)); person.setSureName("Aristov"); session.saveOrUpdate(person); // выгружаем объект из кеша if (session.contains(person)) session.evict(person); // объект не найден в кеше, выполняем sql запрос //Hibernate: select person0_.personId as personId16_0_, person0_.fName as fName16_0_, person0_.sName as sName16_0_ from Persons person0_ where person0_.personId=? person = (Person) session.load(Person.class, 1L); person.setFirstName("Oleg"); session.flush(); session.saveOrUpdate(person); session.getTransaction().commit(); session.close(); System.out.println("InsertCount: " + statistics.getEntityInsertCount() + " " + "UpdateCount: " + statistics.getEntityUpdateCount() + " " + "FlushCount: " + statistics.getFlushCount() + " " + "TransactionCount: " + statistics.getTransactionCount() + " " + "SuccessfulTransactionCount: " + statistics.getSuccessfulTransactionCount()); } @Test //@Ignore public void batchInsert() { Person person; Session session = sessionFactory.openSession(); session.beginTransaction(); for (int i = 0; i < 10000; i++) { person = new Person(); person.setFirstName("Vit" + i); person.setSureName("Lopanov" + i); session.saveOrUpdate(person); if (i % 30 == 0) { //скидываем изменения session.flush(); //очищаем кеш session.clear(); } } session.getTransaction().commit(); session.close(); } }
можно посмотреть весь проект сразу?
ОтветитьУдалитьотчасти интересует настройка логера и прочие мелочи, которые есть в проекте, но не описаны в статье.