Poniżej postaram się opisać, w jaki sposób ujarzmić sesje i kontrolować transakcje podczas pracy ze springowym HibernateDaoSupport.
Pozwoję sobie pominąć szczegóły implementacyjne, skupiając się tylko na meritum.
Klasyczny przykład: mamy klasę Produkt i klasę Kategoria. Jeden produkt może znajdować się tylko w jednej kategorii, kategoria posiada zbiór produktów, a zatem relacja "jeden do wiele".
Klasy POJO i mapowania:
<class name="Kategoria" table="kategoria">
<id name="id" unsaved-value="null" column="kategory_id">
<generator class="sequence">
<param name="sequence">kat_seq</param>
</generator>
</id>
<property name="name"/>
<property name="type"/>
<property name="description"/>
<set name="products" lazy="true">
<key column="kategory_id"/>
<one-to-many class="Produkt"/>
</set>
</class>
<class name="Produkt" table="produkt">
<id name="id" unsaved-value="null" column="produkt_id">
<generator class="sequence">
<param name="sequence">prod_seq</param>
</generator>
</id>
<property name="name"/>
<property name="value"/>
<many-to-one name="kategoria" class="Kategoria"/>
</class>
Ogólne DAO dla wszystkich obiektów:
public class GenericDao extends HibernateDaoSupport {
public void store(Object object) {
getHibernateTemplate().saveOrUpdate(object);
};
public void delete(Object object) {
getHibernateTemplate().delete(object);
};
public List<Object> find(String query) {
return getHibernateTemplate().find(query);
}
public Object findUnique(String query) {
List <Object> list = getHibernateTemplate().find(query);
if (list.size() > 0)
return list.get(0);
else
return null;
}
}Zakładam, że wszystkie DAO bądą dziedziczyć z powyższej klasy:
public class KategoriaDao extends GenericDao {
public Kategoria findKategoriaByName(String name) {
return (Kategoria)findUnique("... where name = "+name);
}
}
public class ProduktDao extends GenericDao {
public Produkt findProduktByName(String name) {
return (Produkt)findUnique("... where name = "+name);
}
}Teraz praktyczne działanie:
public void testInsert() {
Kategoria kat = new Kategoria();
kat.setName("kat");
kat.setDescription("desc");
kategoriaDao.store(kat);
}Po wywolaniu metody store() obiekt kat zostal zapisany w bazie danych. Bez tworzenia sesji, zatwierdzania transakcji, uspójniania bazy danych z objektami...
Po prostu - wywołanie jednej metody i kategoria jest w bazie danych. Proste, prawda?
Załóżmy, że w bazie danych istnieje kategoria o nazwie "kat". W bazie istnieją również cztery produkty należące do tej kategorii.
public void testSelect() {
Kategoria kat = kategoriaDao.findKategoriaByName("kat");
System.out.println(kat.getName()); //kat
System.out.println(kat.getDescription()); //desc
}Hmm... jeszcze prostsza sprawa. Zapisuję do bazy jedną metodą (store), odczytuję też jedną (find...), nigdzie w kodzie nie pojawia się magiczne słowo
"session", HibernateDaoSupport jest rewelacyjne!
Ok, ale zanim stwierdzimy, że Spring do spółki z Hibernatem wszystko za nas zrobią, spójrzmy na kojeny przykład:
public void testSelectCollections() {
Kategoria kat = kategoriaDao.findKategoriaByName("kat");
for (Produkt p : kat.getProducts()) {
System.out.println(p.getName());
}
}W trakcie działania metody rzucony zostanie wyjątek LazyInitializationException: no session or session was closed.
Hmm... pierwszy raz pojawia się więc słówko "session". Co się stało?
Kategoria ma zbiór Produktów, który jest inicjalizowany w sposób "leniwy" (
kategorii z bazy danych, NIE zostaną pobrane produkty należące do danej kategorii. Produkty zostaną ściągnięte z bazy danych dopiero wtedy, gdy
będą potrzebne (nastąpi odwołanie do nich). Takie odwołanie następuje w pętli w metodzie testSelectCollections(). Dlaczego więc produkty nie będą
pobrane w tym momencie? Ponieważ sesja z bazą danych wygasła i połączenie jest nieaktywne. Tylko skąd wiadomo gdzie zaczyna i gdzie kończy się
sesja i jak nad tym zapanować? Otóż Spring samodzielnie zarządza sesjami otwierając je tylko i wyłącznie na czas wywołania metody z HibernateDaoSupport. Gwoli ścisłości:
public void testSelectCollections() {
//nie ma sesji
Kategoria kat = kategoriaDao.findKategoriaByName("kat"); //jest sesja
//nie ma sesji
for (Produkt p : kat.getProducts()) {
System.out.println(p.getName());
}
}Na szczęście Spring pozwala zapanować nad sesją. Z pomocą przychodzi Aspect Oriented Programming (AOP), które (za pomocą np. adnotacji) wskazuje Springowi
gdzie zaczyna i gdzie kończy się transakcja, a co za tym idzie i sesja. Za pomocą adnotacji @Transactional oznaczamy metodę, która ma się wykonać w jednej transakcji:
@Transactional
public void testSelectCollections() {
Kategoria kat = kategoriaDao.findKategoriaByName("kat");
for (Produkt p : kat.getProducts()) {
System.out.println(p.getName());
}
}
Sesja trwa przez cały czas wykonywania metody. Jest tylko jeden warunek - adnotacje @Transactional działają tylko w klasach, które są beanami springowymi!!!
Problem rozwiązany!
Żeby wszystko zadziałało trzeba poinformować Springa o chęci skorzystania z adnotacji i z AOP. Poniżej przykładowy plik konfiguracyjny:
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
classpath:org/springframework/beans/factory/xml/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx
classpath:org/springframework/transaction/config/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop
classpath:org/springframework/aop/config/spring-aop-2.0.xsd">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
...
</bean>
<bean id="localSessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mappingResources">
<list>
...
</list>
</property>
<property name="hibernateProperties">
<ref bean="hibernateProperties" />
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="localSessionFactory" />
</bean>
<tx:annotation-driven/>
</beans>
Z całego pliku najważniejsze są:
- <tx:annotation-driven> - dosłownie: "transakcje sterowane adnotacjami"
- poprawne schemaLocation
Trzeba też zwrócić uwagę na to, aby dołączane do projektu jary chciały ze sobą współpracować. Pewnego razu nie mogłem dojść do ładu z AOP,
ponieważ każdy potrzebny jar był "z innej parafii". Gruntowne porządki w pom'ie były niezbędne.
Poniżej fragment zależności z pom'a, które definiują poprawną konfigurację jar'ów (<springframework.version>2.0.6</springframework.version>):
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-dao</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-hibernate3</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jmx</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-remoting</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>1.5.0</version>
</dependency>
</dependencies>
