En github:
https://github.com/juanpablofuentes/JavaNetmind/tree/main/SpringBootVistas
Entidades:
package com.trifulcas.SpringBootBiblioteca.model; import java.util.Set; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.ManyToMany; import jakarta.persistence.Table; @Entity @Table(name = "autor") public class Autor { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int idautor; private String nombre; // Como todas las indicaciones de tablas intermedias y foreign keys // Las hemos puesto en libro aquí podemos usar solo autores @ManyToMany(mappedBy = "autores") @JsonIgnore // Para evitar bucles infinitos private Set<Libro> libros; // Constructor vacío public Autor() { } // Constructor con parámetros public Autor(String nombre) { this.nombre = nombre; } // Getters y Setters public int getIdautor() { return idautor; } public void setIdautor(int idautor) { this.idautor = idautor; } public String getNombre() { return nombre; } public void setNombre(String nombre) { this.nombre = nombre; } public Set<Libro> getLibros() { return libros; } public void setLibros(Set<Libro> libros) { this.libros = libros; } // Método toString (opcional) @Override public String toString() { return "Autor{" + "idautor=" + idautor + ", nombre='" + nombre + '\'' + '}'; } }
package com.trifulcas.SpringBootBiblioteca.model; import java.util.Set; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; @Entity @Table(name = "genero") public class Genero { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int idgenero; private String nombre; // Carga perezosa, pero al serializar siempre carga // Mapeamos por 'genero', que es el campo en la entidad relacionada @OneToMany(mappedBy = "genero") @JsonIgnore // Quitar este campo NO de la entidad SI de la srialización private Set<Libro> libros; // Constructor vacío public Genero() { } // Constructor con parámetros public Genero(String nombre) { this.nombre = nombre; } // Getters y Setters 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 Set<Libro> getLibros() { return libros; } public void setLibros(Set<Libro> libros) { this.libros = libros; } // Método toString (opcional) @Override public String toString() { return "Genero{" + "idgenero=" + idgenero + ", nombre='" + nombre + '\'' + '}'; } }
package com.trifulcas.SpringBootBiblioteca.model; import java.util.Set; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinTable; import jakarta.persistence.ManyToMany; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; @Entity @Table(name = "libro") public class Libro { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int idlibro; // Carga ansiosa. El nombre del campo es el mapping en genero @ManyToOne @JoinColumn(name = "idgenero") private Genero genero; private String titulo; private int paginas; // Cuando tenemos many to many tenemos que elegir donde montamos // toda la relación con la especificación de la tabla intermedia // Y los campos relacionados. Yo lo he hecho aquí pero podría ser // en autor, es lo mismo @ManyToMany @JoinTable( name = "libro_autor", joinColumns = @JoinColumn(name = "idlibro"), inverseJoinColumns = @JoinColumn(name = "idautor") ) // Utilizamos set porque son valores únicos pero podría ser List private Set<Autor> autores; // Constructor vacío public Libro() { } // Constructor con parámetros public Libro(Genero genero, String titulo, int paginas) { this.genero = genero; this.titulo = titulo; this.paginas = paginas; } // Getters y Setters public int getIdlibro() { return idlibro; } public void setIdlibro(int idlibro) { this.idlibro = idlibro; } public Genero getGenero() { return genero; } public void setGenero(Genero genero) { this.genero = genero; } public String getTitulo() { return titulo; } public void setTitulo(String titulo) { this.titulo = titulo; } public int getPaginas() { return paginas; } public void setPaginas(int paginas) { this.paginas = paginas; } public Set<Autor> getAutores() { return autores; } public void setAutores(Set<Autor> autores) { this.autores = autores; } // Método toString (opcional) @Override public String toString() { return "Libro{" + "idlibro=" + idlibro + ", genero=" + genero + ", titulo='" + titulo + '\'' + ", paginas=" + paginas + '}'; } }
Repositorios:
package com.trifulcas.SpringBootBiblioteca.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import com.trifulcas.SpringBootBiblioteca.model.Autor; import com.trifulcas.SpringBootBiblioteca.model.Genero; public interface AutorRepository extends JpaRepository<Autor, Integer> { List<Genero> findByLibrosGeneroNombreContaining(String cad); List<Autor> findByLibrosTituloContaining(String cad); }
package com.trifulcas.SpringBootBiblioteca.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import com.trifulcas.SpringBootBiblioteca.model.Genero; public interface GeneroRepository extends JpaRepository<Genero, Integer> { // Dentro del repositorio podemos crear consultas de una manera 'mágica' List<Genero> findByNombreContaining(String nombre); }
package com.trifulcas.SpringBootBiblioteca.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import com.trifulcas.SpringBootBiblioteca.model.Libro; public interface LibroRepository extends JpaRepository<Libro, Integer> { List<Libro> findByPaginasBetweenOrderByPaginasAsc(Integer a, Integer b); List<Libro> findByAutoresIdautor(Integer id); List<Libro> findByTituloContaining(String cadena); }
Controladores Vistas
package com.trifulcas.SpringBootVistas.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import com.trifulcas.SpringBootVistas.model.Autor; import com.trifulcas.SpringBootVistas.repository.AutorRepository; // La anotación nos indica que es un controlador normal, que requerirá una vista @Controller // Nos indica la ruta de entrada general a este controlador @RequestMapping("/autor") public class AutorController { // Necesitamos acceder a los datos por lo tanto creamos el repositorio // el autowired nos hace inyección de dependencia automática @Autowired AutorRepository autorRepository; // Aquí especificamos que accedemos vía get @GetMapping("") // Pongo el parámetro Model que nos permita pasar datos a la vista public String getAutores(Model model) { try { // Obtengo los datos como en la API rest List<Autor> autores = autorRepository.findAll(); // Paso la información a la vista vía model // La vista tendrá una variable 'autores' con la lista de autores model.addAttribute("autores", autores); // Le digo que me cargue la vista 'autores' la buscará en templates return "autores"; } catch (Exception ex) { System.out.println(ex.getMessage()); return "error"; } } // Mapeo que me pasen un parámetro id @GetMapping("/{id}") // Tengo el parámetro id que me pasan y el model para pasar datos a la vista public String getAutor(Model model, @PathVariable Integer id) { try { // Recupero el autor Autor autor = autorRepository.findById(id).orElse(null); if (autor != null) { // Lo paso a la vista model.addAttribute("autor", autor); // Devuelvo la vista return "autor"; } else { // Me he creado una vista para mostrar un error return "error"; } } catch (Exception ex) { System.out.println(ex.getMessage()); return "error"; } } // El primer mapeo es con get para simplemente mostrar la vista @GetMapping("/add") // Pasamos como parámetro el autor para que la vista lo pueda tener disponible public String addAutor(Autor autor) { // Simplemente mostramos la vista return "addautor"; } // Cuando desde la vista nos añaden el autor entramos por 'POST' @PostMapping("/add") // Con @Validated recuperamos los datos y los metemos dentro de una entidad, // spring lo hace solo // En result se guardan los datos de la validación, es decir ¿Lo que nos mandan // son datos válidos? Si es que sí, no dará error, en caso contrario // en result tenemos la lista de errores public String addAutorDatos(@Validated Autor autor, BindingResult result) { System.out.println(autor); System.out.println(result); try { // Si hay algún error volvemos a mostrar la vista y además // fields.error tendrá la información de los errores if (result.hasErrors()) { return "addautor"; } // Si no hay ningún error guardamos el autor autorRepository.save(autor); // Y en vez de devolver una vista, redirigimos al índice return "redirect:/autor"; } catch (Exception ex) { System.out.println(ex.getMessage()); return "error"; } } @GetMapping("/edit/{id}") // Usamos el model porque tenemos que recuperar al autor public String addAutor(@PathVariable Integer id, Model model) { try { // Primero, buscamos el autor que se quiere editar Autor autor = autorRepository.findById(id).orElse(null); if (autor != null) { // Añadimos el autor al modelo model.addAttribute("autor", autor); return "updateautor"; } else { return "error"; } } catch (Exception ex) { System.out.println(ex.getMessage()); return "error"; } } @PostMapping("/update/{id}") public String updateAutor(@PathVariable Integer id, @Validated Autor autor, BindingResult result) { System.out.println(autor); try { if (result.hasErrors()) { return "updateautor"; } autorRepository.save(autor); return "redirect:/autor"; } catch (Exception ex) { System.out.println(ex.getMessage()); return "error"; } } @GetMapping("/delete/{id}") // Usamos el model porque tenemos que recuperar al autor public String deleteAutor(@PathVariable Integer id, Model model) { try { // Primero, buscamos el autor que se quiere editar // Añadimos el autor al modelo autorRepository.deleteById(id); return "redirect:/autor"; } catch (Exception ex) { System.out.println(ex.getMessage()); return "error"; } } }
package com.trifulcas.SpringBootVistas.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import com.trifulcas.SpringBootVistas.model.Genero; import com.trifulcas.SpringBootVistas.repository.GeneroRepository; @Controller @RequestMapping("/genero") public class GeneroController { @Autowired GeneroRepository generoRepository; // La primera vez que estamos haciendo un MVC como dios manda @GetMapping("/{id}") public String getGenero(Model model, @PathVariable int id) { // Voy al modelo para obtener los datos del genero Genero genero = generoRepository.findById(id).orElse(null); // Una vez tengo esos datos los envío a la vista vía model model.addAttribute("genero", genero); // Lo que devolvemos aquí es el nombre de la vista // Spring boot automáticamente buscará una página index.html // En la carpeta resources/templates return "genero"; } // La primera vez que estamos haciendo un MVC como dios manda @GetMapping("") public String getGeneros(Model model) { // Voy al modelo para obtener los datos del genero List<Genero> generos = generoRepository.findAll(); // Una vez tengo esos datos los envío a la vista vía model model.addAttribute("generos", generos); // Lo que devolvemos aquí es el nombre de la vista // Spring boot automáticamente buscará una página index.html // En la carpeta resources/templates return "generos"; } // El primer mapeo es con get para simplemente mostrar la vista @GetMapping("/add") // Pasamos como parámetro el autor para que la vista lo pueda tener disponible public String addGenero(Genero genero) { // Simplemente mostramos la vista return "addgenero"; } // Cuando desde la vista nos añaden el autor entramos por 'POST' @PostMapping("/add") // Con @Validated recuperamos los datos y los metemos dentro de una entidad, // spring lo hace solo // En result se guardan los datos de la validación, es decir ¿Lo que nos mandan // son datos válidos? Si es que sí, no dará error, en caso contrario // en result tenemos la lista de errores public String addGeneroDatos(@Validated Genero genero, BindingResult result) { System.out.println(genero); System.out.println(result); try { // Si hay algún error volvemos a mostrar la vista y además // fields.error tendrá la información de los errores if (result.hasErrors()) { return "addautor"; } // Si no hay ningún error guardamos el autor generoRepository.save(genero); // Y en vez de devolver una vista, redirigimos al índice return "redirect:/genero"; } catch (Exception ex) { System.out.println(ex.getMessage()); return "error"; } } @GetMapping("/edit/{id}") // Usamos el model porque tenemos que recuperar al autor public String editGenero(@PathVariable Integer id, Model model) { try { // Primero, buscamos el autor que se quiere editar Genero genero= generoRepository.findById(id).orElse(null); if (genero != null) { // Añadimos el autor al modelo model.addAttribute("genero", genero); return "updategenero"; } else { return "error"; } } catch (Exception ex) { System.out.println(ex.getMessage()); return "error"; } } @PostMapping("/update/{id}") public String updateGenero(@PathVariable Integer id, @Validated Genero genero, BindingResult result) { System.out.println(genero); try { if (result.hasErrors()) { return "updategenero"; } generoRepository.save(genero); return "redirect:/genero"; } catch (Exception ex) { System.out.println(ex.getMessage()); return "error"; } } @GetMapping("/delete/{id}") // Usamos el model porque tenemos que recuperar al autor public String deleteGenero(@PathVariable Integer id, Model model) { try { // Primero, buscamos el autor que se quiere editar Genero genero= generoRepository.findById(id).orElse(null); if (genero != null) { // Añadimos el autor al modelo model.addAttribute("genero", genero); return "deletegenero"; } else { return "error"; } } catch (Exception ex) { System.out.println(ex.getMessage()); return "error"; } } @PostMapping("/delete/{id}") public String destroyGenero(@PathVariable Integer id, @Validated Genero genero, BindingResult result) { System.out.println(genero); try { generoRepository.delete(genero); return "redirect:/genero"; } catch (Exception ex) { System.out.println(ex.getMessage()); return "error"; } } }
package com.trifulcas.SpringBootVistas.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import com.trifulcas.SpringBootVistas.repository.GeneroRepository; @Controller public class MainController { @Autowired GeneroRepository generoRepository; @GetMapping("/") public String index(Model model) { // Para pasar datos a la vista usamos model model.addAttribute("nombre", "Pepito pérez"); // Lo que devolvemos aquí es el nombre de la vista // Spring boot automáticamente buscará una página index.html // En la carpeta resources/templates return "index"; } }
Controladores API
package com.trifulcas.SpringBootVistas.controller; import java.util.List; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; 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.SpringBootVistas.model.Autor; import com.trifulcas.SpringBootVistas.model.Libro; import com.trifulcas.SpringBootVistas.repository.AutorRepository; @RestController @RequestMapping("/api/autor") public class AutorRestController { @Autowired AutorRepository autorRepository; @GetMapping("") public List<Autor> getAll() { try { return autorRepository.findAll(); } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } // Poner los valores en la URL, no parámetros nombrados @GetMapping("/{id}") public ResponseEntity<Autor> getById(@PathVariable int id) { System.out.println(id); try { Autor cat = autorRepository.findById(id).orElse(null); if (cat != null) { return new ResponseEntity<>(cat, HttpStatus.OK); } else { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } } catch (Exception ex) { System.out.println(ex.getMessage()); return new ResponseEntity<>(HttpStatus.EXPECTATION_FAILED); } } // Poner los valores en la URL, no parámetros nombrados @GetMapping("/{id}/libros") public Set<Libro> getLibrosByIdAutor(@PathVariable int id) { System.out.println(id); try { Autor cat = autorRepository.findById(id).orElse(null); if (cat != null) { return cat.getLibros(); } else { return null; } } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } // Poner los valores en la URL, no parámetros nombrados @GetMapping("/titulo/{cadena}") public List<Autor> getAutoresByTitulo(@PathVariable String cadena) { System.out.println(cadena); try { return autorRepository.findByLibrosTituloContaining(cadena); } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } @PostMapping("") public Autor add(@RequestBody Autor cat) { System.out.println(cat); try { if (cat.getIdautor() == 0 && cat.getNombre() != null) { return autorRepository.save(cat); } else { return null; } } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } @PutMapping("/{id}") public Autor put(@RequestBody Autor cat, @PathVariable int id) { System.out.println(cat); System.out.println(id); try { if (cat.getIdautor() == id) { return autorRepository.save(cat); } else { return null; } } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } @DeleteMapping("/{id}") public int delete(@PathVariable int id) { try { System.out.println(id); autorRepository.deleteById(id); return id; } catch (Exception ex) { System.out.println(ex.getMessage()); return 0; } } }
package com.trifulcas.SpringBootVistas.controller; import java.util.List; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; 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.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.trifulcas.SpringBootVistas.model.Genero; import com.trifulcas.SpringBootVistas.model.Libro; import com.trifulcas.SpringBootVistas.repository.GeneroRepository; @RestController @RequestMapping("/api/genero") public class GeneroRestController { @Autowired GeneroRepository generoRepository; @GetMapping("") public List<Genero> getAll(@RequestParam(required = false) String nombre) { try { if (nombre == null) { return generoRepository.findAll(); } else { return generoRepository.findByNombreContaining(nombre); } } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } // Poner los valores en la URL, no parámetros nombrados @GetMapping("/{id}") public ResponseEntity<Genero> getById(@PathVariable int id) { System.out.println(id); try { Genero cat = generoRepository.findById(id).orElse(null); if (cat != null) { return new ResponseEntity<>(cat, HttpStatus.OK); } else { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } // Si yo quiero obtener los libros de un género, algo que he perdido // con el JSON ignore pues me lo monto yo @GetMapping("/{id}/libros") public Set<Libro> getLibrosByIdGenero(@PathVariable int id) { System.out.println(id); try { Genero cat = generoRepository.findById(id).orElse(null); if (cat != null) { return cat.getLibros(); } else { return null; } } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } @PostMapping("/nuevo/{nombre}") public Genero addNuevo(@PathVariable String nombre) { try { System.out.println(nombre); Genero nuevo = new Genero(nombre); generoRepository.save(nuevo); return nuevo; } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } @PostMapping("") public Genero add(@RequestBody Genero cat) { System.out.println(cat); try { if (cat.getIdgenero() == 0 && cat.getNombre() != null) { return generoRepository.save(cat); } else { return null; } } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } @PutMapping("/{id}") public Genero put(@RequestBody Genero cat, @PathVariable int id) { System.out.println(cat); System.out.println(id); try { if (cat.getIdgenero() == id) { return generoRepository.save(cat); } else { return null; } } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } @DeleteMapping("/{id}") public int delete(@PathVariable int id) { try { System.out.println(id); generoRepository.deleteById(id); return id; } catch (Exception ex) { System.out.println(ex.getMessage()); return 0; } } }
package com.trifulcas.SpringBootVistas.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; 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.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.trifulcas.SpringBootVistas.model.Autor; import com.trifulcas.SpringBootVistas.model.Libro; import com.trifulcas.SpringBootVistas.repository.AutorRepository; import com.trifulcas.SpringBootVistas.repository.LibroRepository; @RestController @RequestMapping("/api/libro") public class LibroRestController { @Autowired LibroRepository libroRepository; @Autowired AutorRepository autorRepository; private int pageSize = 5; @GetMapping("") public List<Libro> getAll(@RequestParam(required = false) Integer pagina) { try { if (pagina == null) { return libroRepository.findAll(); } else { return libroRepository.findAll(PageRequest.of(pagina, pageSize)).getContent(); } } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } @GetMapping("/proceso") public String procesarlibros() { try { System.out.println("Esto es un ejemplo sencillo de un proceso"); // Imaginemos que yo quiero obtener una cadena con la primera letra // de cada título // Y para no bloquear la base de datos pidiendo todos los libros los hago por // páginas int pagina = 0; String res = ""; List<Libro> libros = libroRepository.findAll(PageRequest.of(pagina, pageSize)).getContent(); while (libros.size() > 0) { for (Libro libro : libros) { res += libro.getTitulo().substring(0, 1); } res+="#"; pagina++; libros = libroRepository.findAll(PageRequest.of(pagina, pageSize)).getContent(); } return res; } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } @GetMapping("/autor/{id}") public List<Libro> getByIdAutor(@PathVariable int id) { System.out.println(id); try { return libroRepository.findByAutoresIdautor(id); } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } @GetMapping("/titulo/{cadena}") public List<Libro> getByTitle(@PathVariable String cadena) { System.out.println(cadena); try { return libroRepository.findByTituloContaining(cadena); } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } // Poner los valores en la URL, no parámetros nombrados @GetMapping("/{id}") public ResponseEntity<Libro> getById(@PathVariable int id) { System.out.println(id); try { Libro cat = libroRepository.findById(id).orElse(null); if (cat != null) { return new ResponseEntity<>(cat, HttpStatus.OK); } else { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } @PostMapping("") public Libro add(@RequestBody Libro cat) { System.out.println(cat); try { if (cat.getIdlibro() == 0 && cat.getTitulo() != null) { return libroRepository.save(cat); } else { return null; } } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } @PutMapping("/{id}") public Libro put(@RequestBody Libro cat, @PathVariable int id) { System.out.println(cat); System.out.println(id); try { if (cat.getIdlibro() == id) { return libroRepository.save(cat); } else { return null; } } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } @DeleteMapping("/{id}") public int delete(@PathVariable int id) { try { System.out.println(id); libroRepository.deleteById(id); return id; } catch (Exception ex) { System.out.println(ex.getMessage()); return 0; } } // Gestión de libros y autores @PostMapping("{idlibro}/autor/{idautor}") public Libro addLibroAutor(@PathVariable int idlibro, @PathVariable int idautor) { System.out.println(idlibro); System.out.println(idautor); try { Libro libro = libroRepository.findById(idlibro).orElse(null); Autor autor = autorRepository.findById(idautor).orElse(null); if (libro != null && autor != null) { libro.getAutores().add(autor); return libroRepository.save(libro); } else { return null; } } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } // Gestión de libros y autores @DeleteMapping("{idlibro}/autor/{idautor}") public Libro deleteLibroAutor(@PathVariable int idlibro, @PathVariable int idautor) { System.out.println(idlibro); System.out.println(idautor); try { Libro libro = libroRepository.findById(idlibro).orElse(null); Autor autor = autorRepository.findById(idautor).orElse(null); if (libro != null && autor != null) { libro.getAutores().remove(autor); return libroRepository.save(libro); } else { return null; } } catch (Exception ex) { System.out.println(ex.getMessage()); return null; } } }
Vistas:
addautor
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Añadir Autor</title> <!-- Latest compiled and minified CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Latest compiled JavaScript --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> </head> <body> <h2>Añadir Autor</h2> <!-- Lo primero es hacer un formulario con los campos de autor, en este caso el nombre --> <form th:action="@{/autor/add}" th:object="${autor}" method="POST"> <label for="name">Nombre</label> <!-- el campo hace referencia a la propiedad nombre --> <input type="text" th:field="*{nombre}" id="nombre" placeholder="Nombre"> <!-- Si hay algún error lo mostramos (viene de result) --> <span th:if="${#fields.hasErrors('nombre')}" th:errors="*{nombre}"></span> <input class="btn btn-success" type="submit" value="Añadir"> <a class="btn btn-primary" th:href="@{/autor}">Ir al índice</a> </form> </body> </html>
addgenero
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Añadir Género</title> <!-- Latest compiled and minified CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Latest compiled JavaScript --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> </head> <body> <h2>Añadir Género</h2> <!-- Lo primero es hacer un formulario con los campos de genero, en este caso el nombre --> <form th:action="@{/genero/add}" th:object="${genero}" method="POST"> <label for="name">Nombre</label> <!-- el campo hace referencia a la propiedad nombre --> <input type="text" th:field="*{nombre}" id="nombre" placeholder="Nombre"> <!-- Si hay algún error lo mostramos (viene de result) --> <span th:if="${#fields.hasErrors('nombre')}" th:errors="*{nombre}"></span> <input class="btn btn-success" type="submit" value="Añadir"> <a class="btn btn-primary" th:href="@{/genero}">Ir al índice</a> </form> </body> </html>
autor
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Autor</title> <!-- Latest compiled and minified CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Latest compiled JavaScript --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> </head> <body> <h2>Autor</h2> <!-- El th:each es como un bucle foreach, en este caso le estamos diciendo que recorra la variable autores y lo guarde en una variable llamada autor --> <div class="card w-50"> <!-- Si yo estoy guardando cada elemento en una variable llamada 'autor' puedo acceder a sus propiedades y mostrarlas --> <div class="card-header"><span th:text="${autor.idautor}"></span></div> <!-- Cuando accedemos a la propiedad nombre spring boot busca el getter Es decir, intentará acceder a getNombre() --> <div class="card-body"><span th:text="${autor.nombre}"></span></div> </div> <table class="table table-striped"> <thead> <tr> <th>Id</th> <th>Titulo</th> <th>Páginas</th> </tr> </thead> <tbody> <!-- Puedo acceder a la propiedad libros porque mi entidad autor tiene un getLibros() que me devuelve los libros y los recorro con el each --> <tr th:each="libro: ${autor.libros}"> <td><span th:text="${libro.idlibro}"></span></td> <td><span th:text="${libro.titulo}"></span></td> <td><span th:text="${libro.paginas}"></span></td> </tr> </tbody> </table> <a class="btn btn-primary" th:href="@{/autor}">Ir al índice</a> </body> </html>
autores
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Lista de autores</title> <!-- Latest compiled and minified CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Latest compiled JavaScript --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> </head> <body> <h2>Autores</h2> <!-- El th:each es como un bucle foreach, en este caso le estamos diciendo que recorra la variable autores y lo guarde en una variable llamada autor --> <a class="btn btn-success" th:href="@{/autor/add}">Añadir autor</a> <a class="btn btn-primary" th:href="@{/}">Ir al índice</a> <div class="card w-50" th:each="autor: ${autores}"> <!-- Si yo estoy guardando cada elemento en una variable llamada 'autor' puedo acceder a sus propiedades y mostrarlas Pongo un enlace al detalle del autor --> <div class="card-header"><a class="btn btn-primary" th:href="@{/autor/{id}(id=${autor.idautor})}"><span th:text="${autor.idautor}"></span></a></div> <!-- Cuando accedemos a la propiedad nombre spring boot busca el getter Es decir, intentará acceder a getNombre() --> <div class="card-body"><span th:text="${autor.nombre}"></span> <a class="btn btn-secondary" th:href="@{/autor/edit/{id}(id=${autor.idautor})}">Editar</a> <a class="btn btn-danger" th:href="@{/autor/delete/{id}(id=${autor.idautor})}">Borrar</a> </div> </div> </body> </html>
deletegenero
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Editar Género</title> <!-- Latest compiled and minified CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Latest compiled JavaScript --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> </head> <body> <h2>Borrar Género</h2> <!-- Lo primero es hacer un formulario con los campos de autor, en este caso el nombre --> <form th:action="@{/genero/delete/{id}(id=${genero.idgenero})}" th:object="${genero}" method="POST"> <input type="hidden" th:field="*{idgenero}" id="idgenero"> <label for="name">¿Está seguro de que quiere eliminar este género?</label> <!-- el campo hace referencia a la propiedad nombre --> <input type="text" th:field="*{nombre}" id="nombre" placeholder="Nombre" readonly> <input class="btn btn-success" type="submit" value="Eliminar"> <a class="btn btn-primary" th:href="@{/genero}">Ir al índice</a> </form> </body> </html>
error
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Error</title> <!-- Latest compiled and minified CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Latest compiled JavaScript --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> </head> <body> <div class="alert alert-danger"> <strong>Error</strong> La petición ha generado un error. </div> <!-- Creo un enlace con th:href en principio me calcula la ruta adecuada --> <a class="btn btn-primary" th:href="@{/autor}">Ir al índice</a> </body> </html>
genero
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Genero</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Latest compiled JavaScript --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> </head> <body> <h2>Género</h2> <!-- A mí me han pasado los datos en la entidad genero Y muestro las propiedades de esa entidad --> <p th:text="${genero.idgenero}"></p> <!-- Lo que hace Spring aquí es llamar al método genero.getNombre()--> <p th:text="${genero.nombre}"></p> <table class="table table-striped"> <thead> <tr> <th>Id</th> <th>Titulo</th> <th>Páginas</th> </tr> </thead> <tbody> <!-- Puedo acceder a la propiedad libros porque mi entidad autor tiene un getLibros() que me devuelve los libros y los recorro con el each --> <tr th:each="libro: ${genero.libros}"> <td><span th:text="${libro.idlibro}"></span></td> <td><span th:text="${libro.titulo}"></span></td> <td><span th:text="${libro.paginas}"></span></td> </tr> </tbody> </table> <a class="btn btn-primary" th:href="@{/genero}">Ir al índice</a> </body> </html>
generos
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Generos</title> <!-- Latest compiled and minified CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Latest compiled JavaScript --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> </head> <body> <h2>Géneros</h2> <a class="btn btn-success" th:href="@{/genero/add}">Añadir género</a> <a class="btn btn-primary" th:href="@{/}">Ir al índice</a> <div class="card w-50" th:each="genero: ${generos}"> <div class="card-header"><a class="btn btn-primary" th:href="@{/genero/{id}(id=${genero.idgenero})}"><span th:text="${genero.idgenero}"></span></a></div> <div class="card-body"><span th:text="${genero.nombre}"></span> <a class="btn btn-secondary" th:href="@{/genero/edit/{id}(id=${genero.idgenero})}">Editar</a> <a class="btn btn-danger" th:href="@{/genero/delete/{id}(id=${genero.idgenero})}">Eliminar</a> </div> </div> </body> </html>
index
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Gestión biblioteca</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Latest compiled JavaScript --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> </head> <body> <a class="btn btn-success" th:href="@{/autor}">Autores</a> <a class="btn btn-success" th:href="@{/genero}">Géneros</a> </body> </html>
updateautor
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Editar Autor</title> <!-- Latest compiled and minified CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Latest compiled JavaScript --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> </head> <body> <h2>Editar Autor</h2> <!-- Lo primero es hacer un formulario con los campos de autor, en este caso el nombre --> <form th:action="@{/autor/update/{id}(id=${autor.idautor})}" th:object="${autor}" method="POST"> <input type="hidden" th:field="*{idautor}" id="idautor"> <label for="name">Nombre</label> <!-- el campo hace referencia a la propiedad nombre --> <input type="text" th:field="*{nombre}" id="nombre" placeholder="Nombre"> <!-- Si hay algún error lo mostramos (viene de result) --> <span th:if="${#fields.hasErrors('nombre')}" th:errors="*{nombre}"></span> <input class="btn btn-success" type="submit" value="Modificar"> <a class="btn btn-primary" th:href="@{/autor}">Ir al índice</a> </form> </body> </html>
updategenero
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Editar Género</title> <!-- Latest compiled and minified CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- Latest compiled JavaScript --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> </head> <body> <h2>Editar Género</h2> <!-- Lo primero es hacer un formulario con los campos de autor, en este caso el nombre --> <form th:action="@{/genero/update/{id}(id=${genero.idgenero})}" th:object="${genero}" method="POST"> <input type="hidden" th:field="*{idgenero}" id="idgenero"> <label for="name">Nombre</label> <!-- el campo hace referencia a la propiedad nombre --> <input type="text" th:field="*{nombre}" id="nombre" placeholder="Nombre"> <!-- Si hay algún error lo mostramos (viene de result) --> <span th:if="${#fields.hasErrors('nombre')}" th:errors="*{nombre}"></span> <input class="btn btn-success" type="submit" value="Modificar"> <a class="btn btn-primary" th:href="@{/genero}">Ir al índice</a> </form> </body> </html>