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