jueves, 31 de marzo de 2011

Conectar 2 bases de datos con JPA

Leyendo sobre el tema he visto que esto es algo tan sencillo como duplicar los beans entityManager, entityManagerFactory y datasource con la información de conexión a la base de datos nueva. También habría que generar un nuevo persistence-unit en el fichero persistence.xml.

Después en la aplicación, suponiendo que inyectamos el EntityManager a nuestros DAOs mediante la anotación @PersistenceContext, a cada uno de ellos habría que incluirle el atributo unitName especificado en el persistence.xml:

@PersistenceContext(unitName="defaultDB")

En mi caso esto no me ha valido porque en el momento de tener que conectar otra base de datos a mi aplicación, ésta importaba una serie de librerías comunes que utilizamos para más proyectos en los que había objetos DAO con un @PersistenceContext sin especificar ningún unitName. Cambiar la librería común para que especifira el unitName es inviable ya que en cada aplicación en la que se utiliza su valor es diferente.

Al arrancar la aplicación sin hacer nada la excepción que aparece es:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [javax.persistence.EntityManagerFactory] is defined: expected single bean but found 2
at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.findDefaultEntityManagerFactory(PersistenceAnnotationBeanPostProcessor.java:536)

Esta claro. El DAO que tiene el @PersistenceContext no sabe qué entityManager tiene que recibir y no podemos modificarlo para especificárselo.

En la excepción se puede ver que el error ocurre en el método findDefaultEntityManagerFactory de la clase PersistenceAnnotationBeanPostProcessor de Spring. Es el encargado de inyectar el entityManager a los @PersistenceContext.

Me he creado una clase que hereda de PersistenceAnnotationBeanPostProcessor y sobreescribe el método findDefaultEntityManagerFactory de tal manera que voy a devolver el entityManagerFactory que yo quiera. La clase en cuestión quedaría así:



public class PersistenceAnnotationBeanPostProcessor extends org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor{

private EntityManagerFactory defaultEntityManagerFactory;

protected EntityManagerFactory findDefaultEntityManagerFactory(String requestingBeanName)
throws NoSuchBeanDefinitionException{
if (defaultEntityManagerFactory!=null){
return defaultEntityManagerFactory;
}
return super.findDefaultEntityManagerFactory(requestingBeanName);
}

public void setDefaultEntityManagerFactory(
EntityManagerFactory defaultEntityManagerFactory) {
this.defaultEntityManagerFactory = defaultEntityManagerFactory;
}

}



Lo único que tendríamos hacer a continuación es definir un bean en la configuración de Spring con esta clase en vez de la que utiliza Spring por defecto:









Eso sí, tenemos que especificar en el atributo "defaultEntityManagerFactory" qué entityManager queremos que se inyecte cuando nos encontramos con un @PersistenceContext sin especificar un unitName.

La conexión a la segunda base de datos yo sólo la he utilizado para realizar consultas sobre ella, nunca para realizar modificaciones. No sé si habría algún fallo en las transacciones, ya que el JpaTransactionManager está ligado únicamente a un EntityManagerFactory.

10 comentarios:

  1. hola marcos tengo una duda, ¿donde defines el bean? ¿en la configuracion de spring?

    ResponderEliminar
  2. Si, en la configuración de Spring.

    En las aplicaciones normales se define un bean con la clase org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor

    En este caso en vez de usar la clase que nos proporciona Spring, utilizamos la nuestra com.mlopez.utils.spring.PersistenceAnnotationBeanPostProcessor indicando el defaultEntityManagerFactory.

    ResponderEliminar
  3. hola marcos te cuento mi caso:

    Tengo dos aplicaciones (personal y productividad).

    En el fichero config-datasource.xml tengo definidos los datasource y los entityManagerFactory.

    He definido la clase especificada y el bean en el arhivo anterioremente citado pero me da error en uno de los dao's que tengo establecidos "expected single bean but found 2"

    ¿Puedes orientarme un poco porque es la primera vez que trabajo con jpa?

    Por otro lado tambien me he creado dos unidades de persistencia en el persistence.xml

    Y tambien tengo dos archivos orm donde tengo definidos los esquemas propietarios de las bbdd

    La aplicaciones funcionan pero no insertan datos ni tampoco hacen actualizaciones si le pongo a pelo la clausula @PersistenceContext (unitname="")
    en los Dao genericos de donde tiran los demas

    gracias

    ResponderEliminar
  4. Hola David.

    Me da la sensación de que tienes la opción de utilizar la anotación @PersistenceContext especificando un unitName. Si ese es el caso, no te hace falta que hagas todo lo que he puesto en esta entrada.

    En cada Dao que tengas pon un @PersistenceContext(unitName="personal") o (unitName="productividad"), si es así como los has llamado en sus persistence.xml.

    Con eso, entiendo que debería funcionar sin necesidad de hacer nada más.

    Un saludo

    ResponderEliminar
  5. Poniendo el @PersistenceContext en cada dao funciona correctamente pero no inserta ni actualizar en las bbdd establecidas

    alguna idea?
    gracias

    ResponderEliminar
  6. Pon la traza a ver si se me ocurre algo...

    ResponderEliminar
  7. te pongo el config-datasource
    y te pongo el servicio que estoy intentando ejecutar:

    ResponderEliminar
  8. mi correo es marcos PUNTO lopez PUNTO miguel EN gmail PUNTO com

    ResponderEliminar
  9. Buenas,

    tengo problemas igualmente para conectarme a dos BDD, por JPA bajo Hibernate usando Spring.

    La cuestión que me da el mismo error que a ti cuando defino dos EntityManagerFactory pero con la solución que has puesto, los dos EntityManagerFacotry seguirán definidos y por lo tanto dará la misma excepción, no?

    No tienes el código fuente completo?

    Gracias

    ResponderEliminar