Spring + JPA (Hibernate) com DataSources diferentes

5 respostas
M

Boa tarde pessoal.

Estou trabalhando em um projeto onde usamos Struts 2 + Spring 2 com JPA (Hibernate). Estamos usando o SQL Server como banco de dados.

Agora, acabou de surgir um novo requisito no projeto, que algumas entidades não estão no SQL Server, mas num banco de dados a parte, rodando no PostgreSQL.

Um pouco de código para explicar melhor:

applicationContext.xml
<?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:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

	<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
	<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>

	<!-- Daos -->
	<bean id="mapaDao" class="modelo.dao.MapaDaoImpl" />
	<bean id="restricaoDao" class="modelo.dao.RestricaoDaoImpl" />

	<!-- Entity Manager DataSource SQL Server -->
	<bean id="entityManagerFactory"
		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="jpaVendorAdapter">
			<bean
				class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
				<property name="database" value="SQL_SERVER" />
				<property name="showSql" value="true" />
			</bean>
		</property>
	</bean>
    
	<!-- DataSource SQL Server -->
	<bean id="dataSource"
		class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close"
		scope="singleton">
		<property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
		<property name="url" value="jdbc:sqlserver://localhost:1433;databaseName=database;" />
		<property name="username" value="username" />
		<property name="password" value="password" />
	</bean>
    
	<!-- DataSource PostgreSQL -->
	<bean id="dataSourcePgSQL"
		class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close"
		scope="singleton">
		<property name="driverClassName" value="org.postgresql.Driver" />
		<property name="url" value="jdbc:postgresql://localhost/database2" />
		<property name="username" value="username" />
		<property name="password" value="password" />
	</bean>

	<bean id="transactionManager"
		class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>
    
	<tx:annotation-driven transaction-manager="transactionManager" />
</beans>
Meu Dao genérico:
package modelo.dao;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository
@Transactional
public abstract class GenericDaoImpl<T> implements GenericDao<T> {
	
	private final Class<T> objectClass;
	
	private EntityManager em;

	@PersistenceContext
	public void setEntityManager(EntityManager em) {
		this.em = em;
	}
	
	public GenericDaoImpl(final Class<T> objectClass) {
		this.objectClass = objectClass;
	}
	
	// outros métodos.
}
Com essa configuração, tudo funciona perfeitamente. Agora preciso, de alguma forma, manipular entidades do DataSource do PostgreSQL (veja o bean id dataSourcePgSQL do applicationContext.xml acima).

A documentação do Spring não ajudou muito neste caso e, pelo que andei lendo no fórum do Spring, outras pessoas também não encontraram uma solução.

Alguém já teve que fazer alguma coisa parecida e tem alguma dica para ajudar?

Valeu.

[edit]
Tópico complementar no fórum do Spring: [url]http://forum.springframework.org/showthread.php?t=41211[/url]
[/edit]

5 Respostas

U

o problema é o seguinte, tu tem que fazer exatamente como esta na documentação …

outra coisa, com mais de uma persistence unit definida no projeto, quando tu anota alguma coisa com @PersistenceContext tu tem que passar o parametro name para dizer qual persistence unit tu quer utilizar naquele momento.

e em qualquer um dos persistence*.xml tu não pode repetir o nome de uma persistence-unit tem que criar uma persistence unit para cada banco.

U

ahh, mais uma coisa, se tu quer acessar as entidades a partir da mesma persistence unit, ai acho que não vai rolar não …
a unica pouco provavel solução seria o hibernate-shards

M

Seguindo a documentação, minha configuração ficou assim (agora considerando um banco de dados MySQL e outro PostgreSQL):

applicationContext.xml:
<?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:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="
	http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
	http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

	<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
	<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

	<!-- DataSource MySQL -->
	<bean id="dataSourceMySQL"
		class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close"
		scope="singleton">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost/banco1" />
		<property name="username" value="root" />
		<property name="password" value="senha" />
	</bean>

	<!-- DataSource PostgreSQL -->
	<bean id="dataSourcePgSQL"
		class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close"
		scope="singleton">
		<property name="driverClassName" value="org.postgresql.Driver" />
		<property name="url" value="jdbc:postgresql://localhost/banco2" />
		<property name="username" value="postgres" />
		<property name="password" value="senha" />
	</bean>

	<bean id="persistenceUnitManager" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
		<property name="persistenceXmlLocations">
			<list>
				<value>classpath*:META-INF/persistence-mysql.xml</value>
				<value>classpath*:META-INF/persistence-pgsql.xml</value>
			</list>
		</property>
		<property name="dataSources">
			<map>
				<entry key="localDataSource" value-ref="dataSourceMySQL"/>
				<entry key="remoteDataSource" value-ref="dataSourcePgSQL"/>
			</map>
		</property>
		<property name="defaultDataSource" ref="dataSourceMySQL"/>
	</bean>

	<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="persistenceUnitManager" ref="persistenceUnitManager"/>
		<property name="persistenceUnitName" value="puMySQL"/>
	</bean>

	<bean id="transactionManager"
		class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>

	<tx:annotation-driven transaction-manager="transactionManager" />

	<bean id="userDao" class="modelo.dao.UserDao" />
	<bean id="mapaDao" class="modelo.dao.MapaDao" />
</beans>
persistence-mysql.xml
<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="puMySQL">
		<provider>org.hibernate.ejb.HibernatePersistence</provider>
		<non-jta-data-source>localDataSource</non-jta-data-source>
		<properties>
			<property name="default" value="true" />
			<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
		</properties>
	</persistence-unit>
</persistence>
persistence-pgsql.xml
<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="puPgSQL">
		<provider>org.hibernate.ejb.HibernatePersistence</provider>
		<non-jta-data-source>remoteDataSource</non-jta-data-source>
		<properties>
			<property name="default" value="false" />
			<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
		</properties>
	</persistence-unit>
</persistence>
UserDao.java:
@Repository
@Transactional
public abstract class UserDao implements Dao<User> {
	
	private static final Log logger = LogFactory.getLog(GenericDaoImpl.class);
	
	private EntityManager em;

    @PersistenceContext(name = "puMySQL")
    public void setEntityManager(EntityManager em) {
        this.em = em;
    }

	// outros métodos
}
MapaDao.java:
@Repository
@Transactional
public abstract class MapaDao implements Dao<Mapa> {
	
	private static final Log logger = LogFactory.getLog(GenericDaoPgImpl.class);
	
	private EntityManager em;

    @PersistenceContext(name = "puPgSQL")
    public void setEntityManager(EntityManager em) {
        this.em = em;
    }

	// outros métodos
}
PersistenceUnitsTest.java:
public class PersistenceUnitsTest extends AbstractTransactionalSpringContextTests {
	
	protected UserDao userDao;
	
	protected MapaDao mapaDao;
	
	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}

	public void setMapaDao(MapaDao mapaDao) {
		this.mapaDao = mapaDao;
	}

	@Override
	protected String[] getConfigLocations() {
		setDependencyCheck(false);
		setAutowireMode(AUTOWIRE_BY_NAME);
		return new String[] {"applicationContext.xml"};
	}

	@Test
	public void testPersistenceUnits() {
		assertNotNull(this.userDao.findAll());
		assertNotNull(this.mapaDao.findAll());
	}
}
Quando executo o teste acima, ocorre o seguinte erro:
13/07/07 17:10:01 ERROR util.JDBCExceptionReporter:78 logExceptions - Table 'banco1.mapa' doesn't exist
Ou seja, banco1 pertence ao datasource do MySQL, mas tento recuperar as entidades Mapa do MapaDao, que está com a anotação @PersistenceContext(name = "puPgSQL") que pertence ao PostgreSQL.

Aparentemente, ou está sendo usado o datasource errado ou então a persistence unit errada.

Se eu mudar a propriedade persistenceUnitName do bean entityManagerFactory para puPgSQL (que é do PostgreSQL, onde está a tabela Mapa) ai o erro acontece no outro Dao, o UserDao.

Por isso, acredito que o problema seja relativo a persistence unit errada. Funciona apenas o Dao que está com a anotação da persistence unit que está configurado no bean entityManagerFactory.

Se eu simplesmente tentar remover essa propriedade do entityManagerFactory, volto para o erro inicial:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [applicationContext.xml]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: No single default persistence unit defined in {classpath*:META-INF/persistence-mysql.xml, classpath*:META-INF/persistence-pgsql.xml}
Caused by: java.lang.IllegalStateException: No single default persistence unit defined in {classpath*:META-INF/persistence-mysql.xml, classpath*:META-INF/persistence-pgsql.xml}
Desculpe por colocar uma quantidade exagerada de código, mas achei legal para ter uma visão geral do que estou tentando fazer.

Valeu.

C

Amigo, eu consegui fazer o que você está tentando de outra forma.

Eu estava seguindo essa linha quando me indicaram essa documentação: http://static.springsource.org/spring/docs/2.5.x/reference/orm.html#orm-jpa-multiple-pu

Está implementado e funcionando aqui!

Só lembrando que na hora que você for utilizar sua anotação: PersistenceContext coloque o nome de seu datasource (key)

@PersistenceContext (name = "mysqlDb")
private EntityManager em;

Boa sorte, se precisar de ajuda posta aíh!

Ou se sua solução funcionou poste aíh tbm!

abraço

M

Ceara! como você conseguiu? estou com os mesmos erros do amigo acima!

Criado 10 de julho de 2007
Ultima resposta 20 de mar. de 2010
Respostas 5
Participantes 4