1.19.2015

15. Транзакции JTA.



Каждая реляционная база данных поддерживает транзакции,  под транзакцией понимается законченная работа над данными, результатом транзакции может быть одно значение:  фиксация работы над данными или откат к  прежнему состоянию в случае ошибки.  Все транзакции разделяются на два типа, на управляемые и неуправляемые. Начнем с самых простых, неуправляемых они реализуются посредством интерфейса JDBC API.  Вы начинаете транзакцию, обычно с вызова метода  connection. setAutoCommit(false); и заканчиваете методом .commit();  в случае успеха или .rollback();  в случае неудачи.  Все примеры, которые мы реализовали с помощью hibernate в прошлых главах, использовали по умолчанию соединение посредством интерфейса JDBC API. Мы конечно не прямо работали с интерфейсом  JDBC API, а косвенно через hibernate обертку, конкретно с транзакциями работали через  интерфейс  org.hibernate.Transaction.   В программах, которые работают с несколькими системами баз данных  и одновременно меняют данные в двух или более системах нужен другой механизм фиксации изменений.  Для этого используют менеджер транзакций и распределенные транзакции  с двух фазным коммитом,  эти транзакции реализуются посредством Java Transaction API (JTA) . Такие транзакции называются управляемыми, и работа с ними происходит  обычно на сервере приложений, но есть так же Open Source  реализации JTA, такие например как  Atomikos.  JTA используется не только как распределенные транзакции, но и в декларативных управляемых транзакциях контейнера, называемых Container  Manager Transaction (CMT).  У CMT  начало транзакции определяется на основе какого-либо дескриптора,  который обычно применим к методу  DAO класса” или методу EJB.
Рассмотрим простой пример использования  jta транзакции, для этого нам потребуется две вещи,  нужен менеджер транзакций, возьмем Atomikos, и  две библиотеки которые позволят нам создать JNDI сервер и разместить в нем наш connection pool.  И так внесем изменения в наш файл настроек  hibernate.cfg.xml:

<?xml version='1.0' encoding='utf-8'?>
<hibernate-configuration>
    <session-factory>
        <!-- работаем с нашим JNDI  пуллом -->
        <property name="connection.datasource">testDS1</property>
        <property name="dialect">org.hibernate.dialect.DerbyDialect</property>
        <!-- устанавливаем тип сессии для работы  c jta  транзакциями -->
        <property name="current_session_context_class">jta</property>
        <!—дадим подсказку hibernate через какой класс он может взаимодействовать с менеджером транзакций Atomikos -->
        <property name="transaction.manager_lookup_class">
com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup</property>
        <mapping class="org.vit.ch1.Person"/>
    </session-factory>
</hibernate-configuration>

Смотрим на листинг примера, комментарии по ходу кода:

public class Main {
    private static UserTransaction userTransaction;
    private static AtomikosDataSourceBean dataSource1;
    private static AtomikosDataSourceBean dataSource2;

    public static void main(String[] args) throws Exception {
        initDataSources(); /* создаем два datasources  и привязываем их к нашему JNDI серверу.     */
        initTransactionManager(); /*   подготавливаем транзакцию.     */
        SessionFactory sf1 = new Configuration().configure("/hibernate.cfg.xml").buildSessionFactory();
        SessionFactory sf2 = new Configuration().configure("/hibernate1.cfg.xml").buildSessionFactory();
        userTransaction.setTransactionTimeout(600);
        userTransaction.begin();// начинаем jta транзакцию
        try {
            System.out.println("*** работаем с первой DB1 ***");
            doWork(sf1, "user");
            System.out.println("***работаем со второй DB2 ***");
            doWork(sf2, "user");
            userTransaction.commit();// подтверждаем нашу  jta транзакцию
        } catch (Exception ex) {
            ex.printStackTrace();
            userTransaction.rollback();//откат транзакции если что-то пошло не так.
        }
        sf1.close();
        sf2.close();
        dataSource1.close();
        dataSource2.close();
        System.out.println("Both databases updated successfully");
    }

    /**    создаем и выводим данные.     */
    private static void doWork(SessionFactory sf, String username) {
        Session session = sf.getCurrentSession();
        persistPerson(session, username);
        listPersons(session);
        session.close();
    }

    /**  выводим все данные.     */
    private static void listPersons(Session session) {
        List users = session.createQuery("from Person ").list();
        for (int i = 0; i < users.size(); i++) {
            Person person = (Person) users.get(i);
            System.out.println(person.toString());
        }
    }

    /**     создаем person     */
    private static void persistPerson(Session session, String userName) {
        Person u = new Person();
        u.setFirstName(userName);
        session.save(u);
    }

    /**   подготавливаем транзакцию.     */
    private static void initTransactionManager() {
        userTransaction = new com.atomikos.icatch.jta.UserTransactionImp();
    }

    /**     создаем два datasources  и привязываем их к нашему JNDI серверу.     */
    private static void initDataSources() throws NamingException {
        Context ctx = new InitialContext();
        dataSource1 = getDataSource_db1();
        ctx.rebind("testDS1", dataSource1);
        dataSource2 = getDataSource_db2();
        ctx.rebind("testDS2", dataSource2);
        ctx.close();
    }

    /*     создаем первый datasource.    */
    private static AtomikosDataSourceBean getDataSource_db1() {
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        ds.setUniqueResourceName("derby1");
        ds.setXaDataSourceClassName("org.apache.derby.jdbc.EmbeddedXADataSource");
        Properties p = new Properties();
        p.setProperty("databaseName","users1");
        ds.setXaProperties(p);
        ds.setPoolSize(3);
        return ds;
    }

     /*     создаем второй datasource.    */
    private static AtomikosDataSourceBean getDataSource_db2() {
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        ds.setUniqueResourceName("derby2");
        ds.setXaDataSourceClassName("org.apache.derby.jdbc.EmbeddedXADataSource");
        Properties p = new Properties();
        p.setProperty("databaseName","users2");
        ds.setXaProperties(p);
        ds.setPoolSize(3);
        return ds;
    }
}

Из примера видно, что для работы с JTA  транзакциями hibernate  нужно только  получить  через менеджер транзакций,  jta транзакцию и работать с ней как с обычной транзакцией, все остальное за нас сделает hibernate.

14. Hibernate bean validation.



На заре возникновения языка Java,  не было стандартов для валидации данных - проверки на правильность заполнения данных в моделях сущности и разработчики писали свои валидаторы, что было не совсем удобно для сопровождения крупных проектов, страдала переносимость кода. С некоторых пор появился стандарт JSR 303: Bean Validation,  hibernate и некоторые другие вендоры решили реализовать его, мы получили стандарт, которого стоит придерживаться при проверке данных.  Для работы нам нужно подключить библиотеку hibernate-validator все это я делаю через  maven зависимости:

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>4.3.0.Final</version>
        </dependency>

Валидацию в hibernate можно провести двумя способами. Первый способ,  представляет из себя  простую последовательность действий, для этого нужно. Первое описать поля сущностей специальными  аннотациями,  которые будут накладывать на поля определенные ограничения.  Второе запросить валидатор, у фабрики классов.  Третье заполнить сущность данными. В четвертых вызвать у валидатора метод .validate() передав в качестве параметра нашу сущность.   Метод проверит  ограничения, которые были наложены на поля сущностей  аннотациями и если были нарушены какие либо правила, будет возвращен объект Set  типа <ConstraintViolation>,  в котором будут содержаться  “ошибки” валидации.

Стоит упомянуть, что ограничения накладываемые  аннотациями, не имеют ни чего общего с ограничениями баз данных,  т. е. ограничения валидации не переносятся в схему базы данных, если параметр hibernate.validator.apply_to_ddl  выставлен в false, если выставить параметр в true,  то все возможные ограничения перенесутся в схему базы данных:

<property name="hibernate.validator.apply_to_ddl">false</property>


Аннотация
Проверяемые типы данных
Применение
Метаданные переносятся ли в базу данных.
@AssertFalse
Boolean, boolean
Проверяет, имеет ли элемент значение false.
Нет.
@AssertTrue
Boolean, boolean
Проверяет, имеет ли элемент значение true.
Нет.
@DecimalMax(value=, inclusive=)
BigDecimal, BigInteger, CharSequence, byte, short, int, long и их примитивные типы.
Проверяет, имеет ли элемент значение большее чем  value, а если параметр inclusive=true, больше или равно чем value.
Нет.
@DecimalMin(value=, inclusive=)
BigDecimal, BigInteger, CharSequence, byte, short, int, long и их примитивные типы.
Проверяет, имеет ли элемент значение меньше чем  value, а если параметр inclusive=true, меньше или равно чем value.
Нет.
@Digits(integer=, fraction=)
BigDecimal, BigInteger, CharSequence, byte, short, int, long и их примитивные типы.
Проверяет, имеет ли число количество знаков до запятой меньше или равно integer, и кол-во знаков после запятой fraction
Создается  число с точностью до запятой integer и после запятой   fraction.
@Future
java.util.Date, java.util.Calendar;
Проверяет, имеет ли дата значение большее, чем  текущая дата (системная).
Нет.
@Max(value=)
BigDecimal, BigInteger, CharSequence, byte, short, int, long и их примитивные типы.
Проверяет, имеет ли элемент значение меньше или равно чем value.
Создает ограничение на поле таблицы.
@Min(value=)
BigDecimal, BigInteger, CharSequence, byte, short, int, long и их примитивные типы.
Проверяет, имеет ли элемент значение больше или равно чем  value.
Создает ограничение на поле таблицы.
@NotNull
Применим к любым типам данных
Проверяет, имеет ли элемент значение not null.
Создает ограничение на поле таблицы not null.
@Null
Применим к любым типам данных
Проверяет, имеет ли элемент значение  null.
Нет.
@Past
java.util.Date, java.util.Calendar;
Проверяет, имеет ли дата значение меньше чем  текущая дата (системная).
Нет.
@Pattern(regex=, flag=)
CharSequence – Строки.
Проверяет, строку на совпадение с регулярным выражением regex.
Нет.
@Size(min=, max=)
CharSequence, Collection, Map и arrays
Проверяет размерность элемента, т. е.  количество элементов должно быть в пределах минимума min и максимума max включительно.
Создается ограничение,
@Valid
Любой не примитивный тип.
Используется для проверки ассоциированных объектов, коллекций, массивов данных. 
Нет.

Рассмотрим пример, создадим класс Per, с двумя полями пометим их аннотациями:

public class Per implements Serializable{
    @NotNull
    private Long idP;
    @Size(min = 3, message="Длина фамилии должна быть больше трех")
    private String name;
}

Создадим две заведомо ошибочные ситуации в программе,  и отловим наши ошибки:

public class Test_Validation {

    public Test_Validation() {
        ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
        Validator validator = vf.getValidator();
        Per p = new Per();
        p.setName("Vi");
            Set<ConstraintViolation<Per>> constraintViolations = validator.validate(p);
            for (ConstraintViolation<Per> cv : constraintViolations) {
                System.out.format("Error property: [%s], value: [%s], message: [%s]\n",
                        cv.getPropertyPath(), cv.getInvalidValue(), cv.getMessage());
            }
    }

    public static void main(String[] args) {
        new Test_Validation();
    }

}

Отловим две ошибки допущенные нами:

Error property: [idP], value: [null], message: [may not be null]
Error property: [name], value: [Vi], message: [Длина фамилии должна быть больше трех]

И так перейдем ко второму способу,  второй способ, включает валидацию на уровне hibernate,  для этого нужно в файле настроек  hibernate.cfg.xml  добавить 3 слушателя (Listener). Эти слушатели будут перехватывать, и проверять правильность ввода данных, перед вставкой (Insert), перед изменениями (Update) и перед  удалением  (Delete).

        <event type="pre-update">
            <listener class="org.hibernate.cfg.beanvalidation.BeanValidationEventListener"/>
        </event>
        <event type="pre-insert">
            <listener class="org.hibernate.cfg.beanvalidation.BeanValidationEventListener"/>
        </event>
        <event type="pre-delete">
            <listener class="org.hibernate.cfg.beanvalidation.BeanValidationEventListener"/>
        </event>

Применяем аннотации к сущности, в случае нарушения ограничения, какого либо поля будет вызываться исключительная ситуация javax.validation.ValidationException, которое вы всегда можете отловить и обработать.