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.

lunes, 28 de marzo de 2011

Define custom mapping for namespace to package

En esta entrada voy a comentar una cosa bastante simple.

Cuando queremos crear un cliente de servicio web desde eclipse llegamos a un punto en el que podemos configurar un mapeo de paquetes, para que no se creen los stubs automáticamente en el paquete que le corresponda segun el namespace que venga indicado en el wsdl, sino en el sitio en el que nosotros queramos.

Esto se consigue marcando la opción "Define custom mapping for namespace to package".



Para no tener que repetir la tarea de indicar el namespace y el paquete al que queremos que se mapee, podemos crearnos un fichero de propiedades y darle al botón "Import".



Tenemos que tener en cuenta que en este fichero no podemos poner la ruta del namespace tal cual, ya que los dos puntos no lo reconoce. Lo tenemos que poner en utf8. Ejemplo:

http\u003A//namespace.marcos.com=com.marcos.ws.stubs
http\u003A//namespace.sub.dominiomarcos.com=com.marcos.ws.stubs

miércoles, 19 de enero de 2011

Propagar atributos en tiles

Una vez que estamos configurando nuestra plantilla de tiles mediante xml se nos puede presentar la necesidad de utilizar atributos dentro de una jsp que se está visualizando mediante un <tiles:insertAttribute />. Voy a poner un ejemplo para que se entienda mejor el caso:

En nuestro fichero tiles-def.xml tenemos el siguiente definition:












El main.jsp tiene el siguiente contenido:


<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%>
....





Hasta aqui nada raro. Pero, ¿Qué pasa si dentro del header queremos utilizar el atributo menu? header.jsp:


<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%>

<img src="<%=request.getContextPath()%>/images/logo.png" />



Si ejecutamos esto nos dara una excepción NoSuchAttributeException, quejándose de que no existe el atributo 'menu'. ¿Qué soluciones tenemos?

La primera opción que se me ocurre y seguramente sea la más elegante es reordenar un poco la definición que tenemos dejándola de la siguiente manera:














Y el main.jsp quedaría:


<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%>
....





Si esta solución no os convence y no podeis separar la definición existente en dos (como me ha ocurrido recientemente) se puede hacer lo siguiente:

Primero, exponer todos los atributos de la definición como atributos de la request dentro del main.jsp:


<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%>
....








Después modificamos el header para que, en vez de utilizar el <tiles:insertAttribute/>, se use el <jsp:include/>:


<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles"%>

<img src="<%=request.getContextPath()%>/images/logo.png" />



No me gusta nada esto de tener que utilizar el requestScope y jsp:includes directamente y en mi opinión tiene que haber otra forma de hacerlo más
elegante, pero estuve investigando un poco y no encontré nada. Como esto funciona no le dí muchas más vueltas jeje.