API REST 5. Hypermedia

Introducción

Hypermedia es la interrelación entre recursos mediante enlaces. La idea es sólo conocer un recurso principal y de ahí descubrir como acceder al resto de los recursos. Esto permite aumentar la interoperabilidad entre recursos con mucha menos información.

Por ejemplo:

Tenemos la tabla persona donde podemos consultar edad, nombre y sexo. En otra tabla  grupo se almacena el grupo al que pertenece la persona. En otra tabla país se almacena el país al que pertenece la persona.  Si necesitamos consultar los datos, grupo y país de una persona tendríamos que consumir los recursos,

http://localhost:8080/personah/3

De los resultados obtenemos el id del grupo. Para conocer el nombre del grupo,

http://localhost:8080/grupoh/2

De los resultados obtenemos el id del país. Para conocer el nombre del país,

http://localhost:8080/paish/2

Usando hypermedia podríamos presentar:

Y navegaríamos por los link para obtener toda la información requerida sin necesidad de conocer cada uno de los recursos.

Programar hypermedia

Crear Entity

Creamos los entity PersonaHateoas, GrupoHateoas y PaisHateoas.

PersonaHateoas

package com.mio.administrar.entity;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

@Entity
public class PersonaHateoas implements Serializable  {
	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private int idPersona;

	private int idGrupo;
	private int idPais;
	private int edad;
	private String nombre;
	private String sexo;

	@ManyToOne
	@JoinColumn(name="idGrupo",insertable=false,updatable=false)
	private GrupoHateoas grupoHateoas;
	
	@ManyToOne
	@JoinColumn(name="idPais",insertable=false,updatable=false)
	private PaisHateoas paisHateoas;
	
	public PersonaHateoas() {
	}

	public int getIdPersona() {
		return this.idPersona;
	}

	public void setIdPersona(int idPersona) {
		this.idPersona = idPersona;
	}

	public int getEdad() {
		return this.edad;
	}

	public void setEdad(int edad) {
		this.edad = edad;
	}

	public String getNombre() {
		return this.nombre;
	}

	public void setNombre(String nombre) {
		this.nombre = nombre;
	}

	public String getSexo() {
		return this.sexo;
	}

	public void setSexo(String sexo) {
		this.sexo = sexo;
	}

	public int getIdGrupo() {
		return idGrupo;
	}

	public void setIdGrupo(int idGrupo) {
		this.idGrupo = idGrupo;
	}

	public int getIdPais() {
		return idPais;
	}

	public void setIdPais(int idPais) {
		this.idPais = idPais;
	}
}

GrupoHateoas

package com.mio.administrar.entity;

import java.io.Serializable;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name="grupoHateoas")
public class GrupoHateoas implements Serializable  {
	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private int idGrupo;

	private String nombre;

	@OneToMany(mappedBy="grupoHateoas")
	private List<PersonaHateoas> personaHateoas;
	
	public GrupoHateoas() {
	}

	public String getNombre() {
		return nombre;
	}

	public void setNombre(String nombre) {
		this.nombre = nombre;
	}

	public List<PersonaHateoas> getPersonaHateoas() {
		return this.personaHateoas;
	}
}

PaisHateoas

package com.mio.administrar.entity;

import java.io.Serializable;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name="paisHateoas")
public class PaisHateoas implements Serializable  {
	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private int idPais;

	private String nombre;

	@OneToMany(mappedBy="paisHateoas")
	private List<PersonaHateoas> personaHateoas;
	
	public PaisHateoas() {
	}

	public String getNombre() {
		return nombre;
	}

	public void setNombre(String nombre) {
		this.nombre = nombre;
	}

	public List<PersonaHateoas> getPersonaHateoas() {
		return this.personaHateoas;
	}
}

Crear Repository

Creamos los repository PersonaHateoasRepository, GrupoHateoasRepository y PaisHateoasRepository.

PersonaHateoasRepository

package com.mio.administrar.repository;

import java.util.List;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Transactional;
import com.mio.administrar.entity.PersonaHateoas;

@Transactional
public interface PersonaHateoasRepository extends CrudRepository<PersonaHateoas, Long>{
	@Query("select p from PersonaHateoas p where p.idPersona = ?1")
	PersonaHateoas unaPersona(int id);
	
	@Query("select p from PersonaHateoas p")
	List<PersonaHateoas> allPersonas();
}

GrupoHateoasRepository

package com.mio.administrar.repository;

import java.util.List;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Transactional;
import com.mio.administrar.entity.GrupoHateoas;

@Transactional
public interface GrupoHateoasRepository extends CrudRepository<GrupoHateoas, Long>{
	@Query("select g from GrupoHateoas g where g.idGrupo = ?1")
	GrupoHateoas unGrupo(int id);
	
	@Query("select g from GrupoHateoas g")
	List<GrupoHateoas> allGrupos();
}

PaisHateoasRepository

package com.mio.administrar.repository;

import java.util.List;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Transactional;
import com.mio.administrar.entity.PaisHateoas;

@Transactional
public interface PaisHateoasRepository extends CrudRepository<PaisHateoas, Long>{
	@Query("select p from PaisHateoas p where p.idPais = ?1")
	PaisHateoas unPais(int id);
	
	@Query("select p from PaisHateoas p")
	List<PaisHateoas> allPaises();
}

Crear Controller

Creamos los repository PersonaHateoasController, GrupoHateoasController y PaisHateoasController.

PersonaHateoasController

package com.mio.administrar.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.Resource;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.mio.administrar.entity.PersonaHateoas;
import com.mio.administrar.repository.PersonaHateoasRepository;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequestMapping(path="/personah", produces = "application/hal+json")
public class PersonaHateoasController  {
	@Autowired
	private PersonaHateoasRepository personaHateoasRepository;
	
	/**
	 * Obtiene todas las personas: GET http://{servidor}:{puerto}/personah
	 * @return Lista de personas
	 */
	@RequestMapping(method = RequestMethod.GET)
	public Resources<Resource<PersonaHateoas>> allPersonas() {
		List<PersonaHateoas> listPersonaHateoas = personaHateoasRepository.allPersonas();
		return personaToResource(listPersonaHateoas);
	}
	
	 /**
	 * Obtiene una person por id: GET http://{servidor}:{puerto}/personah/{id}
	 * @param id Id de la persona. En la Url
	 * @return Una persona
	 */
	@RequestMapping(value = "/{id}", method = RequestMethod.GET)
	public Resource<PersonaHateoas> unaPersona(@PathVariable int id) {
		PersonaHateoas	personaHateoas	= personaHateoasRepository.unaPersona(id);
		return personaToResource(personaHateoas);
	}
	
	/**
	 * Recorre una lista de Personas para agregar links
	 * @param personas Lista de personas
	 * @return Resources
	 */
	private Resources<Resource<PersonaHateoas>> personaToResource(List<PersonaHateoas> personas) {
		Link selfLink = linkTo(methodOn(PersonaHateoasController.class).allPersonas()).withSelfRel();
		List<Resource<PersonaHateoas>> customerResources = personas.stream().map(persona -> personaToResource(persona)).collect(Collectors.toList());
		return new Resources<>(customerResources, selfLink);
	}

	/**
	 * Agrega links a Una persona
	 * @param persona Una persona
	 * @return Resource
	 */
	private Resource<PersonaHateoas> personaToResource(PersonaHateoas persona) {
		Link selfLink 	= linkTo(methodOn(PersonaHateoasController.class).unaPersona(persona.getIdPersona())).withSelfRel();
		Link grupoLink 	= linkTo(methodOn(GrupoHateoasController.class).unGrupo(persona.getIdGrupo() )).withRel("Grupo");
		Link paisLink 	= linkTo(methodOn(PaisHateoasController.class).unPais(persona.getIdPais() )).withRel("Pais");
		return new Resource<>(persona, selfLink,grupoLink,paisLink);
	}
}

GrupoHateoasController

package com.mio.administrar.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.mio.administrar.entity.GrupoHateoas;
import com.mio.administrar.repository.GrupoHateoasRepository;

@RestController
@RequestMapping(path="/grupoh", produces = "application/hal+json")
public class GrupoHateoasController  {
	@Autowired
	private GrupoHateoasRepository grupoHateoasRepository;
	
	/**
	 * Obtiene todos los grupos: GET http://{servidor}:{puerto}/grupoh
	 * @return Lista de grupos
	 */
	@RequestMapping(method = RequestMethod.GET)
	public Iterable<GrupoHateoas> findAll() {
		return grupoHateoasRepository.findAll() ;
	}
	
	/**
	 * Obtiene un grupo por id: GET http://{servidor}:{puerto}/grupoh/{id}
	 * @param id Id del grupo. En la Url
	 * @return Un grupo
	 */
	@RequestMapping(value = "/{id}", method = RequestMethod.GET)
	public GrupoHateoas unGrupo(@PathVariable int id) {
		return grupoHateoasRepository.unGrupo(id);
	}
}

PaisHateoasController

package com.mio.administrar.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.mio.administrar.entity.PaisHateoas;
import com.mio.administrar.repository.PaisHateoasRepository;

@RestController
@RequestMapping(path="/paish", produces = "application/hal+json")
public class PaisHateoasController  {
	@Autowired
	private PaisHateoasRepository paisHateoasRepository;
	
	/**
	 * Obtiene todos los paises: GET http://{servidor}:{puerto}/paish
	 * @return Lista de paises
	 */
	@RequestMapping(method = RequestMethod.GET)
	public Iterable<PaisHateoas> findAll() {
		return paisHateoasRepository.findAll() ;
	}
	
	/**
	 * Obtiene un pais por id: GET http://{servidor}:{puerto}/paish/{id}
	 * @param id Id del pais. En la Url
	 * @return Un pais
	 */
	@RequestMapping(value = "/{id}", method = RequestMethod.GET)
	public PaisHateoas unPais(@PathVariable int id) {
		return paisHateoasRepository.unPais(id);
	}
}

Explicación de PersonaHateoasController

Todas las demás clases ya las conocemos, solo vamos a explicar algunos detalles de la clase PersonaHateoasController.

  1. Hateoas (Hypermedia As The Engine Of Application State). Aunque en nuestros ejemplos estamos modelando tablas de una Base de Datos, normalmente conocido como CRUD (Create Read Update and Delete),  lo que estamos haciendo es exponer  la capa de datos y dejando que el consumidor interprete e implemente la lógica del negocio. Siguiendo la filosofía de REST deberíamos de exponer los distintos casos de uso del sistema y no solo una simple interfaz. La intención de Hateoas es alejarse de CRUD y basarse en un enfoque hypermedia.
  2. application/hal+json. Existen diferentes formas de exponer los recursos en hypermedia, uno de los más extendidos es HAL (Hypertext Application Language). Es una extensión de json con la capacidad de definir enlaces y controles. application/hal+json es el tipo mime para HAL.
  3. Método personaToResource con parámetro PersonaHateoas. Crea los link y los retorna como un Resource (Recurso).
  4. Método personaToResource con parámetro List<PersonaHateoas>.  Recorre la lista de PersonaHateoas para cargar los Recursos.

Resultado

Al ejecutar

http://localhost:8080/personah

El resultado es:

{
  "_embedded": {
    "personaHateoasList": [
    {
      "idPersona": 1,
      "idGrupo": 1,
      "idPais": 2,
      "edad": 2,
      "nombre": "Juan",
      "sexo": "M",
      "_links": {
        "self": {
          "href": "http://localhost:8080/personah/1"
        },
        "Grupo": {
          "href": "http://localhost:8080/grupoh/1"
        },
        "Pais": {
          "href": "http://localhost:8080/paish/2"
        }
     }
  },
  {
    "idPersona": 2,
    "idGrupo": 2,
    "idPais": 3,
    "edad": 10,
    "nombre": "Linda",
    "sexo": "F",
    "_links": {
      "self": {
        "href": "http://localhost:8080/personah/2"
      },
      "Grupo": {
        "href": "http://localhost:8080/grupoh/2"
      },
      "Pais": {
        "href": "http://localhost:8080/paish/3"
      }
    }
 },
 {
   "idPersona": 3,
   "idGrupo": 2,
   "idPais": 1,
   "edad": 5,
   "nombre": "Luisa",
   "sexo": "M",
   "_links": {
     "self": {
       "href": "http://localhost:8080/personah/3"
     },
     "Grupo": {
       "href": "http://localhost:8080/grupoh/2"
     },
     "Pais": {
       "href": "http://localhost:8080/paish/1"
     }
   }
 }
]
},
"_links": {
  "self": {
    "href": "http://localhost:8080/personah"
  }
 }
}

Otros

Descargar proyecto

Se puede descargar el proyecto completo de https://github.com/arielolivagh/administrarPersona/releases/tag/v2.0

API REST 4. Calidad al implementar aplicacion Web

Introducción

Existen 3 niveles para aplicar API REST en el desarrollo de una Aplicación Web.
Cumplir con estás nos garantiza las mejores practicas para su implementación, integración, estabilidad y escabilidad.
Para detalles se puede consultar  http://asiermarques.com/2013/conceptos-sobre-apis-rest/

Nivel 1. Uso correcto de URIs

  1. Identificar de forma única el recurso
  2. Las URI no deben contener acciones
  3. Independientes del formato
  4. Jerarquía lógica
  5. Los filtrados no se deben hacer en la URI

Nivel 2. HTTP

  1. Usar métodos HTTP; GET, POST, PUT, DELETE y PATCH
  2. Código de estado
  3. Aceptación de tipos de contenido

Nivel 3. Hypermedia

Se vera mas a detalle posteriormente.

Con lo que llevamos hasta este momento, podemos generar un proyecto de API REST en Web con las mejores practicas.
Vamos a retomar la tabla Persona y crear un Sistema Web con API REST para administrar personas; crear, leer, actualizar y eliminar.

Construir aplicación

Crear proyecto

Ir a

File -> New -> Other… -> Spring Boot -> Spring Starter Project

Seleccionamos nombre del proyecto, tipo, versión de java, paquete, entre otras opciones. Clic en Next>. Seleccionamos la versión de Spring Boot y las dependencias:

  1. JPA
  2. H2
  3. MySQL
  4. Web

Clic en Next> y posteriormente clic en Finish.

Crear paquetes

Creamos los paquetes

  • com.mio.administrar.controller
  • com.mio.administrar.repository
  • com.mio.administrar.entity

Copiar clases e interfaces

Tomando las clases e interfaces del proyecto anterior https://github.com/arielolivagh/gs-rest-service-complete/tree/v2.0  copiamos:

  • La clase PersonaController al paquete com.mio.administrar.controller
  • La interface PersonaRepository al paquete com.mio.administrar.repository
  • La clase Persona al paquete com.mio.administrar.entity

Conexión a la Base de Datos

Escribimos los parámetros de conexión a la Base de Datos en el archivo application.properties.

spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/Pruebas
spring.datasource.username=root
spring.datasource.password=root

Crear consulta para búsqueda de personas por id

En la clase repository vamos a crear un nuevo query para obtener una persona por su id.

@Query("select p from Persona p where p.idPersona = ?1")
Persona findPersona(int id);

Crear controller de calidad

Crear personas

  • Método POST
  • La respuesta es Json
  • Documentación JavaDoc
    /**
     * Crea una persona: POST http://{servidor}:{puerto}/persona
     * @param nombre Nombre de la persona
     * @param edad Edad de la persona
     * @param sexo Sexo de la persona
     * @return mensaje de 'Agregado correctamente'
     */
    @RequestMapping(method=RequestMethod.POST,produces={"application/json"})
    public String createPersona(@RequestParam String nombre,@RequestParam int edad,@RequestParam String sexo) {
            Persona p = new Persona();
    	p.setNombre(nombre);
    	p.setEdad(edad);
    	p.setSexo(sexo);
    	personaRepository.save(p);
    		
            return "Agregado correctamente";
    }

    Consumir:

    curl -i -X POST -d "nombre=Mariel&edad=51&sexo=F" http://localhost:8080/persona

Leer todas las personas

  • Método GET
  • La respuesta es Json
  • ocumentación JavaDoc
    /**
     * Obtiene todas las personas: GET http://{servidor}:{puerto}/persona
     * @return Lista de personas
     */
     @RequestMapping(method=RequestMethod.GET,produces={"application/json"})
        public Iterable<Persona> readPersonas() {
        return personaRepository.findAll();
     }

    Consumir:

    curl -i -X GET http://localhost:8080/persona

Leer una persona

  • Método GET
  • La respuesta es Json
  • Documentación JavaDoc
    /**
     * Obtiene una person por id: GET http://{servidor}:{puerto}/persona/{id}
     * @param id Id de la persona. En la Url
     * @return Una personas
     */
     @RequestMapping(value="/{id}",method=RequestMethod.GET,produces={"application/json"})
        public Persona readPersonaById(@PathVariable("id") int id) {
        return personaRepository.findPersona(id);
     }

    Consumir:

    curl -i -X GET http://localhost:8080/persona/7

Actualiza una persona

  • Método PUT
  • La respuesta es Json
  • Documentación JavaDoc
    /**
     * Actualiza una persona por Id: PUT http://{servidor}:{puerto}/persona/{id}
     * @param id Id de la persona. En la Url
     * @param nombre Nombre de la persona
     * @param edad Edad de la persona
     * @param sexo Sexo de la person
     * @return mensaje de 'Actualizado correctamente'
     */
     @RequestMapping(value="/{id}",method=RequestMethod.PUT,produces={"application/json"})
     public String updatePersona(@RequestParam String nombre,@RequestParam int edad,@RequestParam String sexo,@PathVariable("id") int id) {
        Persona p = new Persona();
        p.setIdPersona(id);
        p.setNombre(nombre);
        p.setEdad(edad);
        p.setSexo(sexo);
        personaRepository.save(p);
        return "Actualizado correctamente";
     }

    Consumir:

    curl -i -X PUT -d "nombre=Ariel&edad=100&sexo=M" http://localhost:8080/persona/7

Actualiza edad de una persona

  • Método PATCH
  • La respuesta es Json
  • Documentación JavaDoc
    /**
     * Actualiza la edad de una persona por Id: PATCH http://{servidor}:{puerto}/persona/{id}
     * @param id Id de la persona. En la Url
     * @param edad Edad de la persona
     * @return mensaje de 'Actualizando la edad correctamente'
     */
     @RequestMapping(value="/{id}",method=RequestMethod.PATCH,produces={"application/json"})
     public String updateParcialPersona(@PathVariable("id") int id,@RequestParam int edad) {
        Persona p = personaRepository.findPersona(id);
        p.setEdad(edad);
        personaRepository.save(p);
        return "Actualizando la edad correctamente";
     }

    Consumir:

    curl -i -X PATCH -d "edad=10" http://localhost:8080/persona/7

Elimina todas las personas

  • Método DELETE
  • La respuesta es Json
  • Documentación JavaDoc
    /**
     * Elimina todas las personas: DELETE http://{servidor}:{puerto}/persona
     * @return mensaje de 'Todo eliminado correctamente'
     */
     @RequestMapping(method=RequestMethod.DELETE,produces={"application/json"})
        public String deleteAllPersona() {
        personaRepository.deleteAll();
        return "Todo eliminado correctamente";
     }

    Consumir:

    curl -i -X DELETE http://localhost:8080/persona

Elimina una persona

  • Método DELETE
  • La respuesta es Json
  • Documentación JavaDoc
    /**
     * Elimina una persona por id: DELETE http://{servidor}:{puerto}/persona/{id}
     * @param id Id de la persona. En la Url
     * @return mensaje de 'Eliminado correctamente'
     */
     @RequestMapping(value="/{id}", method=RequestMethod.DELETE,produces={"application/json"})
     public String deletePersona(@PathVariable("id") int id) {
        Persona p = new Persona();
        p.setIdPersona(id);
        personaRepository.delete(p);
        return "Eliminado correctamente";
     }

    Consumir:

    curl -i -X DELETE http://localhost:8080/persona/7

Otras

Descargar proyecto

Se puede descargar el proyecto completo de https://github.com/arielolivagh/administrarPersona/tree/v1.0

API REST 3. Publicar y consumir

Descripción

Publicar y consumir algunos métodos creados del API REST.

Publicar y Consumir

Usando CRUD

Simplemente usando los métodos que ya nos proporciona el CRUD

Crear Persona

Insertamos un nuevo registro a la tabla Persona.

  1. En la clase PersonaController
  2. Se mapea con /add
  3. Recibe los parámetros nombre, edad y sexo
  4. Se carga el Objeto Persona y se ejecuta la función save().
  5. Retorna el mensaje Agregado correctamente.
    @RequestMapping("/add")
    public String addPersona(@RequestParam  String nombre,@RequestParam  int edad,@RequestParam  String sexo) {
       Persona p = new Persona();
       p.setNombre(nombre);
       p.setEdad(edad);
       p.setSexo(sexo);
       personaRepository.save(p);
    		
       return "Agregado correctamente";
    }

     

Resultado

Consultar todas las Personas

Consultamos todos los registros de la tabla Persona.

  1. En la clase PersonaController
  2. Se mapea con /all
  3. No recibe parámetros
  4. Retorna el resultado de la función findAll().
    @RequestMapping("/all")
       public Iterable<Persona> getPersona() {
       return personaRepository.findAll();
    }

Resultado

Borra una Persona

Borramos un registro de la tabla Persona.

  1. En la clase PersonaController
  2. Se mapea con /delete
  3. Recibe el parámetro id
  4. Se carga el Objeto Persona y se ejecuta la función delete().
  5. Retorna el mensaje Borrado correctamente.
    @RequestMapping("/delete")
     public String deletePersona(@RequestParam int id) {
     Persona p = new Persona();
     p.setIdPersona(id);
     personaRepository.delete(p);
     return "Borrado correctamente";
     }
    

Resultado

Total de Personas

Total de registros de la tabla Persona.

  1. En la clase PersonaController
  2. Se mapea con /total
  3. No recibe parámetros
  4. Retorna el resultado de la función count().
    @RequestMapping("/total")
       public long totalPersona() { 
       return personaRepository.count();
    }

Resultado

Creamos consultas propias

Podemos crear otras consultas de acuerdo a nuestras necesidades

Persona por tipo de sexo

Obtenemos las Personas por su tipo de sexo

  1. En la interface PersonaRepository agregamos la consulta que filtra por tipo de sexo
    @Query("select p from Persona p where p.sexo = ?1")
    Iterable<Persona> findBySexo(String sexo);
  2. En la clase PersonaController
  3. Se mapea con /allBySexo
  4. Recibe el parámetro sexo
  5. Retorna el resultado de la función findBySexo().
    @RequestMapping("/allBySexo")
    public Iterable<Persona> getPersonaBySexo(@RequestParam String sexo) {
       return personaRepository.findBySexo(sexo);
    }

Resultado

Persona por tipo de sexo con paginado

Obtenemos las Personas por su tipo de sexo. Los resultados se muestran paginados.

  1. En la interface PersonaRepository agregamos la consulta que filtra por tipo de sexo y agrega paginado.
    @Query("select p from Persona p where p.sexo = ?1")
    Page<Iterable<Persona>> findBySexoPag(@Param("sexo") String sexo,Pageable pageable);
  2. En la clase PersonaController
  3. Se mapea con /sexoPag
  4. Recibe los parámetros sexo, inicio y bloque. Estos 2 últimos parámetros para el paginado.
  5. Se pasa los parámetros de paginado a la función findBySexoPag()
  6. Retorna el resultado de la función findBySexoPag().
    @RequestMapping(value="/sexoPag")
       public Page<Iterable<Persona>> personaPag(@RequestParam String sexo,@RequestParam int inicio,@RequestParam int bloque) {
       return personaRepository.findBySexoPag(sexo, new PageRequest(inicio, bloque));
    }

Resultado

Persona por tipo de sexo con paginado y ordenado por un campo

Obtenemos las Personas por su tipo de sexo. Los resultados se muestran paginados y ordenados por un campo.

  1. En la clase PersonaController
  2. Se mapea con /sexoOrder
  3. Recibe los parámetros sexo, inicio, bloque y orden.
  4. Se pasa los parámetros de paginado y orden a la función findBySexoPag()
  5. Retorna el resultado de la función findBySexoPag().
    @RequestMapping(value="/sexoOrder")
       public Page<Iterable<Persona>> personaOrder(@RequestParam String sexo,@RequestParam int inicio,@RequestParam int bloque,@RequestParam String orden) {
       return personaRepository.findBySexoPag(sexo, new PageRequest(inicio, bloque,Sort.Direction.ASC, orden));
    }

Resultado

Otras opciones

Podemos crear otras consultas con otros métodos HTTP; POST, PUT, DELETE  o PATCH. También enviando HEAD en la petición.

Agregar Persona con el método POST

@RequestMapping(value="/add",method=RequestMethod.POST)
   public String addPersonaPost(@RequestParam  String nombre,@RequestParam  int edad,@RequestParam  String sexo) {
   Persona p = new Persona();
   p.setNombre(nombre);
   p.setEdad(edad);
   p.setSexo(sexo);
   personaRepository.save(p);
   return "Agregado correctamente";
}

Resultado. Utilizando curl:

Obtener Personas del sexo=M mediante HEAD

@RequestMapping(value="/sexo",headers={"sexo=M"})
   public Iterable<Persona> personaM() {
   return personaRepository.findBySexo("M");
}

Resultado. Utilizando curl:

Otros

Descargar Proyecto

Se puede descargar el proyecto completo de https://github.com/arielolivagh/gs-rest-service-complete/tree/v2.0

API REST 2. Conectarse a Base de Datos MySQL

Descripción

Para publicar y consumir algunos métodos del API REST vamos a crear y conectarnos a una Base de Datos MySQL.

Crear la Base de Datos

Con el siguiente script podemos crear la Base de Datos en MySQL.

-- MySQL dump 10.13  Distrib 5.7.19, for Linux (x86_64)
--
-- Host: localhost    Database: Pruebas
-- ------------------------------------------------------
-- Server version	5.7.19-0ubuntu0.16.04.1

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Table structure for table `Persona`
--

DROP TABLE IF EXISTS `persona`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `persona` (
  `idPersona` int(11) NOT NULL AUTO_INCREMENT,
  `nombre` varchar(200) NOT NULL,
  `edad` int(11) NOT NULL,
  `sexo` char(1) NOT NULL,
  PRIMARY KEY (`idPersona`),
  UNIQUE KEY `idPersona_UNIQUE` (`idPersona`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `Persona`
--
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

-- Dump completed on 2017-08-29 12:27:04

Agregar dependencias

Tomamos el proyecto http://arieloliva.com/api-rest-en-spring/.

En el archivo pom.xml agregamos las dependencias necesarias:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
   <groupId>com.h2database</groupId>
   <artifactId>h2</artifactId>
   <scope>runtime</scope>
</dependency>

Crear archivo Application.properties

En la ruta src/main/resources del proyecto creamos el archivo application.properties. Agregamos los siguientes parámetros de la conexión a la Base de Datos.

spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/Pruebas
spring.datasource.username=root
spring.datasource.password=root

Crear entity

Un entity es el modelado de una tabla. Nuestra tabla se llama Persona y tiene los campos:

Nombre Tipo Tamaño Null AutoIncrementar
idPersona int 11 no si
nombre varchar 200 no no
edad int 11 no no
sexo char 1 no no

Con estos datos podemos crear la Clase que modela la tabla.

  1. Crear Clase en el paquete hello con nombre Persona
  2. variables privadas idPersona, nombre, edad y sexo con sus respectivos set y get
  3. Anotación @Entity en la Clase para indicar que es un entity.
  4. Anotación @Id y @GeneratedValue(strategy=GenerationType.IDENTITY) en la variable idPersona. para indicar que es el identificador único y se auto genera.

El resultado final es:

package hello;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Persona implements Serializable {
	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private int idPersona;

	private int edad;
	private String nombre;
	private String sexo;

	public Persona() {
	}
	public int getIdPersona() {
		return this.idPersona;
	}
	public void setIdPersona(int idPersona) {
		this.idPersona = idPersona;
	}
	public int getEdad() {
		return this.edad;
	}
	public void setEdad(int edad) {
		this.edad = edad;
	}
	public String getNombre() {
		return this.nombre;
	}
	public void setNombre(String nombre) {
		this.nombre = nombre;
	}
	public String getSexo() {
		return this.sexo;
	}
	public void setSexo(String sexo) {
		this.sexo = sexo;
	}
}

Crear repositorio

Un repositorio es una interface con los métodos necesarios para acceso a la Base de Datos.

  1. Crear Interface en el paquete hello con nombre PersonaRepository
  2. Extendemos de CrudRepository<Persona, Long>. Crud (Create, Read, Update, Delete). Proporciona todos los métodos para Crear, Leer, Actualizar y Borrar los datos de la tabla.

El resultado final es:

package hello;

import org.springframework.data.repository.CrudRepository;

public interface PersonaRepository extends CrudRepository<Persona, Long>{
		
}

Crear controlador

  1. Crear Clase en el paquete hello con nombre PersonaController
  2. Anotaciones @RestController y @RequestMapping(path=»/persona») en la Clase. Para indicar que que es una API REST y la base del mapeo es /persona.
  3. Variable private PersonaRepository personaRepository con anotación @Autowired la cual obtiene el llamado a PersonaRepository.
  4. Método public Iterable<Persona> getPersona() con la anotación @RequestMapping(«/all») para obtener todos los datos de la tabla persona con el mapeo all.

El resultado final es:

package hello;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(path="/persona")
public class PersonaController {
	@Autowired
	private PersonaRepository personaRepository;
	
	@RequestMapping("/all")
	public Iterable<Persona> getPersona() {
        return personaRepository.findAll();
    }
}

Ejecutar aplicación

Después de ejecutar la aplicación el resultado obtenido es:

Otros

Descargar proyecto

Se puede descargar el proyecto completo de https://github.com/arielolivagh/gs-rest-service-complete/tree/v1.0

API REST 1. Spring

Descripción

Crear un API REST en Spring en sumamente sencillo. Vamos a crear uno utilizando lo siguiente:

  1. Eclipse
  2. Librerías de Spring
  3. Terminal
  4. Java

Pasos

Los pasos se detallan a continuación:

Instalar eclipse

  1. Descargar eclipse http://www.eclipse.org/downloads/packages/eclipse-ide-java-ee-developers/neon3
  2. Descomprimir en alguna ubicación.
  3. Ejecutar y seleccionar carpeta de trabajo (workspace) .

Instalar librerías de Spring

En la pagina https://spring.io/tools/sts/all de Spring nos muestra el repositorio para instalar STS (Spring Tool Suite).

  1. En eclipse ir a Help -> Install New Software… -> Add…
  2. En la ventana que se muestra escribir STS en name y http://dist.springsource.com/release/TOOLS/update/e4.7/ en Location. Y clic en OK.
  3. Seleccione todas las opciones y clic en Next.
  4. Aceptar licencia, esperar a que termine de instalarse y reiniciar eclipse.

Importar proyecto Spring

Con la instalación de STS podemos integrar proyectos en Spring ya terminados.

  1.  Ir a File -> New -> Other… -> Spring Boot -> Import Spring Getting Started Content. Clic en Next.
  2. Seleccionar Rest Service. Maven en la sección Build Type, complete en la sección Code Sets y clic en Finish.
  3. Se crear el proyecto y se abre una pagina de ayuda.

Crear jar del proyecto

  1. Abrir la terminal de eclipse o del Sistema Operativo
  2. Pasarse a la ubicación del proyecto
    cd .../developer_spring/ide/workspace/gs-rest-service-complete
  3. Ejecutar
    ./mvnw clean package
  4. En la terminal se muestra que la construcción fue correcta.

Ejecutar proyecto

  1. Abrir la terminal de eclipse o la terminal del Sistema Operativo.
  2. Pasarse a la ubicación del jar recién creado.
    cd .../developer_spring/ide/workspace/gs-rest-service-complete/target/
  3. Ejecutar jar.
    java -jar gs-rest-service-0.1.0.jar
  4. Inicia la carga de Spring
  5. El mensaje Started Application in … indica que se ha levantado correctamente.

Ver resultado

  1. Abrir explorador de Internet
  2. Capturar la URL http://localhost:8080/greeting
  3. O http://localhost:8080/greeting?name=Ariel

SpringBoot 8. Kanban con JPA

Clase DatabaseConfig.java

Es necesario agregar las clase DatabaseConfig.java en el paquete com.proyecto.config para el acceso a la Base de Datos. Su contenido es:

package com.proyecto.config;

import java.util.Properties;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * Contains database configurations.
 */
@Configuration
@EnableTransactionManagement
public class DatabaseConfig {

  // ------------------------
  // PUBLIC METHODS
  // ------------------------

  /**
   * DataSource definition for database connection. Settings are read from
   * the application.properties file (using the env object).
   */
  @Bean
  public DataSource dataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName(env.getProperty("db.driver"));
    dataSource.setUrl(env.getProperty("db.url"));
    dataSource.setUsername(env.getProperty("db.username"));
    dataSource.setPassword(env.getProperty("db.password"));
    return dataSource;
  }

  /**
   * Declare the JPA entity manager factory.
   */
  @Bean
  public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
    LocalContainerEntityManagerFactoryBean entityManagerFactory =
        new LocalContainerEntityManagerFactoryBean();
    
    entityManagerFactory.setDataSource(dataSource);
    
    // Classpath scanning of @Component, @Service, etc annotated class
    entityManagerFactory.setPackagesToScan(
        env.getProperty("entitymanager.packagesToScan"));
    
    // Vendor adapter
    HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    entityManagerFactory.setJpaVendorAdapter(vendorAdapter);
    
    // Hibernate properties
    Properties additionalProperties = new Properties();
    additionalProperties.put(
        "hibernate.dialect", 
        env.getProperty("hibernate.dialect"));
    additionalProperties.put(
        "hibernate.show_sql", 
        env.getProperty("hibernate.show_sql"));
    additionalProperties.put(
        "hibernate.hbm2ddl.auto", 
        env.getProperty("hibernate.hbm2ddl.auto"));
    entityManagerFactory.setJpaProperties(additionalProperties);
    
    return entityManagerFactory;
  }

  /**
   * Declare the transaction manager.
   */
  @Bean
  public JpaTransactionManager transactionManager() {
    JpaTransactionManager transactionManager = 
        new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(
        entityManagerFactory.getObject());
    return transactionManager;
  }
  
  /**
   * PersistenceExceptionTranslationPostProcessor is a bean post processor
   * which adds an advisor to any bean annotated with Repository so that any
   * platform-specific exceptions are caught and then rethrown as one
   * Spring's unchecked data access exceptions (i.e. a subclass of 
   * DataAccessException).
   */
  @Bean
  public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
    return new PersistenceExceptionTranslationPostProcessor();
  }


  // ------------------------
  // PRIVATE FIELDS
  // ------------------------
  
  @Autowired
  private Environment env;

  @Autowired
  private DataSource dataSource;

  @Autowired
  private LocalContainerEntityManagerFactoryBean entityManagerFactory;


} // class DatabaseConfig

Agregamos la dependencia del conector mysql en el archivo pom.xml.

    	<dependency>
      		<groupId>mysql</groupId>
      		<artifactId>mysql-connector-java</artifactId>
    	</dependency>

Y agregamos los datos de conexión en application.properties.

# Database
db.driver: com.mysql.jdbc.Driver
db.url: jdbc:mysql://localhost:3306/kanban?zeroDateTimeBehavior=convertToNull
db.username: root
db.password: root

# Hibernate
hibernate.dialect: org.hibernate.dialect.MySQL5Dialect
hibernate.show_sql: true
hibernate.hbm2ddl.auto: update
entitymanager.packagesToScan:com.proyecto

Crear repositorios

Los repositorios (Repository) son interfaces que contienen los métodos necesarios para el acceso a Base de Datos.

Vamos a generar las clases RepositoryArticle y RepositoryColumn en el paquete com.proyecto.repository.

Clase RepositoryArticle:

package com.proyecto.repository;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.proyecto.model.Article;

@Repository
@Transactional
public class RepositoryArticle {

	public List<Article> getList(){
		return  entityManager.createQuery("from Article").getResultList();
	}
	
	@PersistenceContext
	private EntityManager entityManager; 

}

Clase RepositoryColumn:

package com.proyecto.repository;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.proyecto.model.Column;

@Repository
@Transactional
public class RepositorioColumn {

	public List<Column> getList(){
		return  entityManager.createQuery("from Column").getResultList();
	}
	
	@PersistenceContext
	private EntityManager entityManager; 

}

Crear dominios

Los dominios son clases que implementan los conceptos del problema que estamos resolviendo. En este caso es el modelado de las tablas Article y Column.

Vamos a generar las clases DomainArticle y DomainColumn en el paquete com.proyecto.domain.

Clase DomainArticle:

package com.proyecto.domain;

import java.io.Serializable;

import com.proyecto.model.Column;

public class DomainArticle implements Serializable {
	private static final long serialVersionUID = 1L;
	private int id;
	private String descripcion;
	private String nombre;
	private String titulo;
	
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getDescripcion() {
		return descripcion;
	}
	public void setDescripcion(String descripcion) {
		this.descripcion = descripcion;
	}
	public String getNombre() {
		return nombre;
	}
	public void setNombre(String nombre) {
		this.nombre = nombre;
	}
	public String getTitulo() {
		return titulo;
	}
	public void setTitulo(String titulo) {
		this.titulo = titulo;
	}

}

Clase DomainColumn:

package com.proyecto.domain;

import java.io.Serializable;
import java.util.List;

public class DomainColumn implements Serializable {

	private static final long serialVersionUID = 1L;
	private int id;
	private String titulo;
	private List<DomainArticle> article;
	
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getTitulo() {
		return titulo;
	}
	public void setTitulo(String titulo) {
		this.titulo = titulo;
	}
	public List<DomainArticle> getArticle() {
		return article;
	}
	public void setArticle(List<DomainArticle> article) {
		this.article = article;
	}
	
}

Modificar Controller

Vamos a modificar los controller para accesar a los datos de las tablas correspondientes.

Clase ControllerArticle

Agregamos

@Autowired
private RepositoryArticle repositoryArticle;

Con esta anotación autoconectamos con el repositorio de Articles.

Creamos el método getList()

@RequestMapping(value="/articulos", method=RequestMethod.GET)
	@ResponseBody
	public List<DomainArticle> getList(){
		List<DomainArticle> articulos = new ArrayList<DomainArticle>();
		List<Article> listArticle = repositoryArticle.getList();
		for (Article article : listArticle) {
			DomainArticle domainArticle = new DomainArticle();
			domainArticle.setId(article.getId());
			domainArticle.setDescripcion(article.getDescripcion());
			domainArticle.setNombre(article.getNombre());
			domainArticle.setTitulo(article.getTitulo());
			
			articulos.add(domainArticle);
		}
		return articulos;
	}
  1. Se conecta al repositorio de Articles y extrae en un List todos los Articles.
  2. Recorremos el arreglo y lo cargamos en su respectivo domain.
  3. Al ejecutar http://localhost:8090/kanban/articulos

Clase ControllerColumn

Agregamos

@Autowired
private RepositoryColumn repositoryColumn;

Con esta anotación autoconectamos con el repositorio de Column.

Creamos el método getList()

@RequestMapping(value="/columnas", method=RequestMethod.GET)
	@ResponseBody
	public List<DomainColumn> getList(){
		List<DomainColumn> columnas = new ArrayList<DomainColumn>();
		List<Column> listColumn = repositoryColumn.getList();
		for (Column colum : listColumn) {
			DomainColumn domainColumn = new DomainColumn();
			domainColumn.setId(colum.getId());
			domainColumn.setTitulo(colum.getTitulo());
			
			List<DomainArticle> articulos = new ArrayList<DomainArticle>();
			for (Article articulo : colum.getArticles()) {
				DomainArticle domainArticle = new DomainArticle();
				domainArticle.setId(articulo.getId());
				domainArticle.setDescripcion(articulo.getDescripcion());
				domainArticle.setNombre(articulo.getNombre());
				domainArticle.setTitulo(articulo.getTitulo());
				articulos.add(domainArticle);
			}
			domainColumn.setArticle(articulos);
			columnas.add(domainColumn);
		}
		return columnas;
	}
  1. Se conecta al repositorio de Column y extrae en un List todos las columnas.
  2. Recorremos el arreglo y lo cargamos en su respectivo domain.
  3. Se extraen los Articles de cada columna y lo agrega a los articulos del dominio.
  4. Al ejecutar http://localhost:8090/kanban/columnas

 

Kanban con JPA

Nuevos archivos js y html

En resources/static/js duplicamos kanbanWeb.js. Le cambiamos el nombre a kanbanjpa.js. Cambiamos  la url a:

url:		"columnas",

y en el método buildsKanban cambiamos la variable columns a:

var columns = data;

En resources/templates duplicamos kanbanWeb.html. Le cambiamos el nombre a kanbanjpa.html. Cambiamos la referencia al script de kanban:

<!-- kanban  -->
<script type="text/javascript" src="js/kanbanjpa.js"	charset="UTF-8"></script>

Crear nuevo Controller

Creamos un nuevo controller en el paquete com.proyecto.controller llamado ControllerKanbanjpa.

package com.proyecto.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class ControllerKanbanjpa {
	@RequestMapping(value = "/jpa",method = RequestMethod.GET)
	public String index(){
		return "kanbanjpa";
	}
}

Al ejecutar http://localhost:8090/kanban/jpa. Tenemos las columnas y artículos de Kanban cargados desde la Base de Datos por medio de JPA.

Se puede descargar el proyecto completo de https://github.com/arielolivagh/kanbanjpa

 

SpringBoot 7. Crear modelo de la Base de Datos

Crear entitys de las tablas

Primero se debe verificar en la ventana Data Source Explorer que la conexión a las Base de Datos esta activa.

 

Clic derecho sobre el proyecto -> New -> Other… -> JPA -> JPA Entities from Tables.

JPA Entities from Tables

Seleccionar la conexión, el esquema y las tablas. También debemos asegurarnos que la opción List generated classes in persistence.xml este habilitado. En nuestro caso seleccionamos las tablas Article y Columns y clic en Next >.

Muestra la relaciones entre las tablas. Clic en Next.

En Key generador seleccionamos identity para que la llave que es numérica se incremente automáticamente. En el campo Package: de la sección Domain java class elegimos el paquete donde se generaran los entitys, en nuestro caso com.proyecto.model. Clic en Next >.

Aquí podemos editar cualquier campo de la tabla de forma individual desplegando la tabla y seleccionando el campo.

Lo recomendable es dejar todo como esta y clic en Finish.

Nos vamos al paquete de com.proyecto.model y podemos ver las clases entity que se generaron.

Descripción de entity

Los entity son JavaBean con anotaciones

  1. @Entity. Anotación que indica que es un entity
  2. @Table(name=”Columns”). Anotación que indica que hace referencia a la tabla correspondiente. Si  el entity se llama igual que la tabla no se muestra.
  3. @NamedQuery(name=»Column.findAll», query=»SELECT c FROM Column c»). Anotación que muestra los querys que podemos utilizar en la aplicación.
  4. @id. Id de la tabla
  5. @GeneratedValue(strategy=GenerationType.IDENTITY). Autoincrementa el id
  6. @OneToMany(mappedBy=»column»). Anotación que indica las relaciones con otras tablas.
  7. Cuenta con los métodos get y set como un JavaBean.

 

SpringBoot 6. Configurar JPA y la Base de Datos en Eclipse

Configurar JPA

En http://arieloliva.com/crearbasededatos/ creamos la Base de Datos Kanban. Vamos a conectar eclipse con está para habilitar JPA y crear los modelos.

Agregamos la dependencia JPA en el archivo pom.xml del proyecto.

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

Después habilitamos JPA.

Clic derecho sobre el proyecto -> Propiedades -> Project Facets

Clic en Convert to faceted form…

Habilitar JPA con la versión 2.1. Clic en ApplyOK.

Crear conexión a MySQL

Para crear la conexión a MySQL,

Clic derecho sobre el proyecto -> Propiedades -> JPA -> Connection -> Add connection…

Seleccionar MySQL, elegir un nombre para la conexión y clic en Next.

Clic en el icono New Driver Definition  Elegir el driver 5.1.

Pasarse a la pestaña JAR List, seleccionar el driver mysql-connector-java-5.1.0-bin.jar y clic en Remove JAR/Zip para eliminarlo.

Selección_158
Clic en Add JAR/Zip… Ir a la ruta …/developer/db y seleccionar el archivo mysql-connector-java-5.1.41.jar (Se puede descargar de http://mvnrepository.com/artifact/mysql/mysql-connector-java/5.1.41 ). Clic en OK.

Escribir los datos de la Base de Datos MySQL.

Database kanban
URL jdbc:mysql://localhost:3306/kanban
User name root
Password El seleccionado en la instalación

Clic en Test Connection para validar que la conexión es correcta. Clic en OK.

Selección_161
Clic en Next >,  en Finish y en OK.

Validar la conexión a MySQL

Sobre Eclipse en la pestaña Data Source Explorer se muestra la conexión recién creada. Clic con el botón derecho y Connect.

Introducir la contraseña y OK.


Podemos ver las tablas de la Base de Datos de MySQL.

 

 

SpringBoot 5. Crear proyecto SpringBoot en Eclipse

Crear proyecto Maven

Hay varias formas de crear el proyecto pero lo mas recomendable es utilizar el asistente en linea de Spring.

Vamos a la ruta https://start.spring.io/ Escribimos el Group y el Artifact. En la sección de Search for dependencies agregamos las dependencias Web, Thymeleaf y DevTools. Clic en Generate Project y guardar el archivo en una ruta local.

Descomprimir el archivo y copiarlo al workspace de Eclipse. Abrir eclipse, Clic derecho en el Explorador de Proyectos.

Import -> Import… -> Maven -> Existing Maven Projects

Clic en el botón Next.

Seleccionamos el proyecto kanban con el botón Browse… Clic en Finish.

Se muestra el proyecto en el Explorador de Proyectos.

Archivo pom.xml

Se agregan automáticamente las dependencias:

spring-boot-starter-web Indica que es una aplicación Web. Agrega la funcionalidad de @Controller, @RequestMapping, etc
spring-boot-starter-thymeleaf Plantillas de servidor para Web.
spring-boot-devtools Elimina la necesidad de reiniciar cada que se hace un cambio. Se recomienda solo para ambientes de desarrollo.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.proyecto</groupId>
	<artifactId>kanban</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>kanban</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.3.8.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

Clase principal

Es necesario una clase main como punto de inicio de la aplicación. Esta es com.proyecto.KanbanApplication. La anotación @SpringBootApplication indica en donde inicia la aplicación.

package com.proyecto;

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

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

Controladores

Para manejar peticiones Web es necesario crear controladores, los cuales manejan la solicitud a partir de una ruta de acceso. Creamos la clase ControllerIndex en el paquete com.proyecto.controller. Agregamos las anotaciones @Controller y @RequestMapping.

@Controller Indica que es un controlador
@RequestMapping Indica que Controller o método de un Controller tiene que direccionar cada llamada del cliente
package com.proyecto.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class ControllerIndex {
	@RequestMapping(value = "/",method = RequestMethod.GET)
	public String index(){
		return "kanban";
	}
}

Copiar archivos del proyecto anterior

Tomamos los archivos del proyecto anterior y los copiamos al proyecto de SpringBoot. Copiamos las carpetas css y js en la ruta scr/main/resource/static y el archivo kanban.html en scr/main/resource/templates.

Configurar application.properties

En este archivo application.properties que se encuentra en src/main/resource se pueden configurar muchos parámetros. Puede consultarlos en http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html. Solo vamos a agregar,

server.contextPath=/kanba Contexto de la aplicación
server.port=8090 Puerto de la aplicación
# Server
server.contextPath=/kanban
server.port=8090

Ejecutar aplicación

Para ejecutar la aplicación abrimos la clase com.proyecto.KanbanApplication. Clic con el botón derecho.

Run As -> Java Application.

Se inicia la aplicación.

El siguiente mensaje indica que la aplicación se levanto correctamente, además muestra el puerto.

Abrimos un explorador de Internet y tecleamos http://localhost:8090/kanban.

Json desde el Servidor

Creamos el Controller ControllerColumn. Este mostrara todas las columnas en formato Json. Los datos de retorno están hardcore, posteriormente veremos como extraerlos de una Base de Datos.

package com.proyecto.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class ControllerColumn {
	@RequestMapping("/column")
	@ResponseBody
	public String index(){
		return "{\"columns\":[{\"titulo\":\"Backlog\",\"article\":[{\"nombre\":\"Nombre\",\"titulo\":\"Titulo\",\"descripcion\":\"Descripcion\"},{\"nombre\":\"Sistema Kanban\",\"titulo\":\"FrontEnd\",\"descripcion\":\"Construir un sistema Kanbad de forma dinamica con json, javascript y jquery\"}]},{\"titulo\":\"Proceso\",\"article\":[]},{\"titulo\":\"Terminado\",\"article\":[]}]}";
	}

}

Si tecleamos en el explorador http://localhost:8090/kanban/column se muestra los datos en json.

/ 20170412164443
// http://localhost:8090/kanban/column

{
  "columns": [
    {
      "titulo": "Backlog",
      "article": [
        {
          "nombre": "Nombre",
          "titulo": "Titulo",
          "descripcion": "Descripcion"
        },
        {
          "nombre": "Sistema Kanban",
          "titulo": "FrontEnd",
          "descripcion": "Construir un sistema Kanbad de forma dinamica con json, javascript y jquery"
        }
      ]
    },
    {
      "titulo": "Proceso",
      "article": [
        
      ]
    },
    {
      "titulo": "Terminado",
      "article": [
        
      ]
    }
  ]
}

Creamos el Controller ControllerKanbanWeb. Este llama a kanbanWeb.xml con el mapeo /Web.

package com.proyecto.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class ControllerKanbanWeb {
	@RequestMapping(value = "/Web",method = RequestMethod.GET)
	public String index(){
		return "kanbanWeb";
	}
}

Duplicamos el archivo kanban.html en la ruta src/main/resource/ y lo nombramos kanbanWeb.html

Abrimos el archivo kanbanWeb.html y cambiamos la ruta del archivo kanban.js a kanbanWeb.js.

<!-- kanban  -->
<script type="text/javascript" src="js/kanbanWeb.js" charset="UTF-8"></script>

Duplicamos el archivo kanban.js en la ruta src/main/resource/static y lo nombramos kanbanWeb.js

Abrimos el archivo kanbanWeb.js y realizamos los siguientes cambios.

  1. Eliminamos las funciones Column y Article, Así como la creación de los arreglos column y articles.
  2. Crear la función buildsKanban que construye las columnas y los articulos de kanban.
    function buildsKanban(data){
    	var columns = data.columns;
    	var varArticle = null;
    	for(var i=0;i<columns.length;i++){
    		varColumna ="<div class=\"panel panel-primary kanban-col\">";
            varColumna+="	<div class=\"panel-heading\">"+columns[i].titulo+" <i class=\"fa fa-2x fa-plus-circle pull-right\"></i></div>";
            varColumna+="   <div class=\"panel-body\">";
            varColumna+="   	<div id=\""+columns[i].titulo+"\" class=\"kanban-centered\">";
            varColumna+="		</div>";
            varColumna+="   </div>";
            varColumna+="   <div class=\"panel-footer\">";
            varColumna+="   	<a href=\"#\">Add a card...</a>";
            varColumna+="   </div>";
            varColumna+="</div>";
    		$("#sortableKanbanBoards").append(varColumna);
    		
    		varArticle	= "";
    		for(var j=0;j<columns[i].article.length;j++){					
    			varArticle	+=  "<article class=\"kanban-entry grab\" id=\"item"+String(i)+String(j)+"\" draggable=\"true\">";
    			varArticle	+=  "  <div class=\"kanban-entry-inner\">";
    			varArticle	+=  "      <div class=\"kanban-label\">";
    			varArticle	+=  "          <h2><a href=\"#\">"+columns[i].article[j].nombre+"</a> <span>"+columns[i].article[j].titulo+"</span></h2>";
    			varArticle	+=  "          <p>"+columns[i].article[j].descripcion+"</p>";
    			varArticle	+=  "      </div>";
    			varArticle	+=  "</div>";
    			varArticle	+=  "</article>";
    		}
    		$("#"+columns[i].titulo).append(varArticle);
    	}
    }
  3. Hacer una llamada ajax para consumir el controller column y llame  a la funcion buildsKanban.
    $.ajax({
        	async:		false, 
            url:		"column",
            type:		'GET',
            dataType: 	'json',
            contentType:'application/json',
            mimeType:	'application/json',
            success: function(data, textStatus, jqXHR) {
            	buildsKanban(data);
            },error: function(jqXHR, textStatus, errorThrown){
            }          
        });

     

Abrimos un explorador de Internet y tecleamos http://localhost:8090/kanban/Web. Nos muestra la aplicación Kanban pero consumiendo los datos desde un servidor.

Se puede descargar el proyecto completo de https://github.com/arielolivagh/kanbanWeb

 

 

 

SpringBoot 4. Crear Base de Datos

La Base de Datos la vamos a crear en MySQL. Si no tiene instalado MySQL se puede apoyar en la liga http://arieloliva.com/java-ee-3-instalar-base-de-datos-mysql . Aquí también se explica como instalar MySQL Workbench. Con MySQL Workbench podemos generar una Base de Datos mediante un modelo. Se puede utilizar cualquier otra herramienta para crear la Base de Datos en MySQL o solo ejecutar el script al final de esta entrada.

Crear modelo en MySQL Workbench

Abrimos MySQL Workbench y seleccionamos la instancia local.

Selección_153

Pedirá la contraseña de root.

Selección_154

Entramos a la pantalla principal. Clic en File -> New Model.

Selección_155

Con + crea un nuevo esquema y con lo eliminamos. Creamos el esquema kanban. Sólo escribimos el Name, los demás parámetros se dejan predeterminados. Doble Clic en Add Diagram.

Crear las tablas y sus relaciones

Aquí ya podemos agregar las tablas de la Base de Datos. Damos clic en Place a New Table. y con otro clic sobre el espacio de trabajo se creara la tabla.

Selección_175

Doble clic en la tabla y se mostraran su propiedades en la parte inferior. Seleccionamos el nombre Columns. (Lo mas recomendable es que el nombre de una tabla sea en singular, pero Column es una palabra reservada de la Base de Datos)

Y en la pestaña Columns agregamos los campos id y titulo.

Nombre Tipo de dato PK(Primary Key) NN (No Null) UQ (Unique Key) AI(Autoincrementar)
id INT Si Si Si Si
titulo VARCHAR(100) No Si No No

 

Ahora creamos la tabla Article  con los campos id, nombre, titulo y descripcion.

Nombre Tipo de dato PK NN UQ AI
id INT Si Si Si Si
nombre VARCHAR(100) No Si No No
titulo VARCHAR(100) No Si No No
descripcion VARCHAR(250) No No No No

Relacionamos la tablas de 1 a muchos. Damos clic en Place a New 1:n Non-Identifying.

Selección_167

Clic en la tabla Article y luego clic en la tabla Columns.

Generar Base de Datos

Ya tenemos nuestro modelo ahora vamos a crear la Base de Datos; Database -> Forward Engineer… Seleccionamos la instancia Local y la contraseña. Clic en Next.

Selección_169

Todo predeterminado y clic en Next.

Selección_170

Debe estar habilitado Export MySQL Table Object. Clic en Next.

Selección_171

Se muestra el script de creación. Clic en Next.

Clic en Close.

Selección_173

Desde la pestaña de la instancia actualizamos los esquemas, seleccionamos kanban  y ejecutamos los querys:

select * from Columns;
select * from Article;

En la parte inferior se debe mostrar que las consultas fueron exitosas, indicando que las tablas ya existen.

Introducir datos

Introducimos datos en las tablas. Doble clic en la celda -> Teclear datos-> Clic en Apply -> Confirmar consultas. El id es unico no se puede repetir.

Para el caso de la tabla Article. El campo Columns_id debe coincidir con un id de un Columns, si no es así se muestra un error de foreign key.

Script para crear la Base de Datos

Por si solo quieres ejecutar el script y ahorrarte toda las creación del modelo.

-- MySQL Workbench Forward Engineering

SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES';

-- -----------------------------------------------------
-- Schema kanban
-- -----------------------------------------------------
CREATE SCHEMA IF NOT EXISTS `kanban` DEFAULT CHARACTER SET latin1 ;
USE `kanban` ;

-- -----------------------------------------------------
-- Table `kanban`.`Columns`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `kanban`.`Columns` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `titulo` VARCHAR(100) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE INDEX `id_UNIQUE` (`id` ASC))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


-- -----------------------------------------------------
-- Table `kanban`.`Article`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `kanban`.`Article` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `nombre` VARCHAR(100) NOT NULL,
  `titulo` VARCHAR(100) NOT NULL,
  `descripcion` VARCHAR(250) NULL DEFAULT NULL,
  `ColumnId` INT(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE INDEX `id_UNIQUE` (`id` ASC),
  INDEX `fk_Article_Column_idx` (`ColumnId` ASC),
  CONSTRAINT `fk_Article_Column`
    FOREIGN KEY (`ColumnId`)
    REFERENCES `kanban`.`Columns` (`id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1;


SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;