Salta el contingut

Spring_Boot_Documentation_Header_1200x200px_with_Xavier_Sastre_Mug_and_Logo_ajustat.png

JPA (Java Persistence API) i Spring Boot

Introducció a JPA

Què és JPA?

JPA (Java Persistence API) és una especificació estàndard de Java que defineix com gestionar la persistència de dades en bases de dades relacionals mitjançant el mapeig objecte-relacional (ORM). En lloc d'escriure queries SQL directament, els desenvolupadors treballem amb objectes Java (entitats) i deixem que JPA es responsible de convertir aquests objectes en registres de base de dades.

Per què és important JPA?

  • Abstracció de la BD: Escrius codi Java, no SQL
  • Portabilitat: El mateixa codi funciona amb MySQL, PostgreSQL, Oracle, etc.
  • Menys boilerplate: Reducció de codi repetitiu
  • Cicle de vida automàtic: JPA gestiona l'estat de les entitats automàticament
  • Relacions fàcils: Mapeig senzill de relacions entre taules

Conceptes clau

JPA no és una implementació, sinó una especificació estàndard. Les implementacions més populars són: - Hibernate (la més usada, integrada per defecte a Spring Boot) Veure aquest document - EclipseLink - OpenJPA


Conceptes fonamentals de JPA

1. Entitats (Entity)

Una entitat és una classe Java que representa una taula en la base de dades. Cada instància de l'entitat correspon a un registre (fila) de la taula.

Characteristics: - Ha de tenir un constructor sense paràmetres - No ha de ser final - Ha de tenir com a mínim un camp marcat amb @Id (clau primaria) - És un objecte POJO (Plain Old Java Object)

2. EntityManager

El EntityManager és la interfície principal per interactuar amb la persistència. S'encarrega de: - Crear, llegir, actualitzar i eliminar entitats (CRUD) - Gestionar transaccions - Mantenir el Persistence Context (context de persistència)

3. Persistence Context

El Persistence Context és un conjunt d'instàncies d'entitats que JPA està monitoritzant. Quan feim canvis a aquestes entitats dins d'una transacció, JPA detecta els canvis i genera les queries SQL necessàries automàticament al moment del commit.

4. Unitat de Persistència (Persistence Unit)

La Unitat de Persistència és una configuració que defineix: - Les entitats que JPA ha de gestionar - Els paràmetres de connexió a la base de dades - Les propietats de configuració de JPA

En Spring Boot, aquesta configuració es manage automàticament.

5. JPQL (Java Persistence Query Language)

JPQL és un llenguatge de queries similar a SQL, però que treballa amb classes i propietats Java en lloc de taules i columnes.

Exemple:

SELECT p FROM Persona p WHERE p.edat > 18

6. Criteria API

API per construir queries de forma type-safe i dinàmica en temps d'execució, sense escriure strings de JPQL.


Configuració de JPA amb Spring Boot

1. Dependències del Maven

Afegir al pom.xml:

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

<!-- Base de dades (exemple: MySQL) -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
</dependency>

<!-- Spring Web (opcional, per a REST APIs) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2. Configuració del application.properties

# Configuració de la base de dades
spring.datasource.url=jdbc:mysql://localhost:3306/mi_base_datos
spring.datasource.username=root
spring.datasource.password=contrasenya
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# Configuració de Hibernate (JPA provider)
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
spring.jpa.properties.hibernate.format_sql=true

Paràmetres importants: - spring.datasource.url: Adreça de la base de dades - spring.jpa.hibernate.ddl-auto: - create-drop: Crea les taules a l'inici i les elimina al final - create: Crea les taules (pot donar error si ja existent) - update: Actualitza les taules (recomanat per desenvolupament) - validate: Verifica que les entitats coincideixen amb la BD - none: No fa res (producció) - spring.jpa.show-sql: Mostra les queries SQL al log


Crear una Entitat JPA

Exemple bàsic: Entitat Alumne

import jakarta.persistence.*;

@Entity
@Table(name = "alumnes")
public class Alumne {

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

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

    @Column(name = "correu", unique = true)
    private String correu;

    @Column(name = "edat")
    private int edat;

    @Transient
    private String informacioTemporal;

    // Constructors
    public Alumne() {}

    public Alumne(String nom, String correu, int edat) {
        this.nom = nom;
        this.correu = correu;
        this.edat = edat;
    }

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

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

    public String getNom() {
        return nom;
    }

    public void setNom(String nom) {
        this.nom = nom;
    }

    public String getCorreu() {
        return correu;
    }

    public void setCorreu(String correu) {
        this.correu = correu;
    }

    public int getEdat() {
        return edat;
    }

    public void setEdat(int edat) {
        this.edat = edat;
    }

    @Override
    public String toString() {
        return "Alumne{" +
                "id=" + id +
                ", nom='" + nom + '\'' +
                ", correu='" + correu + '\'' +
                ", edat=" + edat +
                '}';
    }
}

Anotacions principals

Anotació Función
@Entity Marca la classe com a entitat JPA
@Table(name="...") Especifica el nom de la taula (opcional)
@Id Marca el camp com a clau primaria
@GeneratedValue Genera valors automàticament per a la clau primaria
@Column Configura detalls de la columna (nom, length, nullable, unique)
@Transient El camp no es persista a la BD
@Temporal Per a camps de data/hora
@Enumerated Per a enums
@Version Per a control optimista de concurrència

Repositories de Spring Data JPA

Exemple bàsic amb CrudRepository

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface AlumneRepository extends CrudRepository<Alumne, Long> {
    // Els mètodes CRUD es generen automàticament:
    // save(), findAll(), findById(), delete(), etc.
}

Mètodes automàtics de CrudRepository

// Crear/actualitzar
Alumne alumne = new Alumne("Joan", "joan@example.com", 20);
alumneRepository.save(alumne);

// Buscar tots
Iterable<Alumne> tots = alumneRepository.findAll();

// Buscar per ID
Optional<Alumne> optional = alumneRepository.findById(1L);
if (optional.isPresent()) {
    Alumne alumne = optional.get();
}

// Comptar
long total = alumneRepository.count();

// Verificar si existeix
boolean existeix = alumneRepository.existsById(1L);

// Eliminar
alumneRepository.deleteById(1L);

JpaRepository (més potent)

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

@Repository
public interface AlumneRepository extends JpaRepository<Alumne, Long> {
    // Afegeix funcionalitats addicionals:
    // flush(), saveAndFlush(), deleteInBatch(), etc.
}

Queries personalitzades

Per nom de mètode (Query by Example)

@Repository
public interface AlumneRepository extends JpaRepository<Alumne, Long> {

    // Buscar per nom
    List<Alumne> findByNom(String nom);

    // Buscar per correu
    Optional<Alumne> findByCorreu(String correu);

    // Buscar per edat
    List<Alumne> findByEdat(int edat);

    // Buscar per edat superior a
    List<Alumne> findByEdatGreaterThan(int edat);

    // Buscar per nom que conté una paraula (LIKE)
    List<Alumne> findByNomContaining(String nom);

    // Buscar per edat entre dos valors
    List<Alumne> findByEdatBetween(int minEdat, int maxEdat);
}

Amb anotació @Query (JPQL)

@Repository
public interface AlumneRepository extends JpaRepository<Alumne, Long> {

    @Query("SELECT a FROM Alumne a WHERE a.edat > :edat")
    List<Alumne> trobarAlumnesPerEdatMinima(@Param("edat") int edat);

    @Query("SELECT a FROM Alumne a WHERE a.nom LIKE %:nom%")
    List<Alumne> buscarPerNom(@Param("nom") String nom);
}

Usar JPA en una aplicació Spring Boot

Injecció del Repository

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;

@Service
public class AlumneService {

    @Autowired
    private AlumneRepository alumneRepository;

    // Crear un alumne
    public Alumne crearAlumne(String nom, String correu, int edat) {
        Alumne alumne = new Alumne(nom, correu, edat);
        return alumneRepository.save(alumne);
    }

    // Obtenir tots els alumnes
    public List<Alumne> obtenerTots() {
        return alumneRepository.findAll();
    }

    // Obtenir alumne per ID
    public Optional<Alumne> obtenerPerID(Long id) {
        return alumneRepository.findById(id);
    }

    // Actualitzar un alumne
    public Alumne actualitzar(Alumne alumne) {
        return alumneRepository.save(alumne); // Si l'ID ja existeix, actualitza
    }

    // Eliminar un alumne
    public void eliminar(Long id) {
        alumneRepository.deleteById(id);
    }

    // Buscar per nom
    public List<Alumne> buscarPerNom(String nom) {
        return alumneRepository.findByNom(nom);
    }
}

Controller REST (exemple)

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/api/alumnes")
public class AlumneController {

    @Autowired
    private AlumneService alumneService;

    // GET: Obtenir tots els alumnes
    @GetMapping
    public List<Alumne> obtenerTots() {
        return alumneService.obtenerTots();
    }

    // GET: Obtenir alumne per ID
    @GetMapping("/{id}")
    public Optional<Alumne> obtenerPerID(@PathVariable Long id) {
        return alumneService.obtenerPerID(id);
    }

    // POST: Crear alumne
    @PostMapping
    public Alumne crear(@RequestBody Alumne alumne) {
        return alumneService.crearAlumne(alumne.getNom(), 
                                         alumne.getCorreu(), 
                                         alumne.getEdat());
    }

    // PUT: Actualitzar alumne
    @PutMapping("/{id}")
    public Alumne actualitzar(@PathVariable Long id, @RequestBody Alumne alumne) {
        alumne.setId(id);
        return alumneService.actualitzar(alumne);
    }

    // DELETE: Eliminar alumne
    @DeleteMapping("/{id}")
    public void eliminar(@PathVariable Long id) {
        alumneService.eliminar(id);
    }
}

Relacions entre Entitats

Relació One-to-Many

// Autor.java
@Entity
public class Autor {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String nom;

    @OneToMany(mappedBy = "autor", cascade = CascadeType.ALL)
    private List<Llibre> llibres = new ArrayList<>();
}

// Llibre.java
@Entity
public class Llibre {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String titol;

    @ManyToOne
    @JoinColumn(name = "autor_id")
    private Autor autor;
}

Relació Many-to-Many

// Alumne.java
@Entity
public class Alumne {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String nom;

    @ManyToMany
    @JoinTable(
        name = "alumne_curs",
        joinColumns = @JoinColumn(name = "alumne_id"),
        inverseJoinColumns = @JoinColumn(name = "curs_id")
    )
    private List<Curs> cursos = new ArrayList<>();
}

// Curs.java
@Entity
public class Curs {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String nom;

    @ManyToMany(mappedBy = "cursos")
    private List<Alumne> alumnes = new ArrayList<>();
}

Transaccions amb JPA

Anotació @Transactional

import org.springframework.transaction.annotation.Transactional;

@Service
public class AlumneService {

    @Autowired
    private AlumneRepository alumneRepository;

    @Transactional
    public void operacioComplexe() {
        // Múltiples operacions dins d'una transacció
        Alumne alumne1 = new Alumne("Anna", "anna@example.com", 20);
        Alumne alumne2 = new Alumne("Marc", "marc@example.com", 21);

        alumneRepository.save(alumne1);
        alumneRepository.save(alumne2);

        // Si alguna cosa falla, es revessa tot (rollback)
    }

    @Transactional(readOnly = true)
    public List<Alumne> obtenerTots() {
        return alumneRepository.findAll();
    }
}

Cicle de vida de les Entitats

Els estados que pot tenir una entitat JPA són:

  1. New (Nou): Creat però no persistit
  2. Managed (Gestionat): Dins del Persistence Context, monitoritzat
  3. Detached (Desconnectat): Anteriorment gestionat, però ja no està dins del context
  4. Removed (Eliminat): Marcat per eliminar
// New
Alumne alumne = new Alumne("Joan", "joan@example.com", 20);

// Managed (dins de la transacció)
alumneRepository.save(alumne); // ara JPA el monitoritza

// Detached
entityManager.detach(alumne); // surt del context

// Managed de nou
entityManager.merge(alumne); // torna a entrar al context

// Removed
alumneRepository.delete(alumne); // marcat per eliminar

Callbacks del cicle de vida

import jakarta.persistence.*;

@Entity
public class Alumne {

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

    private String nom;

    @Temporal(TemporalType.TIMESTAMP)
    private LocalDateTime dataCreacio;

    @Temporal(TemporalType.TIMESTAMP)
    private LocalDateTime dataModificacio;

    @PrePersist
    public void prePerssist() {
        this.dataCreacio = LocalDateTime.now();
        this.dataModificacio = LocalDateTime.now();
    }

    @PreUpdate
    public void preUpdate() {
        this.dataModificacio = LocalDateTime.now();
    }

    @PostPersist
    public void postPersist() {
        System.out.println("Alumne creat: " + this.nom);
    }

    @PostUpdate
    public void postUpdate() {
        System.out.println("Alumne actualitzat: " + this.nom);
    }

    @PreRemove
    public void preRemove() {
        System.out.println("Alumne a eliminar: " + this.nom);
    }

    @PostRemove
    public void postRemove() {
        System.out.println("Alumne eliminat");
    }
}

Validació d'Entitats

Utilitzant anotacions de validació

import jakarta.validation.constraints.*;

@Entity
public class Alumne {

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

    @NotBlank(message = "El nom no pot estar buit")
    @Size(min = 3, max = 100, message = "El nom ha de tenir entre 3 i 100 caràcters")
    private String nom;

    @Email(message = "L'email ha de ser vàlid")
    private String correu;

    @Min(value = 18, message = "La minim d'edat és 18")
    @Max(value = 100, message = "L'edat no pot ser superior a 100")
    private int edat;
}

Bàsic de JPA vs JPA amb Spring Data

Aspect JPA pure Spring Data JPA
Mètode persistència EntityManager Repositories
Crear entitat em.persist(obj) repository.save(obj)
Buscar tots JPQL query repository.findAll()
Buscar per ID em.find(Class, id) repository.findById(id)
Queries personalitzades @NamedQuery o JPQL Query by method name o @Query
Transaccions em.getTransaction() @Transactional
Complexitat Més codi Menys codi

Resum de conceptes clau

  1. JPA és una especificació estàndard per a la persistència d'objectes Java
  2. Entitats són classes que representen taules de base de dades
  3. EntityManager gestiona el cicle de vida de les entitats
  4. Persistence Context monitoritza els canvis en les entitats dins d'una transacció
  5. Spring Data JPA simplifica el treball amb JPA gràcies als Repositories
  6. Anotacions com @Entity, @Id, @Column defineixen el mapeig
  7. Transaccions asseguren que múltiples operacions es realitzin atómicament
  8. Relacions entre entitats es defineixen amb @OneToMany, @ManyToOne, @ManyToMany

Recursos per a més informació


Data de creació: Desembre 2025
Destinatari: Alumnes de DWES, DWEC i DAW - CIFP Francesc de Borja Moll