Livro de receitas Spring Data JPA

utilizando Java 11 e Spring Boot 2.5

Cada receita exemplifica um tópico do Spring Data JPA

Spring Data JPA é uma camada de abstração sobre JPA que simplifica a utilização deste.

Fundamentos de JPA e Spring Boot não serão discutidos nas receitas.

🚧 Artigo em construção!

Conteúdo

  1. Configuração
  2. CRUD
  3. Query by Example
  4. JPQL
  5. Native query
  6. Specification
  7. Referências

👉 Algum conceito faltando?

Deixe um comentário no fim do post e escreverei a receita assim que possível.

Configuração

pom.xml

<?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>2.5.4</version>
        <relativePath/>
    </parent>

    <groupId>dev.caiosantesso</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>2021.9</version>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Dependências

  1. Execute mvn dependency:tree para obter as dependências do spring-boot-starter-data-jpa.
[INFO] ------------------< dev.caiosantesso:spring-data-jpa >------------------
[INFO] Building spring-data-jpa 2021.9
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:3.1.2:tree (default-cli) @ spring-data-jpa ---
[INFO] dev.caiosantesso:spring-data-jpa:jar:2021.9
[INFO] +- org.springframework.boot:spring-boot-starter-data-jpa:jar:2.5.4:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-aop:jar:2.5.4:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter:jar:2.5.4:compile
[INFO] |  |  |  +- org.springframework.boot:spring-boot:jar:2.5.4:compile
[INFO] |  |  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:2.5.4:compile
[INFO] |  |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:2.5.4:compile
[INFO] |  |  |  |  +- ch.qos.logback:logback-classic:jar:1.2.5:compile
[INFO] |  |  |  |  |  \- ch.qos.logback:logback-core:jar:1.2.5:compile
[INFO] |  |  |  |  +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.14.1:compile
[INFO] |  |  |  |  |  \- org.apache.logging.log4j:log4j-api:jar:2.14.1:compile
[INFO] |  |  |  |  \- org.slf4j:jul-to-slf4j:jar:1.7.32:compile
[INFO] |  |  |  +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
[INFO] |  |  |  \- org.yaml:snakeyaml:jar:1.28:compile
[INFO] |  |  +- org.springframework:spring-aop:jar:5.3.9:compile
[INFO] |  |  \- org.aspectj:aspectjweaver:jar:1.9.7:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-jdbc:jar:2.5.4:compile
[INFO] |  |  +- com.zaxxer:HikariCP:jar:4.0.3:compile
[INFO] |  |  \- org.springframework:spring-jdbc:jar:5.3.9:compile
[INFO] |  +- jakarta.transaction:jakarta.transaction-api:jar:1.3.3:compile
[INFO] |  +- jakarta.persistence:jakarta.persistence-api:jar:2.2.3:compile
[INFO] |  +- org.hibernate:hibernate-core:jar:5.4.32.Final:compile
[INFO] |  |  +- org.jboss.logging:jboss-logging:jar:3.4.2.Final:compile
[INFO] |  |  +- org.javassist:javassist:jar:3.27.0-GA:compile
[INFO] |  |  +- net.bytebuddy:byte-buddy:jar:1.10.22:compile
[INFO] |  |  +- antlr:antlr:jar:2.7.7:compile
[INFO] |  |  +- org.jboss:jandex:jar:2.2.3.Final:compile
[INFO] |  |  +- com.fasterxml:classmate:jar:1.5.1:compile
[INFO] |  |  +- org.dom4j:dom4j:jar:2.1.3:compile
[INFO] |  |  +- org.hibernate.common:hibernate-commons-annotations:jar:5.1.2.Final:compile
[INFO] |  |  \- org.glassfish.jaxb:jaxb-runtime:jar:2.3.5:compile
[INFO] |  |     +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:compile
[INFO] |  |     +- org.glassfish.jaxb:txw2:jar:2.3.5:compile
[INFO] |  |     +- com.sun.istack:istack-commons-runtime:jar:3.0.12:compile
[INFO] |  |     \- com.sun.activation:jakarta.activation:jar:1.2.2:runtime
[INFO] |  +- org.springframework.data:spring-data-jpa:jar:2.5.4:compile
[INFO] |  |  +- org.springframework.data:spring-data-commons:jar:2.5.4:compile
[INFO] |  |  +- org.springframework:spring-orm:jar:5.3.9:compile
[INFO] |  |  +- org.springframework:spring-context:jar:5.3.9:compile
[INFO] |  |  |  \- org.springframework:spring-expression:jar:5.3.9:compile
[INFO] |  |  +- org.springframework:spring-tx:jar:5.3.9:compile
[INFO] |  |  +- org.springframework:spring-beans:jar:5.3.9:compile
[INFO] |  |  +- org.springframework:spring-core:jar:5.3.9:compile
[INFO] |  |  |  \- org.springframework:spring-jcl:jar:5.3.9:compile
[INFO] |  |  \- org.slf4j:slf4j-api:jar:1.7.32:compile
[INFO] |  \- org.springframework:spring-aspects:jar:5.3.9:compile
[INFO] +- com.h2database:h2:jar:1.4.200:runtime
[INFO] \- org.projectlombok:lombok:jar:1.18.20:compile (optional)
[INFO] ------------------------------------------------------------------------

application.properties

spring.output.ansi.enabled=always

#Cria tabelas para as entidades
spring.jpa.hibernate.ddl-auto=none

#Exibe comandos e tempo de duração de data.sql
logging.level.org.springframework.jdbc.datasource.init.ScriptUtils=DEBUG

#Exibe SQL gerado pelo Hibernate
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
#spring.jpa.properties.hibernate.use_sql_comments=true

#Exibe argumentos utilizados pelo Hibernate
--

data.sql

CREATE TABLE `estado` (
  `estado` char(2) NOT NULL,
  PRIMARY KEY (`estado`)
);

INSERT INTO `estado`
VALUES ('AC'),('AP'),('PE'),('PR'),('RS'),('SC'),('SP');

CREATE TABLE `clube` (
  `id` tinyint NOT NULL AUTO_INCREMENT,
  `nome` varchar(16),
  `estado` char(2),
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`nome`),
  CONSTRAINT `clube_FK` FOREIGN KEY (`estado`) REFERENCES `estado` (`estado`)
);

INSERT INTO `clube`
VALUES (3,'Athlético-PR','PR'),(6,'Avaí','SC'),(27,'Internacional',
'RS'),(32,'Palmeiras','SP'), (39,'Santos','SP'),(42,'Sport','PE');

CRUD

A interface org.springframework.data.jpa.repository.JpaRepository provê métodos para a maior parte dos casos de uso mais simples. Cada um dos repositórios gerencia uma entidade.

  1. Crie uma entidade.
  2. Crie uma interface estendendo JpaRepository<T, ID>.
    1. Substitua o parâmetro de tipo T pela entidade.
    2. E ID pelo campo da entidade anotado com @Id.
  3. Injete a interface criada.
  4. Chame o método.

Buscando todos os registros

findAll() devolve lista de entidades com todos os registros.

package dev.caiosantesso.spring_data_jpa.crud.select.all;

import lombok.*;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.JpaRepository;

import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.PersistenceContext;
import java.util.List;

interface ClubeRepo extends JpaRepository<Clube, Integer> {}

@NoArgsConstructor @Getter @Setter
@Entity
class Clube {
  @Id Integer id;
  String nome;
}

@SpringBootApplication @RequiredArgsConstructor
public class SelecioneTodos implements ApplicationRunner {
  final ClubeRepo repo;

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

  public void run(ApplicationArguments args) {
    List<Clube> todosOsClubes = repo.findAll();
  }
}

Consulta gerada.

select
    clube0_.id as id1_0_,
    clube0_.nome as nome2_0_
from
    clube clube0_

Buscando um registro por ID

findById(ID) traz Optional<T> com um possível registro.

package dev.caiosantesso.spring_data_jpa.crud.select.one;

import lombok.*;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.JpaRepository;

import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.List;
import java.util.Optional;
import java.util.Set;

interface ClubeRepo extends JpaRepository<Clube, Integer> {}

@NoArgsConstructor @Getter @Setter
@Entity
class Clube {
  @Id Integer id;
  String nome;
}

@SpringBootApplication @RequiredArgsConstructor
public class SelecioneUmPorId implements ApplicationRunner {
  final ClubeRepo repo;

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

  public void run(ApplicationArguments args) {
    Optional<Clube> possivelClube = repo.findById(32);
  }
}
select
    clube0_.id as id1_0_0_,
    clube0_.nome as nome2_0_0_
from
    clube clube0_
where
    clube0_.id=?
-binding parameter [1] as [INTEGER] - [32]

Buscando registros por ID

findById(Iterable<ID>) traz lista com todos os registros cujos IDs foram especificados no Iterable<ID>.

package dev.caiosantesso.spring_data_jpa.crud.select.some;

import lombok.*;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.JpaRepository;

import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.List;
import java.util.Optional;
import java.util.Set;

interface ClubeRepo extends JpaRepository<Clube, Integer> {}

@NoArgsConstructor @Getter @Setter
@Entity
class Clube {
  @Id Integer id;
  String nome;
}

@SpringBootApplication @RequiredArgsConstructor
public class SelecioneAlgunsPorId implements ApplicationRunner {
  final ClubeRepo repo;

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

  public void run(ApplicationArguments args) {
    Iterable<Integer> ids = Set.of(27, 32);
    List<Clube> clubes = repo.findAllById(ids);
  }
}
select
    clube0_.id as id1_0_,
    clube0_.nome as nome2_0_
from
    clube clube0_
where
    clube0_.id in (
        ? , ?
    )
-binding parameter [1] as [INTEGER] - [27]
-binding parameter [2] as [INTEGER] - [32]

Buscando todos os registros ordenadamente

  1. Crie um objeto Sort para definir a ordenação.
    • Por padrão Sort ordena a coluna ascendentemente.
    • Order pode expandir a definição de Sort especificando ordem dos nulos e ignorando a caixa das letras.
    • Sort#and(Sort) combina duas instâncias de Sort, priorizando a ordenação da esquerda para direita.
  2. Invoque findAll(Sort).
package dev.caiosantesso.spring_data_jpa.crud.select.sorted;

import lombok.*;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;

import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.List;
import java.util.Set;

interface ClubeRepo extends JpaRepository<Clube, Integer> {}

@NoArgsConstructor @Getter @Setter
@Entity
class Clube {
  @Id Integer id;
  String nome;
  String estado;
}

@SpringBootApplication @RequiredArgsConstructor
public class SelecioneOrdenadamente implements ApplicationRunner {
  final ClubeRepo repo;

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

  public void run(ApplicationArguments args) {
    Sort ordemPorEstado = Sort.by("estado");
    Sort ordemPorNome = Sort.by("id").descending();
    Sort ordemPorEstadoENome = ordemPorEstado.and(ordemPorNome);

    List<Clube> clubes = repo.findAll(ordemPorEstadoENome);
  }
}
select
    clube0_.id as id1_0_,
    clube0_.estado as estado2_0_,
    clube0_.nome as nome3_0_
from
    clube clube0_
order by
    lower(clube0_.nome) asc,
    clube0_.id desc

Buscando registros paginadamente

  1. Crie um Pageable chamando PageRequest#of(int, int, Sort) com, respectivamente:
    1. o número da página (inicia em 0);
    2. a quantidade de registros que cada página deve ter (a última página obviamente pode conter menos);
    3. e a definição da ordenação (opcional).
  2. Invoque findAll(Pageable).
  3. Page então é devolvido contendo a quantidade de registros parametrizada em Pageable.
    1. Chame Page#getContent() para obter as entidades em lista.
package dev.caiosantesso.spring_data_jpa.crud.select.paged;

import lombok.*;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;

import javax.persistence.Entity;
import javax.persistence.Id;

interface ClubeRepo extends JpaRepository<Clube, Integer> {}

@NoArgsConstructor @Getter @Setter @ToString
@Entity
class Clube {
  @Id Integer id;
  String nome;
  String estado;
}

@SpringBootApplication @RequiredArgsConstructor
public class SelecionePaginadamente implements ApplicationRunner {
  final ClubeRepo repo;

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

  public void run(ApplicationArguments args) {
    Pageable paginavel = PageRequest.of(3, 2, Sort.by("nome"));

    Page<Clube> pagina3 = repo.findAll(paginavel);

    System.out.printf("%d itens em %d paginas de até %d itens.%n",
        pagina3.getTotalElements(),
        pagina3.getTotalPages(),
        pagina3.getSize());

    System.out.printf("A página %d contém %d item:%s.%n",
        pagina3.getNumber(),
        pagina3.getNumberOfElements(),
        pagina3.getContent());
  }
}
7 itens em 4 paginas de até 2 itens.
A página 3 contém 1 item:[Clube(id=42, nome=Sport, estado=PE)].
select
    clube0_.id as id1_0_,
    clube0_.estado as estado2_0_,
    clube0_.nome as nome3_0_
from
    clube clube0_
order by
    clube0_.nome asc limit ? offset ?

Verificando se um registro existe

exists(ID) devolve booleano indicando a existência do registro dado um ID.

package dev.caiosantesso.spring_data_jpa.crud.select.exists;

import lombok.*;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.JpaRepository;

import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.Optional;

interface ClubeRepo extends JpaRepository<Clube, Integer> {}

@NoArgsConstructor @Getter @Setter @ToString
@Entity
class Clube {
  @Id Integer id;
  String nome;
}

@SpringBootApplication @RequiredArgsConstructor
public class SelecioneExistente implements ApplicationRunner {
  final ClubeRepo repo;

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

  public void run(ApplicationArguments args) {
    boolean existe = repo.existsById(32);
  }
}
select
    count(*) as col_0_0_
from
    clube clube0_
where
    clube0_.id=?
-binding parameter [1] as [INTEGER] - [32]

Buscando quantidade de registros

count() devolve a quantidade de registros.

package dev.caiosantesso.spring_data_jpa.crud.select.count;

import lombok.*;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.JpaRepository;

import javax.persistence.Entity;
import javax.persistence.Id;

interface ClubeRepo extends JpaRepository<Clube, Integer> {}

@NoArgsConstructor @Getter @Setter @ToString
@Entity
class Clube {
  @Id Integer id;
  String nome;
}

@SpringBootApplication @RequiredArgsConstructor
public class SelecioneContagem implements ApplicationRunner {
  final ClubeRepo repo;

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

  public void run(ApplicationArguments args) {
    long contagem = repo.count();
  }
}
select
    count(*) as col_0_0_
from
    clube clube0_

Excluindo registros por ID

deleteById(ID) busca o registro com o ID especificado, e:

  • caso encontrado exclui o registro;
  • caso contrário lança a exceção org.springframework.dao.EmptyResultDataAccessException.

deleteAllById(Iterable<ID>) itera os IDs informandos chamando deleteById(ID) para cada.

package dev.caiosantesso.spring_data_jpa.crud.delete.id;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.JpaRepository;

import javax.persistence.*;
import java.util.List;

interface EstadoRepo extends JpaRepository<Estado, String> {}

@NoArgsConstructor @Getter @Setter
@Entity
class Estado {
  @Id String estado;
  @OneToMany(mappedBy = "estado", cascade = CascadeType.REMOVE)
  List<Clube> clubes;
}

@NoArgsConstructor @Getter @Setter
@Entity
class Clube {
  @Id Integer id;
  String nome;
  @ManyToOne @JoinColumn(name = "estado") Estado estado;
}

@SpringBootApplication @RequiredArgsConstructor
public class ExcluaPorId implements ApplicationRunner {
  final EstadoRepo repo;

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

  public void run(ApplicationArguments args) {
    repo.deleteById("RS");
  }
}
    select
    estado0_.estado as estado1_1_0_
from
    estado estado0_
where
    estado0_.estado=?
--binding parameter [1] as [VARCHAR] - [RS]
select
    clubes0_.estado as estado3_0_0_,
    clubes0_.id as id1_0_0_,
    clubes0_.id as id1_0_1_,
    clubes0_.estado as estado3_0_1_,
    clubes0_.nome as nome2_0_1_
from
    clube clubes0_
where
    clubes0_.estado=?
--binding parameter [1] as [VARCHAR] - [RS]
delete
from
    clube
where
    id=?
--binding parameter [1] as [INTEGER] - [27]
delete
from
    estado
where
    estado=?
--binding parameter [1] as [VARCHAR] - [RS]

Excluindo registros por entidade

delete(T) obtém o ID da entidade T, busca entidade com esse ID e:

  • caso encontrada verifica então se a entidade passada como argumento consta no cache (EntityManager)
    • se sim, exclui a entidade.
    • se não, sincroniza a entidade passada como argumento com EntityManager e exclui a nova referência sincronizada.
  • caso não encontrada nada faz.

deleteAll(Iterable<T>) itera as entidades chamando delete(T) para cada.

package dev.caiosantesso.spring_data_jpa.crud.delete.entity;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.JpaRepository;

import javax.persistence.*;
import java.util.List;

interface EstadoRepo extends JpaRepository<Estado, String> {}

@NoArgsConstructor @Getter @Setter
@Entity
class Estado {
  @Id String estado;
  @OneToMany(mappedBy = "estado", cascade = CascadeType.REMOVE)
  List<Clube> clubes;
}

@NoArgsConstructor @Getter @Setter
@Entity
class Clube {
  @Id Integer id;
  String nome;
  @ManyToOne @JoinColumn(name = "estado")
  Estado estado;
}

@SpringBootApplication @RequiredArgsConstructor
public class ExcluaEntidade implements ApplicationRunner {
  final EstadoRepo repo;

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

  public void run(ApplicationArguments args) {
    Estado uf = repo.getById("RS");
    repo.delete(uf);
  }
}
select
    estado0_.estado as estado1_1_0_
from
    estado estado0_
where
    estado0_.estado=?
--binding parameter [1] as [VARCHAR] - [RS]
select
    clubes0_.estado as estado3_0_0_,
    clubes0_.id as id1_0_0_,
    clubes0_.id as id1_0_1_,
    clubes0_.estado as estado3_0_1_,
    clubes0_.nome as nome2_0_1_
from
    clube clubes0_
where
    clubes0_.estado=?
--binding parameter [1] as [VARCHAR] - [RS]
delete
from
    clube
where
    id=?
--binding parameter [1] as [INTEGER] - [27]
delete
from
    estado
where
    estado=?
--binding parameter [1] as [VARCHAR] - [RS]

Excluindo todos os registros

deleteAll() busca por todos os registros e então chama delete(T) para cada um.

package dev.caiosantesso.spring_data_jpa.crud.delete.all;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.JpaRepository;

import javax.persistence.*;
import java.util.List;

interface EstadoRepo extends JpaRepository<Estado, String> {}

@NoArgsConstructor @Getter @Setter
@Entity
class Estado {
  @Id String estado;
  @OneToMany(mappedBy = "estado", cascade = CascadeType.REMOVE)
  List<Clube> clubes;
}

@NoArgsConstructor @Getter @Setter
@Entity
class Clube {
  @Id Integer id;
  String nome;
  @ManyToOne @JoinColumn(name = "estado")
  Estado estado;
}

@SpringBootApplication @RequiredArgsConstructor
public class ExcluaTudo implements ApplicationRunner {
  final EstadoRepo repo;

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

  public void run(ApplicationArguments args) {
    repo.deleteAll();
  }
}
select
    estado0_.estado as estado1_1_
from
    estado estado0_
select
    clubes0_.estado as estado3_0_0_,
    clubes0_.id as id1_0_0_,
    clubes0_.id as id1_0_1_,
    clubes0_.estado as estado3_0_1_,
    clubes0_.nome as nome2_0_1_
from
    clube clubes0_
where
    clubes0_.estado=?
--binding parameter [1] as [VARCHAR] - [AC]
--Repete consulta acim para cada estado
--binding parameter [1] as [VARCHAR] - [PE]
--binding parameter [1] as [VARCHAR] - [RS]
--binding parameter [1] as [VARCHAR] - [SP]
delete
from
    estado
where
    estado=?
--binding parameter [1] as [VARCHAR] - [AC]
delete
from
    clube
where
    id=?
--binding parameter [1] as [INTEGER] - [42]
delete
from
    estado
where
    estado=?
--binding parameter [1] as [VARCHAR] - [PE]
delete
from
    clube
where
    id=?
--binding parameter [1] as [INTEGER] - [27]
delete
from
    estado
where
    estado=?
--binding parameter [1] as [VARCHAR] - [RS]
delete
from
    clube
where
    id=?
--binding parameter [1] as [INTEGER] - [32]
delete
from
    estado
where
    estado=?
--binding parameter [1] as [VARCHAR] - [SP]

Excluindo registros em lote

Os métodos delete*InBatch():

  • Ignoram javax.persistence.CascadeType;
  • Não sincronizam com EntityManager, consequentemente aceitando qualquer ID, existente ou não.
  • Ignoram javax.persistence.PreRemove;
package dev.caiosantesso.spring_data_jpa.crud.delete.batch;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.JpaRepository;

import javax.persistence.*;
import java.util.List;
import java.util.Set;

interface ClubeRepo extends JpaRepository<Clube, Integer> {}

@NoArgsConstructor @Getter @Setter
@Entity
class Estado {
  @Id String estado;
}

@NoArgsConstructor @Getter @Setter
@Entity
class Clube {
  @Id Integer id;
  String nome;
  //Cardinalidade errada para demonstrar falta de cascade.
  @OneToOne(cascade = CascadeType.REMOVE) @JoinColumn(name = "estado")
  Estado estado;

  @PreRemove
  void ignorado() {System.out.println("Ignorado");}
}

@SpringBootApplication @RequiredArgsConstructor
public class DeleteInBatch implements ApplicationRunner {
  final ClubeRepo repo;

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

  public void run(ApplicationArguments args) {
    List<Integer> ids = List.of(32, 99);
    repo.deleteAllByIdInBatch(ids);

    Clube inter = repo.getById(27);
    Clube sport = repo.getById(42);
    repo.deleteAllInBatch(Set.of(inter, sport));

    repo.deleteAllInBatch();
  }
}

deleteAllByIdInBatch(Iterable<ID>) exclui todos os registros cujos IDs foram especificados no Iterable<ID> em um só commando SQL.

delete
from
    clube
where
    id in (
        ? , ?
    )
-binding parameter [1] as [INTEGER] - [32]
-binding parameter [2] as [INTEGER] - [99]

deleteAllInBatch(Iterable<T>) exclui todas as entidades especificadas no Iterable<T> em um só comando SQL.

delete
from
    clube
where
    id=?
    or id=?
-binding parameter [1] as [INTEGER] - [27]
-binding parameter [2] as [INTEGER] - [42]

deleteAllInBatch() exclui todos os registros em um só comando SQL.

delete
from
    clube

Inserindo registros sem ID ou versão

  1. Crie uma entidade:
    1. com @Id nulo;
    2. e/ou com @Version nulo.
  2. Chame save(T).
package dev.caiosantesso.spring_data_jpa.crud.insert.no_id;

import lombok.*;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.JpaRepository;

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

interface ClubeRepo extends JpaRepository<Clube, Integer> {}

@NoArgsConstructor @Getter @Setter @AllArgsConstructor
@Entity
class Clube {
  @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Integer id;
  String nome;
  String estado;
}

@SpringBootApplication @RequiredArgsConstructor
public class InsiraSemId implements ApplicationRunner {
  final ClubeRepo repo;

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

  public void run(ApplicationArguments args) {
    Clube clube = new Clube(null, "Bragantino", "SP");
    repo.save(clube);
  }
}
insert
  into
      clube
      (id, estado, nome)
  values
      (null, ?, ?)
--binding parameter [1] as [VARCHAR] - [SP]
--binding parameter [2] as [VARCHAR] - [Bragantino]

Inserindo registros com ID

  1. Implemente Persistable<ID> na entidade a ser inserida.
  2. Implemente isNew() devolvendo true quando a entidade não existir no BD.
  3. Crie a entidade a ser inserida.
  4. Invoque save(T) passando a entidade.
package dev.caiosantesso.spring_data_jpa.crud.insert.with_id;

import lombok.*;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.domain.Persistable;
import org.springframework.data.jpa.repository.JpaRepository;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Transient;

interface ClubeRepo extends JpaRepository<Clube, Integer> {}

@NoArgsConstructor @Getter @Setter @AllArgsConstructor 
@Entity
class Clube implements Persistable<Integer> {
  @Id Integer id;
  String nome;
  String estado;
  @Transient boolean isNew;

  @Override
  public boolean isNew() {
    return isNew;
  }
}

@SpringBootApplication @RequiredArgsConstructor
public class InsiraComId implements ApplicationRunner {
  final ClubeRepo repo;

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

  public void run(ApplicationArguments args) {
    Clube clube = new Clube(99, "Bragantino", "SP", true);
    repo.save(clube);
  }
}
insert 
  into
      clube
      (estado, nome, id) 
  values
      (?, ?, ?)
--binding parameter [1] as [VARCHAR] - [SP]
--binding parameter [2] as [VARCHAR] - [Bragantino]
--binding parameter [3] as [INTEGER] - [99]

Atualizando um registro com ID conhecido

  1. Crie uma entidade informando o ID.
  2. Chame save(T) passando a entidade.
package dev.caiosantesso.spring_data_jpa.crud.update;

import lombok.*;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.JpaRepository;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.Optional;

interface ClubeRepo extends JpaRepository<Clube, Integer> {}

@NoArgsConstructor @Getter @Setter @AllArgsConstructor
@Entity
class Clube {
  @Id Integer id;
  String nome;
  String estado;
}

@SpringBootApplication @RequiredArgsConstructor
public class AtualizeUm implements ApplicationRunner {
  final ClubeRepo repo;

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

  public void run(ApplicationArguments args) {
    Clube palmeiras = new Clube(32, "Palestra", "SP");
    repo.save(palmeiras);
  }
}
select
    clube0_.id as id1_0_0_,
    clube0_.estado as estado2_0_0_,
    clube0_.nome as nome3_0_0_ 
from
    clube clube0_ 
where
    clube0_.id=?
--binding parameter [1] as [INTEGER] - [32]
update
    clube 
set
    estado=?,
    nome=? 
where
    id=?
--binding parameter [1] as [VARCHAR] - [SP]
--binding parameter [2] as [VARCHAR] - [Palestra]
--binding parameter [3] as [INTEGER] - [32]

Buscando e atualizando um registro

  1. Anote o método com @Transactional.
  2. Busque a entidade preferencialmente com getById(ID) ou com algum métod find*().
  3. Altere o campo da entidade com o respectivo setter.
package dev.caiosantesso.spring_data_jpa.crud.update.transaction;

import lombok.*;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.Optional;

interface ClubeRepo extends JpaRepository<Clube, Integer> {}

@NoArgsConstructor @Getter @Setter @AllArgsConstructor
@Entity
class Clube {
  @Id Integer id;
  String nome;
  String estado;
}

@SpringBootApplication @RequiredArgsConstructor
public class SelecioneEAtualize implements ApplicationRunner {
  final ClubeRepo repo;

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

  @Transactional
  public void run(ApplicationArguments args) {
    Clube palmeiras = repo.getById(32);
    palmeiras.setNome("Palestra");
  }
}
select
    clube0_.id as id1_0_0_,
    clube0_.estado as estado2_0_0_,
    clube0_.nome as nome3_0_0_ 
from
    clube clube0_ 
where
    clube0_.id=?
--binding parameter [1] as [INTEGER] - [32]
update
    clube 
set
    estado=?,
    nome=? 
where
    id=?
--binding parameter [1] as [VARCHAR] - [SP]
--binding parameter [2] as [VARCHAR] - [Palestra]
--binding parameter [3] as [INTEGER] - [32]

Query by Example

🚧 Artigo em construção!

JPQL

🚧 Artigo em construção!

Native query

🚧 Artigo em construção!

Projections

🚧 Artigo em construção!

Specification

🚧 Artigo em construção!

Referências

Escrito por Caio Santesso.

Comentários

  • Conteúdo dos posts, exceto onde indicado contrário, licenciado sob a licença CC BY-SA 4.0 .