2.27.2013

Hibernate интересные моменты.

Замапленый объект может находиться в четырех состояниях:
  1. transient-объект создаётся командой new, может быть заполнен данными но, ни когда не сохранялся в сессии т.е. не ассоциирован со строкой в таблице БД.
  2. persistent-объект, объект в данный момент связанный с некоторой сессией, hibernate сессия "работает" с экземпляром объекта в данный промежуток времени, т.е. объект ассоциирован со строкой в таблице БД. Получить persistent-объект можно двумя способами:  1-й способ запросить объект такими методами как get(), load(),  list(), uniqueResult(), iterate(), scroll(), find(), getReference(). 2-й способ перевести наш transient-объект в persistent-объект путем вызова таких методов как save(), saveOrUpdate(), persist(), merge().
  3. detached-объект - это persistent-объект отсоединенный от сессии, это состояние объекта возникает после закрытия сессии close(), которая работала с объектом до этого или при вызовах методов evict(), clear() сессии. Переход из состояния detached в persistent объектом возможно при вызове методов сессии - update(), saveOrUpdate(), merge().
  4.  removed - объект - это persistent-объект удаленный в сессии методом delete() или remove() в jpa.
Еще хотелось бы сказать пару слов о detached-объектах, их можно использовать дальше, при работе с новой сессией, если к ним применить такие команды как update(); saveOrUpdate() или
merge() (в jpa), то они переходят в состояние persistent - в Hibernate такие операции называются reattached mode или merging mode - в jpa. 
Замапленный объект должен удовлетворять требованиям, тогда и только тогда он будет правильно взаимодействовать с hibernate:
  1.  Иметь конструктор по умолчанию без параметров.
  2.  Переопределять методы toString(), Equals(), HashCode().
  3.  Иметь поле "первичного ключа" (id) как минимум, как максимум сложный составной ключ.
  4.  Класс сущности не должен быть объявлен как final класс.
  5.  Класс сущности должен наследовать интерфейс Serializable.
Немножко отступим от темы и поговорим о Serializable. С каждым сериализуемым классом связан уникальный идентификационный номер. Если вы не указываете этот идентификатор явно, декларируя поле private static final long с названием serialVersionUID, система генерирует его автоматически, используя для класса сложную схему расчетов. При этом на автоматически генерируемое значение оказывают влияние название класса, названия реализуемых им интерфейсов, а также все открытые и защищенные члены. Если вы каким-то образом поменяете что-либо в этом наборе, например, добавите простой и удобный метод, изменится
и автоматически генерируемый 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 List events = 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);
        List events = 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();
    }


}

1 комментарий:

  1. можно посмотреть весь проект сразу?
    отчасти интересует настройка логера и прочие мелочи, которые есть в проекте, но не описаны в статье.

    ОтветитьУдалить