🌿 Spring Data JPA Cheatsheet Completo 🌿

Spring Data JPA es una parte del paraguas de Spring Data, que tiene como objetivo reducir la cantidad de código repetitivo necesario para implementar la capa de acceso a datos (Repository) en aplicaciones basadas en la tecnología JPA (Java Persistence API). Se integra perfectamente con Hibernate (la implementación JPA por defecto en Spring Boot).


1. 🌟 Conceptos Clave


2. 🛠️ Configuración Inicial (Spring Boot)

  1. Añadir dependencias en pom.xml (Maven):

    <dependencies>
        &lt;!-- Spring Boot Starter Data JPA (incluye Hibernate y H2 por defecto) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        &lt;!-- Driver de base de datos (ej. PostgreSQL) -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        &lt;!-- Para usar Jakarta EE Persistence API (Spring Boot 3+ usa Jakarta) -->
        <dependency>
            <groupId>jakarta.persistence</groupId>
            <artifactId>jakarta.persistence-api</artifactId>
            <version>3.1.0</version> &lt;!-- Usar la versión compatible con tu Spring Boot -->
        </dependency>
        &lt;!-- JUnit 5 y Mockito para pruebas -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
  2. Configuración de la Base de Datos (application.properties o application.yml):

    # application.properties
    spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
    spring.datasource.username=user
    spring.datasource.password=password
    spring.datasource.driver-class-name=org.postgresql.Driver
    
    # Configuración de JPA/Hibernate
    spring.jpa.hibernate.ddl-auto=update # create, create-drop, validate, none
    spring.jpa.show-sql=true
    spring.jpa.properties.hibernate.format_sql=true
    spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect # O el dialecto de tu DB

3. 📝 Definición de Entidades JPA

Las entidades son clases Java que se mapean a tablas de base de datos.

import jakarta.persistence.*; // Para Spring Boot 3+ (Jakarta EE 9+)
// import javax.persistence.*; // Para Spring Boot 2.x (Java EE 8)

@Entity // Marca la clase como una entidad JPA
@Table(name = "productos") // Opcional: Especifica el nombre de la tabla si es diferente al nombre de la clase
public class Product {

    @Id // Marca el campo como clave primaria
    @GeneratedValue(strategy = GenerationType.IDENTITY) // Estrategia de generación de ID (auto-incremento en DB)
    // Otras estrategias: AUTO, SEQUENCE, TABLE (menos comunes)
    private Long id;

    @Column(name = "nombre_producto", nullable = false, length = 150) // Mapea a columna, no nulo, longitud
    private String name;

    @Column(unique = true) // Valor único en esta columna
    private String sku;

    private Double price;

    private Boolean active;

    @Temporal(TemporalType.TIMESTAMP) // Mapeo para fechas/horas
    @Column(name = "created_at", updatable = false) // No se actualiza en UPDATEs
    private java.util.Date createdAt;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "updated_at")
    private java.util.Date updatedAt;

    @Transient // No se mapea a la base de datos
    private String transientField;

    // Relaciones (Ejemplos, pueden ser muy complejas)
    @ManyToOne(fetch = FetchType.LAZY) // Relación Muchos a Uno (varios productos a una categoría)
    @JoinColumn(name = "category_id") // Columna de clave foránea en la tabla 'productos'
    private Category category;

    @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true) // Uno a Muchos (un producto tiene muchos ítems de pedido)
    // 'mappedBy' indica que la relación es gestionada por el campo 'product' en la entidad OrderItem
    private java.util.List<OrderItem> orderItems;

    // Constructor(es), Getters y Setters
    public Product() { }
    public Product(String name, String sku, Double price, Boolean active) {
        this.name = name;
        this.sku = sku;
        this.price = price;
        this.active = active;
    }

    @PrePersist // Método que se ejecuta antes de persistir la entidad por primera vez
    protected void onCreate() {
        this.createdAt = new java.util.Date();
        this.updatedAt = new java.util.Date();
    }

    @PreUpdate // Método que se ejecuta antes de actualizar la entidad
    protected void onUpdate() {
        this.updatedAt = new java.util.Date();
    }
}

4. 🗃️ Definición de Repositorios

Simplemente crea una interfaz que extienda JpaRepository (u otras interfaces de Spring Data). Spring Data JPA creará la implementación en tiempo de ejecución.

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; // Opcional pero buena práctica

import java.util.List;
import java.util.Optional;

@Repository // Anotación opcional, pero buena práctica para la claridad del rol
public interface ProductRepository extends JpaRepository<Product, Long> {
    // Aquí no se escribe ningún código, ¡solo la firma del método!

    // 1. Métodos Derivados de Consultas (Query Methods)
    // Encuentra productos por nombre
    List<Product> findByName(String name);

    // Encuentra productos por nombre O SKU
    List<Product> findByNameOrSku(String name, String sku);

    // Encuentra productos con precio mayor que un valor dado
    List<Product> findByPriceGreaterThan(Double price);

    // Encuentra productos activos ordenados por nombre ascendente
    List<Product> findByActiveTrueOrderByNameAsc();

    // Encuentra productos por nombre que contenga una cadena (case-insensitive)
    List<Product> findByNameContainingIgnoreCase(String name);

    // Encuentra los 5 productos más caros
    List<Product> findTop5ByOrderByPriceDesc();

    // Comprueba si un producto con un SKU dado existe
    boolean existsBySku(String sku);

    // Cuenta el número de productos activos
    long countByActiveTrue();

    // Elimina productos por nombre
    long deleteByName(String name); // Devuelve el número de entidades eliminadas

    // 2. Consultas Personalizadas con @Query (JPQL o Native SQL)
    // JPQL (Java Persistence Query Language): Usa nombres de entidades y campos de entidad
    @Query("SELECT p FROM Product p WHERE p.price BETWEEN ?1 AND ?2")
    List<Product> findProductsInPriceRange(Double minPrice, Double maxPrice);

    @Query("SELECT p FROM Product p WHERE p.category.name = :categoryName")
    List<Product> findByCategoryName(@Param("categoryName") String categoryName);

    // Native SQL Query (SQL puro de tu base de datos)
    @Query(value = "SELECT * FROM productos WHERE nombre_producto LIKE %?1%", nativeQuery = true)
    List<Product> searchProductsNative(String keyword);

    // Consultas de actualización/eliminación con @Modifying y @Transactional
    @Modifying // Indica que la consulta es una operación DML (INSERT, UPDATE, DELETE)
    @Transactional // Para que la operación sea transaccional (importante para DML)
    @Query("UPDATE Product p SET p.price = p.price * (1 + :percentage / 100) WHERE p.active = TRUE")
    int updatePriceForActiveProducts(@Param("percentage") Double percentage);
}

5. 🚀 Métodos Heredados de JpaRepository (CRUD y Paginación/Ordenación)

JpaRepository extiende PagingAndSortingRepository y CrudRepository, proporcionando estos métodos listos para usar:

5.1. Paginación y Ordenación

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

public interface ProductRepository extends JpaRepository<Product, Long> {
    // Obtener todos los productos paginados y ordenados
    Page<Product> findAll(Pageable pageable);

    // Obtener productos activos paginados y ordenados por precio
    Page<Product> findByActiveTrue(Pageable pageable);

    // Solo ordenación (retorna una List)
    List<Product> findAll(Sort sort);
}

// Uso en un Service o Controller
// Pageable pageable = PageRequest.of(0, 10, Sort.by("name").ascending());
// Page<Product> productPage = productRepository.findAll(pageable);
// List<Product> products = productPage.getContent();
// int totalPages = productPage.getTotalPages();
// long totalElements = productPage.getTotalElements();

6. 🌐 Transacciones (@Transactional)

Gestiona el comportamiento transaccional de los métodos. Generalmente se usa en la capa de Servicio.

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class ProductService {
    private final ProductRepository productRepository;

    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    @Transactional(readOnly = true) // Método de solo lectura
    public Product getProductDetails(Long id) {
        return productRepository.findById(id)
                .orElseThrow(() -&gt; new IllegalArgumentException("Producto no encontrado"));
    }

    @Transactional // Método de escritura, transaccional
    public Product createProduct(Product product) {
        if (productRepository.existsBySku(product.getSku())) {
            throw new IllegalArgumentException("SKU de producto ya existente");
        }
        return productRepository.save(product);
    }

    @Transactional
    public void transferStock(Long fromProductId, Long toProductId, int quantity) {
        Product fromProduct = productRepository.findById(fromProductId)
            .orElseThrow(() -&gt; new IllegalArgumentException("Producto origen no encontrado"));
        Product toProduct = productRepository.findById(toProductId)
            .orElseThrow(() -&gt; new IllegalArgumentException("Producto destino no encontrado"));

        if (fromProduct.getStock() &lt; quantity) { // Suponiendo un campo 'stock'
            throw new IllegalArgumentException("Stock insuficiente");
        }

        fromProduct.setStock(fromProduct.getStock() - quantity);
        toProduct.setStock(toProduct.getStock() + quantity);

        productRepository.save(fromProduct);
        productRepository.save(toProduct);
        // Si hay una excepción aquí, ambos saves se desharán (rollback)
    }
}

7. ❌ Manejo de Errores Comunes


8. 💡 Buenas Prácticas y Consejos


Este cheatsheet te proporciona una referencia completa de Spring Data JPA, cubriendo sus conceptos esenciales, la definición de entidades y repositorios, los métodos derivados de consultas, @Query, paginación, transacciones y las mejores prácticas para construir una capa de acceso a datos eficiente y mantenible en tus aplicaciones Spring Boot.