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