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:
- New (Nou): Creat però no persistit
- Managed (Gestionat): Dins del Persistence Context, monitoritzat
- Detached (Desconnectat): Anteriorment gestionat, però ja no està dins del context
- 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¶
- JPA és una especificació estàndard per a la persistència d'objectes Java
- Entitats són classes que representen taules de base de dades
- EntityManager gestiona el cicle de vida de les entitats
- Persistence Context monitoritza els canvis en les entitats dins d'una transacció
- Spring Data JPA simplifica el treball amb JPA gràcies als Repositories
- Anotacions com
@Entity,@Id,@Columndefineixen el mapeig - Transaccions asseguren que múltiples operacions es realitzin atómicament
- 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
