Spring Boot + REST Jersey (Agregando Spring Hateoas y Dto) Parte 3


En los ejemplos anteriores se explicó la configuración básica de Jersey utilizando Spring boot y la configuración de Spring data en el mismo proyecto. En este tutorial se explicará de forma simple tomando los dos proyectos anteriores como base el uso y funcionamiento de HATEOAS en REST utilizando Spring boot + Jersey.

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 HATEOAS se requiere agregar la siguiente dependencia:

<dependency>
    <groupId>org.springframework.hateoas</groupId>
    <artifactId>spring-hateoas</artifactId>
</dependency>

2. Agregando una entidad extra, en el ejemplo anterior se explicó de forma simple como utilizar Spring data para obtener información y Spring Jersey para exponerla en web services REST. En dicho ejemplo se utilizó una entidad llamada User para realizar las pruebas, en este ejemplo se modificará la entidad User y se agregará una nueva llamada Role.

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

import java.io.Serializable;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

/**
 * @author raidentrance
 *
 */
@Entity
@Table(name = "ROLE")
public class Role implements Serializable {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "ID_ROLE")
	private Integer idRole;

	@Basic(optional = false)
	@NotNull
	@Size(min = 1, max = 45)
	@Column(name = "NAME")
	private String name;

	@Size(max = 100)
	@Column(name = "DESCRIPTION")
	private String description;

	private static final long serialVersionUID = 3428234636660051311L;

	........
}

Ahora se modifica la entidad usuario.

/**
 *
 */
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.JoinColumn;
import javax.persistence.ManyToOne;
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;

	@JoinColumn(name = "ROLE_ID", referencedColumnName = "ID_ROLE")
	@ManyToOne(optional = false)
	private Role role;
.....
}

3. Actualización a scripts sql, la entidad usuario ha sido modificada y se ha agregado una entidad nueva llamada role, para soportar esto se deben realizar las siguientes modificaciones al schema de la la base de datos, del mismo modo que en el ejemplo anterior la definición del schema se realizará en el archivo schema.sql y la inserción de los datos de ejemplo en el archivo init.sql.

schema.sql

CREATE TABLE ROLE(
ID_ROLE INTEGER PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(45) NOT NULL ,
DESCRIPTION VARCHAR(100) NULL
);

CREATE TABLE USER(
USER_ID INTEGER PRIMARY KEY AUTO_INCREMENT,
USERNAME VARCHAR(100) NOT NULL,
PASSWORD VARCHAR(100) NOT NULL,
ROLE_ID INTEGER NOT NULL,
    FOREIGN KEY (ROLE_ID)
    REFERENCES ROLE (ID_ROLE)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION);

init.sql

INSERT INTO ROLE (NAME,DESCRIPTION)VALUES("ADMIN","Administrator");
INSERT INTO ROLE (NAME,DESCRIPTION)VALUES("USER","Normal user");

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

 

HATEOAS es la abreviatura de Hypermedia as the Engine of Application state y permitirá navegar a través de los recursos REST, de tal modo que cada uno de los recursos devueltos debe contener un link al recurso completo. Con esto es posible acceder a los recursos de forma completa sin necesidad de documentación.

4. Creando Dtos con soporte para HATEOAS, lo primero que se debe hacer para agregar soporte para HATEOS es crear Dto’s con soporte a los links para realizar la navegación.

/**
 *
 */
package com.raidentrance.dto;

import java.io.Serializable;
import org.springframework.hateoas.ResourceSupport;

/**
 * @author raidentrance
 *
 */
public class RoleDto extends ResourceSupport implements Serializable {

	private Long idRole;

	private String name;

	private String description;

	........

}

/**
 *
 */
package com.raidentrance.dto;

import java.io.Serializable;
import org.springframework.hateoas.ResourceSupport;
import com.raidentrance.entities.Role;

/**
 * @author raidentrance
 *
 */
public class UserDto extends ResourceSupport implements Serializable {

	private Long idUser;

	private String username;

	private String password;

	private RoleDto role;

..........
}

Se puede observar que los Dto’s creados heredan de la clase ResourceSupport la cuál brindará soporte para agregar links a los recursos que serán devueltos por los servicios REST.

5. Creando un AbstractAssembler, a continuación se muestra un assembler abstracto, el cuál servirá de base para generar los links para los recursos que serán devueltos por los servicios rest.

/**
 *
 */
package com.raidentrance.assembler;
 
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.jaxrs.JaxRsLinkBuilder;
import org.springframework.hateoas.mvc.ResourceAssemblerSupport;
 
import jersey.repackaged.com.google.common.base.Preconditions;
 
/**
 * @author raidentrance
 *
 */
public abstract class JaxRsResourceAssemblerSupport<T, D extends ResourceSupport>
        extends ResourceAssemblerSupport<T, D> {
    private final Class<?> controllerClass;
 
    public JaxRsResourceAssemblerSupport(Class<?> controllerClass, Class<D> resourceType) {
 
        super(controllerClass, resourceType);
        this.controllerClass = controllerClass;
    }
 
    @Override
    protected D createResourceWithId(Object id, T entity, Object... parameters) {
        Preconditions.checkNotNull(entity);
        Preconditions.checkNotNull(id);
        D instance = instantiateResource(entity);
        instance.add(JaxRsLinkBuilder.linkTo(controllerClass).slash(id).withSelfRel());
        return instance;
    }
}

6. Creando mapper, el siguiente paso es crear un mapper para transformar de Entidades a Dtos de forma simple, para hacer esto se hará uso de MapStruct, para más información sobre la configuración de MapStruct Mapeo de Beans con MapStruct.

/**
 *
 */
package com.raidentrance.mapper;

import org.mapstruct.Mapper;
import com.raidentrance.dto.RoleDto;
import com.raidentrance.dto.UserDto;
import com.raidentrance.entities.Role;
import com.raidentrance.entities.User;

/**
 * @author raidentrance
 *
 */

@Mapper
public interface UserMapper {
	UserDto userEntityToUser(User entity);

	User userToUserEntity(UserDto dto);

	RoleDto roleEntityToRole(Role entity);

	Role roleToRoleEntity(RoleDto role);
}

7. Agregando Endpoints, El primer paso es asegurar que se cuenta con los endpoints tanto para “users” como para “roles”. Más adelante se detallarán los endpoints que contiene cada uno de ellos.

UserResource.java

/**
 *
 */
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 {

	......
}

RoleResource.java

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

import javax.ws.rs.Consumes;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

/**
 * @author raidentrance
 *
 */
@Component
@Path("/roles")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class RoleResource {
......
}

8. Registrando los servicios, Cómo se explicó en posts anteriores es necesario registrar cada servicio en Jersey para que funcione de forma correcta.

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

import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;

import com.raidentrance.resource.RoleResource;
import com.raidentrance.resource.UserResource;

/**
 * @author raidentrance
 *
 */
@Component
public class JerseyConfig extends ResourceConfig {
	public JerseyConfig() {
		 register(UserResource.class);
		 register(RoleResource.class);
	}
}

9. Creando los assemblers , el siguiente paso es crear los assemblers, estos serán los responsables de 2 puntos transformar de entities a Dto’s y agregar los links de hateoas.

RoleAssembler.java

/**
 *
 */
package com.raidentrance.assembler;
 
import org.mapstruct.factory.Mappers;
import org.springframework.stereotype.Component;
import com.raidentrance.dto.RoleDto;
import com.raidentrance.entities.Role;
import com.raidentrance.mapper.UserMapper;
import com.raidentrance.resource.RoleResource;
 
/**
 * @author raidentrance
 *
 */
@Component
public class RoleAssembler extends JaxRsResourceAssemblerSupport<Role, RoleDto> {
 
    private UserMapper mapper = Mappers.getMapper(UserMapper.class);
 
    public RoleAssembler() {
        super(RoleResource.class, RoleDto.class);
    }
 
    @Override
    public RoleDto toResource(Role entity) {
        RoleDto role = createResourceWithId(entity.getIdRole(), entity);
        RoleDto result = mapper.roleEntityToRole(entity);
        result.add(role.getLinks());
        return result;
    }
}

UserAssembler.java

/**
 *
 */
package com.raidentrance.assembler;
 
import org.mapstruct.factory.Mappers;
import org.springframework.beans.factory.annotation.Autowired;
import com.raidentrance.dto.RoleDto;
import com.raidentrance.dto.UserDto;
import com.raidentrance.entities.User;
import com.raidentrance.mapper.UserMapper;
import com.raidentrance.resource.UserResource;
 
/**
 * @author raidentrance
 *
 */
@Component
public class UserAssembler extends JaxRsResourceAssemblerSupport<User, UserDto> {
    @Autowired
    private RoleAssembler assembler;
 
    private UserMapper mapper = Mappers.getMapper(UserMapper.class);
 
    public UserAssembler() {
        super(UserResource.class, UserDto.class);
    }
 
    @Override
    public UserDto toResource(User entity) {
        UserDto resource = createResourceWithId(entity.getIdUser(), entity);
        UserDto result = mapper.userEntityToUser(entity);
        RoleDto role = assembler.toResource(entity.getRole());
        result.add(resource.getLinks());
        result.setRole(role);
        return result;
    }
}

10. Agregando Repositorio para la entidad Role

/**
 *
 */
package com.raidentrance.repositories;
 
import org.springframework.data.repository.CrudRepository;
 
import com.raidentrance.entities.Role;
 
/**
 * @author raidentrance
 *
 */
public interface RoleRepository extends CrudRepository<Role, Integer>{
 
}

11. Detallando los endpoints necesarios , A continuación se muestran los endpoints que se utilizarán para exponer los recursos y para generar la navegación entre los servicios.

UserResource.java


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

import java.util.List;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.raidentrance.assembler.UserAssembler;
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 UserAssembler userAssembler;

	@Autowired
	private UserRepository userRepository;

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

	@GET
	@Path("/{idUser}")
	public Response getById(@PathParam("idUser") Integer idUser) {
		User requested = userRepository.findOne(idUser);
		return Response.ok(userAssembler.toResource(requested)).build();
	}

}

RoleResource.java

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

import java.util.List;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.raidentrance.assembler.RoleAssembler;
import com.raidentrance.entities.Role;
import com.raidentrance.repositories.RoleRepository;
import jersey.repackaged.com.google.common.collect.Lists;

/**
 * @author raidentrance
 *
 */
@Component
@Path("/roles")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class RoleResource {

	@Autowired
	private RoleRepository roleRepository;
	@Autowired
	private RoleAssembler assembler;

	@GET
	public Response getRoles() {
		List<Role> role = Lists.newArrayList(roleRepository.findAll());
		return Response.ok(assembler.toResources(role)).build();
	}

	@GET
	@Path("/{idRole}")
	public Response getById(@PathParam("idRole") Integer idRole) {
		Role requested = roleRepository.findOne(idRole);
		return Response.ok(assembler.toResource(requested)).build();
	}
}

12. Probando todo junto ,para ejecutar la aplicación el primer punto es generar el mapper de MapStruct utilizando el goal mvn generate-sources una vez generados los mappers es necesario compilar el proyecto y el proyecto queda listo para probarlo. Para esto se ejecutará la clase principal de la aplicación SprinBootSampleApplication, con esto se iniciará un contenedor.

Probando los recursos:

http://localhost:8080/users .

[
{
idUser: 1,
username: "raidentrance",
password: "superSecret",
role: {
idRole: 1,
name: "ADMIN",
description: "Administrator",
links: [
{
rel: "self",
href: "http://localhost:8080/roles/1"
}
]
},
links: [
{
rel: "self",
href: "http://localhost:8080/users/1"
}
]
},
{
idUser: 2,
username: "john",
password: "smith",
role: {
idRole: 1,
name: "ADMIN",
description: "Administrator",
links: [
{
rel: "self",
href: "http://localhost:8080/roles/1"
}
]
},
links: [
{
rel: "self",
href: "http://localhost:8080/users/2"
}
]
},
{
idUser: 3,
username: "juan",
password: "hola123",
role: {
idRole: 2,
name: "USER",
description: "Normal user",
links: [
{
rel: "self",
href: "http://localhost:8080/roles/2"
}
]
},
links: [
{
rel: "self",
href: "http://localhost:8080/users/3"
}
]
}
]

http://localhost:8080/users/1 .

{
idUser: 1,
username: "raidentrance",
password: "superSecret",
role: {
idRole: 1,
name: "ADMIN",
description: "Administrator",
links: [
{
rel: "self",
href: "http://localhost:8080/roles/1"
}
]
},
links: [
{
rel: "self",
href: "http://localhost:8080/users/1"
}
]
}

http://localhost:8080/roles .

[
{
idRole: 1,
name: "ADMIN",
description: "Administrator",
links: [
{
rel: "self",
href: "http://localhost:8080/roles/1"
}
]
},
{
idRole: 2,
name: "USER",
description: "Normal user",
links: [
{
rel: "self",
href: "http://localhost:8080/roles/2"
}
]
}
]

http://localhost:8080/roles/1 .

{
idRole: 1,
name: "ADMIN",
description: "Administrator",
links: [
{
rel: "self",
href: "http://localhost:8080/roles/1"
}
]
}

 

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

 

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

 

Spring Boot + REST Jersey Parte 1


En este tutorial se explica de forma simple la configuración básica de Spring boot + Jersey, como se puede observar este post será la parte 1, próximamente se publicarán las siguientes partes en las que se explicarán temas más avanzados sobre Spring boot + REST Jersey.

1. Configuración, para configurar spring boot es muy simple, solo se deben realizar las siguientes tareas: heredar del proyecto base de spring boot, incluir las dependencias de los módulos de spring a utilizar y configurar los plugins que se utilizarán en el proyecto.

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>1.4.0.RELEASE</version>
</parent>

Al heredar del proyecto padre de Spring boot se heredan las versiones definidas en esa version, de tal modo que no se deben definir las versiones en el proyecto que se desarrolla.

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-jersey</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-tomcat</artifactId>
		<scope>provided</scope>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
	</dependency>
</dependencies>

Como se puede observar solo se deben definir las dependencias más no las versiones, ya que estas versiones están definidas en el proyecto spring-boot-starter-parent.

<build>
	<plugins>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-compiler-plugin</artifactId>
			<configuration>
				<source>1.8</source>
				<target>1.8</target>
			</configuration>
		</plugin>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>
	</plugins>
</build>

2.Una vez realizada la configuración de maven se debe crear una clase principal de nuestra aplicación, esta clase será la responsable de iniciar la aplicación e iniciar el contenedor, el contenedor default es Jetty, pero como se puede ver en las dependencias en este proyecto se utilizará Tomcat.

/**
 *
 */
package com.raidentrance;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author raidentrance
 *
 */
@SpringBootApplication
public class SprinBootSampleApplication  {
	public static void main(String[] args) throws Exception {
		SpringApplication.run(SprinBootSampleApplication.class, args);
	}

}

La pregunta más común que surge en este punto es : Si se está realizando una aplicación web ¿Por qué crear un método main en la aplicación? La respuesta es simple, la aplicación no generará un war sino que generará un fat jar, se conoce como fat jar porque contiene a demás de la aplicación, un contenedor embebido ya sea un Jetty o un Tomcat depende de como se configure, en este caso será un Tomcat. en futuros posts se explicará como desplegarlo en un ambiente productivo

3. Escribiendo el web service rest, para realizar un servicio REST con Spring solo se deben utilizar las anotaciones de Jersey y anotar las clases con @Component para agregarla al contexto de Spring.

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

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 org.springframework.stereotype.Component;

/**
 * @author raidentrance
 *
 */

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

	@GET
	public String getUsers() {
	 return "{\"username\":\"raidentrance\"}";
	}

}

A continuación se describe el significado de cada una de las anotaciones utilizadas:

  • @Component : Utilizada para indicar que la clase UserResource se encontrará en el contexto de Spring.
  • @Path(“users”) : Utilizada para definir el endpoint a través del cual se accederá al servicio, es posible colocar otra anotación a nivel de método para definir endpoints más complejos.
  • @Produces(MediaType.APPLICATION_JSON) / @Consumes(MediaType.APPLICATION_JSON) : Indican que los endpoints registrados en esa clase consumirán y producirán JSON, por este motivo lo que devuelve el método  getUsers() se encuentra en formato JSON, es posible colocar estas anotaciones a nivel de método para modificar el comportamiento dependiendo del método.

4. Registrando el servicio, una vez que se cuenta con el servicio y con Spring boot configurado, lo único que resta es registrar el servicio en Jersey, para hacer esto se debe crear una clase que registre los servicios a utilizar en la aplicación.

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

import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;

import com.raidentrance.resource.UserResource;

/**
 * @author raidentrance
 *
 */
@Component
public class JerseyConfig extends ResourceConfig {
	public JerseyConfig() {
		 register(UserResource.class);
	}
}

Esta clase no se debe invocar desde ningún otro lado de la aplicación, con el hecho de estar anotada con @Component y heredar de ResourceConfig Spring sabrá que la debe utilizar para registrar los servicios.

5. Ejecutando y probando, para probar la aplicación lo único que se debe hacer es ejecutar la clase SprinBootSampleApplication como una aplicación de escritorio, si se desea ejecutar el jar utilizando:

 java -jar target/nombre-del-jar.jar

Salida:

. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.4.0.RELEASE)

2016-09-02 11:23:28.005 INFO 20231 --- [ restartedMain] c.r.SprinBootSampleApplication : Starting SprinBootSampleApplication on m-C02RV1WXG8WP.local with PID 20231 (/Users/maagapi/Documents/Github/spring-boot-example/target/classes started by maagapi in /Users/maagapi/Documents/Github/spring-boot-example)
2016-09-02 11:23:28.008 INFO 20231 --- [ restartedMain] c.r.SprinBootSampleApplication : No active profile set, falling back to default profiles: default
2016-09-02 11:23:28.064 INFO 20231 --- [ restartedMain] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@53294542: startup date [Fri Sep 02 11:23:28 CDT 2016]; root of context hierarchy
2016-09-02 11:23:28.646 INFO 20231 --- [ restartedMain] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2016-09-02 11:23:29.000 INFO 20231 --- [ restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2016-09-02 11:23:29.012 INFO 20231 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service Tomcat
2016-09-02 11:23:29.013 INFO 20231 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.4
2016-09-02 11:23:29.075 INFO 20231 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2016-09-02 11:23:29.075 INFO 20231 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1013 ms
2016-09-02 11:23:29.260 INFO 20231 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2016-09-02 11:23:29.261 INFO 20231 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2016-09-02 11:23:29.261 INFO 20231 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'com.raidentrance.config.JerseyConfig' to [/*]
2016-09-02 11:23:29.441 INFO 20231 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
2016-09-02 11:23:29.469 INFO 20231 --- [ restartedMain] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2016-09-02 11:23:29.510 INFO 20231 --- [ restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2016-09-02 11:23:29.513 INFO 20231 --- [ restartedMain] c.r.SprinBootSampleApplication : Started SprinBootSampleApplication in 2.206 seconds (JVM running for 2.542)

Para probar el servicio se debe acceder a la URL http://localhost:8080/users

En las siguientes partes de este post se explicará lo siguiente: agregar logs, configurar spring security, spring data, despliegue en producción y manejo de errores.

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

Autor:

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Mapeo de Beans con MapStruct


En este tutorial se explica de forma simple como utilizar MapStruct para realizar el mapeo de beans de forma automática en tiempo de compilación sin uso del Java reflection API.

1. Para configurar Map struct se debe incluir la siguiente dependencia en el archivo pom.xml.

<properties>
	<org.mapstruct.version>1.1.0.Beta2</org.mapstruct.version>
</properties>
<dependencies>
	<dependency>
		<groupId>org.mapstruct</groupId>
		<artifactId>mapstruct-jdk8</artifactId>
		<version>${org.mapstruct.version}</version>
	</dependency>
</dependencies>

MapStruct no utiliza reflection para hacer el mapeo de los beans en tiempo de ejecución, sino que en tiempo de compilación utiliza el goal generate-sources de Maven para generar las clases necesarias de forma automática, para hacer esto se debe incluir el siguiente plugin en el archivo pom.xml:

<build>
	<plugins>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-compiler-plugin</artifactId>
			<configuration>
				<source>1.8</source>
				<target>1.8</target>
			</configuration>
		</plugin>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-compiler-plugin</artifactId>
			<version>3.5.1</version>
			<configuration>
				<source>1.8</source>
				<target>1.8</target>
				<annotationProcessorPaths>
					<path>
						<groupId>org.mapstruct</groupId>
						<artifactId>mapstruct-processor</artifactId>
						<version>${org.mapstruct.version}</version>
					</path>
				</annotationProcessorPaths>
			</configuration>
		</plugin>
	</plugins>
</build>

2. Creando el modelo, a continuación se muestran las clases de dominio entre las que se encuentran User.java, Role.java, UserDto.java y RoleDto.java. El objetivo del ejemplo será transformar un objeto tipo User.java a uno UserDto.java utilizando MapStruct.

Beans (Pueden ser entities o cualquier tipo de objeto que se desee transformar)

package com.raidentrance.model.pojo;

/**
 * @author raidentrance
 *
 */
public class User {
	private String username;
	private Role role;
	private String password;

	public String getUsername() {
		return username;
	}

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

	public Role getRole() {
		return role;
	}

	public void setRole(Role role) {
		this.role = role;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}
}
package com.raidentrance.model.pojo;

/**
 * @author raidentrance
 *
 */
public class Role {

	private String name;

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

Dto’s (Los objetos a los que serán transformados los beans )

package com.raidentrance.model.dto;

import com.raidentrance.model.pojo.Role;

/**
 * @author raidentrance
 *
 */
public class UserDto  {
	private String username;
	private Role role;
	private String password;

	public String getUsername() {
		return username;
	}

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

	public Role getRole() {
		return role;
	}

	public void setRole(Role role) {
		this.role = role;
	}

	public String getPassword() {
		return password;
	}

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

}
package com.raidentrance.model.dto;

/**
 * @author raidentrance
 *
 */
public class RoleDto  {
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

}

3. Una vez que se cuenta con las clases desde y a las que se va a transformar se debe crear un mapper de MapStruct, el cuál definirá los métodos que se utilizarán para transformar los objetos.

package com.raidentrance.mapper;

import org.mapstruct.Mapper;

import com.raidentrance.model.dto.RoleDto;
import com.raidentrance.model.dto.UserDto;
import com.raidentrance.model.pojo.Role;
import com.raidentrance.model.pojo.User;

/**
 * @author raidentrance
 *
 */
@Mapper
public interface UserMapper {

	UserDto userToUserDto(User user);

	RoleDto roleToRoleDto(Role user);

}

Como se puede observar solo se necesita crear la interfaz más no la implementación, esta será generada por MapStruct cuando se ejecute el goal de maven generate-sources.

4. Probando todo junto.

package com.raidentrance;

import org.mapstruct.factory.Mappers;

import com.raidentrance.mapper.UserMapper;
import com.raidentrance.model.dto.UserDto;
import com.raidentrance.model.pojo.Role;
import com.raidentrance.model.pojo.User;

/**
 * @author raidentrance
 *
 */
public class MapStructSample {
	public static User buildUser() {
		Role r = new Role();
		r.setName("Admin");
		User user = new User();
		user.setUsername("raidentrance");
		user.setPassword("Super secret");
		user.setRole(r);
		return user;
	}

	public static void main(String[] args) {
		UserMapper instance = Mappers.getMapper(UserMapper.class);
		User user = buildUser();

		UserDto dto = instance.userToUserDto(user);
		System.out.println(dto.getUsername());
		System.out.println(dto.getPassword());
		System.out.println(dto.getRole().getName());
		System.out.println(dto.getRole().getClass());
	}
}

Si no se ha ejecutado el build generate-sources y se intenta ejecutar, se producirá la siguiente excepción:

Exception in thread "main" java.lang.RuntimeException: java.lang.ClassNotFoundException: com.raidentrance.mapper.UserMapperImpl
	at org.mapstruct.factory.Mappers.getMapper(Mappers.java:82)
	at com.raidentrance.MapStructSample.main(MapStructSample.java:26)
Caused by: java.lang.ClassNotFoundException: com.raidentrance.mapper.UserMapperImpl
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at org.mapstruct.factory.Mappers.getMapper(Mappers.java:77)
	... 1 more

Esto se debe a que la clase UserMapperImpl aún no existe y se debe generar, para hacer esto se debe ejecutar el goal mvn generate-sources. Una vez hecho esto se puede compilar normalmente el proyecto y al ejecutarlo producirá la siguiente salida:

raidentrance
Super secret
Admin
class com.raidentrance.model.pojo.Role

Puedes encontrar el código completo del ejemplo en el siguiente enlace: https://github.com/raidentrance/mapstruct-example

Autor:

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com

Jackson 2 – Transforma objetos java a JSON


En este tutorial se explica de forma simple como utilizar Jackson 2.x para transformar objetos Java a Json y viceversa .

1. Configuración

Para utilizar Jackson 2.x es necesario agregar la siguiente dependencia a nuestro archivo pom.xml:

  • jackson-databind
    1. jackson-core  (incluida en jackson-databind)
    2. jackson-annotations (incluida en jackson-databind)

	
		com.fasterxml.jackson.core
		jackson-databind
		2.8.1
	

2. Creando objeto a transformar de Java a JSON

El objetivo de Jackson 2.x es transformar un objeto java a JSON y un JSON a objetos Java, para hacer esto el primer paso es crear un POJO(Plain Old Java Object) .

/**
 *
 */
package com.raidentrance.pojo;

/**
 * @author raidentrance
 *
 */
public class User {

	private Integer idUser;
	private String username;
	private String password;

	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 + ", password=" + password + "]";
	}

}

3. Transformación de objeto Java a JSON

/**
 *
 */
package com.raidentrance.pojo.test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.raidentrance.pojo.User;

/**
 * @author raidentrance
 *
 */
public class FromObjectToJSON {
	public static void main(String[] args) throws JsonProcessingException {
		User user=new User();
		user.setIdUser(1);
		user.setUsername("raidentrance");
		user.setPassword("superSecret"<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;">&#65279;</span>);

		ObjectMapper mapper=new ObjectMapper();
		String json = mapper.writeValueAsString(user);
		System.out.println(json);
	}
}

Salida:

{"idUser":1,"username":"raidentrance","password":"superSecret"}

4. Transformación de JSON a objeto Java

/**
 *
 */
package com.raidentrance.pojo.test;

import java.io.IOException;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.raidentrance.pojo.User;

/**
 * @author raidentrance
 *
 */
public class FromJSONToObject {
	public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
		String json = "{\"idUser\":1,\"username\":\"raidentrance\",\"password\":\"superSecret\"}";
		ObjectMapper mapper = new ObjectMapper();
		User user = mapper.readValue(json, User.class);
		System.out.println(user);
	}
}

Salida:

User [idUser=1, username=raidentrance, password=superSecret]

5. JsonViews

JsonViews son utilizadas para serializar los objetos de diferentes formas, en el ejemplo actual se utilizarán para serializar y deserializar objetos tipo usuario incluyendo y sin incluir el password.

Definición de vistas a utilizar.

/**
 *
 */
package com.raidentrance.util;

/**
 * @author raidentrance
 *
 */
public class Views {

	public static class Public {
	}

	public static class Internal extends Public {
	}
}

Modificación a la clase User para definir la vista a utilizar

/**
 *
 */
package com.raidentrance.pojo;

import com.fasterxml.jackson.annotation.JsonView;
import com.raidentrance.util.Views;

/**
 * @author raidentrance
 *
 */
public class User {

	@JsonView(Views.Public.class)
	private Integer idUser;
	@JsonView(Views.Public.class)
	private String username;
	@JsonView(Views.Internal.class)
	private String password;
......
}

Salida:

{"idUser":1,"username":"raidentrance"}

Puedes encontrar el código completo del ejemplo en el siguiente enlace: https://github.com/raidentrance/jackon-example

Autor:

Autor: Alejandro Agapito Bautista

Twitter: @raidentrance

Contacto:raidentrance@gmail.com