GrabDuck

Как подружить Hibernate со Spring и обеспечить управление транзакциями через @ннотации

:


На работе завершена большая и сложная задача и перед началом решения следующей хочется немного отвлечься и поделиться чем-нибудь с вами, уважаемые читатели. Сегодняшний пост будет из серии "для самых маленьких". Давайте поговорим о связке Spring-Hibernate, слое DAO и динамическом управлении транзакциями.

SpringFramework штука довольно сложная и интересная. В частности, в его состав входит package org.springframework.orm.hibernate3, который обеспечивает взаимодействие SpringFramework и Hibernate ORM.

Давайте создадим простое консольное приложение (чтобы не заморачиваться на определение сервлетов и прочего overhead'а), которое что-то пишет в БД.


Соответственно, прежде всего определим сущность, с которой будем работать. Назовем ее непритязательно: MyEntity.

Код сущности будет таким:

package ru.naumen.demo.entity ;

import java.io.Serializable ;

import javax.persistence.Column ;

import javax.persistence.Entity ;

import javax.persistence.GeneratedValue ;

import javax.persistence.Id ;

import org.hibernate.annotations.GenericGenerator ;

@ Entity

public class MyEntity implements Serializable

{

    private static final long serialVersionUID = 382157955767771714L ;

    @Id

    @Column (name = "uuid" )

    @GeneratedValue (generator = "system-uuid" )

    @GenericGenerator (name = "system-uuid", strategy = "uuid" )

    private String id ;

    @Column (name = "name" )

    private String name ;

    public MyEntity ( )

    {

    }

    public MyEntity ( String id, String name )

    {

        this. id = id ;

        this. name = name ;

    }

    public String getId ( )

    {

        return id ;

    }

    public void setId ( String id )

    {

        this. id = id ;

    }

    public String getName ( )

    {

        return name ;

    }

    public void setName ( String name )

    {

        this. name = name ;

    }

}

 

Напомню, что аннотации @Entity, @Id и т.д. относятся к JPA и заменяют собой Hibernate-mapping.

Работать с сущностью мы будем не напрямую, а через DAO. Использование DAO является одним из устоявшихся паттернов работы со SpringFramework. Определив бин, реализующий DAO можно легко и просто инъектировать его в бины, реализующие бизнес-логику приложения и тем самым полностью отделить бизнес-логику от работы с данными. DAO у нас будет реализовано следующим интерфейсом:

package ru.naumen.demo.dao ;

import ru.naumen.demo.entity.MyEntity ;

public interface IEntityDao

{

    public void save (MyEntity entity ) ;

}

 

Для примера мы определим один метод - save, который будет сохранять сущность в БД. Реализация DAO довольно примитивна:

package ru.naumen.demo.dao ;

import org.springframework.orm.hibernate3.support.HibernateDaoSupport ;

import ru.naumen.demo.entity.MyEntity ;

public class EntityDao extends HibernateDaoSupport implements IEntityDao

{

    @ Override

    public void save (MyEntity entity )

    {

        getHibernateTemplate ( ). save (entity ) ;

    }

}

 

Мы наследуемся от класса HibernateDaoSupport, который инкапсулирует работу с Hibernate Session, Hibernate Session Factory и предоставляет нам простое API для взаимодействия с Hibernate. Рекомендую статью, в которой описано, как грамотно организовать слой DAO в своем приложении.

Теперь перейдем к классам, которые будут реализовывать бизнес-логику. В нашем случае бизнес-логика будет проста - мы будем просто брать и сохранять сущность.

Интерфейс IMyEntityService:

package ru.naumen.demo.services ;

import ru.naumen.demo.entity.MyEntity ;

public interface IMyEntityService

{

    public void saveEntity (MyEntity entity ) ;

}

 

Реализация - класс MyEntityService:

package ru.naumen.demo.services ;

import org.springframework.transaction.annotation.Propagation ;

import org.springframework.transaction.annotation.Transactional ;

import ru.naumen.demo.dao.IEntityDao ;

import ru.naumen.demo.entity.MyEntity ;

@Transactional (readOnly = true )

public class MyEntityService implements IMyEntityService

{

    private IEntityDao dao ;

    public void setDao (IEntityDao dao )

    {

        this. dao = dao ;

    }

    @ Override

    @Transactional (readOnly = false, propagation = Propagation. REQUIRED )

    public void saveEntity (MyEntity entity )

    {

        dao. save (entity ) ;

    }

}

 

Данный класс - самое интересное, что есть в нашей программе. Нам необходимо обернуть метод saveEntity в транзакцию. Для этого существует аннотация @Transactional, которой можно аннотировать методы или целый класс. Параметрами данной аннотации задается поведение транзакции. Основными параметрами являются readOnly, который указывает на возможность или невозможность менять состояние БД и propagation, который задает стратегию создания транзакции (не создавать транзакцию, создать новую, присоединиться к существующей и т.д.). Помимо этих параметров можно указывать таймаут, уровень изоляции, классы и типы исключений для которых надо и ненадо делать rollback.

Подробнее про параметры и их значения можно прочесть в официальном руководстве по SpringFramework.

Собственно, теперь надо рассмотреть конфигурацию Spring-контекста, которая будет храниться в файле applicationContext.xml. Файл будем рассматривать по частям, небольшими порциями. Прежде всего создадим "рыбу" файла:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xmlns:aop="http://www.springframework.org/schema/aop"

       xmlns:tx="http://www.springframework.org/schema/tx"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd

                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">

   

</beans>

Обратите внимание! Очень важно правильно прописать все namespaces и пути к схемам, иначе конфиг просто не будет парситься.

Итак, сначала добавим в контекст необходимые конфигурационные файлы, в нашем случае - jdbc.properties, в котором мы будем хранить параметры подключения к СУБД. Для работы с конфигурационными файлами SpringFramework содержит класс org.springframework.beans.factory.config.PropertyPlaceholderConfigurer. Разметка будет вот такой:

<bean id="propertyConfigurer"  class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

    <property name="location" value="jdbc.properties" />

</bean>

Далее следует определить источник данных - мост между СУБД и Hibernate. Я предпочитаю использовать для этого замечательную библиотеку apache.commons.dbcp.

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">

    <property name="driverClassName" value="${jdbc.driverClassName}" />

    <property name="url" value="${jdbc.url}" />

    <property name="username" value="${jdbc.username}" />

    <property name="password" value="${jdbc.password}" />

</bean>

После того, как определили источник данных, пришла пора описать фабрику, которая будет строить Hibernate-сессии. Для этого существует класс org.springframework.orm.hibernate3.LocalSessionFactoryBean. Мы опишем этот бин следующим образом:

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">

    <property name="dataSource" ref="dataSource" />

    <property name="configLocation" value="classpath:/hibernate.cfg.xml" />

    <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />

    <property name="hibernateProperties">

        <props>

            <prop key="hibernate.dialect">${hibernate.dialect} </prop>

        </props>

    </property>

</bean>

Все специфичные настройки Hibernate будем хранить в файле hibernate.cfg.xml, диалект - в файле jdbc.properties. Обратите внимание, что т.к. мы определяем мэппинг аннотациями, то работать с такой конфигурацией должен класс org.hibernate.cfg.AnnotationConfiguration.

С базой данных мы соединились и Hibernate-сессию создали. Пришла пора указать приложению на то, что нужно динамически управлять транзакциями. Что значит "динамически управлять транзакциями?" Это значит, что нам не нужно писать код, который создает/закрывает/откатывает транзакции и размещать его везде, где нужно. Нам достаточно лишь передать классу HibernateTransactionManager некие правила создания/завершения транзакций, а остальное он возьмет на себя.
Понятно, что все это счастье работает через AOP. Правило представляет собой соответствие между методом и типом создаваемой транзакции. Это обозначает, что когда мы входим в метод (перед самым началом выполнения кода метода) - необходимо создать транзакцию, а перед выходом из метода (после выполнения последней инструкции метода) транзакцию закоммитить. Ну и дополнительно можно описать при каких типах исключений должен быть выполнен откат транзакции.

Существует два основных способа определения правил: использование нотации Spring AOP в xml-конфигах Spring и использование аннотаций в Java-коде. Каждый метод имеет свои достоинства и недостатки, но это уже тема другой статьи. Мы же рассмотрим как управлять транзакциями с помощью аннотаций.

Для управления транзакциями в Spring существует пространство имен tx, в котором определена, в частности, директива tx:annotation-driven, включающая механизм управления транзакциями через аннотации. Про параметры этой директивы можно прочитать в секции 9.5.6. документа.

Мы определим менеджер транзакций следующим образом:

<tx:annotation-driven transaction-manager="txManager" />

<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">

    <property name="sessionFactory" ref="sessionFactory" />

</bean>

Ну и остается определить бины для слоя DAO и слоя бизнес-логики:

<bean id="entityDAO" class="ru.naumen.demo.dao.EntityDao">

    <property name="sessionFactory" ref="sessionFactory" />

</bean>

<bean id="entityService" class="ru.naumen.demo.services.MyEntityService">

    <property name="dao" ref="entityDAO" />

</bean>

Напоследок приведу код класса Main, который запускает приложение:

package ru.naumen.demo ;

import org.springframework.context.support.ClassPathXmlApplicationContext ;

import ru.naumen.demo.entity.MyEntity ;

import ru.naumen.demo.services.IMyEntityService ;

public class Main

{

    public static void main ( String [ ] args )

    {

        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext ( "applicationContext.xml" ) ;

        IMyEntityService service = (IMyEntityService ) ctx. getBean ( "entityService" ) ;

        MyEntity entity = new MyEntity ( ) ;

        entity. setName ( "Pavel" ) ;

        service. saveEntity (entity ) ;

    }

}

 

Код не сложный. Сначала мы загружаем контекст приложения, затем из контекста достаем нужный нам бин (в данном случае - "entityService". Ну а дальше используем бин по назначению - сохраняем с его помощью сущность в БД.

Вообще, я считаю, что конфигурация аннотациями проще, чем xml, да и читается лучше. В принципе, можно было бы и взаимодействие бинов сконфигурировать с помощью аннотаций, Spring это позволяет уже довольно давно. На тему конфигурирования Spring-бинов через аннотации можно почитать статьи на habrahabr: эту и эту.

Теперь вы знаете, как подключить к SpringFramework СУБД и Hibernate, опеспечить динамическое управление транзакциями, описать слой DAO и подключить DAO к бизнес-логике. Фактически, мы создали "рыбу" приложения и теперь можем неограниченно наращивать его функционал.

Если вам интересна тема интеграции Spring Framework и Hibernate, то возможно вам будет интересно почему-бы и не заплатить за ваш Spring Framework, а так же серия заметок об эффективном использовании данной ORM - Hibernate: это должен помнить каждый: типы каскадных операций, стандартные алгоритмы генерации идентификаторов и управление сессиями и привязка транзакции к сессии.

UPD 23.02.2011: Исходные коды примера ( Maven-проект, GitHub).

Понравилось сообщение - подпишитесь на блог или читайте меня в twitter