Aprende a crear un connection pool utilizando Hikari CP en Español


connectionPool

Hikari CP es un Framework que permite crear pools de conexiones desarrollado en 2012, su particularidad es que si comparamos su performance con sus competidores c3p0, dbcp2, tomcat y vibur veremos los siguientes resultados:

HikariCP-bench-2.6.0 (1)

Esto nos da una idea de porque debemos aprender a usarlo y su impacto en nuestras aplicaciones.

Paso 1 Crear base de datos

Antes de empezar a utilizar hikaricp crearemos una pequeña base de datos de prueba, en este ejemplo utilizaremos Mysql

Paso 2 Configuración de hikaricp

El primer paso será configurarlo, para esto agregaremos las siguiente dependencias :

Captura de pantalla 2018-09-04 a las 10.38.40 a.m..png

Puedes copiar la dependencia de aquí.

Paso 3 Crear un datasource

Para crear un datasource debemos hacer lo siguiente:


import java.sql.Connection;
import java.sql.SQLException;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

/**
 * @author maagapi
 *
 */
public class Devs4jDatasource {

	private static final HikariConfig config;
	private static final HikariDataSource ds;

	static {
		config = new HikariConfig();
		config.setJdbcUrl("jdbc:mysql://localhost:3306/information_schema");
		config.setUsername("root");
		config.setPassword("root");
		config.addDataSourceProperty("cachePrepStmts", "true");
		config.addDataSourceProperty("prepStmtCacheSize", "250");
		config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
		ds = new HikariDataSource(config);
	}

	private Devs4jDatasource() {
	}

	public static final Connection getConnection() throws SQLException {
		return ds.getConnection();
	}
}

Analicemos los siguientes puntos:

  • Solo crearemos una instancia del tipo HikariDataSource ya que esta nos ayudará a generar muchas conexiones.
  • Inicializamos nuestras configuraciones en un bloque estático ya que necesitaremos un objeto de tipo HikariConfig para crear nuestro HikariDataSource.
  • El método ds.getConnection() devolverá una conexión a nuestra base de datos.
  • En nuestro ejemplo nos conectaremos a la base de datos information_schema que ya viene por default con nuestra instalación de MySQL.

Es necesario que entendamos que podemos definir las siguientes configuraciones en nuestro HikariConfig:

  • autoCommit
  • connectionTimeout
  • idleTimeout
  • maxLifetime
  • connectionTestQuery
  • connectionInitSql
  • validationTimeout
  • maximumPoolSize
  • poolName
  • allowPoolSuspension
  • readOnly
  • transactionIsolation
  • leakDetectionThreshold

Es posible crear un objeto de tipo HikariConfig leyendo las properties de un archivo externo como se muestra a continuación:

private static HikariConfig config = new HikariConfig(
    "datasource.properties" );

El archivo datasource.properties contendrá todas las propiedades necesarias para inicializar nuestro pool.

Paso 4: Utilizando nuestro Datasource

El siguiente paso será utilizar nuestro DataSource, veamos el siguiente código:


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

/**
 * @author raidentrance
 *
 */
public class Devs4jApplication {
	public static void main(String[] args) throws SQLException {
		Connection connection = Devs4jDatasource.getConnection();

		PreparedStatement statement = connection.prepareStatement("SELECT TABLE_NAME FROM TABLES");
		ResultSet resultSet = statement.executeQuery();
		while(resultSet.next()){
			System.out.println(resultSet.getString("TABLE_NAME"));
		}
	}
}

En el código anterior podemos ver como obtener una conexión del pool de conexiones y utilizarla para obtener el nombre de las tablas que tenemos e imprimirlas.

Puedes encontrar el código completo en el siguiente repositorio https://github.com/raidentrance/hikaricp-example.

Síguenos en nuestras redes sociales para enterarte sobre nuevo contenido https://www.facebook.com/devs4j/ y https://twitter.com/devs4j.

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

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

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

 

Spring Boot + REST Jersey (Agregando Spring data) Parte 2


En este tutorial se explica de forma simple la configuración y uso de Spring Data y Spring boot, para esto se agregará a un proyecto existente, para ver detalles del mismo ver el post Spring Boot + REST Jersey Parte 1.

1. Configuración, Spring boot generó starter dependencies, estas dependencias contienen todos los recursos necesarios para utilizar el módulo de spring deseado de una forma simple y manejable de una forma más simple, las versiones de estas dependencias no son requeridas ya que se heredan del proyecto padre de spring boot. Para configurar Spring data se requiere agregar la siguiente dependencia:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
</dependency>

2. El siguiente paso es crear un archivo llamado application.properties en el cuál se definirán los datos de la conexión a la base de datos, para este ejemplo se mostrará como crear un datasource a MySQL.

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

El archivo de configuración application.properties puede ser utilizado para configurar diferentes áreas en spring boot y puede ser tanto un archivo .properties como un archivo .yml

3. Registrando los Repositorios de Spring data, en este punto ya se cuenta con las bibliotecas necesarias para utilizar Spring data y se cuenta con la configuración del datasource a utilizar, lo siguiente es registrar la configuración de Spring data en Spring boot.

/**
 *
 */
package com.raidentrance.config;

import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * @author raidentrance
 *
 */
@EnableTransactionManagement
@EnableJpaRepositories("com.raidentrance.repositories")
public class SpringDataConfig {

}

com.raidentrance.repositories es el paquete de la aplicación que contendrá los repositorios de Spring data a utilizar en el proyecto.

4. Creando tabla a utilizar como ejemplo en la aplicación, este script se encontrará en el archivo schema.sql de la aplicación.

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

5. Insertando algunos datos de ejemplo, este script se encontrará en el archivo init.sql de la aplicación.

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

6. Entidad de ejemplo, una vez configurada y creada la base de datos es necesario crear una Entidad JPA para representar el modelo relacional en el modelo de dominio creando la entidad User.java.

/**
 *
 */
package com.raidentrance.entities;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * @author raidentrance
 *
 */
@Entity
@Table(name = "USER")
public class User implements Serializable {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "USER_ID")
	private Integer idUser;

	@Column(name = "USERNAME")
	private String username;

	@Column(name = "PASSWORD")
	private String password;

	private static final long serialVersionUID = -5290198995172316155L;

	public Integer getIdUser() {
		return idUser;
	}

	public void setIdUser(Integer idUser) {
		this.idUser = idUser;
	}

	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;
	}

	@Override
	public String toString() {
		return "User [idUser=" + idUser + ", username=" + username + "]";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((idUser == null) ? 0 : idUser.hashCode());
		result = prime * result + ((username == null) ? 0 : username.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		User other = (User) obj;
		if (idUser == null) {
			if (other.idUser != null)
				return false;
		} else if (!idUser.equals(other.idUser))
			return false;
		if (username == null) {
			if (other.username != null)
				return false;
		} else if (!username.equals(other.username))
			return false;
		return true;
	}

}

7. Crear repositorio de Spring data, el siguiente paso es crear un repositorio de Spring data, este repositorio será el utilizado para realizar operaciones en la base de datos.

/**
 *
 */
package com.raidentrance.repositories;

import org.springframework.data.repository.CrudRepository;

import com.raidentrance.entities.User;

/**
 * @author raidentrance
 *
 */
public interface UserRepository extends CrudRepository<User, Integer> {
}

UserRepoitory es una interfaz, la pregunta más común sería decir ¿Debo crear una clase que implemente la interfaz UserRepository ? La respuesta es NO, lo único que se debe hacer es definir la interfaz a utilizar y Spring Data será el responsable de crear la implementación de la interfaz que definimos.

8. Utilizando el Repository creado, la última parte es utilizar el repositorio creado, para esto se integrará en el servicio rest creado en la parte 1 del tutorial Spring Boot + REST Jersey Parte 1.

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

import java.util.ArrayList;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.raidentrance.entities.User;
import com.raidentrance.repositories.UserRepository;
import jersey.repackaged.com.google.common.collect.Lists;

/**
 * @author raidentrance
 *
 */

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

	@Autowired
	private UserRepository userRepository;

	@GET
	public Response getUsers() {
		ArrayList<User> users = Lists.newArrayList(userRepository.findAll());
		return Response.ok(users).build();
	}

}

Para inyectar el repositorio creado se utiliza la anotación @Autowired , para obtener todos los usuarios se utilizará el método findAll() éste método no esta definido en la interfaz que se creó, sino que se hereda de CrudRepository.

9. Probando todo, para probar todo lo único que se debe hacer es ejecutar la clase  SprinBootSampleApplication , una vez ejecutado se debe probar el endpoint http://localhost:8080/users .

Salida :

[
{
idUser: 1,
username: "raidentrance",
password: "superSecret"
},
{
idUser: 2,
username: "john",
password: "smith"
},
{
idUser: 3,
username: "juan",
password: "hola123"
}
]

10. Siguientes pasos, los siguientes pasos en este ejemplo son los siguientes:

  • Devolver un Dto en lugar de una lista de entidades en el servicio REST.
  • Agregar manejo de errores para devolver diferentes estatus HTTP en caso de errores.
  • Agregar Soporte para HATEOAS.
  • Agregar Seguridad al servicio Web.
  • Agregar Logs
  • Agregar perfiles

Puedes encontrar el código completo del ejemplo en el siguiente enlace:https://github.com/raidentrance/spring-boot-example/tree/part2-adding-springdata

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com