7.11.2013

Hibernate cache второго уровня.

В одной из прошлых статей я расказывал про hibernate cache первого уровня, настало время расказать и про cache второго уровня. И так в крации напомню что кеш первого уровня встроен в hibernate, он всегда включен, его не возможно выключить. Область действия, в течении одной транзакции в сессии hibernate, т.е. между вызовами session.beginTransaction() и session.getTransaction().commit(). И так, зачем нам нужен hibernate cache второго уровня, прежде всего он уменьшает количество sql-запросов к базе данных, увеличивая при этом время отклика программы, что отражается на её быстродействии. Один раз попав в cache второго уровня  объект-сущность используется на всем протяжении жизни объекта sessionFactory, т. е. облать действия кеша - вся наша программа, а если быть точнее, то как вы настроете свой кеш, так он себя и поведет. В отличии от кеша первого уровня кеш  второго уровня нужно включать непосредственно в настройках Hibernate, и он реализуется second-level cache провайдером - сторонней библиотекой кешем. Выбор провайдера зависит от стратегии которые он поддерживает, под стратегией понимается что можно делать над объектом кеша: изменять, удалять, вставлять, читать, давайте рассмотрим их:
  1.  read-only - самая простая стратегия, кеш может только читаться, операции обновления (update) и удаления (delete) не разрешены, однако можно вставлять новые данные (insert) отлично подходит к кешированию различных справочников - наименование регионов, городов, улиц ... и т. д.
  2.  nonstrict read-write - данные этого кеша могут меняться, возможен конкурентный доступ к одному и тому же объекту.  Может произойти ситуация когда в кеше содержатся не последняя измененная сущность, т. е. данные сущности в кеше могут быть не равны данным в базе данных. Отсуда следует что нужно избегать конкурентного доступа, точнее одни и те же записи не должны редактировать 2 пользователя. Преведу пример: идеальный случаи это когда  кадровики редактируют только свои данные по работникам:  1-й кадровик с 1 по 200 таб. номер, 2-й кадровик с 201 по 400 таб. номер и т. д.
  3.  read-write - в целом похож на nonstrict read-write, позволяет более гибко настроить конкурентный доступ, поведение кеша зависит от настройки transaction isolation уровня базы данных, т. е. поведение изменения данных в кеше копирует поведение транзакции.  Максимум, чего можно выжать - это "repeatable read transaction isolation" уровень базы данных.
  4.  transactional - изменения в кеше и изменения в БД, полностью записываются в одной транзакции. У предыдущих двух стратегий была отложенная запись кеша, т. е. при изменении сущности, с начала происходит блокировка в кеше(soft locked), после применения транзакции с некоторым опазданием выполняется замена старого значения в кеше, новым. Уровнь этого кеша - это "serializable transaction isolation" уровень базы данных.
 Давайте я вам попробую объяснить, как происходит поиск сущности в кеше, с начала ищатся данные в кеше первого уровня, если нашли возвращаем, иначе ищется в кеше второго уровня, нашли возвращаем, иначе выполняем sql-запрос к БД. Стоит отметить что кеш первого уровня и второго уровня применим только к одиночным сущностям, т.е. когда запрашивается 1 объект,  если выполняется запрос нескольких сущностей, то нужно задействовать кеш запросов, который так же как и кеш второго уровня, нужно с начала включить в настройках hibernata. Хочу сказать пару слов о кеше запросов все что написано про стратегии кеша второго уровня справедливо и для кеша запросов.  Забегая вперед хочу что бы вы знали что такое регион кеша - это его логическое имя, он нужен для более тщательной и глубокой настройки кеша отдельно взятой сущности. И так приступим к настройке Ehcache:
  1. Включим кеш второго уровня в настройках Hibernate и подключим ehcache:
    <!-- Enable the second-level cache  -->
    <property name="cache.use_second_level_cache">true</property>
    <property name="cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
  2. Включим кеш запросов:
    <!-- Enable the query cache  -->
    <property name="cache.use_query_cache">true</property>
  3. Опищем сначала наши сущности (1), а потом какие сущности (2) должны, у нас кешироваться в кеше второго уровня, какую стратегию при этом применять,  поведение с lazy объектами, имя региона кеша:
    <!-- (1) Names the annotated entity class -->
    <mapping class="org.hibernate.tutorial.annotations.Event"/>
    <mapping class="org.hibernate.tutorial.annotations.Person"/>
    
    <!-- (2) Entity class the second-level cache -->
    <class-cache class="org.hibernate.tutorial.annotations.Person" usage="read-write" include="non-lazy"
                         region="org.hibernate.tutorial.annotations.Person1"/>
    <class-cache class="org.hibernate.tutorial.annotations.Event" usage="read-write" include="non-lazy"
                         region="org.hibernate.tutorial.annotations.Event1"/>
  4. Переходим к тонкой настройке сущностей кеша, конечно все можно настроить через аннотации но мне ближе старинный метод настройки через файл ehcache.xml:
    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache name="Foo"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:noNamespaceSchemaLocation="ehcache.xsd">
        <!-- где будут у нас храниться данные на диске, в случае выгрузки
        кеша на диск -->
        <diskStore path="java.io.tmpdir"/>
        <!-- если вы не указали имя региона то по умолчанию используются параметры
         default кеша -->
        <defaultCache
                maxElementsInMemory="1000"
                eternal="false"
                timeToIdleSeconds="1200"
                timeToLiveSeconds="1200"
                overflowToDisk="true">
        </defaultCache>
        <!-- настраиваем наши регионы описанные раннее -->
        <cache name="org.hibernate.tutorial.annotations.Person1" maxElementsInMemory="1"
               eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600"
               overflowToDisk="true"
                />
        <cache name="org.hibernate.tutorial.annotations.Event1" maxElementsInMemory="1"
               eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600"
               overflowToDisk="true"
                />
    </ehcache>
  5. задействуем кеш запросов, его просто включить, вызовите метод setCacheable(true) и все сущности запроса попадут в кеш запросов:
    List result2 = session1.createQuery("from Person as p join fetch p.events").
                    setCacheable(true).list();
ну вот и все приведу на последок свой pom.xml с зависимостями:
<?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>org.slf4j</groupId>
            <artifactId>slf4j-jdk14</artifactId>
            <version>1.6.6</version>
        </dependency>
        
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>0.11.0</version>
            <scope>provided</scope>
        </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>

5.13.2013

Hibernate интересные моменты часть 4.

В прошлой статья я рассказал вам о оптимистической блокировке - Hibernate интересные моменты часть 3. В этой статье я постараюсь кратко рассказать о pessimistic locking. В Hibernate, пессимистическая блокировка - реализована блокировками уровня баз данных. То есть при использовании pessimistic locking имеет смысл глянуть прежде всего на то, какая СУБД используется, на основе каких блокировок самой СУБД, могут реализовываться механизмы блокировки Hibernate. И так pessimistic locking случается когда вы считываете данные (Сущность - Entity) и пытаетесь овладеть данными для монопольного владения или изменения Сущности, блокировка  удерживатся пока транзакция в hibrnate сессии не закончится. В Hibernate существует специальный класс LockMode который позволяет нам выбрать тип блокировки. Обычно этот класс используется в следующих методах:

Session.load(Class, id, LockMode);
Session.get(Class, id, LockMode);
Session.lock(Object, LockMode);
Query.setLockMode(alias, LockMode);
И так какие уровни блокировки у нас есть:

 LockMode.NONE - не используются, ни какие блокировки БД на уровне запроса, по умолчанию load() и get() используют LockMode.NONE, если запрашивается объект(Entity) и он существует в любом кеше hibernate, то используется кеш, sql запрос не формируется.

LockMode.READ - блокировка используется обычно для принудительной проверки версионности у detached-объектов - это persistent-объект отсоединенный от сессии. т. е. заставляет hibernate всегда запрашивать данные из БД, даже если есть данные в любом кэше, одним словом происходит игнорирование любого кеша hibernate, что приводит к проверке версионности у detached-объекта. Не используются,  ни какие внутренние блокировки Баз Данных на уровне запроса.

LockMode.UPGRADE - блокировка использует механизм внутренний монопольной блокировки  Баз Данных на уровне запроса - select ... for update, т. е. запрошенная запись (Entity) блокируется для дальнейшего изменения, если запись не может быть заблокирована немедленно, то происходит ожидание освобождения данных.

LockMode.UPGRADE_NOWAIT - блокировка похожа на LockMode.UPGRADE, но только  использует запрос select for update nowait, отличие в том, если запись не может быть заблокирована сразу, то происходит исключение. На моей памяти запрос select for update nowait поддерживает только БД Oracle, соответственно если БД не поддерживает такие запросы, то LockMode.UPGRADE_NOWAIT преобразуется к блокировке LockMode.UPGRADE.

LockMode.FORCE - используется для принудительного увеличения версионного поля в объекте сущности(Entity), да же если в текущей транзакции, не менялось состояние полей у Entity-объекта. 

LockMode.WRITE - случается когда объект сущности(Entity) обновляется (update) или вставляется (inserte), этот уровень блокировки используется только внутренним механизмом hibernate и его использовать в методах запрешено.

На практике я пользовался в основном, только LockMode.UPGRADE и LockMode.FORCE блокировками, но если вы часто используете их, то может луче задуматься и  повысить уровень изоляции транзакции? - о них читайте мою статью - Hibernate интересные моменты часть 2.

4.22.2013

Hibernate интересные моменты часть 3.

Версионность - это оптимистическая блокировка, она реализована специальным, внутренним механизмом в Hibernate, который позволяет нам  контролировать транзакции. Зачем она (версионность) нам нужна - представте что одну и ту же запись, практически в одно и то же время редактируют 2 пользователя (две разные транзакции) и перезаписывают данные, чьи изменения применятся, а чьи потеряются? Вот на этот вопрос я и постараюсь ответить в этой статье. Оптимистическая блокировка обычно (практически всегда)  применяется в месте с базой данных у которой выставлен read committed transaction isolation level, который позволяет наилучше и наибыстрее обрабатывать данные поступающие от различных пользователей (транзакций). И так ответ на вопрос - возможны 3 случая:
  1. Last commit wins - обе транзакции применятся успешно, и 2-я транзакция перезапишит изменения 1-й транзакции, изменения внесенные 1-й транзакцией потеряются, ни каких ошибок не будет!
  2. First commit wins - 1-я транзакция примениться успешно и при коммите (commit) 2-й транзакции будет выдана ошибка, даные 2-й транзакции потеряются!
  3. Merge conflicting updates - 1-я транзакция примениться успешно, при коммите 2-й транзакции будет предложено объединить результаты двух транзакций.
По умолчанию в Hibernate применяется Last commit wins - для чистоты эксперимента будем запускать 2-ю транзакцию в отдельном потоке, имитирую при этом  многопользовательский режим работы:

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import java.util.concurrent.TimeUnit;

public class OneSession implements Runnable {
    private SessionFactory factory;
    private Long id;

    public OneSession(SessionFactory factory, Long id) {
        this.factory = factory;
        this.id = new Long(id);
    }

    @Override
    public void run() {
        Session session = factory.openSession();
        session.beginTransaction();
        Person person2 = (Person) session.get(Person.class, id);
        person2.setSureName("Insert Thread %s\n" + Thread.currentThread().getName());
        try {
        //выжидаем 3 секунды - даем main запросить не измененный Person и применяем изменения
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        session.getTransaction().commit();
        session.close();
    }
}
код основной программы:
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

public class Main {
    private SessionFactory sessionFactory;

    public Main() {
        try {
            sessionFactory = new Configuration().configure().buildSessionFactory();
            Long id = insertPersons(sessionFactory);
            OneSession oneSession = new OneSession(sessionFactory, id);
            Thread thread = new Thread(oneSession);
            thread.start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Session session = sessionFactory.openSession();
            session.getTransaction().begin();
            Person person2 = (Person) session.get(Person.class, id);
            person2.setSureName("Main!");            
            try {
            //ждем пока 1-я транзакция завершится и коммитем наши изменения во 2-й транзакции.
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            session.getTransaction().commit();
            session.close();
            session = sessionFactory.openSession();
            session.getTransaction().begin();
            person2 = (Person) session.get(Person.class, id);
            System.out.printf("Person after change: %s\n", person2);
            session.getTransaction().commit();
            session.close();
        } finally {
            if (sessionFactory != null) sessionFactory.close();
        }
    }

    private Long insertPersons(SessionFactory sessionFactory) {
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        Person person = new Person();
        person.setFirstName("Vitaly");
        person.setSureName("Lopanov");
        Set<Phone> phones = new HashSet<Phone>();
        Phone phone1 = new Phone();
        phone1.setNumber("515555");
        Phone phone2 = new Phone();
        phone2.setNumber("525555");
        phones.add(phone1);
        phones.add(phone2);
        person.setPhones(phones);
        session.save(person);
        System.out.printf("Person before change: %s\n", person);
        session.getTransaction().commit();
        session.close();
        return person.getId();
    }

    public static void main(String args[]) {
        new Main();
    }
}
в выводе программы прослеживается потеря данных внесенных 1-й транзакцией:
....
Person before change: Person(id=1, firstName=Vitaly, sureName=Lopanov, phones=[Phone(id=1, number=525555), Phone(id=2, number=515555)])
....
Person after  change: Person(id=1, firstName=Vitaly, sureName=Main!, phones=[Phone(id=1, number=525555), Phone(id=2, number=515555)])
Когда мы включаем версионность - то начинает работать First commit wins, а так как Merge conflicting updates - это часный случай First commit wins, то и он реализуем для пользователей. Для того что бы включить версионность в Hibernate нужно лишь добавить числовое поле в сушность(Entity):
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Set;

@Entity @Table(name = "Persons") @ToString @EqualsAndHashCode
public class Person implements Serializable {
    static final long serialVersionUID = -7593775012501239455L;
    @Id  @GeneratedValue()  @Column(name = "personId")
    private Long id;
    @Version //включаем версионность
    private Long version1;
    @Column(name = "fName")
    private String firstName;
    @Column(name = "sName")
    private String sureName;
    @OneToMany(cascade = CascadeType.ALL,orphanRemoval = false)
    @JoinColumn(name = "phoneId1",referencedColumnName = "personId")
    private Set<Phone> phones;

 ... getters and setters
  
}
и Hibernate автоматически все сделает за нас. Hibernate увеличит на единицу поле version1, когда измениться Person, автоматически проверит поле version1 и если был изменен Person - то вызовет исключительную ситуацию org.hibernate.StaleObjectStateException. Опять запустим нашу программу и посмотрим как работает hibernate:
До изменения Person:
Person(id=1, version1=0, firstName=Vitaly, sureName=Insert Thread, phones=[Phone(id=1, number=525555), Phone(id=2, number=515555)])

Hibernate: update Persons set fName=Vitaly, sName=Insert Thread, version1=1 where personId=1 and version1=0
1-я транзакция закончилась успешно поле version1=1
Пытаемся применить вторую транзакцию
Hibernate: update Persons set fName=Vitaly, sName=Main!, version1=1 where personId=1 and version1=0
т.к. такого поля version1=0 уже не существует то Hibernete проверит количество измененных строк (row count) 
который вернул нам JDBC driver и вызовет исключительную ситуацию
...
Exception in thread "main" org.hibernate.StaleObjectStateException:
 Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [Person#1]
 
В принципе есть другой способ включить оптимистическую блокировку добавить optimisticLock, а поле закоментировать //@Version  private Long version1; :
...

@Entity @Table(name = "Persons") @ToString @EqualsAndHashCode
@org.hibernate.annotations.Entity(
optimisticLock = org.hibernate.annotations.OptimisticLockType.ALL
        ,dynamicUpdate = true
)
public class Person implements Serializable {
....
OptimisticLockType.ALL в условии where присутствуют все поля:
update Persons set sName=? where personId=? and fName=? and sName=?

OptimisticLockType.DIRTY в условии where присутствуют только измененные поля:
Hibernate: update Persons set sName=? where personId=? and sName=?

4.18.2013

Hibernate интересные моменты часть 2

Эту статью я бы хотел посвятить транзакциям и как они реализованы в Hibernate. Сначала давайте расмотрим какие интересные моменты могут случаться в "черном ящике" - Баз Данных (oracle,mysql,interbase,mssql ...):

  1. lost update -  может случиться когда две транзакции одновременно обновляют, выполняют операцию update над некоторой записью и 2 транзакция (самая последния транзакция по времени) заканчиваеться не удачно, она откатывает все изменения внесенные 1 и 2 транзакцией. Такая ситуация может случиться когда БД не использует блокировок (locking) и конкурирующие транзакции (concurrent transactions) в ней  не изолированны друг от друга.
  2. dirty read - может случиться когда 1-я транзакция изменяет строку, 2-я транзакция читает эту строку, 1-я откатывает изменения зделанные в транзакции. 2-я транзакция имеет не достоверные данные.
  3. unrepeatable read - может случиться когда 1-я транзакция дважды читает одну и ту же строку, и получает разные результаты, в промежутке между двумя чтениями 2-я транзакция изменяет ту же строку, и 1-я транзакция при повторном чтении получает новые данные. Так же частным случаем unrepeatable read является  проблема second lost updates problem. Представте себе что две конкурирующих транзакций одновременно читают одну и туже строчку, потом 1-я транзакция изменяет её и завершается удачно(commit), потом 2-я транзакция изменяет ту же строчку и перезаписывает данные, при этом теряются все изменения внесенные 1-й транзакцией.
  4. phantom read - может случиться когда одна и та же транзакция читает строки дважды через какой-то промежуток времени, и 2-я транзакция вставляет новую строчку или удаляет, до второго чтения.
И для предотвращения этих казусов в ANSI SQL стандарте предусмотрены standard isolation levels:
  1. read uncommitted transaction isolation - сдесь может случиться  dirty read,  но никогда не случиться lost update. Ни какая транзакция не может перезаписать не подтвержденные данные, достигается за счет блокировки (exclusive write locks), однако они могут быть прочтены любой транзакцией.
  2.  read committed transaction isolation - сдесь может случиться  unrepeatable read, но никогда не случиться dirty read. Транзакции чтения ни когда не блокируют читаемые строки, но при изменении данных, блокируются изменяемые строки.
  3. repeatable read transaction isolation - сдесь может случиться  phantom read, но никогда не случиться dirty read и unrepeatable read. Транзакции чтения блокируют данные строки. И в них не могут писать, ни какие данные транзакции, читать могут все. Транзакции записи блокирют все другие транзакции.
  4. serializable transaction isolation - представте очередь из транзакций и они выполняются одна, за другой по "очереди".
И так отсюда следует что, чем выше уровень транзакции тем не поворотнее становиться работа с базой данных, по этому нужно правильно выбрать устраивающий вас уровень, чем и займемся далее.Каждая база данных имеет по умолчанию transaction isolation level - обычно это read committed (чаще встречается) или repeatable read. Вы конечно можете изминить этот уровень в объекте java.sql.Connection драйвера JDBC:

1 - Read uncommitted isolation
2 - Read committed isolation
4 - Repeatable read isolation
8 - Serializable isolation

Или в Hibernate в параметре hibernate.connection.isolation проставить нужный вам уровень изоляции транзакции:

    <?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>
    ...        
            <property name="connection.isolation">1</property>
            <!-- 1-Read uncommitted isolation
                 2-Read committed isolation
                 4-Repeatable read isolation
                 8-Serializable isolation   -->
    ...
        </session-factory>
    </hibernate-configuration> 
    
    при запуске приложения в логе hibernate  появятся такие строчки:
    17.04.2013 21:45:31 org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
    INFO: HHH000149: JDBC isolation level: READ_UNCOMMITTED
    
    PS.
    если вы используете пул подключения баз данных ч/з application server - то меняйте настройки у него (у сервера), параметр hibernate.connection.isolation не влияет на настройки сервера. Так же хочу сказать что у Hibernate реализованы 2 механизма контроля транзакций - это version checking (версионность - оптимистическая блокировка,  реализован специальный внутренний механизм в Hibernate) и pessimistic locking (песимистическая блокировка - реализовано блокировками  уровня баз данных). Надеюсь вскоре я напишу пару статей посвященных блокировкам в Hibernate.

    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();
        }
    
    
    }

    2.25.2013

    MongoDB + Java.

    В этой статье хотелось бы кратко описать работу MongoDB + Java и расмотреть основные операции CRUD. Прежде всего установим MongoDB для этого идем на официальный сайт и скачиваем дистрибутив, у меня ОС winXP 32 - к ней подходит дистрибутив mongodb-win32-i386-2.0.8, скачайте правильно дистрибутив, ибо под разные версии ОС (xp - mongodb-win32-i386-2.0.XXX, win7 - mongodb-win32-i386-2.2.XXX) идут разные дистрибутивы MongoDB. Под winXP мне пришлось скачать старую версию дистрибутива. Создайте папку на с:\data\db - там по умолчанию MongoDB будет хранить все файлы баз данных, распакуйте архив, перейдите в папку c:\mongodb-win32-i386-2.0.8\bin. Наберите в командной строчке start mongod.exe и запустите, после выполнения команды у вас в окне должна последней строчкой появиться такая надпись:
    ...
    Tue Feb 12 21:17:51 [initandlisten] waiting for connections on port 27017
    MongoDB запушена и ждет подключения к порту 27017. Запустим клиет наберем команду start mongo.exe --host localhost, по умолчанию при подключени к MongoDB не нужно указывать ни пользователя, ни пароля. У вас на экране должны появиться строчки:
    MongoDB shell version: 2.0.8
    connecting to: localhost:27017/test
    >  
    
    Подключаемся по умолчанию к БД test. В MongoDB основной рабочей лошадкой является коллекция которая содержит документы, документы представляют собой пары ключ-значения в формате JSON. По умолчанию базы данных и коллекции создаются при вставке первого документа. В MongoDB структуру документов не нужно определять заранее, как делается в реляционных СУБД во время разработки (create table, alter ...), мета-данные создаются во время выполнения команд вставки и изменения. Переключимся на БД vit и вставим документ в коллекцию persons - этот документ содержит одну пару ключ значение для хранения имени - {name:'vit'} введем данные:
    MongoDB shell version: 2.0.8
    connecting to: test
    > use vit
    switched to db vit
    > db.persons.insert({name:'vit'})
    
    выведем только что вставленый документ командой find:
    > db.persons.find()
    { "_id" : ObjectId("511bdbf296970d429769f39e"), "name" : "vit" }
    
    В распечатке консоли вы можете видеть новое поле _id, его можно считать первичным ключом документа. В любом документе MongoDB должно присутствовать поле _id которое гарантированно будет уникальным среди всех значений _id в данной коллекции. Если _id не задано в момент создания документа, то MongoDB генирирует его за вас. Вставим новый документ задав _id вручную:
    > db.persons.insert({_id:ObjectId("012345678901234567890123"),telHome:515595})
    выведем данные на экран:
    > db.persons.find()
    { "_id" : ObjectId("511bdbf296970d429769f39e"), "name" : "vit" }
    { "_id" : ObjectId("012345678901234567890123"), "telHome" : 515595 }
    
    Усложним немного наш пример введем понятие селектор запроса - условие поиска документа. Селектор представляет собой пару-ключ значение допустим найдем документ который содержит в поле name - значение vit:
    > db.persons.find({name:"vit"})
    { "_id" : ObjectId("511bdbf296970d429769f39e"), "name" : "vit" }
    >
    не всегда нам нужно возвращать множество полей, ограничить их можно в виде возвращаемого набора полей:
    > db.persons.find({},{name:1})
    { "_id" : ObjectId("511bdbf296970d429769f39e"), "name" : "vit" }
    { "_id" : ObjectId("012345678901234567890123") }
    Этот запрос возвращает только два поля из документа - _id и name, _id - всегда включено по умолчанию. Иногда нам нужно исключить набор возвращаемых полей из набора:
    > db.persons.find({},{name:0})
    { "_id" : ObjectId("511bdbf296970d429769f39e") }
    { "_id" : ObjectId("012345678901234567890123"), "telHome" : 515595 }
    и так попробуем изменить документ
    > db.persons.update({telHome:515595},{$set: {name:"Joy"}})
    > db.persons.find()
    { "_id" : ObjectId("511bdbf296970d429769f39e"), "name" : "vit" }
    { "_id" : ObjectId("012345678901234567890123"), "name" : "Joy", "telHome" : 515595 }
    и удалим документ
    > db.persons.remove({name:"vit"})
    > db.persons.find()
    { "_id" : ObjectId("012345678901234567890123"), "name" : "Joy", "telHome" : 515595 } 
    еще последние две важные команды: выводит какие БД у нас есть
    > show dbs
    local   0.03125GB
    test    0.03125GB
    tutorial        0.0625GB
    vit     0.03125GB
    
    выводит имена коллекций в текущей БД
    > show collections
    persons
    system.indexes
    и еще одна важная команда - завершение работы сервера MongoDB, сначала нужно перейти в БД admin и потом вызвать команду db.shutdownServer():
    > use admin
    switched to db admin
    > db.shutdownServer()
    Fri Feb 15 22:38:04 DBClientCursor::init call() failed
    Fri Feb 15 22:38:04 query failed : admin.$cmd { shutdown: 1.0 } to: 127.0.0.1
    server should be down...
    Fri Feb 15 22:38:04 trying reconnect to 127.0.0.1
    Fri Feb 15 22:38:05 reconnect 127.0.0.1 failed couldn't connect to server 127
    0.1
    Fri Feb 15 22:38:05 Error: error doing query: unknown shell/collection.js:151
    И так переходим к программированию на Java, для доступа к MongoDB нужно подключить "драйвер" в maven я делаю так:
            <dependency>
       <groupId>org.mongodb</groupId>
        <artifactId>mongo-java-driver</artifactId>
        <version>2.9.1</version>
       </dependency>
    и так смотрим коментарии в коде:
    package com.vit;
    
    import com.mongodb.*;
    
    import java.net.UnknownHostException;
    import java.util.List;
    import java.util.Set;
    
    public class MainDb {
        private final String dbName = "vit";
        private final String collectionName = "persons";
    
        public static void main(String[] args) {
            new MainDb();
        }
    
        public MainDb() {
            Mongo mongo = null;
            try {
                //и так подключимся к MongoDB host port
                mongo = new Mongo("localhost", 27017);
            } catch (UnknownHostException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
            try {
                //получим все имена баз данных
                List databases = mongo.getDatabaseNames();
                System.out.println("Databases MongoDB:");
                for (String database : databases) System.out.println(database);
                //подключимся к БД vit
                DB db = mongo.getDB(dbName);
                System.out.println("\nCollections DB " + dbName + " MongoDB:");
                //запросим все коллекции в БД vit
                Set collections = db.getCollectionNames();
                for (String collection : collections) System.out.println(collection);
                DBCollection collection = db.getCollection(collectionName);
                //выведем все записи из коллекции
                DBCursor cursor;
                cursor = collection.find();
                PrintCursor(cursor);
                //вставка документа
                System.out.println("after Insert into MongoDB");
                BasicDBObject doc = new BasicDBObject();
                doc.put("name", "Vit");
                doc.put("telHome", 522318);
                collection.insert(doc);
                //выведем все записи из коллекции
                cursor = collection.find();
                PrintCursor(cursor);
                System.out.println("print Document with selector MongoDB");
                //выведем по условию поиска - "селектора" {"name","Joy"} из коллекции
                cursor = collection.find(
                        new BasicDBObject().append("name","Joy")
                , new BasicDBObject().append("name",1));
                PrintCursor(cursor);
                //изменение документа
                System.out.println("after update document MongoDB");
                doc = new BasicDBObject();
                doc.put("name", "Vit");
                doc.put("telHome", 522318);
                BasicDBObject doc1 = new BasicDBObject();
                doc1.put("$set",new BasicDBObject("name","Tom"));
                collection.update(doc, doc1, false, true);
                //выведем запись
                cursor = collection.find();
                PrintCursor(cursor);
                //удаление документа
                System.out.println("after delete document MongoDB");
                doc = new BasicDBObject();
                doc.put("name","Tom");
                doc.put("telHome", 522318);
                collection.remove(doc);
                //выведем все записи из коллекции
                cursor = collection.find();
                PrintCursor(cursor);
            } finally {
                // закрываем соединение с БД MongoDB
                if (mongo != null) mongo.close();
            }
    
        }
    
        /**
         * распечатываем документы возвращаемые курсором
         *
         * @param cursor
         */
        private void PrintCursor(DBCursor cursor) {
            DBObject doc;
            StringBuilder temp;
            while (cursor.hasNext()) {
                doc = cursor.next();
                temp = new StringBuilder();
                temp.append("{");
                for (String keyName : doc.keySet())
                    temp.append(keyName + ":" + doc.get(keyName) + ", ");
                int i = temp.lastIndexOf(",");
                System.out.println(
                        temp.replace(i, i + ", ".length(), "}").toString());
    
            }
        }
    }