API Biblioteca completo (2) Repositorios

Los repositorios son nuestra capa de DAO, que nos la proporciona Spring Boot automáticamente y de manera ‘mágica’

Tenemos dos tipoos de repository, Crud y JPA. Crud solo proporciona comandos básicos de altas, bajas, modificaciones y consultas. JPA permite eso y mucho más.

package com.trifulcas.SpringBootBiblioteca.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.trifulcas.SpringBootBiblioteca.model.Genero;

public interface GeneroRepository extends JpaRepository<Genero, Integer> {

}

package com.trifulcas.SpringBootBiblioteca.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.trifulcas.SpringBootBiblioteca.model.Libro;

public interface LibroRepository extends JpaRepository<Libro, Integer> {

}

package com.trifulcas.SpringBootBiblioteca.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.trifulcas.SpringBootBiblioteca.model.Autor;

public interface AutorRepository extends JpaRepository<Autor, Integer> {

}

API Biblioteca completo (1) Entidades

Las entidades, que son las mismas que en hibernate:

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 +
                '}';
    }
}
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 + '\'' +
                '}';
    }
}

Entidades Biblioteca

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.OneToMany;
import java.util.Set;

@Entity
@Table(name = "genero")
public class Genero {

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

    private String nombre;

    @OneToMany(mappedBy = "genero")
    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 + '\'' +
                '}';
    }
}
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.JoinColumn;
import javax.persistence.Table;
import javax.persistence.ManyToMany;
import javax.persistence.JoinTable;
import javax.persistence.Column;
import java.util.Set;

@Entity
@Table(name = "libro")
public class Libro {

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

    @ManyToOne
    @JoinColumn(name = "idgenero")
    private Genero genero;

    private String titulo;

    private int paginas;

    @ManyToMany
    @JoinTable(
        name = "libro_autor",
        joinColumns = @JoinColumn(name = "idlibro"),
        inverseJoinColumns = @JoinColumn(name = "idautor")
    )
    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 +
                '}';
    }
}
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import java.util.Set;

@Entity
@Table(name = "autor")
public class Autor {

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

    private String nombre;

    @ManyToMany(mappedBy = "autores")
    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 + '\'' +
                '}';
    }
}

Biblioteca Genero

package com.trifulcas.SpringBootBiblioteca.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "genero")
public class Genero {

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

    private String nombre;

    // 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;
    }

    // Método toString (opcional)
    @Override
    public String toString() {
        return "Genero{" +
                "idgenero=" + idgenero +
                ", nombre='" + nombre + '\'' +
                '}';
    }
}

package com.trifulcas.SpringBootBiblioteca.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.trifulcas.SpringBootBiblioteca.model.Genero;

public interface GeneroRepository extends JpaRepository<Genero, Integer> {

}

package com.trifulcas.SpringBootBiblioteca.controller;

import java.util.List;

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.SpringBootBiblioteca.model.Genero;
import com.trifulcas.SpringBootBiblioteca.repository.GeneroRepository;

@RestController
@RequestMapping("/genero")
public class GeneroController {

	@Autowired
	GeneroRepository generoRepository;

	@GetMapping("")
	public List<Genero> getAll() {
		try {
			return generoRepository.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<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;
		}
	}

	@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;
		}
	}
}

Swagger

Añadimos la siguiente dependencia:

<dependency>
			<groupId>org.springdoc</groupId>
			<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
			<version>2.3.0</version>
		</dependency>

Y vamos a la siguiente url:

http://localhost:8080/swagger-ui/index.html

API Rest completa

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.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() {
		try {
			return categoryRepository.findAll();
		} catch (Exception ex) {
			System.out.println(ex.getMessage());
			return null;
		}
	}

	// Poner los valores en la URL, no parámetros nombrados
	@GetMapping("/{id}")
	public Category getById(@PathVariable int id) {
		System.out.println(id);

		try {
			return categoryRepository.findById(id).orElse(null);
		} catch (Exception ex) {
			System.out.println(ex.getMessage());
			return null;
		}
	}

	@PostMapping("")
	public Category add(@RequestBody Category cat) {
		System.out.println(cat);
		try {
			if (cat.getCategoryId() == 0 && cat.getName() != null) {
				return categoryRepository.save(cat);
			} else {
				return null;
			}
		} catch (Exception ex) {
			System.out.println(ex.getMessage());
			return null;
		}
	}

	@PutMapping("/{id}")
	public Category put(@RequestBody Category cat, @PathVariable int id) {
		System.out.println(cat);
		System.out.println(id);
		try {
			if (cat.getCategoryId() == id) {
				return categoryRepository.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);
			categoryRepository.deleteById(id);
			return id;
		} catch (Exception ex) {
			System.out.println(ex.getMessage());
			return 0;
		}
	}
}

RestController Parcial

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";
	}
}

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();
	}
}