Configuração de múltiplas unidades de persistência com JPA + Spring
Depois de um longo período sem postar (para variar) volto com uma dica que me foi muito útil: configurar o JPA com múltiplas unidades de persistência, ou seja, utilizar mais de um banco de dados em um projeto java.
Recentemente voltei a desenvolver (de verdade) em Java e estou encarando desafios inéditos até então para mim. Estou vendo muitas coisas novas e espero tornar o blog um pouco mais ativo com essas “descobertas”. Talvez 1 ou 2 posts por semestre… =]
Anyway, a motivação desse post é que precisei utilizar dois bancos de dados, um MySQL para leituras e outro PgSQL para escrita. Procurei bastante e achei bastante material, mas nada completo envolvendo configurações dos data sources, das unidades de persistências e das transações num lugar só. Por isso venho tentar unificar isso e ajudar quem precise.
Ah, não irei demonstrar uma aplicação completa, como geralmente faria… Se precisarem de um projeto com as configurações básicas procurem pelo arquétipo do Maven “spring-mvn-jpa-archtype/”.
Bem, para inicio de conversa, nesse meu projeto compatível com Java 6, utilizei Spring 3.0.5, Hibernate 3.6.0 e a implementação do JPA 2.0 pelo Hibernate. Utilizarei annotations, mas não quero exagerar e abolir os XMLs ^^ .
Primeiramente a configuração dos bancos:
database.xml
<bean id="placeHolderDefault" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations" value="classpath:db.properties" /> </bean> <bean id="my_dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${my.driver}" /> <property name="url" value="${my.url}" /> <property name="username" value="${my.username}" /> <property name="password" value="${my.password}" /> </bean> <bean id="pg_dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${pg.driver}" /> <property name="url" value="${pg.url}" /> <property name="username" value="${pg.username}" /> <property name="password" value="${pg.password}" /> </bean> <bean id="my_entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="my_dataSource" /> <property name="persistenceXmlLocation" value="classpath:META-INF/my_persistence.xml" /> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="showSql" value="false" /> <property name="generateDdl" value="false" /> <property name="databasePlatform" value="${my.dialect}" /> </bean> </property> </bean> <bean id="pg_entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="pg_dataSource" /> <property name="persistenceXmlLocation" value="classpath:META-INF/pg_persistence.xml" /> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="showSql" value="false" /> <property name="generateDdl" value="false" /> <property name="databasePlatform" value="${pg.dialect}" /> </bean> </property> </bean> <bean id="my_transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="my_entityManagerFactory" /> <qualifier value="my_transactionManager" /> </bean> <bean id="pg_transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="pg_entityManagerFactory" /> <qualifier value="pg_transactionManager" /> </bean> |
Observe que criei uma série de recursos para cada banco, isolando assim as conexões a eles.
Nesse caso, referenciei 2 locais diferentes para as configurações do Persistence Unit. Por padrão, o arquivo “META-INF/persistence.xml” é carregado. Mas, como quero poder referenciar diferentes Unidades de Persistência por nome, criei dois arquivos diferentes com praticamente o mesmo conteúdo.
META-INF/my_persistence.xml
<?xml version="1.0" encoding="UTF-8"?> <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="my_persistenceUnit" transaction-type="RESOURCE_LOCAL"> <properties> <property name="hibernate.hbm2ddl.auto" value="update" /> <property name="hibernate.show_sql" value="false" /> <property name="hibernate.transaction.flush_before_completion" value="true" /> <property name="hibernate.cache.provider_class" value="org.hibernate.cache.HashtableCacheProvider" /> </properties> </persistence-unit> </persistence> |
META-INF/pg_persistence.xml
<?xml version="1.0" encoding="UTF-8"?> <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="pg_persistenceUnit" transaction-type="RESOURCE_LOCAL"> <properties> <property name="hibernate.hbm2ddl.auto" value="update" /> <property name="hibernate.show_sql" value="false" /> <property name="hibernate.transaction.flush_before_completion" value="true" /> <property name="hibernate.cache.provider_class" value="org.hibernate.cache.HashtableCacheProvider" /> </properties> </persistence-unit> </persistence> |
A única diferença é o nome da Unidade de Persistência. Para o mesmo efeito pode-se manter as definições acima no mesmo arquivo (por exemplo no padrão ”META-INF/persistence.xml”) e setar o nome das unidades nos beans LocalContainerEntityManagerFactoryBean.
Sobre o placeHolderDefault (PropertyPlaceholderConfigurer), serve para injetar valores de propriedades que utilizo depois (como ${my.driver}) obtidos de um simples arquivo de texto.
db.properties
#MySQL Options my.username=myuser my.password=mypass my.url=jdbc:mysql://localhost/mydatabase my.dialect=org.hibernate.dialect.MySQL5Dialect my.driver=com.mysql.jdbc.Driver #PostgreSQL Options pg.username=pguser pg.password=pgpass pg.url=jdbc:postgresql://localhost/pgdatabase pg.dialect=org.hibernate.dialect.PostgreSQLDialect pg.driver=org.postgresql.Driver |
Agora, para podermos utilizar as anotações @Transactional e @PersistenceUnit, temos que incluir:
<tx:annotation-driven /> <context:component-scan base-package="br.com.package" /> |
Observação: é comum a inclusão do PersistenceAnnotationBeanPostProcessor, mas isso é feito automaticamente quando usa-se context:component-scan ou context:annotation-config.
Agora basta definir os EntityManagers para permitir as operações em ambos os bancos e as Transactions para persistir corretamente.
No meu caso, utilizei uma classe abstrata BaseManager com algumas operações básicas. Nas classes filhas especifiquei as unidades de persistência.
BaseManager.java
public abstract class BaseManager { protected abstract EntityManager getEntityManager(); public synchronized BaseModel persist(BaseModel model) { BaseModel returnValue; if(model.getId() == null) { getEntityManager().persist(model); returnValue = model; } else { returnValue = getEntityManager().merge(model); } return returnValue; } public BaseModel findById(Class clazz, Long id) { BaseModel model = (BaseModel)getEntityManager().find(clazz, id); return model; } public synchronized void delete(BaseModel model) { if(model.getId() != null) { getEntityManager().remove(model); } } } |
Observe que o getEntityManager(), essencial para as operações, é abstrato.
UserManager.java
@Service public class UserManager extends BaseManager { @PersistenceContext(unitName="pg_persistenceUnit") protected EntityManager entityManager; @Override protected EntityManager getEntityManager() { return entityManager; } @Override @Transactional(value="pg_transactionManager") public BaseModel persist(BaseModel model) { return super.persist(model); } @Override @Transactional(value="pg_transactionManager") public void delete(BaseModel model) { super.delete(model); } } |
Assim, para a classe UserManager o banco usado será o PostgreSQL.
Note que as operações que precisam de uma transação foram sobrescritas para permitir a utilização de um @Transactional específico.
Done! Com isso feito, basta testar!
E por hoje é só pessoal.

junho 19th, 2012 at %H:%M 03Tue, 19 Jun 2012 15:33:52 +000052.
Jairton, primeiramente gostaria de agradecer a contribuição. Eu estava com um problema parecido e minha solução após testar muitos códigos esse seu exemplo atendeu. Porém estou enfrentando um problema. Tenho dois bancos como seu exemplo, mas ao subir a aplicação o erro de validação é encontrado. O erro segue abaixo.
A dúvida é: como você fez para que a jpa não tentasse validar um model (Entity) no banco errado?
[code]
org.hibernate.HibernateException: Missing table: 'nome da tabela'
[/code]
junho 20th, 2012 at %H:%M 08Wed, 20 Jun 2012 08:57:06 +000006.
Olá Marcus.
Eu não entendi completamente o cenário do seu problema. Ao iniciar a aplicação é disparada uma exceção sobre a inexistência de uma tabela em um banco que realmente não deveria tê-la?
Isso realmente é na inicialização da app ou durante alguma operação no banco?
Qual o valor da propriedade ‘hibernate.hbm2ddl.auto’ você está usando no persistence.xml?
dezembro 21st, 2012 at %H:%M 07Fri, 21 Dec 2012 07:31:23 +000023.
Eu tenho um problema parecido com o seu aqui na empresa onde trabalho.
O nosso sistema tem um banco centralizado para os clientes, e utilizamos dados de outro sistema que conforme a regional do cliente temos que mudar o acesso aos dados do outro sistema. É possível fazer consultas no Banco Centralizado e ao mesmo tempo trazer Dados do Banco B? O Framework faz isso para mim ou eu tenho que fazer uma consulta ao meu banco e só depois fazer uma consulta ao B?
obs.: Existem chaves primarias/estrangeiras que fazem o join com as tabelas.
dezembro 21st, 2012 at %H:%M 07Fri, 21 Dec 2012 07:50:35 +000035.
Olá Claudio.
Seguindo a solução descrita no post, não há como obter os dados de ambos os bancos em uma única consulta.
Cada EntityManager é específico de um PersistenceUnit que, por sua vez, é específico para um banco.
Existem soluções para esse problema, como o UnityJDBC ( http://www.unityjdbc.com/ ). Nunca usei, mas é uma opção.
abril 2nd, 2013 at %H:%M 09Tue, 02 Apr 2013 09:55:30 +000030.
oi!!! Gostei muito to tutorial
estou tendo um problema com o tx:annotation driven
que ao coloca-lo no xml acusa esse erro:
Error occured processing XML ‘org/springframework/transaction/interceptor/TransactionInterceptor’. See Error Log for more details
Voce poderia me ajudar com isso?
não encontrei nada até agora
os jars estão todos corretos!
abril 2nd, 2013 at %H:%M 01Tue, 02 Apr 2013 13:25:07 +000007.
Olá Mario.
De cara parece ser falta ou conflito de jars.
Mesmo você falando que estão corretos, confirme se existe o AOP Alliance (aopalliance-1.0.jar).
O Spring TX depende dele e se você não está usando Maven recomendo que use para evitar problemas com dependências de dependências.
Uma outra tentativa seria usar todos os namespaces do Spring 3.0 no XML.
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
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" >
Se não resolver, poste outras informações como o seu XML e o POM.