martes, 18 de enero de 2022

Spring Boot: Configuracion de multiples datasources

Hoy vamos a ver como configurar un proyecto de Spring Boot para el uso de varias fuentes de datos. Y hay mucha información en internet sobre este tema, en la mayoría de los casos no se indica un ejemplo completo, tal y como haremos aquí, donde además también indicaremos distintas opciones de configuración. 

Para el ejemplo hemos utilizado las siguientes librerías:

  • spring-boot-starter-parent: 2.5.7
  • spring-boot-starter-web
  • spring-boot-starter-data-jpa
  • mysql-connector-java
Y para simular las dos bases de datos hemos utilizado un docker compose, con tres instancias de MySQL con mismo esquema y tablas pero desplegadas en puertos distintos y con datos distintos.. 

Comenzaremos configurando los dataSources. Para ello tendremos que indicar las propiedades de la conexión en un fichero properties. Estas propiedades estarán unificadas con un mismo sufijo por cada uno de las BBDD que creemos. Por defecto las podemos crear en el fichero fichero  application.properties

## Datasource properties
app.datasource.pre.url=jdbc:mysql://localhost:3306/library?createDatabaseIfNotExist=true&autoReconnect=true&useSSL=false
app.datasource.pre.username=reader
app.datasource.pre.password=password
app.datasource.pre.driverClassName=com.mysql.cj.jdbc.Driver
app.datasource.pre.schema=library

#hikari configuration
app.datasource.pre.poolConf.maximum-pool-size=30

El siguiente paso será configurar dicho datasource de forma programática. La configuración la debemos realizar en una clase con la anotación @Configuration, para que los bean sean tenidos en cuenta durante la ejecución. Y teniendo en cuenta que uno de los dataSources debe ser configurado como principal a través de la anotación @Primary. Lo mismo deberemos hacer con los entity o transaction managers, que configuraremos posteriormente.  

La configuración del dataSource la podemos realizar de distintas formas. En el siguiente ejemplo podemos ver la opción más completa. Por un lado configuraremos las propiedades del dataSource y por otro lado configuraremos las del pool de conexiones. Todo lo haremos con ayuda de la anotación @ConfigurationProperties que nos permitirá indicar un sufijo de las propiedades que queremos usar. 

@Bean
@Primary
@ConfigurationProperties("app.datasource.pre.poolConf")
public DataSource preDataSource() {
    return preDataSourceProperties().initializeDataSourceBuilder()
        .type(HikariDataSource.class).build();
}
@Bean
@Primary
@ConfigurationProperties("app.datasource.pre")
public DataSourceProperties preDataSourceProperties() {
    return new DataSourceProperties();
}

El pool de conexiones por defecto es HikariCP. Y el conjunto de propiedades que permite configurar las puedes ver en la documentación oficial, aquí

Podemos realizar una configuración aún más básica, sin indicar la configuración del pool y creando el DataSource en un único método. Pero en dicho caso la propiedad url deberíamos renombrarla a jdbcUrl, que es la propiedad que conoce HikariCP. En el caso anterior, el bean preDataSourceProperties se encarga de dicha conversión. 

@Bean
@ConfigurationProperties("app.datasource.pro")
public DataSource proDataSource() throws Exception {
	return DataSourceBuilder.create().build();
}

También podemos hacer una configuración igual a la anterior a nivel de código Java pero cambiando el origen del fichero de propiedades. Con la anotación @PropertySource podemos indicar otro origen del fichero de configuración. Esto nos ayudaría a implementar el twelve-factor app al indicar ficheros que se encuentren en el entorno y no en el classpath. 

Ahora configuraremos el entityManager necesario para el funcionamiento de nuestros objetos DAOs/Repository. Y el transactionManager que nos posibilitará gestionar las transacciones de forma automática. Esto lo podremos realizar en tres sencillos pasos:
  • Crear el bean del entityManager a partir de un Datasource
  • Crear el bean del transactionManager a partir del EntityManager
  • Incluir la anotación @EnableJpaRepositories a nivel de clase. 
En la creación del entityManager deberemos al menos configurar el dataSource asociado. Aparte de esto podremos dar más o menos detalle a la configuración del mismo. De forma opcional, podremos indicar a que entidades se debe aplicar el objeto a crear y que propiedades tendrá la implementación de JPA que utilicemos, en este caso Hibernate. 

@Bean
@Primary
public LocalContainerEntityManagerFactoryBean preEntityManager(
		final @Qualifier("preDataSource") DataSource preDataSource) {

	LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
	em.setDataSource(preDataSource);
	em.setPackagesToScan(new String[] { "es.home.example.knowledge.entity" });

	HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
	em.setJpaVendorAdapter(vendorAdapter);
	HashMap<String, Object> properties = new HashMap<>();
	properties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
	properties.put("hibernate.dialect", env.getProperty("hibernate.dialect"));
	em.setJpaPropertyMap(properties);

	return em;
}

La configuración del paquete de las entidades a tener en cuenta por el objeto entityManager lo podemos omitir si indicamos en cada una de las clases a que esquema esta asociado a través de la anotación @Table. Y las propiedades especificas de Hibernate las podemos recoger con un objeto Environment, de cualquiera de los ficheros de propiedades que hayamos configurado.

Para el otro dataSource, si queremos realizar una configuración más básica, lo podríamos realizar de la siguiente forma. 

@Bean
public LocalContainerEntityManagerFactoryBean proEntityManager(
		final EntityManagerFactoryBuilder builder,
		final @Qualifier("proDataSource") DataSource proDataSource) {
	return builder.dataSource(proDataSource)
		.packages("es.home.example.knowledge.entity").build();
}

La configuración del transactionManager será más sencilla. Bastará con la siguiente declaración:

@Bean
@Primary
public PlatformTransactionManager preTransactionManager(
	final @Qualifier("preEntityManager") LocalContainerEntityManagerFactoryBean preEntityManager) {
    return new JpaTransactionManager(preEntityManager.getObject());
}

Y por último nos quedaría la anotación a nivel de clase @EnableJpaRepositories. Con esta anotación podremos indicar un conjunto de objetos DAOs que sean configurados con el entityManager y transactionManager que deseemos. Realizando la configuración de forma automática. 

@EnableJpaRepositories(basePackages = "es.home.example.knowledge.pre.repository", 
    entityManagerFactoryRef = "preEntityManager", 
    transactionManagerRef = "preTransactionManager")

Con esto ya habriamos acabado la configuración y podriamos obtener la información de distintas BBDD sin problemas. Por un lado creariamos las interfaces Repository para cada una de las entidades de cada uno de los dataSource.

package es.home.example.knowledge.pre.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import es.home.example.knowledge.entity.Book;

public interface PreBookDao extends JpaRepository<Book, Integer> {}

Y por otro lado las usaríamos en nuestro controlador:

@RestController
@RequestMapping(path = "/book")
public class BookRestController {
	@Autowired
	private PreBookDao preRepository;
	@Autowired
	private ProBookDao proRepository;
	@Autowired
	private DevBookDao devRepository;

	@GetMapping
	public List<Book> findAll() {
		List<Book> books = devRepository.findAll();
		books.addAll(preRepository.findAll());
books.addAll(proRepository.findAll());
return books;
} }

Si quieres ver todo el código lo tienes, como siempre, aquí

No hay comentarios:

Publicar un comentario