Commit 9ca0772c by Antonio Rueda

Resuelto merge con modelo y uso de notaciones jpa de jakarta

parents 1c879729 9ef7b48f
Showing with 292 additions and 159 deletions
## Proyecto UJACoin con persistencia mediante JPA/Hibernate/MySQL
### Conexión con la base de datos
La forma más sencilla de preparar la base de datos es utilizando la imagen
`mysql` de docker, para ello hay que tener instalado Docker Desktop
(https://www.docker.com/products/docker-desktop). Después basta con ejecutar
los siguientes comandos:
```
docker run -d -p 33060:3306 --name mysql-db -e MYSQL_ROOT_PASSWORD=secret mysql
```
Esto descarga e instala la imagen oficial de mysql (última versión).
Después arranca el contenedor, define _secret_ como clave de root y
asocia MySQL al puerto de la máquina anfitrión 33060.
```
docker exec mysql-db mysql -psecret -e "create database ujacoin; use ujacoin; create user 'ujacoin' identified by 'secret'; grant all privileges on ujacoin.* to 'ujacoin'@'%'"
```
Este comando ejecuta la utilidad de administración `mysql` dentro del contenedor,
crea la base de datos *ujacoin*, un usuario con el mismo nombre y clave _secret_
y finalmente le otorga los permisos necesarios para trabajar con la base
de datos.
Para el testing, crear una nueva base de datos ujacoin_test y dar permisos al usuario creado anteriormente.
```
docker exec mysql-db mysql -psecret -e "create database ujacoin_test; use ujacon_test; grant all privileges on ujacoin_test.* to 'ujacoin'@'%'"
```
......@@ -7,8 +7,8 @@
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<parent>
......@@ -25,10 +25,27 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
......@@ -40,18 +57,6 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<!--
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>3.0.3</version>
</dependency>
-->
</dependencies>
<build>
......
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.boot.autoconfigure.domain.EntityScan;
/**
*
* @author admin
* Clase principal
* @author ajrueda
*/
@SpringBootApplication(scanBasePackages="es.ujaen.dae.ujacoin.servicios")
@SpringBootApplication(scanBasePackages={
"es.ujaen.dae.ujacoin.servicios",
"es.ujaen.dae.ujacoin.repositorios"
})
@EntityScan(basePackages="es.ujaen.dae.ujacoin.entidades")
public class UjaCoinApp {
public static void main(String[] args) throws Exception {
// Creación de servidor
SpringApplication servidor = new SpringApplication(UjaCoinApp.class);
ApplicationContext context = servidor.run(args);
SpringApplication.run(UjaCoinApp.class, args);
}
}
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.entidades;
import es.ujaen.dae.ujacoin.util.ExprReg;
import es.ujaen.dae.ujacoin.util.CodificadorMd5;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Past;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
......@@ -21,8 +25,12 @@ import java.util.Optional;
* Cliente del banco virtual UjaCoin
* @author ajrueda
*/
public class Cliente {
@Entity
public class Cliente implements Serializable {
/** DNI del cliente*/
@Id
@NotNull
@Size(min=9, max=9)
@Pattern(regexp=ExprReg.DNI)
String dni;
/** Nombre completo */
......@@ -30,27 +38,39 @@ public class Cliente {
String nombre;
/** Fecha de nacimiento */
@NotNull
@Past
LocalDate fNacimiento;
/** Dirección del domicilio */
@NotBlank
String direccion;
/** Teléfono */
@NotNull
@Size(min=9, max=13)
@Pattern(regexp=ExprReg.TLF)
String tlf;
/** Email */
@NotNull
@Email
String email;
/** Clave de acceso al sistema */
@NotNull
String clave;
/** Tarjetas asociadas al cliente (no tiene por qué ser el titular */
@OneToMany(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
@JoinColumn(name="cliente_dni")
List<Tarjeta> tarjetas;
/** Cuentas asociadas al cliente */
@OneToMany(mappedBy="titular")
List<Cuenta> cuentas;
public Cliente() {
}
public Cliente(String dni, String nombre, LocalDate fNacimiento, String direccion, String tlf, String email, String clave) {
this.dni = dni;
......@@ -60,7 +80,7 @@ public class Cliente {
this.tlf = tlf;
this.email = email;
this.clave = CodificadorMd5.codificar(clave);
this.clave = (clave != null ? CodificadorMd5.codificar(clave) : null);
tarjetas = new ArrayList<>();
cuentas = new ArrayList<>();
......
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.entidades;
import es.ujaen.dae.ujacoin.excepciones.SaldoInsuficienteParaOperacion;
import es.ujaen.dae.ujacoin.entidades.movimientos.Movimiento;
import es.ujaen.dae.ujacoin.util.ExprReg;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.PositiveOrZero;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
......@@ -21,12 +23,16 @@ import java.util.stream.Collectors;
* Clase para representar cuentas de moneda virtual UjaCoin
* @author ajrueda
*/
@Entity
public class Cuenta {
static final LocalDateTime PASADO_DISTANTE = LocalDateTime.of(1970, 1, 1, 0, 0);
static final LocalDateTime FUTURO_DISTANTE = LocalDateTime.of(2100, 1, 1, 0, 0);
/** Número de cuenta */
@Id
@NotNull
@Size(min=10, max=10)
@Pattern(regexp=ExprReg.NUM_CUENTA)
String num;
/** Saldo de la cuenta en Ujacoins */
......@@ -34,11 +40,20 @@ public class Cuenta {
float saldo;
/** Titular de la cuenta */
@NotNull
@ManyToOne
Cliente titular;
/** Lista de movimientos */
@OneToMany(cascade=CascadeType.ALL)
@JoinColumn(name="cuenta_num")
List<Movimiento> movimientos;
// @Version
// int version;
public Cuenta() {
}
public Cuenta(String num, Cliente titular) {
this.num = num;
this.titular = titular;
......@@ -81,9 +96,10 @@ public class Cuenta {
* @return la lista de movimientos dentro del intervalo de fechas indicado
*/
public List<Movimiento> listarMovimientosDesdeHasta(LocalDateTime fechaHoraDesde, LocalDateTime fechaHoraHasta) {
LocalDateTime fechaHoraDesdeConsulta = Optional.ofNullable(fechaHoraDesde).orElse(PASADO_DISTANTE);
LocalDateTime fechaHoraHastaConsulta = Optional.ofNullable(fechaHoraHasta).orElse(FUTURO_DISTANTE);
return movimientos.stream().filter(m ->
m.getFechaHora().isAfter(fechaHoraDesdeConsulta) &&
m.getFechaHora().isBefore(fechaHoraHastaConsulta)
......
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.entidades;
import es.ujaen.dae.ujacoin.util.ExprReg;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import java.io.Serializable;
import java.time.LocalDate;
/**
* Tarjeta para la realización de ingresos o reintegros en moneda real
* @author ajrueda
*/
public class Tarjeta {
@Entity
public class Tarjeta implements Serializable {
/** Número de tarjeta */
@Id
@NotNull
@Size(min=16, max=16)
@Pattern(regexp=ExprReg.NUM_TARJETA)
String num;
/** Titular de la tarjeta (puede ser diferente al cliente que la usa */
@NotBlank
String titular;
/** Fecha de caducidad */
@NotNull
@Future
LocalDate fechaCaducidad;
/** Código de seguridad (CVC) */
@NotNull
@Size(min=3, max=3)
@Pattern(regexp=ExprReg.CVC)
String cvc;
public Tarjeta() {
}
public Tarjeta(String num, String titular, LocalDate fechaCaducidad, String cvc) {
this.num = num;
this.titular = titular;
......
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.entidades.movimientos;
import es.ujaen.dae.ujacoin.entidades.Tarjeta;
import jakarta.persistence.Entity;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.NotNull;
/**
* Ingreso de dinero en cuenta desde una tarjeta
* @author ajrueda
*/
@Entity
public class Ingreso extends Movimiento {
@NotNull
@ManyToOne
Tarjeta tarjeta;
public Ingreso() {
}
/**
* Constructor del igreso
* @param tarjeta la tarjeta desde donde se transfiere el dinero
......
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.entidades.movimientos;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
import jakarta.persistence.InheritanceType;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.PastOrPresent;
import jakarta.validation.constraints.Positive;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* Clase que representa un movimiento en cuenta
* @author ajrueda
*/
public abstract class Movimiento {
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Movimiento implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
int id;
/** Fecha del movimiento */
@NotNull
@PastOrPresent
LocalDateTime fechaHora;
/** Importe del movimiento. Los valores negativos representan retiradas de dinero */
@Positive
float importe;
public Movimiento() {
}
/**
* Constructor del movimiento
* @param importe el importe asociado al movimiento
......
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.entidades.movimientos;
import es.ujaen.dae.ujacoin.entidades.Tarjeta;
import jakarta.persistence.Entity;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.NotNull;
/**
* Reintegro desde la cuenta a una tarjeta destino
* @author ajrueda
*/
@Entity
public class Reintegro extends Movimiento {
@NotNull
@ManyToOne
Tarjeta tarjeta;
public Reintegro() {
}
/**
* Constructor del reintegro
......
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.entidades.movimientos;
import es.ujaen.dae.ujacoin.entidades.Cuenta;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.NotNull;
/**
* Transferencia emitida a otra cuenta
* @author ajrueda
*/
@Entity
public class TransferenciaEmitida extends Movimiento {
@NotNull
@ManyToOne
// Columna renombrada para que no colisione con la cuenta del movimiento
@JoinColumn(name="cuenta_dest_num")
Cuenta cuenta;
public TransferenciaEmitida() {
}
public TransferenciaEmitida(Cuenta cuenta, float importe) {
super(-importe);
......
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.entidades.movimientos;
import es.ujaen.dae.ujacoin.entidades.Cuenta;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.NotNull;
/**
* Transferencia recibida de otra cuenta
* @author ajrueda
*/
@Entity
public class TransferenciaRecibida extends Movimiento {
@NotNull
@ManyToOne
// Columna renombrada para que no coincida con la cuenta del movimiento
@JoinColumn(name="cuenta_orig_num")
Cuenta cuenta;
public TransferenciaRecibida() {
}
public TransferenciaRecibida(Cuenta cuenta, float importe) {
super(importe);
......
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.excepciones;
/**
......
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.excepciones;
/**
......
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.excepciones;
/**
......
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.excepciones;
/**
......
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.excepciones;
/**
......
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.excepciones;
/**
......
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.objetosvalor;
/**
* Simple random token for autorization
* NO SE USA EN EL PROYECTO POR EL MOMENTO
* @author ajrueda
*/
/*
public class Token {
public final long id;
public Token() { id = 0; }
private Token(long id) {
this.id = id;
}
public static Token generarAleatorio() {
return new Token(new Random().nextLong());
}
}
*/
\ No newline at end of file
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.repositorios;
import es.ujaen.dae.ujacoin.entidades.Cliente;
import es.ujaen.dae.ujacoin.entidades.Tarjeta;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.util.Optional;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
*
* @author ajrueda
*/
@Repository
@Transactional(propagation = Propagation.REQUIRED)
public class RepositorioClientes {
@PersistenceContext
EntityManager em;
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public Optional<Cliente> buscar(String dni) {
return Optional.ofNullable(em.find(Cliente.class, dni));
}
public void guardar(Cliente cliente) {
em.persist(cliente);
}
public void nuevaTarjeta(Cliente cliente, Tarjeta tarjeta) {
em.persist(tarjeta);
cliente = em.merge(cliente);
cliente.nuevaTarjeta(tarjeta);
}
public void actualizar(Cliente cliente) {
em.merge(cliente);
}
}
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.repositorios;
import es.ujaen.dae.ujacoin.entidades.Cuenta;
import jakarta.persistence.EntityManager;
import jakarta.persistence.LockModeType;
import jakarta.persistence.PersistenceContext;
import java.util.Optional;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
*
* @author ajrueda
*/
@Repository
@Transactional(propagation = Propagation.REQUIRED)
public class RepositorioCuentas {
@PersistenceContext
EntityManager em;
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public Optional<Cuenta> buscar(String num) {
return Optional.ofNullable(em.find(Cuenta.class, num));
}
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public Optional<Cuenta> buscarYBloquear(String num) {
return Optional.ofNullable(em.find(Cuenta.class, num, LockModeType.PESSIMISTIC_WRITE));
}
public void guardar(Cuenta cuenta) {
em.persist(cuenta);
}
}
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.util;
import java.security.MessageDigest;
......@@ -10,8 +5,8 @@ import java.security.NoSuchAlgorithmException;
import java.util.Base64;
/**
*
* @author admin
* Codificador sencillo para contraseñas basado en Md5 (no seguro)
* @author ajrueda
*/
public class CodificadorMd5 {
......
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.util;
/**
*
* Recopilación de expresiones regulares para validación
* @author ajrueda
*/
public class ExprReg {
private ExprReg() {}
private ExprReg() {
}
public static final String DNI = "\\d{8}[A-HJ-NP-TV-Z]";
public static final String TLF = "^(\\+34|0034|34)?[6789]\\d{8}$";
......
## Fichero de configuración para UjaCoin durante testing
# spring.datasource.url: jdbc:mysql://localhost:33060/ujacoin_test
spring.datasource.url: jdbc:h2:mem:ujacoin_test;MODE=MYSQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1
spring.jpa.properties.javax.persistence.schema-generation.database.action: drop-and-create
## Fichero de configuración para UjaCoin
spring.datasource.url: jdbc:mysql://localhost:33060/ujacoin
spring.datasource.username: ujacoin
spring.datasource.password: secret
spring.jpa.properties.javax.persistence.schema-generation.database.action: none
spring.data.jpa.repositories.bootstrap-mode: default
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.entidades;
import jakarta.validation.ConstraintViolation;
......@@ -15,7 +10,7 @@ import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
/**
* Test para clase Cliente
* Test unitario para clase Cliente
* @author ajrueda
*/
public class ClienteTest {
......
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package es.ujaen.dae.ujacoin.entidades;
import es.ujaen.dae.ujacoin.entidades.movimientos.Ingreso;
......@@ -15,7 +10,7 @@ import org.junit.jupiter.api.Test;
/**
*
* Test unitario para clase Cuenta
* @author ajrueda
*/
public class CuentaTest {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment