1.18.2015

1. Настройка среды разработки. Введение в Hibernate.




Hibernate довольно не молодая технология и самая известная, и ей пользуются пару миллионов  разработчиков Java во всем мире. С начала все настройки у Hibernate  находились  в xml файлах, потом с появлением аннотаций в мире Java, появились аннотации и у  Hibernate.  Сейчас он поддерживает два стиля конфигурирования,  мне удобней пользоваться при настройке Hibernate xml  файлами, а при описании ORM сущностей аннотациями, так что я буду использовать  два стиля.  Структура Hibernate состоит из множества java библиотек которые сгруппированы по основным модулям: hibernate-core, hibernate-entitymanager, hibernate-ehcache и т. д. Иногда сложно определить какие зависимости библиотек  использовать, по этому я предпочитаю использовать maven.  Давайте начнем изучение hibernate  с настройки среды разработки и утилит,  которые помогут нам в освоении этой сложной технологии. Я работаю в операционной системе Windows 7,  использую jdk1.7.0_25, среду разработки IntelliJ Idea,  apache-maven-2.2.1.  Для начала нам нужно установить и правильно настроить переменные окружения ОС Windows . Скачайте из интернета  jdk1.7.0_25 установите её в нужный вам каталог, я обычно устанавливаю на диск C:\ jdk1.7.0_25. Дальше нужно правильно настроить  переменные окружения,  выберите меню Пуск – Панель управления  – Система и безопасность – Система:


Выберите подменю Дополнительные параметры системы, появиться окно – Свойства системы, выберите вкладку – Дополнительно:





Нажмите на кнопку Переменные среды, появиться окно как на следующем рисунке:


Нажмите на кнопку создать, введите JAVA_HOME  и путь, куда установили jdk , c:\jdk1.7.0_25, нажмите на кнопку OK и создайте переменную среды JAVA_HOME:

Скачайте и распакуйте maven, я установил его на диск c:\apache-maven-2.2.1, создайте переменную m2_home со значением c:\apache-maven-2.2.1:

Создайте переменную MAVEN_OPTS, для установки переменных выполнения maven, которые будут вызываться  по умолчанию  при выполнении жизненного цикла mavena:
 -Xms512M –Xmx512M -XX:MaxPermSize=256M  -Dfile.encoding=UTF-8:







Осталось добавить пути  поиска программ в системную переменную path рис 7:
 …. ;%JAVA_HOME%\bin;%m2_home%\bin;   



И так проверим все ли правильно установлено у нас, запустите командную строчку cmd.exe и выполните команду java, у вас должна появиться  подсказка, с какими параметрами можно вызывать компилятор java


Наберите в командной строчке команду mvn,  у вас должна появиться информация, как и с какими параметрами можно вызывать сборку проекта  maven:




Если вы все настроили правильно, то можно двигаться дальше.
 





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.