Aprende a utilizar anotaciones creando tu propio ORM


Cuando desarrollamos aplicaciones en Java el uso de anotaciones es muy común, pero se han puesto a pensar ¿Cómo crear tus propias anotaciones y como utilizarlas en Java ?, en este post explicaremos como hacerlo con ejemplos prácticos.

Tipos de anotaciones

Existen 3 tipos de anotaciones:

  • Marker annotation
  • Single value annotation
  • Multi value annotation

Marker annotation

A continuación se presenta como hacer una anotación de tipo Marker annotation:

public @interface Example {
}

Single value annotation

A continuación se presenta como hacer una anotación de tipo Single value annotation:

public @interface Example {
	int value() default 100;
}

Como se puede ver se creó una anotación llamada example la cual tiene un atributo llamado value con un valor por defecto de 100, para utilizarla se hará del siguiente modo:

@Example(value=200)

Esto cambiará el valor de value de 100 a 200.

Multi value annotation

A continuación se presenta como hacer una anotación de tipo Multi value annotation:

public @interface Example {
	int value() default 100;
	int value2() default 200;
	int value3() ;
}

Una anotación puede tener multiples atributos, el ejemplo anterior muestra una anotación con 3 valores value, value2 y value3, para utilizarla haremos lo siguiente:

@Example(value=200, value2=300, value3=400)

@Taget

Es posible definir en que lugar es o no permitido utilizar una anotación, para esto utilizaremos la anotación @Target la cual recibe como parámetro un valor de la enumeración java.lang.annotation.ElementType, a continuación se presentan los posibles valores y su significado:

  • TYPE : A nivel de clase, interfaz o enumeración
  • FIELD: A nivel de campos
  • METHOD: A nivel de métodos
  • CONSTRUCTOR: A nivel de constructores
  • LOCAL_VARIABLE : A nivel de variables locales
  • ANNOTATION_TYPE: A nivel de anotaciones
  • PARAMETER: A nivel de parámetros

Ejemplo práctico

Un caso de ejemplo muy común para utilizar anotaciones es un ORM, en nuestro caso práctico crearemos algunas anotaciones para definirlo y veremos como leerlo.

Anotación @Table:

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
public @interface Table {
	String name();
	String schema();
}

Ejemplo práctico

En este ejemplo mostraremos como crear algunas anotaciones y obtener el valor de las mismas para simular un pequeño ORM.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author raidentrance
 *
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
	String name();

	String schema();
}

La anotación table se utilizará para determinar el nombre de la tabla y el esquema de la base de datos a utilizar.


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author raidentrance
 *
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
	String name();

	String description();
}

La anotación @Column se utilizará para definir el nombre de una columna en una tabla de una base de datos.


import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.logging.Logger;

import com.raidentrance.orm.annotations.Column;
import com.raidentrance.orm.annotations.Table;

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

	private static final CustomEntityManager manager = new CustomEntityManager();

	private static final Logger log = Logger.getLogger(CustomEntityManager.class.getName());

	private CustomEntityManager() {
	}

	public void persist(Object user) {
		Table table = user.getClass().getAnnotation(Table.class);
		log.info(String.format("Inserting in table %s", table.name()));
		Field[] fields = user.getClass().getDeclaredFields();
		for (Field field : fields) {
			Column annotation = field.getAnnotation(Column.class);
			if (annotation != null) {
				try {
					Object invoke = new PropertyDescriptor(field.getName(), user.getClass()).getReadMethod().invoke(user);
					log.info(String.format("Column %s = %s", annotation.name(), invoke));
				} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException | IntrospectionException e) {
					log.info(e.getMessage());
				}
			}
		}
	}

	public static CustomEntityManager getEntityManager() {
		return manager;
	}

}

La clase CustomEntityManager se utilizará para simular el EntityManagerFactory de JPA, como se puede ver tiene un constructor privado y una instancia static para hacerlo singleton.

A continuación se muestra como obtener el valor de las anotaciones aplicadas en la clase haciendo uso del objeto:

Table table = user.getClass().getAnnotation(Table.class);
log.info(String.format("Inserting in table %s", table.name()));

Probando todo junto

Para probar todo junto crearemos una clase que hará uso del CustomEntityManager:

import com.raidentrance.orm.persistence.CustomEntityManager;

/**
 * @author raidentrance
 *
 */
public class TestApplication {
	public static void main(String[] args) {
		CustomEntityManager entityManager = CustomEntityManager.getEntityManager();

		entityManager.persist(new User("raidentrance", "SuperSecret"));
	}
}

Como se puede entender el uso de las anotaciones simulando un pequeño ORM, si ejecutas el programa la salida será la siguiente:

ene 22, 2018 5:09:10 PM com.raidentrance.orm.persistence.CustomEntityManager persist
INFORMACIÓN: Inserting in table USER
ene 22, 2018 5:09:10 PM com.raidentrance.orm.persistence.CustomEntityManager persist
INFORMACIÓN: Column USERNAME = raidentrance
ene 22, 2018 5:09:10 PM com.raidentrance.orm.persistence.CustomEntityManager persist
INFORMACIÓN: Column PASSWORD = SuperSecret

Si te gustó este ejemplo coméntalo en la sección de comentarios o en nuestras redes sociales https://twitter.com/geeks_mx y https://www.facebook.com/geeksJavaMexico/, para agregar la integración de JDBC para hacer funcionar nuestro ORM.

 

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