Hasta ahora, antes de empezar con las clases de Service, preparábamos una interfaz para darle forma a todos nuestros servicios, y, aunque eso está bien, puede que queráis pensar otra forma de hacerlo, y es con una clase abstracta.
La preparación
Como siempre, necesitamos la entidad, y el repositorio; eso no puede cambiar.
Para realizar la entidad, como siempre, implementaremos de Modelo, e incluiremos los atributos necesarios
@Entity
public class Pelicula implements Modelo {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id_pelicula;
@NotNull
private String pe_titulo;
private int pe_identificador;
public Pelicula() {
super();
}
public Pelicula(long id_pelicula, String pe_titulo, int pe_identificador) {
super();
this.id_pelicula = id_pelicula;
this.pe_titulo = pe_titulo;
this.pe_identificador = pe_identificador;
}
public long getId_pelicula() {
return id_pelicula;
}
public void setId_pelicula(long id_pelicula) {
this.id_pelicula = id_pelicula;
}
public String getPe_titulo() {
return pe_titulo;
}
public void setPe_titulo(String pe_titulo) {
this.pe_titulo = pe_titulo;
}
public int getPe_identificador() {
return pe_identificador;
}
public void setPe_identificador(int pe_identificador) {
this.pe_identificador = pe_identificador;
}
@Override
public String toString() {
return "Pelicula [id_pelicula=" + id_pelicula + ", pe_titulo=" + pe_titulo + ", pe_identificador="
+ pe_identificador + "]";
}
@Override
public boolean isValidInsert() {
return true;
}
@Override
public boolean isValidUpdate() {
return true;
}
}
Se trata de una entity muy simple, por lo que no vamos a preparar ni DTO, Asumo que en una aplicacion real, deberia ser mas compleja, pero para nosotros, ya esta bien.
Como siempre, trabajando con STS, he creado los tres atributos, y luego he pedido a eclipse que generase los dos constructores, los getters, los setters, y el toString.
Es poco mas trabajo que hacerlo con Lombock, y, a mi me queda mucho mas claro.
El repositorio, de momento solo necesitamos lo basico, de forma que en el package correspondiente, preparamos
public interface IPelicula extends JpaRepository<Pelicula, Long>{
}
Y otro tema listo
La clase abstracta
Ante todo aclarar que esta clase podía implementar la interface que hemos creado para los otros servicios, solo tendríamos que empezar con algo como
public abstract class IServicioMas<T, S, U extends JpaRepository<T, S>> implements IServicioNew<T, S>{
Vigilad porque he creado una nueva interface, que al final sustituirá a la antigua, pero como eso obliga a refactorizar, esta la utilizaremos de aquí en adelante, y lo demas, ya lo arreglaremos. Solo hemos añadido dos método más y hemos dejado un poco mas coherente los retornos, y los nombres….
La nueva interfaz es
@Service
public interface IServicioNew<T, S> {
public T insert(T t) throws DomainException, DAOException ;
public T update(T t) throws DomainException, DAOException ;
public T patch(T t) throws DomainException, DAOException ;
public boolean borrar(T t) throws DAOException;
public boolean borrarPorId(S s) throws DAOException;
public List<T> listarTodos();
public Optional<T>leerUno(S s);
public boolean existe(S s);
}
y, entonces, ¿para que quiero una clase abstracta?
He añadido la clase abstracta, precisamente porque he pensado que puedo encontrarme con código que repito en todos (o en muchos) servicios, y es precisamente el código que escribire aqui.
Tambien aclarar que todo esto no es mas que una herramienta para hacernos mas cómodo el código, por lo que sois vosotros los que necesitareis tomar decisiones acerca de como va a quedar el codigo en vuestro caso, y ya lo iremos comentando. Ahora os presento el codigo que he escrito. (podéis bajarlo de Github, al final del capitulo esta la direccion)
public abstract class IServicioMas<T, S, U extends JpaRepository<T, S>> implements IServicioNew<T, S>{
U cDao;
protected IServicioMas(final U peliculaRepository){
this.cDao = peliculaRepository;
}
public T insert(final T t) throws DomainException, DAOException {
return cDao.save(t);
}
public T update(final T t) throws DomainException, DAOException {
return cDao.save(t);
}
public abstract T patch(final T t) throws DomainException, DAOException ;
public boolean borrarPorId(final S s) throws DAOException {
if (!existe(s)) {
throw new DAOException("El registro:" + s + ", ya no existe");
}
cDao.deleteById(s);
return true;
}
public boolean borrar(final T t) throws DAOException {
cDao.delete(t);
return true;
}
public List<T> listarTodos() {
return cDao.findAll();
}
public Optional<T> leerUno(final S s) {
return cDao.findById(s);
}
public boolean existe(final S s) {
return cDao.existsById(s);
}
}
Definiendo la clase
Como ya suponeis, nuestros genéricos son
- T : la entity
- S : el tipo de id que utiliza nuestra clase
- U : el repositorio para nuestra entity, y ha de ser un JpaRepository
En nuestro caso. nuestro primer uso, pueden ser (como ejemplo):
- T : Pelicula.java
- S : Long, como el id que utilizaPelicula
- U : nuestro repositorio IPelicula.java
Para construir nuestra clase, vamos a pensar así, como si T fuera Pelicula… y demas, aunque escribiremos genéricos, claro
Tambien, podemos observar que esta clase no la verá Spring, ya que no la hemos calificado, y es correcto, Spring necesita las extensiones de la clase y no la abstract
Por lo demas, vamos a escribir todas las funciones que podamos resolver, para que nuestro usuario no tenga que escribir todo el servicio. Naturalmente, a nuestro usuario siempre le queda la posibilidad de hacer un @Override de los métodos cuando necesite hacer algo mas
Nosotros, por ejemplo, podemos ofrecer soluciones muy básicas para insert, update, borrarPorId, borrar, listarTodos, leerUno y existe
pero nos es totalmente imposible facilitar codigo para patch, con lo que esa la dejamos abstract, y asi obligamos a nuestro usuario el tener que escribirla.
Yo he decidido, en mi caso, escribir esto, pero, insisto, una vez mas que vosotros debéis decidir que métodos queréis forzar, y que codigo queréis colocar en el resto de casos, pero la mia, queda asi
public abstract class IServicioMas<T, S, U extends JpaRepository<T, S>> implements IServicioNew<T, S>{
U cDao;
protected IServicioMas(final U peliculaRepository){
this.cDao = peliculaRepository;
}
public T insert(final T t) throws DomainException, DAOException {
return cDao.save(t);
}
public T update(final T t) throws DomainException, DAOException {
return cDao.save(t);
}
public abstract T patch(final T t) throws DomainException, DAOException ;
public boolean borrarPorId(final S s) throws DAOException {
if (!existe(s)) {
throw new DAOException("El registro:" + s + ", ya no existe");
}
cDao.deleteById(s);
return true;
}
public boolean borrar(final T t) throws DAOException {
cDao.delete(t);
return true;
}
public List<T> listarTodos() {
return cDao.findAll();
}
public Optional<T> leerUno(final S s) {
return cDao.findById(s);
}
public boolean existe(final S s) {
return cDao.existsById(s);
}
}
Y nuestro servicio
Ahora, para escribir el servicio, deberemos extender la clase que estábamos comentando (IServicioMas), y no hace falta implementar nada, porque lo hicimos a nivel de clase padre.
@Service
public class PeliculaService extends IServicioMas<Pelicula, Long, IPelicula>{
private final IPelicula cDao;
PeliculaService(IPelicula peliculaRepository){
super(peliculaRepository);
this.cDao = peliculaRepository;
}
@Override
public Pelicula insert(Pelicula peli) throws DomainException, DAOException {
peli.setId_pelicula(0);
return cDao.save(peli);
}
@Override
public Pelicula update(Pelicula peli) throws DomainException, DAOException {
Optional<Pelicula> dbo = cDao.findById(peli.getId_pelicula());
if (dbo.isEmpty()) {
throw new DAOException("El registro:" + peli.getId_pelicula() + ", ya no existe");
}
return cDao.save(peli);
}
@Override
public Pelicula patch(Pelicula peli) throws DomainException, DAOException {
Optional<Pelicula> dbo = cDao.findById(peli.getId_pelicula());
if (dbo.isEmpty()) {
throw new DAOException("El registro:" + peli.getId_pelicula() + ", ya no existe");
}
Pelicula peliDbo = dbo.get();
if (peli.getPe_titulo() != null) {
peliDbo.setPe_titulo(peli.getPe_titulo());
}
if (peli.getPe_identificador() != 0) {
peliDbo.setPe_identificador(peli.getPe_identificador());
}
return cDao.save(peliDbo);
}
}
He sobrecargado todos los métodos que me han parecido oportuno, para mi forma de trabajar, y, aun así, me ha quedado un servicio mucho más sencillo
En este punto, ya podemos realizar el test de este Dao, y eso lo haremos en el siguiente artículo
Conclusión
Todo este desarrollo lo teneis explicado con mas detalle en youTube (a partir de 30/05/24) , y, aunque es conveniente que intentéis escribirlo TODO vosotros, si queréis renunciar a ello, lo teneis tambien en GitHub
Este desarrollo esta hecho para disponer de un fuente para explicar otros temas, tal y como se indica en Visión de conjunto con Spring
En YouTube
Relacionado
Descubre más desde Recursos para formacion
Suscríbete y recibe las últimas entradas en tu correo electrónico.