Configure JPA project for Multiple Transacction Manager


Configuracion del proyecto de persistencia A

Configuracion de los ficheros de configuracion

applicationContext_ddbb-A.xml. Personalizamos el nombre del application context pues deberemos llamarlo en otro proyecto para que spring carge esta configuracion.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:p="http://www.springframework.org/schema/p" 
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:context="http://www.springframework.org/schema/context"
	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-3.0.xsd
	http://www.springframework.org/schema/tx
	http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-3.0.xsd">

	<context:component-scan base-package="es.tecnocom.auditoria">
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" />
	</context:component-scan>
	
	<tx:annotation-driven />

<!-- 	<context:property-placeholder location="classpath*:ddbba_database.properties" /> -->
<!--  Por motivos de implementacion es obligatorio que quien me llame, me ponga en el contexto este properties -->

	<bean id="aDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
		p:driverClass="${ddbba.database.driver}" p:jdbcUrl="${ddbba.database.url}" p:user="${ddbba.database.user}"
		p:password="${ddbba.database.password}" p:acquireIncrement="${ddbba.database.c3p0.acquireIncrement}"
		p:idleConnectionTestPeriod="${ddbba.database.c3p0.idleConnectionTestPeriod}"
		p:maxPoolSize="${ddbba.database.c3p0.maxPoolSize}" p:maxStatements="${ddbba.database.c3p0.maxStatements}"
		p:minPoolSize="${ddbba.database.c3p0.minPoolSize}" />

	<bean id="aEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="dataSource" ref="aDataSource" />
		<property name="persistenceUnitName" value="ddbbAPersist" />
		<property name="jpaVendorAdapter">
	      	<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
		        <property name="showSql" value="${ddbba.jpa.show_sql}" />
		        <property name="generateDdl" value="${ddbba.jpa.generateDdl}" />
		        <property name="databasePlatform" value="${ddbba.jpa.dialect}" />
	      	</bean>
  		 </property>
  		 <property name="jpaProperties">
            <props>
            	<prop key="hibernate.hbm2ddl.auto">${ddbba.hibernate.hbm2ddl.auto}</prop>
            	<prop key="hibernate.archive.autodetection">${ddbba.hibernate.archive.autodetection}</prop>
<!--             	<prop key="hibernate.transaction.flush_before_completion">${ddbba.hibernate.transaction.flush_before_completion}</prop> -->
            	<prop key="hibernate.connection.driver_class">${ddbba.hibernate.connection.driver_class}</prop>
            </props>
        </property>
	</bean>

	<bean id="aTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="aEntityManagerFactory" />
		<qualifier value="ddbbA"/>
	</bean>

</beans>

Como se puede observar la etiqueta va sin mas configuración, a pesar que habría que indicar que transacción es la que se esta lanzando. Pero como vamos a lanzar mas de una, no ahora en este proyecto, sino que el proyecto que llame a éste, también llamará a otro que se conectará a otra base de datos para persistir o consultar. Por tanto en el entorno de spring tendremos mas de un Transaction Manager. Eso es lo que vamos a configurar.

El fichero de propiedades ddbba_database.properties, esta personalizado para que las propiedades no sean las mismas para los distintos proyectos que vamos a configurar. La personalizacion en este proyecto viene dada por la etiqueta: {ddbba.}

################################################################
# database properties
################################################################
# MySQL
ddbba.database.driver				= com.mysql.jdbc.Driver
ddbba.database.url				= jdbc:mysql://localhost/ddbba
# ORACLE
#ddbba.database.driver			= oracle.jdbc.driver.OracleDriver
#ddbba.database.url				= jdbc:oracle:thin:@127.0.0.1:1521:Oracle

ddbba.database.user				= root
ddbba.database.password			= root

# for apache commons-dbcp. SIN POOL
#ddbba.database.initialsize								= 5
#ddbba.database.maxactive								= 10

# for c3p0
ddbba.database.c3p0.acquireIncrement				         	= 5
ddbba.database.c3p0.idleConnectionTestPeriod		                = 60
ddbba.database.c3p0.maxPoolSize							= 10
ddbba.database.c3p0.maxStatements						= 5
ddbba.database.c3p0.minPoolSize							= 5

# hibernate JPA properties
#jpa.dialect									= org.hibernate.dialect.Oracle10gDialect
ddbba.jpa.dialect									= org.hibernate.dialect.MySQLDialect
ddbba.jpa.generateDdl								= false
ddbba.jpa.show_sql								= true
ddbba.hibernate.archive.autodetection					= class
ddbba.hibernate.hbm2ddl.auto						= validate
ddbba.hibernate.transaction.flush_before_completion		= true
#ddbba.hibernate.connection.driver_class				= oracle.jdbc.driver.OracleDriver
ddbba.hibernate.connection.driver_class					= com.mysql.jdbc.Driver
ddbba.hibernate.cache.provider_class					= org.hibernate.cache.HashtableCacheProvider

En /src/main/resources/META-INF/persistence.xml. Solo el nombre de la unidad de persistencia, ya que el resto se lo configuramos a traves del applicationContext de spring.

<persistence 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"
	version="1.0">

	<persistence-unit name="ddbbAPersist" />

</persistence>

Configuracion de las clases

La interfaz del DAO.

package mi.paquete..dao;

import java.util.List;

import org.springframework.dao.DataAccessException;

import mi.paquete.domain.Audit;

public interface DdbbADao {
	public List<DdbbA> getAllXXX() throws DataAccessException;
	public DdbbA getXXX(Long aId) throws DataAccessException;
	public Long save(final String description1, final String description2) throws DataAccessException;
	public void delete(Long id) throws DataAccessException;
	public void updateD2(final Long idA, final String description2) throws DataAccessException;
}

La implementacion del DAO. Fijate que la anotacion @DdbbATx es propia de este proyecto que esta a nivel de clase. Esto ya lo he discutido en este articulo.

package es.tecnocom.persistence.auditoria.dao.spi;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Repository;

import es.tecnocom.persistence.auditoria.dao.AuditDao;
import es.tecnocom.persistence.auditoria.dao.MessageDao;
import es.tecnocom.persistence.auditoria.domain.Audit;
import es.tecnocom.persistence.auditoria.domain.Message;

@DdbbATx
@Repository
public class DdbbADaoImpl extends GenericJPAController<DdbbA> implements DdbbADao {

	@Autowired
	private OtraTablaDao otroDao;
	
	public DdbbADaoImpl(){
		setClazz(DdbbA.class);
	}
	
	public List<DdbbA> getAllXXX() throws DataAccessException {
		return findAll();
	}
	
	public Audit getXXX(Long aId) throws DataAccessException {
		return findOne(aId);
	}
	
	public Long save(String description1, String description1) throws DataAccessException {
		otroDao.save(description1);
		
		DdbbA a = new DdbbA();
		a.setUser(user);
		a.setOperation(operation);
		a.setRequest(messageRequest);
		
		save(a);
		return a.getId();
	}

	public void delete(final Long id) throws DataAccessException{
		delete(getXXX(id));
	}
	
	public void updateD2(final Long idA, final String description2) throws DataAccessException {
		DdbbA a = getXXX(idA);
		a.setDescription2(description2);
		
		updateD2(a);
	}
}

Ahora toca mostrar el

package mi.paquete.auditoria.dao.spi;

import java.io.Serializable;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceUnit;

import org.springframework.dao.DataAccessException;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import mi.paquete.dao.ConstDbbADao;

public abstract class GenericJPAController <T extends Serializable>{

	private Class<T> clazz;
	
	@PersistenceContext(unitName =ConstDbbADao.PERSISTENCE_UNIT_NAME)
	protected EntityManager entityManager;
	
	@PersistenceUnit(unitName =ConstDbbADao.ENTITY_MANAGER_FACTORY)
	private EntityManagerFactory entityManagerFactory;

	public EntityManagerFactory getEntityManagerFactory() {
		return entityManagerFactory;
	}

	public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
		this.entityManagerFactory = entityManagerFactory;
	}
	
	protected EntityManager getEntityManager() {
		return entityManager;
	}

	protected void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}

	public void setClazz(final Class<T> clazzToSet){
		this.clazz = clazzToSet;
	}

	@DdbbATx
	public T findOne(final Long id) throws DataAccessException{
		return getEntityManager().find(clazz, id);
	}
	
	@SuppressWarnings("unchecked")
	@DdbbATx
	public List<T> findAll() throws DataAccessException{
		return getEntityManager().createQuery("from " + clazz.getName()).getResultList();
	}

	@Transactional(value=ConstDbbADao.TRANSACTION_MANAGER, propagation = Propagation.REQUIRED, readOnly = false)
	public void save(T entity) throws DataAccessException{
		getEntityManager().persist(entity);
	}

	@DdbbATx
	public void actualize(final T entity) throws DataAccessException{
		getEntityManager().merge(entity);
	}

	@DdbbATx
	public void delete(final T entity) throws DataAccessException{
		getEntityManager().remove(entity);
	}

}

Vamos por metodos:
– Todos los que deben tener la anotacion @Transactional llevan la anotacion personalizada @DdbbATx
– El metodo save(…). Como quiero personalizarlo poniendo el tipo de propagacion lo pongo con la anotacion @Transactional. Es decir mi anotacion personalizada no permite modificaciones.
– Fijate que tanto EntityManager, como EntityManagerFactory las tengo con anotaciones, indicando cuales debe instanciar.

La anotacion Transactional personalizada:

package mi.paquete.dao.spi;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.transaction.annotation.Transactional;

import mi.paquete.dao.ConstDbbADao;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(ConstDbbADao.TRANSACTION_MANAGER)
public @interface DdbbATx{

}

Fijate que ya indico la transaccion que voy a usar: @Transactional(ConstDbbADao.TRANSACTION_MANAGER)

las constantes usadas. Se corresponden con los nombres que estan en persistence.xml, y applicationContext….xml

package mi.paquete.dao;

public class ConstDbbADao{

	public static final String TRANSACTION_MANAGER 		= "aTransactionManager";
	public static final String ENTITY_MANAGER_FACTORY 	= "aEntityManagerFactory";
	public static final String PERSISTENCE_UNIT_NAME 	        = "aPersist";
}

La clase DdbbA que esta en el paquete domain la deduces tu mismo.

Configuracion de los test

Para los test vamos a necesitar indicar al contexto spring donde estan las propiedades. applicationContext_ddbb-A-test.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:context="http://www.springframework.org/schema/context"
	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-3.0.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-3.0.xsd">

	<!-- El test llama, entonces tiene que indicar donde estan las propiedades -->
	<bean id="properties"
		class="org.springframework.beans.factory.config.PropertiesFactoryBean">
		<property name="locations">
			<list>
				<value>classpath*:ddbba_database.properties</value>
			</list>
		</property>
	</bean>
	<context:property-placeholder properties-ref="properties" />
</beans>

Ejemplo de la clase test.

import java.io.IOException;
import java.sql.SQLException;
import java.util.List;

import javax.sql.rowset.serial.SerialException;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;

import mi.paquete.dao.DdbbADao;
import mi.paquete.domain.DdbbA;

@Transactional
@TransactionConfiguration(transactionManager=ConstTest.TRANSACTION_MANAGER, defaultRollback=false)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {ConstTest.APPLICATION_CONTEXT, ConstTest.APPLICATION_CONTEXT_TEST})
public class AuditTest {

	@Autowired
	private DdbbADao dao;

	@Test
	public void getAuditTest() throws SQLException, IOException {
		DdbbA a= dao.getXXX(1L);
		Assert.assertTrue(1 == a.getId());
		Assert.assertEquals("description1", a.getDescription1());
		Assert.assertEquals("description2", a.getDescription2());
	}

	@Test
	public void getAuditsTest(){
		List<DdbbA > list = dao.getAllXXX();
		Assert.assertEquals(5, list.size());
	}
	
	@Test
	@Rollback(true)
	public void saveUpdateDeleteTest() throws DataAccessException {
		List<DdbbA > list = dao.getAllXXX();
		int tot = list.size();
		
		//SAVE
		Long id = dao.save("description11", "description22");
		
		list = dao.getAllXXX();
		Assert.assertTrue((tot + 1) == list.size());
		
		//UPDATE
		dao.updateD2(id, "description2222");
		
		DdbbA a = dao.getXXX(id);
		Assert.assertEquals(a.getDescription2(), "description2222");
		
		//DELETE
		dao.delete(id);
		
		list = dao.getAllXXX();
		Assert.assertEquals(tot, list.size());
	}

la clase de constantes.

public class ConstTest {

	public static final String APPLICATION_CONTEXT = "/applicationContext_ddbb-A.xml";
	public static final String APPLICATION_CONTEXT_TEST = "/applicationContext_ddbb-A-test.xml";
	public static final String TRANSACTION_MANAGER = "aTransactionManager";
}

Configuracion del proyecto de persistencia B

Lo mismo que el A pero siendo B. No se si me explico…

Configuracion del proyecto que llama a persistencia A y B

applicationContext-proyectoAB.xml. Fijate que llamamos a los applicationContext de los proyectos de persistencia que se encuentran dentro de los jar de los proyectos. Cargamos los ficheros de propiedades que se encuentran en /src/main/resources.

<?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:xsd="http://www.w3.org/2001/XMLSchema"
	xmlns:p="http://www.springframework.org/schema/p" 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/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">

	<import resource="classpath*:applicationContext_ddbb-A.xml" />
	<import resource="classpath*:applicationContext_ddbb-B.xml" />

	<!-- Se cargan las propiedades de las bases de datos -->
	<bean id="properties"
		class="org.springframework.beans.factory.config.PropertiesFactoryBean">
		<property name="locations">
			<list>
				<value>classpath:ddbba_database.properties</value>
				<value>classpath:ddbbb_database.properties</value>
			</list>
		</property>
	</bean>
	<context:property-placeholder properties-ref="properties" />    

	<context:annotation-config />
	<context:component-scan base-package="mi.paquete.ab" />

        <!-- ... definiciones varias -->
	
</beans>

Todo ok. ¿Te ha funcionado?.

Ahora bien, y si quiero que el proyecto A sea con transacciones dinamicas, pues va a grabar en la base de datos A1 y A2… entonces mira estos enlaces:
http://stackoverflow.com/questions/4106078/dynamic-jpa-connection
http://blog.springsource.org/2007/01/23/dynamic-datasource-routing/
http://static.springsource.org/spring/docs/3.1.x/javadoc-api/
http://blog.springsource.org/2011/08/15/configuring-spring-and-jta-without-full-java-ee/

Basicamente lo que recomienda mucha gente es cambiar a JTA y

Anuncios
Configure JPA project for Multiple Transacction Manager