package com.trifulcas.SpringBootAPI.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.trifulcas.SpringBootAPI.model.Category; import com.trifulcas.SpringBootAPI.repository.CategoryRepository; @RestController @RequestMapping("/category") public class CategoryController { @Autowired CategoryRepository categoryRepository; @GetMapping("") public List<Category> getAll() { return categoryRepository.findAll(); } // Poner los valores en la URL, no parámetros nombrados @GetMapping("/{id}") public Category getById(@PathVariable int id) { System.out.println(id); return categoryRepository.findById(id).orElse(null); } @PostMapping("") public Category add(@RequestBody Category cat) { System.out.println(cat); if (categoryRepository.existsById(cat.getCategoryId())) { return null; } return categoryRepository.save(cat); } @PutMapping("/put") public String put() { return "put"; } @PatchMapping("/patch") public String patch() { return "patch"; } @DeleteMapping("/delete") public String delete() { return "delete"; } }
Categoría: Java
Ejemplo map verbos en Controller
package com.trifulcas.SpringBootAPI.controller; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class CategoryController { @GetMapping("/get") public String get() { return "get"; } @PostMapping("/post") public String post() { return "post"; } @PutMapping("/put") public String put() { return "put"; } @PatchMapping("/patch") public String patch() { return "patch"; } @DeleteMapping("/delete") public String delete() { return "delete"; } }
Organización de los elementos en una app Spring
Cuando utilizamos la anotación @SpringBootApplication spring boot busca los elementos habituales tales como controladores, repositorios o entidades.
Si están en subpaquetes del paquete raíz no hay problema, los encuentra automáticamente
Elemento raíz
package com.trifulcas.SpringBootData
Subpaquetes
package com.trifulcas.SpringBootData.model;
Si no pasa así le tenemos que decir al springboot donde está cada elemento. Si no, nos dará error.
@SpringBootApplication // Esta anotación nos dice donde buscar los controladores @ComponentScan("com.trifulcas") // Esta donde buscar las entidades @EntityScan("com.trifulcas.models") // Donde buscar los repositorios @EnableJpaRepositories("com.trifulcas.repository") public class SpringBoot04Application { [...]
Country
Entidad
package com.trifulcas.SpringBootData; import java.sql.Timestamp; import java.util.Date; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; @Entity @Table(name = "country") public class Country { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "country_id") private int countryId; @Column(name = "country", nullable = false) private String country; @Column(name = "last_update", nullable = false) private Timestamp lastUpdate; public Country() { super(); // TODO Auto-generated constructor stub } public Country(String country) { super(); this.country = country; Date now = new Date(); this.lastUpdate = new Timestamp(now.getTime()); } public int getCountryId() { return countryId; } public void setCountryId(int countryId) { this.countryId = countryId; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public Timestamp getLastUpdate() { return lastUpdate; } public void setLastUpdate(Timestamp lastUpdate) { this.lastUpdate = lastUpdate; } @Override public String toString() { return "Country [countryId=" + countryId + ", name=" + country + ", lastUpdate=" + lastUpdate + "]"; } }
Repositorio
package com.trifulcas.SpringBootData; import org.springframework.data.repository.CrudRepository; public interface CountryRepository extends CrudRepository<Country, Integer> { }
Controlador
package com.trifulcas.SpringBootData; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; // Esta anotación serializa por defecto todo lo que las funciones retornen @RestController @RequestMapping("/country") // Esto permite poner un prefijo a todas las peticiones public class CountryController { // Lo que hace es crear e inyectar un objeto de tipo // CRUD con la entidad 'Country' // Este objeto nos servirá como un DAO, nos permite crear, modificar // obtener y borrar cualquier elemento de la web // el objeto repository nos permite usar findall, findbyid, deletebyid, save... @Autowired private CountryRepository countryRepository; @GetMapping("/add") public Country addNewCountry(@RequestParam(value = "name", defaultValue = "Nueva categoría") String name) { Country cat = new Country(name); countryRepository.save(cat); return cat; } @GetMapping("/delete") public String deleteCountry(@RequestParam(value = "id", defaultValue = "0") String id) { try { countryRepository.deleteById(Integer.parseInt(id)); return "Borrado " + id; } catch (Exception ex) { return ex.getMessage(); } } @GetMapping("/get") public Optional<Country> getCountry(@RequestParam(value = "id", defaultValue = "0") String id) { try { Optional<Country> cat = countryRepository.findById(Integer.parseInt(id)); return cat; } catch (Exception ex) { return Optional.empty(); } } @GetMapping("/all") public Iterable<Country> viewAll() { return countryRepository.findAll(); } }
Como gestionar tipos optional
El tipo optional es un nuevo tipo que permite gestionar búsquedas en la base de datos que no devuelven un valor. Nos da más información que simplemente tener null.
https://www.baeldung.com/java-optional-return
¿Cómo gestionarlo, por ejemplo, en un controlador?
Opción a, convertir a nulo una búsqueda sin resultado
@GetMapping("/get") public Category getCategory(@RequestParam(value = "id", defaultValue = "0") String id) { try { Category cat=categoryRepository.findById(Integer.parseInt(id)).orElse(null); return cat; } catch (Exception ex) { return null; } }
Opción b, tratarlo como Optional
@GetMapping("/get") public Optional<Category> getCategory(@RequestParam(value = "id", defaultValue = "0") String id) { try { Optional<Category> cat=categoryRepository.findById(Integer.parseInt(id)); return cat; } catch (Exception ex) { return Optional.empty(); } }
En nuestro caso no hay problema porque el serializador de Spring lo trata bien.
Crear un modelo-controlador
Para hacer con Spring Boot un Modelo – Controlador, es decir, que podamos acceder a una base de datos y mostrar información tenemos que hacer los siguientes pasos:
1.- Crear un proyecto con las dependencias SpringBoot, JPA y Mysql
2.- Configurar el acceso a la base de datos:
application.properties
spring.jpa.hibernate.ddl-auto=none spring.datasource.url=jdbc:mysql://localhost:3306/sakila spring.datasource.username=root spring.datasource.password= spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.show-sql: true
3.- Crear una entidad, igual que hacía en hibernate
package com.trifulcas.SpringBootData; import java.sql.Timestamp; import java.util.Date; import java.util.HashSet; import java.util.Set; import jakarta.persistence.*; @Entity @Table(name = "category") public class Category { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "category_id") private int categoryId; @Column(name = "name", nullable = false) private String name; @Column(name = "last_update", nullable = false) private Timestamp lastUpdate; public Category() { super(); // TODO Auto-generated constructor stub } public Category(String name) { super(); this.name = name; Date now = new Date(); this.lastUpdate = new Timestamp(now.getTime()); } public int getCategoryId() { return categoryId; } public void setCategoryId(int categoryId) { this.categoryId = categoryId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Timestamp getLastUpdate() { return lastUpdate; } public void setLastUpdate(Timestamp lastUpdate) { this.lastUpdate = lastUpdate; } @Override public String toString() { return "Category [categoryId=" + categoryId + ", name=" + name + ", lastUpdate=" + lastUpdate + "]"; } }
4.- Crear un ‘repository’ para la entidad. ¿Qué es esto? Es una especie de DAO pero ya prefabricado.¡OJO! Es un interface, no una clase.
public interface CategoryRepository extends CrudRepository<Category, Integer> { }
5.- Por último podremos usar todas las utilidades del repository en cualquier controlador. Para hacerlo tenemos que definir una clase del mismo tipo que el repository y anotarla con @AutoWired
package com.trifulcas.SpringBootData; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; // Esta anotación serializa por defecto todo lo que las funciones retornen @RestController public class CategoryController { // Lo que hace es crear e inyectar un objeto de tipo // CRUD con la entidad 'Category' // Este objeto nos servirá como un DAO, nos permite crear, modificar // obtener y borrar cualquier elemento de la web // el objeto repository nos permite usar findall, findbyid, deletebyid, save... @Autowired private CategoryRepository categoryRepository; @GetMapping("/add") public String addNewCategory( @RequestParam(value = "name", defaultValue = "Nueva categoría") String name) { Category cat = new Category(name); categoryRepository.save(cat); return "Saved "+cat.getCategoryId(); } @GetMapping("/all") public Iterable<Category> viewAll() { return categoryRepository.findAll(); } @GetMapping("/delete") public String deleteCategory( @RequestParam(value = "id", defaultValue = "0") String id) { try { categoryRepository.deleteById(Integer.parseInt(id)); return "Borrado "+id; }catch(Exception ex) { return ex.getMessage(); } } }
Solución ejercicio
package com.trifulcas.controller; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class EjercicioController { // Endpoint. Cualquier programa que acceda a este endpoint // Tendrá esta respuesta // url del servidor + '/ola' @GetMapping("/ola") public String ola() { return "Ola k ase???"; } @GetMapping("/normal") public String normal() { return "Hola ¿Qué tal?"; } @GetMapping("/personal") public String personal( @RequestParam(value = "nombre", defaultValue = "Anónimo") String nombre) { return String.format("Hola %s ¿Cómo estás?", nombre); } @GetMapping("/azar") public String azar() { return azarRandom(); } private String azarRandom() { List<String> mensajes = new ArrayList<>(Arrays.asList("Hola", "Que hay?", "Buenas", "Jelou", "Hey")); Random r=new Random(); return mensajes.get(r.nextInt(mensajes.size())); } }
Spring boot
Asó queda el main
package com.trifulcas.TestSpringBoot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; @SpringBootApplication @ComponentScan("com.trifulcas") public class TestSpringBootApplication { public static void main(String[] args) { SpringApplication.run(TestSpringBootApplication.class, args); } }
Así queda el controlador
package com.trifulcas.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { // Dentro del controlador pondré todas las entradas a mi app // Se mapean con Mapping y la ruta @GetMapping("/") public String hello(@RequestParam(value = "name", defaultValue = "World") String name) { return String.format("Hello %s!", name); } }
Controlador con más endpoints
package com.trifulcas.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { // Dentro del controlador pondré todas las entradas a mi app // Se mapean con Mapping y la ruta // Defino un punto de entrada que es la raiz @GetMapping("/") public String hello(@RequestParam(value = "name", defaultValue = "World") String name) { System.out.println("Han entrado en la raiz. Name vale"+name); return String.format("Hello %s!", name); } // mi punto de entrada es /pepe @GetMapping("/pepe") public String pepe() { System.out.println("Han entrado en pepe"); return "Me llamo pepe"; } // Punto de entrada /suma y recupero dos parámetros (a y b) con los que hago la suma @GetMapping("/suma") public String suma(@RequestParam(value = "a", defaultValue = "1") String a, @RequestParam(value = "b", defaultValue = "1") String b) { System.out.println("Han entrado en suma con valores "+a+","+b); return (Integer.parseInt(a)+Integer.parseInt(b))+""; } }
Ejemplos HQL
Session session = HibernateUtil.getSessionFactory().openSession(); // Ojo aquí, el from es caseinsensitive pero la entidad 'Autor' // SI LO ES tiene que ser como se llame la entidad // Esto es lo más sencillo, seleccionar todo de una entidad List<Autor> autores = session.createQuery("from Autor", Autor.class).list(); System.out.println(autores); // Podemos añadir condiciones con Where autores = session.createQuery("from Autor where idautor=6", Autor.class).list(); System.out.println(autores); // Podemos seleccionar algunas propiedades List<String> nombres = session.createQuery("select nombre from Autor", String.class).list(); System.out.println(nombres); // Podemos usar parámetros Query<Autor> query = session.createQuery("from Autor where idautor=:id", Autor.class); query.setParameter("id",6); autores=query.list(); System.out.println(autores); // Si nos fijamos después de cada query ponemos list() // Esto nos sirve para dos cosas: obtener una lista // Y, sobre todo, ejecutar la query. // Las queries son como los procedimientos preparados // No se lanzan hasta que se lo decimos
Ejercicio de trabajar con Hibernate
Por partes.
Crear un género:
Genero g=new Genero(); g.setNombre("SteamPunk"); DAO<Genero> gdao=new DAO<>(Genero.class); gdao.save(g);
Crear un libro:
DAO<Genero> gdao=new DAO<>(Genero.class); Genero g=gdao.get(8); Libro l=new Libro(); l.setTitulo("Luna cruel"); l.setPaginas(750); l.setGenero(g); DAO<Libro> ldao=new DAO<>(Libro.class); ldao.save(l);
Crear un autor
DAO<Libro> ldao=new DAO<>(Libro.class); Libro l=ldao.get(9); DAO<Autor> adao=new DAO<>(Autor.class); Autor a=new Autor(); a.setNombre("Juan Perez"); a.getLibros().add(l); adao.save(a);
Todo junto
// Todo junto DAO<Genero> gdao=new DAO<>(Genero.class); DAO<Libro> ldao=new DAO<>(Libro.class); DAO<Autor> adao=new DAO<>(Autor.class); // Creo el género Genero g=new Genero(); g.setNombre("Realismo sucio"); gdao.save(g); // Creo el libro Libro l=new Libro(); l.setTitulo("Cloacas de Barcelona"); l.setPaginas(750); // Le asigno el género que acao de crear l.setGenero(g); ldao.save(l); // Creo el autor Autor a=new Autor(); a.setNombre("Andreu Martín"); // Le añado el libro que acabo de crear a.getLibros().add(l); adao.save(a);
Autores por género.
Con un método:
package com.trifulcas.Biblioteca; import java.util.ArrayList; import java.util.List; import com.trifulcas.DAO.DAO; import com.trifulcas.models.Autor; import com.trifulcas.models.Genero; import com.trifulcas.models.Libro; public class GetAutores { public static void main(String[] args) { DAO<Genero> gdao=new DAO<>(Genero.class); Genero g=gdao.get(8); System.out.println(getAutores2(g)); System.out.println(g.getAutores()); } public static List<Autor> getAutores(Genero genero){ List<Autor> res=new ArrayList<>(); for(Libro libro:genero.getLibros()) { for(Autor autor:libro.getAutores()) { res.add(autor); } } return res; } public static List<Autor> getAutores2(Genero genero){ List<Autor> res=new ArrayList<>(); for(Libro libro:genero.getLibros()) { res.addAll(libro.getAutores()); } return res; } }
Dentro del POJO (no recomendable pero… ya se sabe 😉 )
package com.trifulcas.models; import java.util.ArrayList; import java.util.List; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; @Entity public class Genero { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "idgenero") private int idGenero; private String nombre; @OneToMany(mappedBy="genero") List<Libro> libros=new ArrayList(); public Genero() { super(); // TODO Auto-generated constructor stub } public int getIdGenero() { return idGenero; } public void setIdGenero(int idGenero) { this.idGenero = idGenero; } public String getNombre() { return nombre; } public void setNombre(String nombre) { this.nombre = nombre; } public List<Libro> getLibros() { return libros; } public void setLibros(List<Libro> libros) { this.libros = libros; } public List<Autor> getAutores() { List<Autor> res=new ArrayList<>(); for(Libro libro:this.getLibros()) { res.addAll(libro.getAutores()); } return res; } @Override public String toString() { return "Genero [idGenero=" + idGenero + ", nombre=" + nombre + "]"; } }