Aplicación standAlone JPA + Hibernate +Maven en Español !


En este post se explicará paso a paso como crear una aplicación Stand alone que accede a una base de datos Mysql utilizando la implementación de Hibernate para JPA.

Configuración

El primer paso es definir las dependencias a utilizar en el proyecto, para este caso se utilizarán las siguientes:

<dependencies>
	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-entitymanager</artifactId>
		<version>5.2.3.Final</version>
	</dependency>

	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.3</version>
	</dependency>
</dependencies>

Las dependencias a utilizar son :

  • hibernate-entitymanager : Implementación de Hibernate para JPA
  • mysql-connector-java : Driver de MySQL (Puede variar dependiendo del manejador a utilizar)

Definir tablas a utilizar, en este ejemplo se utilizará solo una tabla llamada USER:

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

Una vez definidas las dependencias y la tabla a utilizar se debe crear el archivo de configuración más importante en JPA este es el archivo persistence.xml y se debe colocar en el folder META-INF dentro de la carpeta src/main/resources. La estructura quedaría del siguiente modo src/main/resources/META-INF/persistence.xml con lo siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" 	xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
	<persistence-unit name="MyPersistenceUnit" 		transaction-type="RESOURCE_LOCAL">
		<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

		<class>com.raidentrance.entities.User</class>
		<properties>
			<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
			<property name="javax.persistence.jdbc.url" 				value="jdbc:mysql://localhost:3306/jpaexample?zeroDateTimeBehavior=convertToNull" />
			<property name="javax.persistence.jdbc.user" value="root" />
			<property name="javax.persistence.jdbc.password" value="root" />
		</properties>
	</persistence-unit>
</persistence>

Crear entidad a utilizar

JPA es un framework ORM, por esto es necesario definir una entidad Java que represente la tabla que se utiliza en la base de datos.

/**
 *
 */
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 userId;

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

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

	private static final long serialVersionUID = -1382782006959182944L;

	public User() {
	}

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

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

	public Integer getUserId() {
		return userId;
	}

	public void setUserId(Integer userId) {
		this.userId = userId;
	}

	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 int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((userId == null) ? 0 : userId.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 (userId == null) {
			if (other.userId != null)
				return false;
		} else if (!userId.equals(other.userId))
			return false;
		if (username == null) {
			if (other.username != null)
				return false;
		} else if (!username.equals(other.username))
			return false;
		return true;
	}

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

}
  • Por cada campo definido en la base de datos existe un campo definido en la entidad JPA que lo representa con esto cuando se realice una consulta en lugar de recibir un ResultSet como en Jdbc recibiremos un List lo cual facilitará el desarrollo.

Creando un contexto para la aplicación

Como este ejemplo no depende de Spring la aplicación debe ser capaz de crear los objetos y administrarlo.

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

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

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

	private EntityManagerFactory entityManagerFactory;

	private static ApplicationContext instance = new ApplicationContext();

	private ApplicationContext() {
		entityManagerFactory = Persistence.createEntityManagerFactory("MyPersistenceUnit");
	}

	public static ApplicationContext getInstance() {
		return instance;
	}

	public EntityManager getEntityManager() {
		return entityManagerFactory.createEntityManager();
	}

	public void closeEntityManager() {
		entityManagerFactory.close();
	}

}

Como se puede observar  este contexto sigue el patron de diseño Singleton y es responsable de crear un EntityManagerFactory e instancias de EntityManager.

El objeto EntityManager será el responsable de acceder a la base de datos, crear las consultas sql, traducir las respuestas a objetos java, entre otras cosas.

Creando un DAO abstracto

DAO es un patrón de diseño que significa ( Data access object ) el cual es una interfaz de acceso a una base de datos en una aplicación. En este ejemplo se creará un DAO abstracto el cuál definirá acciones comunes CRUD (Create, read, update y delete). Con esto las implementaciones de este ya no tendrán que reescribir código para estas acciones comunes.

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

/**
 * @author raidentrance
 *
 */
import java.util.List;

import javax.persistence.EntityManager;

public abstract class AbstractFacade<T> {

	private final Class<T> entityClass;
	private EntityManager entityManager;

	public AbstractFacade(Class<T> entityClass, EntityManager entityManager) {
		this.entityClass = entityClass;
		this.entityManager = entityManager;
	}

	protected EntityManager getEntityManager() {
		return entityManager;
	}

	public void create(T entity) {
		getEntityManager().getTransaction().begin();
		getEntityManager().persist(entity);
		getEntityManager().getTransaction().commit();
	}

	public void edit(T entity) {
		getEntityManager().getTransaction().begin();
		getEntityManager().merge(entity);
		getEntityManager().getTransaction().commit();
	}

	public void remove(T entity) {
		getEntityManager().getTransaction().begin();
		T find = getEntityManager().find(entityClass, entity);
		getEntityManager().remove(find);
		getEntityManager().getTransaction().commit();
	}

	public T find(Object id) {
		return getEntityManager().find(entityClass, id);
	}

	public List<T> findAll() {
		javax.persistence.criteria.CriteriaQuery<T> cq = getEntityManager().getCriteriaBuilder()
				.createQuery(entityClass);
		cq.select(cq.from(entityClass));
		return getEntityManager().createQuery(cq).getResultList();
	}

	public void close() {
		entityManager.close();
	}
}

Como se puede observar, en lugar de utilizar alguna entidad en específico se define un dato genérico que será definido en la clase hija.

Creando DAO para usuarios

El siguiente paso es crear un DAO específico para la clase Usuario que es la entidad que se creó para este ejemplo. En esta clase ya no se deben implementar todas las acciones CRUD ya que estas ya se encuentran definidas en el DAO abstracto.

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

import javax.persistence.EntityManager;

import com.raidentrance.entities.User;

/**
 * @author raidentrance
 *
 */
public class UserDao extends AbstractFacade<User> {

	public UserDao(EntityManager entityManager) {
		super(User.class, entityManager);
	}

}

Lo bueno de este tipo de patrón es que si se necesita crear 100 entidades, no es necesario reescribir el código para las operaciones CRUD para todos, solo es necesario heredar de el DAO abstracto.

Ejecutar la aplicación

El último paso es crear la clase principal utilizará todos los componentes creados para persistir objetos de tipo usuario y para obtenerlos de la base de datos.

/**
 *
 */
package com.raidentrance;

import java.util.List;
import java.util.Random;
import java.util.logging.Logger;

import javax.persistence.EntityManager;

import com.raidentrance.dao.UserDao;
import com.raidentrance.entities.User;
import com.raidentrance.util.ApplicationContext;

/**
 * @author raidentrance
 *
 */
public class JpaApplication {
	private static final Logger log = Logger.getLogger(JpaApplication.class.getName());

	public static void main(String[] args) {
		ApplicationContext context = ApplicationContext.getInstance();
		EntityManager entityManager = context.getEntityManager();

		UserDao dao = new UserDao(entityManager);

		User userEntity = new User("raidentrance ".concat(new Integer(new Random(100).nextInt()).toString()), "López");
		dao.create(userEntity);

		List<User> list = dao.findAll();
		for (User user : list) {
			log.info(user.toString());
		}

		dao.close();
		context.closeEntityManager();
	}
}

Conclusión

En este ejemplo se explica como acceder a una base de datos utilizando JPA sin depender de otros frameworks como Spring data, Spring Hibernate, etc. Sólo se utiliza la implementación de Hibernate de JPA, el driver de Jdbc de MySQL y patrones de diseño como AbstractFacade, DAO y Singleton.

Puedes encontrar el código completo del ejemplo en el siguiente enlace:

https://github.com/raidentrance/jpa-example

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