Crear micro servicios que obtengan información de una base de datos es muy común, pero muchas veces la información que queremos obtener se encuentra en más de una base de datos, en este post explicaremos como escribir una aplicación REST spring boot que obtenga información de más de una base de datos.
Paso 1 Preparando las bases de datos
El primer paso será crear las dos bases de datos a las que conectaremos nuestra aplicación, en este ejemplo utilizaremos el motor de bases de datos MySQL, a continuación se presentan los scripts a utilizar para cada una:
- Base de datos 1
create database database1; use database1; CREATE TABLE USER( USER_ID INTEGER PRIMARY KEY AUTO_INCREMENT, USERNAME VARCHAR(100) NOT NULL, PASSWORD VARCHAR(100) NOT NULL ); INSERT INTO USER (USERNAME,PASSWORD)VALUES('raidentrance','superSecret'); INSERT INTO USER (USERNAME,PASSWORD)VALUES('john','smith'); INSERT INTO USER (USERNAME,PASSWORD)VALUES('juan','hola123');
- Base de datos 2
create database database2; use database2; CREATE TABLE USER_LEGACY( USER_ID INTEGER PRIMARY KEY AUTO_INCREMENT, USERNAME VARCHAR(100) NOT NULL, PASSWORD VARCHAR(100) NOT NULL ); INSERT INTO USER_LEGACY (USERNAME,PASSWORD)VALUES('rocky','Adrianna'); INSERT INTO USER_LEGACY (USERNAME,PASSWORD)VALUES('ivanDrago','golpesFuertes'); INSERT INTO USER_LEGACY (USERNAME,PASSWORD)VALUES('apolloCreed','theBest');
Con esto tendremos dos bases de datos, las cuales contienen tablas diferentes y datos diferentes.
Paso 2 Crear aplicación Spring boot
En este ejemplo tomaremos como base el proyecto Spring Boot + REST Jersey Parte 1 que explica como configurar un proyecto básico de spring boot y le agregaremos las siguientes dependencias:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
Como se puede ver haremos la conexión utilizando Spring JDBC e incluiremos el driver de mysql para conectarnos a las bases de datos.
Paso 3 Crear clase para representar a los usuarios
Como se puede ver, aunque son dos bases de datos diferentes y tablas diferentes ambas comparten las mismas columnas y tipos de datos, por esto solo crearemos una clase User para representar la tabla USER que se encuentra en la base de datos database1 y la tabla USER_LEGACY que se encuentra en la base de datos database2.
/** * * @author raidentrance * */ public class User { private Integer id; private String user; private String password; public User() { } public User(Integer id, String user, String password) { super(); this.id = id; this.user = user; this.password = password; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUser() { return user; } public void setUser(String user) { this.user = user; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
Como se puede ver la clase cuenta con los atributos id, user y password, esto es suficiente para hacer el mapping en las dos tablas.
Paso 4 Agregar la configuración de las bases de datos
El siguiente paso será agregar al archivo application.properties los datos necesarios para conectarse a ambas bases de datos, para esto agregaremos las siguientes líneas:
#Settings for database conneection to database1 spring.datasource.url=jdbc:mysql://localhost:3306/database1 spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.Driver #Settings for database conneection to database2 legacy.datasource.url=jdbc:mysql://localhost:3306/database2 legacy.datasource.username=root legacy.datasource.password=root legacy.datasource.driver-class-name=com.mysql.jdbc.Driver
Como se puede ver se incluye la información de ambos datasources.
Paso 5 Agregar configuración para ambos datasources
Agregar las líneas al archivo properties no es suficiente, ahora crearemos una clase de configuración que contendrá los dos datasources y jdbcTemplates a utilizar.
import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.jdbc.core.JdbcTemplate; /** * @author raidentrance * */ @Configuration public class DatabaseConfig { @Bean(name = "dsSlave") @ConfigurationProperties(prefix = "legacy.datasource") public DataSource slaveDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "dsMaster") @Primary @ConfigurationProperties(prefix = "spring.datasource") public DataSource masterDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "jdbcSlave") @Autowired public JdbcTemplate slaveJdbcTemplate(@Qualifier("dsSlave") DataSource dsSlave) { return new JdbcTemplate(dsSlave); } @Bean(name = "jdbcMaster") @Autowired public JdbcTemplate masterJdbcTemplate(@Qualifier("dsMaster") DataSource dsMaster) { return new JdbcTemplate(dsMaster); } }
En el código anterior se crean 4 objetos 2 de tipo DataSource y 2 de tipo JdbcTemplate :
- DataSource
- slaveDataSource : En la anotación @ConfigurationProperties(prefix = «legacy.datasource») se define la configuración de la base de datos legacy, que en este caso es la database2, como se puede ver es posible asignarle un nombre al bean que se está generando, en este caso es dsSlave.
- masterDataSource: Este método devolverá el datasource para la base de datos database1 con el nombre dsMaster.
- slaveJdbcTemplate: Para utilizar Spring JDBC es necesario utilizar un objeto de este tipo, como se puede ver el método recibe un objeto de tipo datasource el cuál es inyectado gracias a la anotación @Autowired, en este caso existen 2 beans de este tipo, por esto es necesario incluir la anotación @Qualifier(«dsSlave») para indicarle a Spring cuál de los dos datasources va a inyectar. Por último del mismo modo que en el anterior es posible definir un nombre al bean que se generará en este caso es jdbcSlave.
- masterJdbcTemplate: Este método devolverá el objeto de tipo JdbcTemplate con referencia a el datasource dsMaster con el nombre de jdbcMaster.
Paso 6 Creación de los daos
Como tendremos dos tablas diferentes en dos bases de datos diferentes crearemos dos daos uno apuntando a la tabla user en la base de datos database1 y el otro apuntando a la tabla user_legacy en la base de datos database2.
UserDao.java
import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Component; import com.raidentrance.model.api.User; /** * @author raidentrance * */ @Component public class UserDao { @Autowired @Qualifier("jdbcMaster") private JdbcTemplate jdbcTemplate; public List<User> findAll() { return jdbcTemplate.query("select * from user", new RowMapper<User>() { @Override public User mapRow(ResultSet rs, int arg1) throws SQLException { return new User(rs.getInt("USER_ID"), rs.getString("USERNAME"), rs.getString("PASSWORD")); } }); } }
En este DAO se inyecta la referencia al JDBC template utilizando @Autowired y un @Qualifier(«jdbcMaster») esto es para determinar cuál de los dos jdbctemplates tiene que inyectar en esta referencia, en este caso es el que contiene el datasource que apunta a la base de datos database1.
UserLegacyDao.java
import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Component; import com.raidentrance.model.api.User; /** * @author raidentrance * */ @Component public class UserLegacyDao { @Autowired @Qualifier("jdbcSlave") private JdbcTemplate jdbcTemplate; public List<User> findAll() { return jdbcTemplate.query("select * from user_legacy", new RowMapper<User>() { @Override public User mapRow(ResultSet rs, int arg1) throws SQLException { return new User(rs.getInt("USER_ID"), rs.getString("USERNAME"), rs.getString("PASSWORD")); } }); } }
En este DAO se inyecta la referencia al JDBC template utilizando @Autowired y un @Qualifier(«jdbcSlave») esto es para determinar cuál de los dos jdbctemplates tiene que inyectar en esta referencia, en este caso es el que contiene el datasource que apunta a la base de datos database2.
Paso 7 Creando un servicio común
Una vez que tenemos los dos DAOs en nuestra aplicación, el siguiente paso será crear un servicio común, en este se inyectarán ambos daos:
import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.raidentrance.dao.UserDao; import com.raidentrance.dao.UserLegacyDao; import com.raidentrance.model.api.User; /** * @author raidentrance * */ @Service public class UserService { @Autowired private UserDao userDao; @Autowired private UserLegacyDao userLegacyDao; public List<User> getUsers() { return userDao.findAll(); } public List<User> getLegacyUsers() { return userLegacyDao.findAll(); } }
Como se puede ver en esta clase se inyectan ambos daos, ya no es necesario utilizar algún qualifier porque son beans diferentes.
Paso 8 Exponiendo la información vía REST
El último paso será exponer esta información en servicios REST, para esto crearemos la siguiente clase:
import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.raidentrance.service.UserService; /** * @author raidentrance * */ @Component @Path("/users") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public class UserResource { @Autowired private UserService userService; private static final Logger log = LoggerFactory.getLogger(UserResource.class); @GET public Response getUsers() { log.info("Getting user"); return Response.ok(userService.getUsers()).build(); } @GET @Path("/legacy") public Response getLegacyUsers() { log.info("Getting user"); return Response.ok(userService.getLegacyUsers()).build(); } }
En el código anterior se puede ver que se exponen 2 endpoints uno es /users que devolverá a todos los usuarios que se encuentran en la base de datos database1, y el otro es /users/legacy que devuelve a todos los usuarios que se encuentran en la base de datos database2.
Paso 9 Ejecutando los endpoints
Para ejecutar la aplicación solo ejecutaremos la clase SprinBootSampleApplication que es la que contiene el main de nuestra aplicación e invocaremos los siguientes endpoints para ver sus salidas:
- GET /users
Salida:
[ { "id": 1, "user": "raidentrance", "password": "superSecret" }, { "id": 2, "user": "john", "password": "smith" }, { "id": 3, "user": "juan", "password": "hola123" } ]
- GET /users/legacy
Salida
[ { "id": 1, "user": "rocky", "password": "Adrianna" }, { "id": 2, "user": "ivanDrago", "password": "golpesFuertes" }, { "id": 3, "user": "apolloCreed", "password": "theBest" } ]
Puedes encontrar el código completo en el siguiente enlace https://github.com/raidentrance/spring-boot-example/tree/part11-multipledb.
Si te gusta el contenido y quieres enterarte cuando realicemos un post nuevo síguenos en nuestras redes sociales https://twitter.com/geeks_mx y https://www.facebook.com/geeksJavaMexico/.
Autor: Alejandro Agapito Bautista
Twitter: @raidentrance
Contacto:raidentrance@gmail.com
Tengo mi proyecto avanzado con una base de datos, ahora requiero usar otra base de datos, pero utilizo repositorios en lugar de DAOs, como pudiera adaptarlo para no reescribir el código, gracias
Me gustaMe gusta
excelente ejemplo, si quisiera hacerlo con Spring Data JPA como seria?
Me gustaMe gusta
Hola yo me conecto a dos bases de datos SQL Server y PostgreSQL pero cuando voy a guardar una entidad en PostgreSQL una realacion que interviene otra entidad de la base datosSQL Server me da error. me dice que no reconoce la relacion. Sabes como puedo trabajar eso. Me puedes orientar.
Saludos y gracias de antemano Yanett
Me gustaMe gusta
Hola, alguna idea para realizarlo pero con diferentes gestores de base de datos, como Oracle y SQL Server, teniendo en ambas base de datos la misma tabla
Me gustaMe gusta
Hola, muchas gracias por el aporte, alguién ha intentado realizar este mismo ejemplo pero usando multiples JNDI?
Es decir utilizar las declaraciones de conexión de base de datos, configuradas en el servidor de aplicaciones o contenedor?
Me gustaMe gusta
Hola.
Muchas gracias por el aporte, muy bien explicado.
He intentado replicarlo pero con bases de datos Oracle, pero al arrancar el servidor de spring boot me devuelve el siguiente error:
Caused by: java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.
¿Alguna idea de por qué me lo devuelve?
Gracias de antemano
Me gustaMe gusta
Hola @eakida, un par de preguntas en relación a tu consulta:
1. ¿En el pom.xml has declarado correctamente el drver de ojdbc?
2. ¿Cuál es la configuración exacta de base de datos que has declarado en el application.properties?
En el aplication properties puedes probar algo como esto:
#Settings for database conneection to database1
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:xe
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.username=your.schema
spring.datasource.password=your.pass
#Settings for database conneection to database2
legacy.datasource.url=jdbc:oracle:thin:@localhost:1521:xe
legacy.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
legacy.datasource.username=your.schema
legacy.datasource.password=your.pass
Me gustaMe gusta
Excelente ejemplo!
Funciona muy bien.
Gracias.
Me gustaMe gusta
Muchas gracias por el blog.
Un ejemplo muy claro.
Gracias.
Me gustaMe gusta