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

Como gestionar tipos optional

El tipo optional es un nuevo tipo que permite gestionar búsquedas en la base de datos que no devuelven un valor. Nos da más información que simplemente tener null.

https://www.baeldung.com/java-optional-return

¿Cómo gestionarlo, por ejemplo, en un controlador?

Opción a, convertir a nulo una búsqueda sin resultado

@GetMapping("/get")
	public Category getCategory(@RequestParam(value = "id", defaultValue = "0") String id) {
		try {
			Category cat=categoryRepository.findById(Integer.parseInt(id)).orElse(null);
			
			return cat;
		} catch (Exception ex) {
			return null;
		}
	}

Opción b, tratarlo como Optional

@GetMapping("/get")
	public Optional<Category> getCategory(@RequestParam(value = "id", defaultValue = "0") String id) {
		try {
			Optional<Category> cat=categoryRepository.findById(Integer.parseInt(id));
			
			return cat;
		} catch (Exception ex) {
			return Optional.empty();
		}
	}

En nuestro caso no hay problema porque el serializador de Spring lo trata bien.

Crear un modelo-controlador

Para hacer con Spring Boot un Modelo – Controlador, es decir, que podamos acceder a una base de datos y mostrar información tenemos que hacer los siguientes pasos:

1.- Crear un proyecto con las dependencias SpringBoot, JPA y Mysql

2.- Configurar el acceso a la base de datos:
application.properties

spring.jpa.hibernate.ddl-auto=none
spring.datasource.url=jdbc:mysql://localhost:3306/sakila
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.show-sql: true

3.- Crear una entidad, igual que hacía en hibernate

package com.trifulcas.SpringBootData;

import java.sql.Timestamp;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import jakarta.persistence.*;

@Entity
@Table(name = "category")
public class Category {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "category_id")
	private int categoryId;

	@Column(name = "name", nullable = false)
	private String name;

	@Column(name = "last_update", nullable = false)
	private Timestamp lastUpdate;

	
	public Category() {
		super();
		// TODO Auto-generated constructor stub
	}

	public Category(String name) {
		super();
		this.name = name;
		Date now = new Date();
		this.lastUpdate = new Timestamp(now.getTime());
	}

	public int getCategoryId() {
		return categoryId;
	}

	public void setCategoryId(int categoryId) {
		this.categoryId = categoryId;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Timestamp getLastUpdate() {
		return lastUpdate;
	}

	public void setLastUpdate(Timestamp lastUpdate) {
		this.lastUpdate = lastUpdate;
	}

	@Override
	public String toString() {
		return "Category [categoryId=" + categoryId + ", name=" + name + ", lastUpdate=" + lastUpdate + "]";
	}

}

4.- Crear un ‘repository’ para la entidad. ¿Qué es esto? Es una especie de DAO pero ya prefabricado.¡OJO! Es un interface, no una clase.

public interface CategoryRepository extends CrudRepository<Category, Integer> {

}

5.- Por último podremos usar todas las utilidades del repository en cualquier controlador. Para hacerlo tenemos que definir una clase del mismo tipo que el repository y anotarla con @AutoWired

package com.trifulcas.SpringBootData;

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

// Esta anotación serializa por defecto todo lo que las funciones retornen
@RestController
public class CategoryController {
	
	// Lo que hace es crear e inyectar un objeto de tipo
	// CRUD con la entidad 'Category'
	// Este objeto nos servirá como un DAO, nos permite crear, modificar
	// obtener y borrar cualquier elemento de la web
	// el objeto repository nos permite usar findall, findbyid, deletebyid, save...
	@Autowired
	private CategoryRepository categoryRepository;

	@GetMapping("/add")
	public String addNewCategory(
			@RequestParam(value = "name", defaultValue = "Nueva categoría") 
			String name) {
		Category cat = new Category(name);
		categoryRepository.save(cat);
		return "Saved "+cat.getCategoryId();
	}
	
	@GetMapping("/all")
	public Iterable<Category> viewAll() {
		return categoryRepository.findAll();
	}

@GetMapping("/delete")
	public String deleteCategory(
			@RequestParam(value = "id", defaultValue = "0") 
			String id) {
		try {
			categoryRepository.deleteById(Integer.parseInt(id));
			return "Borrado "+id;
		}catch(Exception ex) {
			return ex.getMessage();
		}
	}
	
}