1 listopada 2007

Aplikacja Hibernate z adnotacjami i Spring'iem

W wyniku długotrwałej ewolucji, z chęci dotrzymania kroku, ale i potrzeby zachowania zgodności wstecznej dzisiejszy Hibernate stał się technologią o wielu obliczach. Z jednej strony możemy używać Hibernate jako implementacji JPA, z drugiej strony ciągle żywe jest jego tradycyjne API. Używając Hibernate w sposób, nazwijmy to tradycyjny (tj. nie jako JPA) możemy powierzyć konfigurację szkieletowi (ang. framework) Spring lub wykonać ją samodzielnie, z wykorzystaniem plików konfiguracyjnych hibernate.cfg.xml lub hibernate.properties. Generalnie, wszystko sprowadza się do zbudowania obiektu implementującego interfejs org.hibernate.SessionFactory, który jest sercem technologii Hibernate. Jeśli nie używamy do tego celu Spring’a to pierwszym krokiem jest zbudowanie obiektu klasy org.hibernate.cfg.Configuration (albo AnnotationConfiguration). Możemy to zrobić wczytując właśnie plik konfiguracyjny, ale możemy też zbudować go w sposób programowy. Na podstawie tego obiektu tworzymy obiekt fabryki sesji (implementujący org.hibernate.SessionFactory). Jeśli budowę obiektu fabryki sesji powierzymy Spring’owi, to konfigurację wykonujemy definiując odpowiednio ziarno zarządzane (ang. managed bean) kontekstu Spring’a. Dodatkowo, oprócz konfiguracji obiektu fabryki sesji definiujemy odwzorowanie obiektowo-relacyjne dla naszych encji (ang. entity). Odwzorowanie to możemy wykonać zasadniczo na jeden z dwu sposobów; z użyciem plików XML lub z użyciem adnotacji. W dzisiejszym artykule opiszę, jak używać Hibernate konfigurowany przez Spring z odwzorowaniem zdefiniowanym w postaci adnotacji.

Zacznijmy od utworzenia zwykłego (tj. nie www) projektu Java (w Eclipse jest to New > Project > Java Project). Projekt nazwijmy 'Hibernate-Spring'. Teraz pobierzmy biblioteki Hibernate z działu 'Download' portalu http://www.hibernate.org/. Potrzebne będą 'Hibernate Core' (ja użyłem wersji 3.2.4.sp1) oraz 'Hibernate Annotations' (użyłem wersji 3.3.0.GA). Pobrane archiwa trzeba rozpakować a biblioteki dodać do aplikacji. Jeśli używamy któregoś z klonów Eclipse to możemy to zrobić definiując zestaw .jar'ów jako bibliotekę użytkownika. W tym celu wybierzmy opcję Window > Preferences, gałąź Java > Build Path > User Libraries. Następnie kliknijmy guzik New i podajmy nazwę biblioteki, np. 'Hibernate'. Teraz używając guzika 'Add JARs' dodajmy do nowo zdefiniowanej biblioteki 'Hibernate' wymagane pliki .jar (to jakie są wymagane pokazuje niżej). Analogicznie jak z bibliotekami dla Hibernate musimy zrobić z bibliotekami dla Spring'a. Ze strony http://www.springframework.org/download pobieramy więc 'Spring Framework' i dodajemy do projektu (ja mam wersję 2.1 M3). Jeśli używamy Eclipse'a możemy zdefiniować bibliotekę użytkownika o nazwie 'Spring'. Potrzebujemy jeszcze sterownika JDBC odpowiedniego dla naszej bazy danych, ja używam MySQL i zdefiniowałem sobie bibliotekę użytkownika 'MySQL-JDBC'. To, jakie biblioteki są wystarczające dla naszego przykładu widać na poniższej ilustracji. Jest to widok definiowania bibliotek użytkownika:


Aby tak zdefiniowane biblioteki użytkownika dodać do projektu, klikamy na nim prawym guzikiem myszy i wybieramy Build Path > Add Libraries > User Library, klikamy Next, zaznaczamy nasze biblioteki i klikamy Finish.

Jesteśmy gotowi, by zacząć w końcu tworzyć naszą aplikację. Zaimplementujemy klasę DAO (dao.ProductDAOBean i interfejs dao.ProductDAO) dla encji produktu (entity.Product), która będzie miała proste metody do zapisu i wyszukania z użyciem identyfikatora. Oto ich kod źródłowy:

public interface ProductDAO {
public void saveProduct(Product product);

public Product findProduct(Long productId);
}

public class ProductDAOBean extends HibernateDaoSupport implements ProductDAO {

public void saveProduct(Product product) {
getHibernateTemplate().saveOrUpdate(product);
}

public Product findProduct(Long productId) {
return (Product) getHibernateTemplate().get(Product.class, productId);
}
}

Klasa DAO dziedziczy z klasy pomocniczej HibernateDaoSupport dostarczanej przez Spring. Ułatwia ona znacznie programowanie z użyciem Hibernate, zwalnia między innymi z koniczności samodzielnego zarządzania zasobami, tj. obiektami sesji oraz zapewnia spójną strukturę wyjątków. Poniżej kod naszej encji:

@Entity
public class Product {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long productId;

private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Long getProductId() {
return productId;
}

public void setProductId(Long productId) {
this.productId = productId;
}
}

Proszę zauważyć, że mimo iż używamy tradycyjnego API Hibernate’a, odwzorowanie klasy na relację definiujemy z użyciem adnotacji JPA. Naturalnie w bardziej skomplikowanych przypadkach będziemy chcieli sięgać po rozszerzenia specyficzne dla Hibernate. A teraz najważniejsze, czyli plik kontekstu Spring, który stanowi de facto konfigurację Hibernate, oto on:

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

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

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

<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/hibernate" />
<property name="username" value="root" />
<property name="password" value="passwd" />
</bean>

<bean id="hibernateSessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">

<property name="dataSource" ref="hibernateDataSource" />
<property name="schemaUpdate" value="true" />
<property name="hibernateProperties">
<value>
hibernate.dialect = org.hibernate.dialect.MySQLDialect
</value>
</property>
<property name="annotatedClasses">
<list>
<value>entity.Product</value>
</list>
</property>
</bean>

<bean id="productDAO" class="dao.ProductDAOBean">
<property name="sessionFactory" ref="hibernateSessionFactory" />
</bean>

</beans>

Charakterystyczne jest tutaj użycie klasy AnnotationSessionFactoryBean jako obiektu implementującego interfejs org.hibernate.SessionFactory. Właśnie tej klasy należy użyć, aby móc definiować odwzorowanie obiektowo-relacyjne w formie adnotacji. Na koniec przygotujemy jeszcze klasę testu JUnit 4. Naturalnie do projektu należy dodać bibliotekę JUnit w wersji co najmniej 4. Oto owa klasa, przy założeniu, że plik definiujący kontekst Spring’a nazwaliśmy 'dao.xml' i umieściliśmy go w pakiecie 'config':

public class ProductDAOBeanTest {
private ProductDAOBean productDAOBean;

@Before
public void initialize() {
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] {"config/dao.xml"});

productDAOBean = (ProductDAOBean)context.getBean("productDAO");
}

@Test
public void testSaveProduct() {
Product product = new Product();

product.setName("Super produkt");

productDAOBean.saveProduct(product);

product = productDAOBean.findProduct(product.getProductId());

Assert.assertEquals("Super produkt", product.getName());
}
}

To by było na tyle. Aby przekonać się, że rzeczywiście działa możemy jeszcze zerknąć do bazy danych. Dla jasności, poniżej widok na strukturę projektu:

13 komentarzy:

Anonimowy pisze...

Świetne, dokładnie tego potrzebowałem, dzięki.

Anonimowy pisze...

witam,

niestety podczas przeprowadzania testu w junit pojawia sie wyjatek:
org.springframework.orm.hibernate3.HibernateSystemException: Unknown entity: entity.Product; nested exception is org.hibernate.MappingException: Unknown entity: entity.Product

co moze byc tego przyczyna?

Mariusz Lipiński pisze...

Witam,

upewnij się, że masz klasę 'Product' w pakiecie 'entity'.

Anonimowy pisze...

dziekuje za odpowiedz, myslalem ze na tak stare wpisy nie zagladasz. niestety mam product w pakiecie entity. moze cos nie tak mam z sama baza danych(zaczynam z tym dopiero). jak to powinno wygladac w mysql?

dziekuje i pozdrawiam

Mariusz Lipiński pisze...

Nie zaglądam,

dostaje mailem powiadomienia o komentarzach :)

W tak trywialnych przypadkach jak ten prezentowany, nie ma znaczenia jakiej bazy danych używasz. Zwłaszcza z MySQL'em nie powinno być problemów. Spróbuj może jeszcze raz wykonać uważnie kroki opisane w artykule i dopiero jak to zacznie ci działać wprowadzaj swoje modyfikacje.

Anonimowy pisze...

Witam, mam ten sam problem,
org.springframework.orm.hibernate3.HibernateSystemException: Unknown entity: entity.Product; nested exception is org.hibernate.MappingException: Unknown entity: entity.Product
Także mam Product w pakiecie entity.

Michał Bartyzel pisze...

Sprawdź w importach jakiego rodzaju adnotacji @Entity użyłeś - najprawdopodbniej org.hibernate.annotations.Entity a poinieneś javax.presistence.Entity.

Anonimowy pisze...

Czy jest możliwe użycie adnotacji mając aplikację na J2EE 1.4 ale odpalona na vm javie 1.5?

Anonimowy pisze...

Error creating bean with name 'hibernateSessionFactory' defined in class path resource [config/dao.xml]: Invocation of init method failed

jedyna moja zmiana to baza postgresql.

Janek pisze...

No i dziala wszystko mialem hibernate annotations 3.4 a nie 3.3 i dlatego nie działalo kolejnym błedem byla literówka w dialekcie. Niestety nie widac powodu rzucenia, ale po zaladowniu zrodel i przedebagowaniu podejrzalem sobie co nie gra. Prawde mowiląc myslalem ze mam znowu zlego jara ze sterownikiem do Postgresa.

No to teraz zabawy hibernate i springiem ciag dalszy ;).

Adam Barczewski pisze...

Witam!

U mnie jest inny błąd:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'hibernateSessionFactory' defined in class path resource [config/dao.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean]: Constructor threw exception; nested exception is java.lang.NoClassDefFoundError: org/slf4j/LoggerFactory
...



Używam jre6 i robię krok po kroku tak samo jak wyżej. Co robię źle?

Pozdrawiam.

Anonimowy pisze...

wielkie dzięki! przydało się ;-)

Paweł J pisze...

Dobry tutorial.
a jak to w kontekście aplikacji webowej?