Conecta una aplicación a multiples bases de datos condicionalmente


Un problema común en aplicaciones complejas, es cuando deseas desarrollar una aplicación que consume multiples bases de datos de acuerdo al país, area, cliente, etc. Una decisión común para resolver este problema es crear diferentes repositorios de código para cada uno, el problema es al momento de mantener el código y actualizaciones al sistema.

En este post explicaremos como podemos contar con un solo código que se pueda conectar a diferentes bases de datos de acuerdo a una configuración.

Paso 1 Creando bases de datos

El primer paso será crear dos bases de datos, una será utilizada para almacenar usuarios de Estados unidos y la otra para México:

create database us_users;
use us_users;
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('emma','superSecret');
INSERT INTO USER (USERNAME,PASSWORD)VALUES('john','smith');
INSERT INTO USER (USERNAME,PASSWORD)VALUES('kc','helloworld');

Lo anterior creará una tabla de usuarios para Estados Unidos junto con algunos registros de ejemplo.

create database mx_users;
use mx_users;
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('Alejandro','superSecreta');
INSERT INTO USER (USERNAME,PASSWORD)VALUES('Pedro','Pablito');
INSERT INTO USER (USERNAME,PASSWORD)VALUES('Pancho','Pantera');

Lo anterior creará una tabla de usuarios para México junto con algunos registros de ejemplo.

Paso 2 Configurado el proyecto

El proyecto se creará con Spring boot con las siguientes dependencias:

https://github.com/raidentrance/spring-multiple-db/blob/master/pom.xml

Con lo anterior tendremos todo lo necesario para trabajar con spring-mvc y con spring-jdbc.

Paso 3 Creando la clase aplicación

Una vez que tenemos configurado spring boot tendremos que crear la clase aplicación, que será quien inicie nuestra api.


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

/**
 * @author raidentrance
 *
 */
@SpringBootApplication
public class SampleApplication extends SpringBootServletInitializer{
	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
		return application.sources(SampleApplication.class);
	}

	public static void main(String[] args) {
		SpringApplication.run(SampleApplication.class, args);
	}

}

Para ejecutar nuestra aplicación solo ejecutaremos la clase anterior y los servicios se expondrán de forma exitosa.

Paso 4 Configurando datasources

El siguiente paso será configurar nuestros datasources, para esto crearemos un paquete llamado com.devs4j.example.config.db con la siguiente clase:


import javax.sql.DataSource;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author raidentrance
 *
 */
@Configuration
public class DataSourceConfig {
	@Bean
	@ConditionalOnProperty(name = "market", havingValue = "mx")
	public DataSource mxDatasource() {
		return DataSourceBuilder.create().driverClassName("com.mysql.jdbc.Driver")
				.url("jdbc:mysql://localhost:3306/mx_users").username("root").password("root").build();

	}

	@Bean
	@ConditionalOnProperty(name = "market", havingValue = "us")
	public DataSource usDatasource() {
		return DataSourceBuilder.create().driverClassName("com.mysql.jdbc.Driver")
				.url("jdbc:mysql://localhost:3306/us_users").username("root").password("root").build();
	}

}

Como se puede ver se crearon 2 datasources cada uno apuntando a una base de datos diferente, como se puede ver se hace uso de @ConditionalOnProperty, esto significa que creará el bean siempre y cuando se cumpla con la condición que define, que en este caso es que el valor de la propiedad market sea igual a us o mx. Otro punto importante es que solo un bean se creará, en caso contrario Spring no sabría que bean inyectar.

Paso 5 Creando el modelo de nuestra aplicación

Una vez que creamos la tabla, el siguiente paso será crear una clase que la represente, para esto crearemos la siguiente clase:


/**
 * @author raidentrance
 *
 */
public class User {
	private Integer id;
	private String username;
	private String password;

	public User() {
	}

	public User(Integer id, String username, String password) {
		super();
		this.id = id;
		this.username = username;
		this.password = password;
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

}

Se creará un objeto de la clase User por cada registro que se desee devolver.

Paso 6 Creando un DAO de spring jdbc

El siguiente paso será hacer uso de Spring jdbc para ejecutar una consulta a nuestra base de datos, para esto crearemos el siguiente DAO (Data access object):


import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;

import com.devs4j.example.model.User;

/**
 * @author raidentrance
 *
 */
@Component
public class UserDao {
	@Autowired
	private JdbcTemplate jdbcTemplate;

	private static final String GET_ALL = "select * from user";

	public List getUsers() {
		return jdbcTemplate.query(GET_ALL, new RowMapper() {

			@Override
			public User mapRow(ResultSet rs, int rowNum) throws SQLException {
				return new User(rs.getInt(1), rs.getString(2), rs.getString(3));
			}
		});
	}
}

Nuestro DAO solo cuenta con un método que devuelve todos los usuarios en la tabla.

Paso 7 Creando un Service de spring

En este ejemplo no es tan necesario, pero lo crearemos para que quede clara la capa de servicios en nuestro servicio:


import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.devs4j.example.dao.UserDao;
import com.devs4j.example.model.User;

/**
 * @author raidentrance
 *
 */
@Service
public class UserService {
	@Autowired
	private UserDao dao;

	public List getUsers() {
		return dao.getUsers();
	}
}

El servicio UserService utilizará al DAO creado previamente para obtener la información de los usuarios a devolver.

Paso 8 Creando el Controller

Una vez hecho lo anterior, el siguiente paso será crear un controller que expondrá la información obtenida vía HTTP a través de un servicio REST:


import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.devs4j.example.model.User;
import com.devs4j.example.service.UserService;

/**
 * @author raidentrance
 *
 */
@RestController
@RequestMapping("/api")
public class UserController {
	@Autowired
	private UserService service;

	@RequestMapping("/users")
	@ResponseBody
	public ResponseEntity getUsers() {
		return new ResponseEntity(service.getUsers(), HttpStatus.OK);
	}
}

Como se puede ver se expondrá un endpoint GET /api/users que devolverá la lista de usuarios.

Paso 9 Creando nuestro archivo application.properties

Por último crearemos un archivo llamado application.properties en el folder /src/main/resources con lo siguiente:

market=mx

Lo anterior definirá el mercado que utilizará la aplicación, en este caso será México.

Paso 10 Probando la aplicación

Una vez que tenemos todo lo anterior, el último paso será probar el api, para esto ejecutaremos nuestra aplicación (recordando que el mercado que definimos fue México) y abriremos la url http://localhost:8080/api/users con la siguiente salida:

[
    {
        "id": 5,
        "username": "Alejandro",
        "password": "superSecreta"
    },
    {
        "id": 6,
        "username": "Pedro",
        "password": "Pablito"
    },
    {
        "id": 7,
        "username": "Pancho",
        "password": "Pantera"
    }
]

Como podemos ver los usuarios que se muestran son los que definimos en la base de datos de México, ahora modificaremos el archivo application.properties con lo siguiente:

market=us

Una vez hecho esto ejecutaremos nuevamente la aplicación e invocaremos de nuevo la url http://localhost:8080/api/users con la siguiente salida:

[
    {
        "id": 1,
        "username": "emma",
        "password": "superSecret"
    },
    {
        "id": 2,
        "username": "john",
        "password": "smith"
    },
    {
        "id": 3,
        "username": "kc",
        "password": "helloworld"
    }
]

Como vemos ahora estamos obteniendo la información de la base de datos de Estados Unidos.

Puedes encontrar el código completo en el siguiente link https://github.com/raidentrance/spring-multiple-db/blob/master/pom.xml.

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

Utiliza multiples bases de datos con Spring boot y Spring JDBC


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

Spring boot + Spring JDBC (English version)


In this post we will explain how to access to a database by using Spring boot + Spring JDBC step by step. For this we will use the project Spring Boot + REST Jersey Part 1 as base.

Step 1 : Configure the required dependencies

The first step will be add the required dependencies to the project, in this case we will use 2 spring-boot-starter-jdbc and mysql-connector-java.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
  • spring-boot-starter-jdbc : Contains all the necessary classes that we will use to enable spring jdbc.
  • mysql-connector-java: Contains the mysql driver to connect via JDBC.

Step 2: Create the tables we will use in the database

For this example we will use MySQL as database engine and we will create a database named jdbc_example with the following table:

CREATE TABLE USER(
USER_ID INTEGER PRIMARY KEY AUTO_INCREMENT,
USERNAME VARCHAR(100) NOT NULL,
PASSWORD VARCHAR(100) NOT NULL
);

And with the following data:

INSERT INTO USER (USERNAME,PASSWORD)VALUES('raidentrance','superSecret');
INSERT INTO USER (USERNAME,PASSWORD)VALUES('john','smith');
INSERT INTO USER (USERNAME,PASSWORD)VALUES('juan','hola123');

Step 3: Create the class to represent a User

Now we will create a POJO to represent the information stored in the User table.

/**
 *
 */
package com.raidentrance.model;

/**
 * @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;
    }

}

Step 4: Add the database configuration to the project

The next step is to include the database configuration to the project, in order to do it we will edit the file application.properties with the following information:

spring.datasource.url=jdbc:mysql://localhost:3306/jdbc_example
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

Step 5 : Create a DAO (Data access object)

Once Spring has the information to connect to the database the next step is create a data access object, it will be used to execute operations over the User table in the database:

/**
 *
 */
package com.raidentrance.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import javax.ws.rs.core.Response.Status;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;

import com.raidentrance.model.ServiceException;
import com.raidentrance.model.User;

/**
 * @author raidentrance
 *
 */
@Component
public class UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public List<User> findAll() {
        List<User> users = jdbcTemplate.query("select * from user", new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet rs, int arg1) throws SQLException {
                User user = new User(rs.getInt("USER_ID"), rs.getString("USERNAME"), rs.getString("PASSWORD"));
                return user;
            }
        });
        return users;
    }

    public User findByUsername(String username) throws ServiceException {
        User user = jdbcTemplate.query(new PreparedStatementCreator() {

            @Override
            public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
                PreparedStatement ps = con.prepareStatement("select * from user where username=?");
                ps.setString(1, username);
                return ps;
            }
        }, new ResultSetExtractor<User>() {
            @Override
            public User extractData(ResultSet rs) throws SQLException, DataAccessException {
                if (rs.next()) {
                    User user = new User(rs.getInt("USER_ID"), rs.getString("USERNAME"), rs.getString("PASSWORD"));
                    return user;
                } else {
                    return null;
                }
            }
        });
        if (user != null) {
            return user;
        } else {
            throw new ServiceException(Status.NOT_FOUND.getStatusCode(), "User not found ", 4004);
        }
    }
}

In the previous code we can see the following points :

  • the @Componen annotation: It means that the object will be living in the spring context and we can access to the instance by using the @Autowired annotation.
  • @Autowired JdbcTemplate jdbcTemplate : The JdbcTemplate will use the configuration that we establish in the application.properties file to connect to our database. We use @Autowired to get a reference to the object that is living in the Spring context.
  • public List findAll() : This method will be used to get all the users in the table. As you can see this method receives the sql query that we want to execute and an object that implements the RowMapper interface, this object will be used to transform from a ResultSet to a Java list.
  • public User findByUsername(String username): The method findByUsername will be used to get a user by username. In this example we are using a PreparedStatement to prevent SQL Injection because this sql query receives a parameter. Other important difference is that this method is receiving a ResultSetExtractor instead a RowMapper and the reason to do it is because this method will return a single object in the response.
  • The last point is that we can see that in case that we cant find a user by its username we will throw a ServiceException with a message, code and http status.

Step 6: Using the DAO in our web service

Once we have a DAO created we have to use it in our endpoint, in future posts we will see that is a good practice to separate this logic in a separated service, but for now we will inject the DAO directly in the endpoint UserResource as follow:

/**
 *
 */
package com.raidentrance.resource;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
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.dao.UserDao;
import com.raidentrance.model.ServiceException;

/**
 * @author raidentrance
 *
 */

@Component
@Path("/users")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {

    @Autowired
    private UserDao userDao;

    private static final Logger log = LoggerFactory.getLogger(UserResource.class);

    @GET
    public Response getUsers() {
        log.info("Getting users");
        return Response.ok(userDao.findAll()).build();
    }

    @GET
    @Path("/user/{username}")
    public Response getUser(@PathParam("username")String username) throws ServiceException {
        log.info("Getting users");
        return Response.ok(userDao.findByUsername(username)).build();
    }

}

As you can see, to inject the DAO we just need to use the annotation @Autowired because the object lives in the spring context.

Step 7: Testing all together

To execute the application we have to execute the main class as in all the Spring boot applications and access to the following url http://localhost:8080/users, we can see the following output:

Captura de pantalla 2017-09-18 a las 2.20.01 p.m.

If we want to get a single user by using the username we will use the url http://localhost:8080/users/user/raidentrance and it will show the following output:

Captura de pantalla 2017-09-18 a las 2.21.40 p.m.

You can find the complete code in the url https://github.com/raidentrance/spring-boot-example/tree/part6-spring-jdbc .

And if you want to learn more about Spring boot and web services we recommend the following books:

Also you can find version in Spanish of this post here.

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Spring boot + Spring JDBC


En este post se explicará como acceder a una base de datos con Spring boot utilizando Spring JDBC paso a paso. Para esto se utilizará como base el proyecto Spring Boot + REST Jersey Parte 1.

Paso 1 :  Configurar las dependencias necesarias

El primer paso es agregar las dependencias necesarias para el proyecto, en este caso se necesitarán 2 spring-boot-starter-jdbc y mysql-connector-java.

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
</dependency>
  • spring-boot-starter-jdbc : Contiene todas las clases necesarias para utilizar spring jdbc.
  • mysql-connector-java: Es el driver de mysql para Java que permitirá conectarnos vía jdbc.

Paso 2: Crear las tablas a utilizar en la base de datos

Para este ejemplo se utilizará como base de datos mysql para esto se creará una base de datos llamada jdbc_example con la siguiente estructura:

CREATE TABLE USER(
USER_ID INTEGER PRIMARY KEY AUTO_INCREMENT,
USERNAME VARCHAR(100) NOT NULL,
PASSWORD VARCHAR(100) NOT NULL
);

Con los siguientes datos:

INSERT INTO USER (USERNAME,PASSWORD)VALUES('raidentrance','superSecret');
INSERT INTO USER (USERNAME,PASSWORD)VALUES('john','smith');
INSERT INTO USER (USERNAME,PASSWORD)VALUES('juan','hola123');

Paso 3: Crear clase para representar el User

Ahora es necesario crear POJOS que representen los registros en la base de datos para esto se creará la clase User.java.

/**
 *
 */
package com.raidentrance.model;

/**
 * @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;
	}

}

Paso 4: Agregar la configuración de la base de datos al proyecto

El siguiente paso es incluir la configuración de la base de datos al proyecto, para esto es necesario editar el archivo application.properties con la siguiente configuración:

spring.datasource.url=jdbc:mysql://localhost:3306/jdbc_example
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

Paso 5: Crear un DAO(Data access object)

Una vez que Spring conoce los datos de conexión, el siguiente paso es crear un DAO el cuál nos servirá para ejecutar todas las acciones sobre la base de datos.

/**
 *
 */
package com.raidentrance.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import javax.ws.rs.core.Response.Status;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;

import com.raidentrance.model.ServiceException;
import com.raidentrance.model.User;

/**
 * @author raidentrance
 *
 */
@Component
public class UserDao {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	public List<User> findAll() {
		List<User> users = jdbcTemplate.query("select * from user", new RowMapper<User>() {
			@Override
			public User mapRow(ResultSet rs, int arg1) throws SQLException {
				User user = new User(rs.getInt("USER_ID"), rs.getString("USERNAME"), rs.getString("PASSWORD"));
				return user;
			}
		});
		return users;
	}

	public User findByUsername(String username) throws ServiceException {
		User user = jdbcTemplate.query(new PreparedStatementCreator() {

			@Override
			public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
				PreparedStatement ps = con.prepareStatement("select * from user where username=?");
				ps.setString(1, username);
				return ps;
			}
		}, new ResultSetExtractor<User>() {
			@Override
			public User extractData(ResultSet rs) throws SQLException, DataAccessException {
				if (rs.next()) {
					User user = new User(rs.getInt("USER_ID"), rs.getString("USERNAME"), rs.getString("PASSWORD"));
					return user;
				} else {
					return null;
				}
			}
		});
		if (user != null) {
			return user;
		} else {
			throw new ServiceException(Status.NOT_FOUND.getStatusCode(), "User not found ", 4004);
		}
	}
}

En el código anterior se pueden observar los siguientes puntos importantes:

  • La anotación @Component : Significa que tendremos un objeto de esa clase viviendo dentro del contexto de Spring y que no será necesario utilizar el operador new para crearlo.
  • @Autowired JdbcTemplate jdbcTemplate : Indica que utilizando las configuraciones que se definieron en el archivo application.properties se creará un template de la clase JdbcTemplate(El cual es parte de spring data) y se inyectará en la referencia jdbcTemplate para que lo utilicemos.

  • public List findAll() : Este método será utilizado para buscar todos los usuarios que se encuentren en la tabla. Como se puede observar este objeto recibe el query que se desea ejecutar y un objeto del tipo RowMapper el cual define como se va a traducir de un ResultSet a un objeto Java de tipo User.

  • public User findByUsername(String username): Del mismo modo el método findByUsername será utilizado para buscar en la base de datos al usuario que tiene el username especificado. En este ejemplo se puede apreciar que se utiliza un PreparedStatement para prevenir SQL injection debido a que esta consulta recibe parámetros. Otro punto diferente es que a diferencia del método findAll() este utiliza un ResultSetExtractor en lugar de un RowMapper debido a que solo se espera un resultado en la repuesta.

  • Por último podemos ver que en caso de que no se encuentre el usuario se arrojará una excepción de tipo ServiceException con el mensaje, código y estatus http.

Paso 6: Utilizar el DAO en nuestro servicio

Una vez que ya se creo el DAO el siguiente paso es utilizarlo en nuestro endpoint, en futuros posts se verá que es mejor separarlo en servicios pero por ahora se inyectará en el endpoint UserResource.

/**
 *
 */
package com.raidentrance.resource;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
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.dao.UserDao;
import com.raidentrance.model.ServiceException;

/**
 * @author raidentrance
 *
 */

@Component
@Path("/users")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {

	@Autowired
	private UserDao userDao;

	private static final Logger log = LoggerFactory.getLogger(UserResource.class);

	@GET
	public Response getUsers() {
		log.info("Getting users");
		return Response.ok(userDao.findAll()).build();
	}

	@GET
	@Path("/user/{username}")
	public Response getUser(@PathParam("username")String username) throws ServiceException {
		log.info("Getting users");
		return Response.ok(userDao.findByUsername(username)).build();
	}

}

Como se puede observar para inyectar el objeto de tipo UserDao lo único que se debe hacer es utilizar la anotación @Autowired ya que el objeto ya vive dentro del contexto de Spring.

Paso 7: Probando todo junto

Para ejecutar la aplicación se debe ejecutar la clase principal del mismo modo que en cualquier aplicación spring boot y se accederá a la siguiente url http://localhost:8080/users la cual mostrará lo siguiente:

Captura de pantalla 2017-09-18 a las 2.20.01 p.m.

Y para obtener solo un usuario se utilizará la url http://localhost:8080/users/user/raidentrance con la siguiente salida:

Captura de pantalla 2017-09-18 a las 2.21.40 p.m.

El código completo lo puedes encontrar en https://github.com/raidentrance/spring-boot-example/tree/part6-spring-jdbc.

Si quieres aprender más sobre web services o Spring boot recomendamos los siguientes libros:

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com