mai272011

Gestion des transactions Spring JPA

un commentaire

dao-small.pngComment gérer les transactions quand on utilise Spring et JPA ?

Ce n'est pas bien compliqué.... ou presque.

 

Il nous faut tout d'abord un projet avec des objets persités en base de données, quelques dao et les services d'interrogation ad hoc. Pour cela je vous propose de repartir du projet java de ce billet : "How to : JPA, Hibernate & Co".

Si vous avez déjà lu ce billet, vous pouvez passer directement au chapitre suivant.

 

Presentation du projet

Voici le modèle de données de ce projet :

sample-model.jpg

 

1. Créations des entitées et attributs

Pour l'entité site :

/**
* Our Site entity.
*
* @author <a href="mailto:jguibert@intelligents-ia.com">Jérôme Guibert</a>
* @version 1.0.0
*/
@Entity
@Table(name = "SITE")
@NamedQueries({
@NamedQuery(name = "selectAllSite", query = "SELECT s FROM Site s")
})
public class Site implements Serializable, TimeStamped {
/**
* serialVersionUID
*/
private static final long serialVersionUID = -1823543375371695270L;
/**
* Automatic version number
*/
@Version
protected Long version;
/**
* Identity with an automatic sequence
*/
@Id
@GeneratedValue(generator = "SEQ_SITE_ID")
@SequenceGenerator(name = "SEQ_SITE_ID", sequenceName = "SEQ_SITE_ID")
@Column(name = "SITE_ID")
private Long id;
/**
* Name of site with an index
*/
@Column(name = "NAME", length = 48, nullable = true)
@Index(name = "SITE_NAME_INDEX", columnNames = {
"NAME"
})
private String name;
...
/**
*  all pages
*/
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "SITE_ID")
private List<page> pages = new ArrayList<page>();
/**
* Time stamp data embedded in this entity
*/
@Embedded
private TimeStamp timeStamp = new TimeStamp();
...
}

Pour l'entité Page :

/**
* a page included in our site entity
*
* @author <a href="mailto:jguibert@intelligents-ia.com">Jérôme Guibert</a>
* @version 1.0.0
*/
@Entity
@Table(name = "PAGE")
public class Page implements  Serializable, TimeStamped{
private static final long serialVersionUID = -773904423928687747L;
/**
* Automatic version number
*/
@Version
protected Long version;
/**
* Identity with an automatic sequence
*/
@Id
@GeneratedValue(generator = "SEQ_PAGE_ID")
@SequenceGenerator(name = "SEQ_PAGE_ID", sequenceName = "SEQ_PAGE_ID")
@Column(name = "PAGE_ID")
private Long id;
/**
* Name of  page
*/
@Column(name = "NAME", length = 48, nullable = false)
private String name;
/**
* Time stamp data embedded in this entity
*/
@Embedded
private TimeStamp timeStamp = new TimeStamp();
/**
* An example of many to many association
*/
@ManyToMany(cascade = {
CascadeType.PERSIST, CascadeType.MERGE
}, mappedBy = "pages", targetEntity = org.intelligentsia.utility.jpa.model.Tag.class)
private List<tag> tags = new ArrayList<tag>();
...
}

 

2. Configuration du fichier "my-persistence.xml"

 

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="my-model" transaction-type="RESOURCE_LOCAL">
<description>
Unite du projet
</description>
<class>org.intelligentsia.utility.jpa.model.Page</class>
<class>org.intelligentsia.utility.jpa.model.Site</class>
<class>org.intelligentsia.utility.jpa.model.Tag</class>
<exclude-unlisted-classes />
</persistence-unit>
</persistence>

3. Configuration Spring

 

La localisation du fichier de propriétées

<?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:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
// Here we specify where to find our property file
<context:property-placeholder location="classpath:META-INF/model.properties" />
<context:annotation-config />

Instanciation des objets du package "org.intelligentsia.utility.jpa" (et ses sous-package)

	// Where are your service and your class model ?
<context:component-scan base-package="org.intelligentsia.utility.jpa" />

Déclaration de l'entity manager...

<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<!-- ENTITY MANAGER -->
<bean id="modelEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="my-model" />
<property name="persistenceXmlLocation" value="classpath:META-INF/my-persistence.xml" />
<property name="dataSource" ref="model-dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="${model.hibernate.dialect}" />
<property name="showSql" value="${model.hibernate.showsql}" />
</bean>
</property>
<property name="jpaPropertyMap">
<map>
<entry key="hibernate.dialect" value="${model.hibernate.dialect}" />
<entry key="hibernate.hbm2ddl.auto" value="${model.hibernate.hbm2ddl.auto}" />
<entry key="hibernate.format_sql" value="${model.hibernate.format_sql}" />
<entry key="hibernate.cache.use_second_level_cache" value="${model.hibernate.cache.use_second_level_cache}" />
<entry key="hibernate.cache.provider_class" value="${model.hibernate.cache.provider_class}" />
<entry key="hibernate.cache.use_query_cache" value="${model.hibernate.cache.use_query_cache}" />
<entry key="hibernate.cache.use_second_level_cache" value="${model.hibernate.cache.use_second_level_cache}" />
<entry key="hibernate.cache.use_structured_cache" value="${model.hibernate.cache.use_structured_cache}" />
<entry key="hibernate.ejb.naming_strategy" value="${model.hibernate.namingStrategy}" />
</map>
</property>
</bean>
<!--  TRANSACTION MANAGEMENT  -->
<bean id="modelTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="modelEntityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="modelTransactionManager" />

Instanciation de nos Dao, et definission de la connection à la base de données.

<!-- DAO DECLARATION -->
<bean id="pageDao" class="org.intelligentsia.utility.jpa.GenericJpaDao">
<constructor-arg value="org.intelligentsia.utility.jpa.model.Page" />
</bean>
<bean id="tagDao" class="org.intelligentsia.utility.jpa.GenericJpaDao">
<constructor-arg value="org.intelligentsia.utility.jpa.model.Tag" />
</bean>
<bean id="model-dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:~/test" />
</bean>
</beans>

 

4. Le fichier de propriétés

 

model.hibernate.dialect=org.hibernate.dialect.H2Dialect
model.hibernate.showsql=true
# validate | update | create | create-drop
model.hibernate.hbm2ddl.auto=update
model.hibernate.format_sql=true
model.hibernate.cache.use_second_level_cache=true
model.hibernate.cache.provider_class=org.hibernate.cache.HashtableCacheProvider
model.hibernate.cache.use_query_cache=true
model.hibernate.cache.use_second_level_cache=true
model.hibernate.cache.use_structured_cache=true
#org.hibernate.cfg.DefaultNamingStrategy| org.intelligentsia.utility.jpa.DevTableNamingStrategy
model.hibernate.namingStrategy=org.intelligentsia.utility.jpa.DevTableNamingStrategy

 

 

Gestion des transactions

Transaction Manager

Apres avoir déclarée notre "entity manager factory", nous avons besoin d'un gestionnaire de transaction. Cela ce déclare comme suit :

<!--  TRANSACTION MANAGEMENT  -->
<bean id="modelTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="modelEntityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="modelTransactionManager" />

Nous spécifions à Spring que nous allons configurer les transaction via des annotations.

Déclaration des services transactionels

La classe "GenericJpaDao" mère de nos autres DAO, ne déclare pas d'attribut de transaction. Ceci vous oblige à spécifier où et comment une transactions doit commencer.

 

Ma méthode est la suivante :

  • Ajout du support des transactions (en lecture seulement) au service
  • Ajout du support des transactions en écriture pour les méthodes qui en ont besoin

Concrétement pour impleménter le service :

public interface BlogService {
public void createBlog(final String name);
public Iterable<Site> findAllSite();
}

Cela donne :

@Service("blogService")
@Scope(BeanDefinition.SCOPE_SINGLETON)
@Transactional(readOnly = true, propagation = Propagation.REQUIRED)
public class BlogServiceImpl implements BlogService {
@Autowired
private SiteDao siteDao;
@Autowired
private GenericJpaDao<Page, Long> pageDao;
@Autowired
private GenericJpaDao<Tag, Long> tagDao;
@Override
@Transactional(readOnly = false)
public void createBlog(final String name) {
/**
* Create a new site
*/
Site site = new Site();
site.setName(name);
siteDao.persist(site);
/**
* Add a default page
*/
Page page = new Page();
page.setName("home page");
pageDao.persist(page);
// associate
site.getPages().add(page);
siteDao.persist(site);
}
@Override
public Iterable<Site> findAllSite() {
return siteDao.findAll();
}
}

La méthode "findAllSite" est bien en lecture seule, alors que "createBlog" doit pouvoir supporter l'écriture

Et c'est tout!

 

Bien évidement, pour aller un peu plus loin dans la configuration et la gestion des transactions au sein de Spring, je vous invite à consulter la documentation à ce sujet.

 

 

Par Jérôme Guibert, dans Technique
un commentaire

Envoyer à un ami 

un commentaire

Jérôme Guibert

Jérôme Guibert

Le lundi 5 décembre 2011, 21:05

Un des points clefs sur l'implémentation du service est la déclaration de " Propagation.REQUIRED" qui indique à spring d'utiliser une transaction existante si elle existe OU d'en créer une si il n'y en a pas encore.

Ajouter un commentaire

Identité

S'abonner pour recevoir les commentaires suivants par email

Texte

Les commentaires peuvent être formatés en utilisant une syntaxe wiki simplifiée.

:) :(( :p X-( :x :-/ 0:) B-) &gt;-)