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=?

Комментариев нет:

Отправить комментарий