JPA con Spring Boot

Índice

En este apartado vamos a ver el funcionamiento de la parte ORM del framework llamado Spring Boot

Un ORM facilita el trabajo de creación de aplicaciones de base de datos ya que prácticamente automatiza la persistencia y consulta de datos basándose para ello en el mapeo de las entidades en objetos y viceversa.

Esqueleto

El primer paso es descargar el esqueleto de proyecto desde aquí. En el archivo pom.xml están definidas las siguientes dependencias

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.0.5</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>org.ieselcaminas</groupId>
	<artifactId>jpa</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>jpa</name>
	<description>Proyecto demo para JPA</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
			<version>3.1.7</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc -->
		<dependency>
			<groupId>org.xerial</groupId>
			<artifactId>sqlite-jdbc</artifactId>
			<version>3.40.0.0</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.hibernate.orm/hibernate-community-dialects -->
		<dependency>
			<groupId>org.hibernate.orm</groupId>
			<artifactId>hibernate-community-dialects</artifactId>
			<version>6.4.1.Final</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

  • spring-boot-starter-data-jpa define que utilizamos la capa JPA de Spring (Java Persistent API)
  • sqlite-jdbc define la base de datos SQLite
  • hibernate-community-dialects se define que vamos a usar dialectos JDBC

La clase java que tiene el método main es JpaApplication.java

image-20230419094909718

Que contiene el siguiente código. Es común para todas las aplicaciones

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.ieselcaminas.jpa;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JpaApplication implements CommandLineRunner {

	public static void main(String[] args) {
		SpringApplication.run(JpaApplication.class, args);
	}
	//En este método definimos nuestro propio código
	@Override
	public void run(String... args) {
		
	}
}

SpringApplication.run(JpaApplication.class, args); es quien inicia la aplicación SpringBoot

Propiedades

Debemos definir un archivo llamado application.properties dentro de la carpeta resources con el siguiente contenido:

1
2
3
spring.datasource.url=jdbc:sqlite:src/main/resources/customers.sqlite
spring.datasource.driver-class-name=org.sqlite.JDBC
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
  • spring.datasource.url=jdbc:sqlite:src/main/resources/customers.sqlite define como base de datos customers.sqlite dentro del directorio resources
  • spring.datasource.driver-class-name=org.sqlite.JDBC define la clase JDBC que se va a usar
  • spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect y aquí se define que, de todos los dialectos SQL propietarios o no, se va a usar org.hibernate.community.dialect.SQLiteDialect

Base de datos

Creamos la base de datos SQLite y creamos la siguiente tabla:

1
2
3
4
5
CREATE TABLE customer (
	id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
	first_name TEXT NOT NULL,
	last_name TEXT NOT NULL
);

Entidad

Al igual que hemos hecho en Repository Pattern hemos de definir las entidades como clases POJO.

Es necesario que las columnas de la base de datos tengan el mismo nombre que los atributos de la clase. Antes de cada camelCase se introduce un _ y se pone en minúscula camel_case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package org.ieselcaminas.jpa.entity;

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

@Entity
public class Customer {

  @Id
  @GeneratedValue(strategy=GenerationType.IDENTITY)
  private Long id;
  private String firstName;
  private String lastName;

  protected Customer() {}
  public Customer(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  public Long getId() {
    return id;
  }
  public String getFirstName() {
    return firstName;
  }
  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }
  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  @Override	
  public String toString() {
      return id + " - " + firstName + " - " + lastName;
  }

}

Vemos que esta clase se anota con @Entity para indicarle al framework que esta clase la debe tratar como una entidad que tiene persistencia en base de datos.

1
2
3
  @Id
  @GeneratedValue(strategy=GenerationType.IDENTITY)
  private Long id;

Estas anotaciones le indican a JPA que el atributo id es la clave primaria y que el valor se genera automáticamente. Después cada atributo de la entidad se mapea en un campo en la base de datos.

Repositorio

El siguiente paso es crear CustomerRepository

1
2
3
4
5
6
7
8
package org.ieselcaminas.jpa.repository;

import org.ieselcaminas.jpa.entity.Customer;
import org.springframework.data.repository.CrudRepository;

public interface CustomerRepository extends CrudRepository<Customer, Long> {

}

Extiende CrudRepository para poder realizar las operaciones CRUD <Customer, Long>. Long es el tipo de la clave primaria de la entidad Customer.

De momento no nos hace falta definir métodos de acceso a datos porque CrudRepository ya provee findById que selecciona un elemento a partir su id y findAll que devuelve una lista de todos los elementos.

Vamos a probar el repositorio creando algunos Customer. Primero hemos de crear un constructor para que Spring cree la clase customerRepository por nosotros. Esto se denomina Dependency Injection

1
2
3
4
private final CustomerRepository customerRepository;
public JpaApplication(CustomerRepository customerRepository){
    this.customerRepository = customerRepository;
}

Y ahora ya podemos crear algún Customer y mostrar todos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Override
public void run(String... args) {
    Customer c = new Customer("Pepe", "García");
    //El repositorio es donde están todos los métodos que tratan con la base de datos.
    //En este caso está haciendo un INSERT ya que el objecto es nuevo
    this.customerRepository.save(c);
    
    //Pero también puedeo modificar un registro
    c.setFirstName("Juan");
	this.customerRepository.save(c);
    
    //Vamos a seleccionar el Customer con id 1
    //Se escribe 1L porque es un dato escrito a mano de tipo long
    Optional<Customer> clienteOp = this.customerRepository.findById(1L);
    clienteOp.ifPresent(System.out::println);
    
    //Si queremos acceder al objeto Customer
    if (clienteOp.isPresent()){
        c = clienteOp.get();
        System.out.println(c);
    }

    //El método findAll devuelve todos los registros de la entidad asociada
    this.customerRepository.findAll().forEach(System.out::println);

    //En este código estamos guardando los datos en un Iterable que es lo que devuelven los métodos findAll
    Iterable<Customer> l = this.customerRepository.findAll();
    for (Customer customer : l) {
        System.out.println(customer);
    }
    
    //Cuidado que si no existe el registro con ID 5, saltará una excepción
    c = this.customerRepository.findById(5L).get();
	this.customerRepository.delete(c);
}

Construcción de Consultas

En el repositorio se definen todas aquellas consultas que sean necesarias. Hay algunas consultas que ya están hechas. Las puedes consultar en org.springframework.data.repository.CrudRepository.

En general, no hace falta escribir el cuerpo de las consultas SQL ya que lo hace Spring Boot automáticamente por nosotros a partir del nombre del método y los parámetros. Por ejemplo:

1
2
public List<Customer> findCustomersByFirstName(String firstName);
public Customer findCustomerByFirstNameAndLastName(String firstName, String lastName)

Si creamos el método findCustomersByFirstName(String firstName) devolvería aquellos con dicho firstName y findCustomerByFirstNameAndLastName que coincida el firstName y el lastName.

Fijaos en la diferencia:

  • Si empieza por findCustomerBy devuelve un único Customer
  • Si empieza por findCustomersBy (con s) devuelve una lista de Customer

Tienes el listado completo de Querys aquí.

Si la consulta que queremos hacer no se puede crear con el asistente, siempre se puede escribir a mano el SQL de la misma.

1
2
@Query("SELECT COUNT(c) FROM Customer c")
public int countAllRecords();

Notes

Vamos a implementar la funcionalidad de poder crear notas que se adjuntan a un cliente. Es decir, vamos a crear una relación 1:N

Primero creamos la tabla note:

1
2
3
4
5
6
CREATE TABLE note (
	id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
	"text" TEXT NOT NULL,
	id_customer INTEGER,
	CONSTRAINT note_customer_FK FOREIGN KEY (id_customer) REFERENCES customer(id)
);

Ahora creamos la entidad Note:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package org.ieselcaminas.jpa;

import jakarta.persistence.*;

@Entity
public class Note {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String text;
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name="id_customer")
    private Customer customer;

    protected Note() {}
    
    public Note(String text, Customer customer) {
        this.text = text;
        this.customer = customer;
    }
    
    public Long getId() {
        return id;
    }
    public String getText() {
        return text;
    }

    public Customer getCustomer() {
        return customer;
    }

    @Override
    public String toString() {
        return text;
    }

}

Mediante …

1
2
3
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name="customer_id")
private Customer customer;

.. estamos creando un relación M:1, la columna se llamará customer_id y la entidad relacionada es Customer

Y ahora vamos a ver la parte 1, es decir, Customer

1
2
@OneToMany(mappedBy = "customer",  fetch=FetchType.EAGER)
private List<Note> notes = new ArrayList<>;

En este caso le indicamos mappedBy = "customer" donde customer es el nombre del campo en la relación N

Ahora creamos los setters.

1
2
3
4
5
6
7
public List<Note> getNotes(){
    return this.notes;
}

public void addNote(Note note){
    this.notes.add(note);
}

Y generamos el repositorio para Note

1
2
3
4
5
6
7
8
9
10
11
package org.ieselcaminas.jpa.repository;

import org.ieselcaminas.jpa.entity.Note;
import org.springframework.data.repository.CrudRepository;

import java.util.List;

public interface NoteRepository extends CrudRepository<Note, Long> {


}

Y lo inyectamos en el constructor:

1
2
3
4
5
private final NoteRepository noteRepository;
public JpaApplication(CustomerRepository customerRepository, NoteRepository noteRepository){
	this.customerRepository = customerRepository;
	this.noteRepository = noteRepository;
}

Y ya podemos crear y consultar notas:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void run(String... args) {
    //Creamos una nota
    // 1º el cliente
    Customer c = customerRepository.findById(1L).get();
    //Y ahora la nota
    Note n = new Note("Primera nota", c);
    noteRepository.save(n);

    Customer c2 = customerRepository.findById(2L).get();
    n = new Note("Primera nota María", c2);
    noteRepository.save(n);

    customerRepository.findCustomersByFirstName("Pepe").forEach(System.out::println);
}

Controladores

Además de los repositorios es común crearse un controlador que haga de intermediario entre la base de datos y la entidad.

En el siguiente ejemplo tenemos un método en el controlador de Customer que permite la creación de un Customer desde la entrada estándar.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package org.ieselcaminas.jpa.controller;

import org.ieselcaminas.jpa.entity.Customer;
import org.ieselcaminas.jpa.repository.CustomerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Scanner;

@Component
public class CustomerController {

    private final CustomerRepository customerRepository;
    public CustomerController(CustomerRepository customerRepository){
        this.customerRepository = customerRepository;

    }
    public Customer createCustomer(){
        String firstName, lastName;
        Customer c;
        Scanner sc = new Scanner(System.in);

        System.out.println("Enter customer's first name:");
        firstName = sc.nextLine();
        System.out.println("Enter customer's last name:");
        lastName = sc.nextLine();
        c =  new Customer(firstName, lastName);
        customerRepository.save(c);

        return c;

    }
}

Y lo usamos en el programa principal JpaApplication:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
......
private final CustomerController customerController;
public JpaApplication(CustomerRepository customerRepository,
                      NoteRepository noteRepository, CustomerController customerController){
    this.customerRepository = customerRepository;
    this.noteRepository = noteRepository;
    this.customerController = customerController;
}
.....
@Override
public void run(String... args) {
    this.customerController.createCustomer();
    this.customerRepository.findAll().forEach(System.out::println);
}

Banco

Vamos a crear una relación n:m como la que vimos anteriormente entre cliente y cuenta corriente

Archivo pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>org.ieselcaminas</groupId>
    <artifactId>jpa</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>jpa</name>
    <description>Proyecto demo para JPA</description>

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <!-- Spring Boot JPA -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- SQLite JDBC -->
        <dependency>
            <groupId>org.xerial</groupId>
            <artifactId>sqlite-jdbc</artifactId>
            <version>3.41.2.2</version>
        </dependency>

        <!-- Hibernate Community Dialects -->
        <dependency>
            <groupId>org.hibernate.orm</groupId>
            <artifactId>hibernate-community-dialects</artifactId>
            <version>6.1.7.Final</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CREATE TABLE clientes (
                          id INTEGER PRIMARY KEY AUTOINCREMENT,
                          nombre TEXT NOT NULL
);

CREATE TABLE cuentas_corrientes (
                                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                                    nombre TEXT NOT NULL,
    								 cantidad REAL
);

CREATE TABLE cliente_cuenta (
                                cliente_id INTEGER NOT NULL,
                                cuenta_id INTEGER NOT NULL,
                                PRIMARY KEY (cliente_id, cuenta_id),
                                FOREIGN KEY (cliente_id) REFERENCES clientes(id) ON DELETE CASCADE,
                                FOREIGN KEY (cuenta_id) REFERENCES cuentas_corrientes(id) ON DELETE CASCADE
);

Y application.properties

1
2
3
spring.datasource.url=jdbc:sqlite:src/main/resources/banco.sqlite
spring.datasource.driver-class-name=org.sqlite.JDBC
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect

La entidad Cliente

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package jpa;

import jakarta.persistence.*;

@Entity
// Se le puede indicar que el nombre de la tabla no es "cliente", nombre por defecto que es // igual al nombre la entidad (Cliente), sino "clientes"
@Table(name = "clientes")
public class Cliente {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 100)
    private String nombre;

    // Siempre un constructor por defecto
    public Cliente() {
    }
    public Cliente(String nombre) {
        this.nombre = nombre;
    }

    // Getters y Setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    @Override
    public String toString(){
        return this.nombre;
    }
}

Y la entidad CuentaCorriente

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package jpa;

import jakarta.persistence.*;
import jpa.Cliente;

@Entity
// Se le puede indicar que el nombre de la tabla no es "cuenta_corriente", nombre por 
// defecto que es igual al nombre la entidad (CuentaCorriente), sino "cuentas_corrientes"
@Table(name = "cuentas_corrientes")
public class CuentaCorriente {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 100)
    private String nombre;

    @Column(nullable = false)
    private double cantidad;

    //Siempre debe tener un constructor vacío
    public CuentaCorriente() {
    }

    //Se pueden hacer tantos constructores como deseemos
    public CuentaCorriente(String nombre) {
        this.nombre = nombre;
    }

    // Getters y Setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public double getCantidad() {
        return cantidad;
    }

    public void setCantidad(double cantidad) {
        this.cantidad = cantidad;
    }

    // Otros setters
    public void ingresar(double cantidad){
        if (cantidad > 0 )
            this.cantidad += cantidad;
    }

    public void retirar(double cantidad){
        if (cantidad > 0 )
            this.cantidad -= cantidad;
    }

    @Override
    public String toString(){
        return this.nombre + " - " + this.cantidad;
    }
}

Y ahora ya podemos crear la relación ` n:m `:

En la entidad Cliente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    /**
     * IMPORTANTE fetch = FetchType.EAGER. En caso contrario es FetchType.LAZY y da 
     * error porque no se inicializa la lista de cuentas
     */
    @ManyToMany(fetch = FetchType.EAGER)
    /*
       En este caso le decimos:
       - que la tabla se llama cliente_cuenta
       - que mi columna id se llama en esa tabla cliente_id
       - que la columna de la cuenta se llama cuenta_id en dicha tabla
     */
    @JoinTable(
            name = "cliente_cuenta",
            joinColumns = @JoinColumn(name = "cliente_id"),
            inverseJoinColumns = @JoinColumn(name = "cuenta_id")

    )
    // Inicializar siempre el Set
    private Set<CuentaCorriente> cuentas = new HashSet<>();

	// Demás código ....

    public Set<CuentaCorriente> getCuentas() {
        return this.cuentas;
    }

    public void setCuentas(Set<CuentaCorriente> cuentas) {
        this.cuentas = cuentas;
    }

    // Se puede crear un método para añadir la entidad relacionada a la lista.
    // En otro caso, habría que usar cliente.getCuentas().add(cuenta)
    public void addCuenta(CuentaCorriente cuenta) {
        this.cuentas.add(cuenta);
    }

Y en la entidad CuentaCorriente

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    //Le decimos que, en la entidad Cliente, las cuentas se almacenan en el atributo con nombre cuentas
    @ManyToMany(mappedBy = "cuentas", fetch = FetchType.EAGER)
    //Hay que inicializar la lista siempre
    private Set<Cliente> clientes = new HashSet<>();

	// Demás código ....

    public Set<Cliente> getClientes() {
        return this.clientes;
    }

    public void setClientes(Set<Cliente> clientes) {
        this.clientes = clientes;
    }

    // Se puede crear un método para añadir la entidad relacionada a la lista.
    // En otro caso, habría que usar cuenta.getClientes().add(cliente)
    public void addCliente(Cliente cliente){
        this.clientes.add(cliente);
    }

Los repositorios

1
2
3
4
5
package jpa;

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

interface ClienteRepository extends JpaRepository<Cliente, Long> {}

y

1
2
3
4
5
package jpa;

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

interface CuentaCorrienteRepository extends JpaRepository<CuentaCorriente, Long> {}

Y ahora ya podemos probarlo.

Vamos a hacerlo mediante un Servicio. En un servicio vamos creando los métodos relacionados con una entidad y que no deban estar en el repositorio.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package jpa;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * Se pueden hacer tantos servicios como deseemos, pero siempre hemos de crear métodos relacionados
 * en ellos.
 */
@Service
class ClienteService {
    /*
    La anotación @Autowired indica a Spring que nos "auto inyecte" esta clase, en este caso ClienteRepository y
    CuentaCorrienteRepository.
    De esta forma ya no es necesario inyectarlas en el constructor
     */
    @Autowired
    private ClienteRepository clienteRepository;
    
    @Autowired
    private CuentaCorrienteRepository cuentaCorrienteRepository;

    public void crearClienteConCuenta(String nombreCliente, String nombreCuenta) {
        Cliente cliente = new Cliente(nombreCliente);
        CuentaCorriente cuenta = new CuentaCorriente();
        cuenta.setNombre(nombreCuenta);
        
        cliente.getCuentas().add(cuenta);
        cuenta.getClientes().add(cliente);
        
        cuentaCorrienteRepository.save(cuenta);
        clienteRepository.save(cliente);
    }
}

Y ahora en main lo probamos

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package jpa;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JpaApplication implements CommandLineRunner {
    private final ClienteService clienteService;
    public JpaApplication(ClienteService clienteService) {
        this.clienteService = clienteService;
    }

    public static void main(String[] args) {
        SpringApplication.run(JpaApplication.class, args);
    }

    //En este método definimos nuestro propio código
    @Override
    public void run(String... args) {
        this.clienteService.crearClienteConCuenta("Mengano", "Cmengano");        
    }
}

Vamos a crear otro método que permita asociar una cuenta con varios clientes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void crearCuentaConClientes(String nombreCuenta) {
    CuentaCorriente cuenta = new CuentaCorriente();
    cuenta.setNombre(nombreCuenta);
    cuenta.setCantidad(100);

    Cliente cliente1 = new Cliente("Cliente 1");
    Cliente cliente2 = new Cliente("Cliente 2");

    cliente1.getCuentas().add(cuenta); // o cliente1.addCuenta(cuenta);
    cuenta.getClientes().add(cliente1); // o cuenta.addCliente(cliente);

    cliente2.getCuentas().add(cuenta);
    cuenta.getClientes().add(cliente2);

    this.cuentaCorrienteRepository.save(cuenta);
    this.clienteRepository.save(cliente1);
    this.clienteRepository.save(cliente2);
}

y en main

1
2
3
4
@Override
public void run(String... args) { 
    this.clienteService.crearCuentaConClientes("CuentaConMásDeUnCliente");
}

Resumen

Los pasos para crear una aplicación con Spring boot son:

  • definir las dependencias en el archivo pom.xml
  • crear las entidades donde se mapean los campos de la tabla con los atributos de la clase POJO
  • crear los repositorios. Uno para cada entidad. Se puede ir definiendo los métodos conforme nos vayan haciendo falta.
  • crear los controladores, donde crearemos métodos que usarán las entidades y los repositorios.
  • crear la aplicación, según el ejemplo.