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>