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.

Share