На работе завершена большая и сложная задача и перед началом решения следующей хочется немного отвлечься и поделиться чем-нибудь с вами, уважаемые читатели. Сегодняшний пост будет из серии "для самых маленьких". Давайте поговорим о связке 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;
}
}
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);
}
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);
}
}
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);
}
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);
}
}
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>
<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>
<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>
<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>
<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>
<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>
<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);
}
}
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 к бизнес-логике. Фактически, мы создали "рыбу" приложения и теперь можем неограниченно наращивать его функционал.
Скачать приложение со всеми исходниками и библиотеками можно отсюда (6.3 Мб).
Понравилось сообщение - подпишитесь на блог или читайте меня в twitter





10 комментариев:
А почему для дао классов не используются дженерики?
Например,
интерфейс дао класса:
public interface MyEntityDao extends BaseDao < MyEntity > {}
класс дао:
public class MyEntityHibernateDao extends AbstractHibernateDao< MyEntity > implements MyEntityDao {}
Кстати в приведенном мной случае реализовывать метод save не требуется, все уже написано:)
Ну я про эту фишку знаю, тем более привел ссылку на соответствующую статью. Просто хотелось написать максимально простую демку. Основной упор сделать на управление транзакциями.
По моему скромному мнению учить надо использовать готовые средства. А дао на дженериках это уже стандартный подход в спринге. Я буквально неделю назад объяснял студенту про дао классы на дженериках. Он написал методы save, delete, update сам:)
Да, вы правы - учить надо прежде всего типовым решениям. Чтож, может быть есть смысл поправить статью :)
Просто и понятно. Пишите еще про Spring :)
Спасибо! Обязательно буду писать еще
Статью в закладки!
Сам на данный момент разбираюсь со Spring Security, не рассматриватете возможность написания статьи по этой тематике? :)
Рад, что вам понравилось. Конкретно про Spring Security знаю лишь то, что в девичестве оно называлось Acegi. Про него пишут вот здесь: http://nkoksharov.blogspot.com/search/label/spring%20security
Спасибо, пишите еще.
Статью в закладки!
Очень помогло, благодарю :)
Отправить комментарий