Java é uma linguagem de programação que usa o paradigma Orientado a Objetos. E assim como em outras linguagens, podemos integrar nossos programas com um banco de dados para armazenar dados importantes.
Dentre os tipos de banco de dados, o mais tradicional é o Banco de Dados Relacional. Esse tipo de banco trabalha com tabela relacionais, onde as colunas são os campos da tabela e as linhas são os registros.
Mas, então como podemos unir o Java, orientado a objetos, com um banco de dados relacional, orientado a tabelas?
JDBC
A forma mais simples de unir um programa Java com um banco de dados é usando o JDBC (Java Database Connectivity).
O JDBC é uma API do java para comunicação com o banco de dados. Essa API é formada pelos pacotes java.sql e javax.sql. Ela permite que você realize queries no banco de dados ao usar o driver JDBC do banco que precisa.
Por exemplo, se você tem um projeto maven, pode usar o driver JDBC do PostgreSQL
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.13</version>
<scope>compile</scope>
</dependency>
package com.example;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static record User(String name, String email) {}
public static void main(String[] args) {
// url de conexão
String url = "jdbc:postgresql://localhost:5432/db4java";
String username = "dev";
String password = "dev";
try (Connection conn = DriverManager.getConnection(url, username, password)) {
// INSERT
User user = new User("Fulano", "fulano@mail.com");
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
PreparedStatement statement = conn.prepareStatement(sql);
statement.setString(1, user.name());
statement.setString(2, user.email());
int rowsInserted = statement.executeUpdate();
if (rowsInserted > 0) {
System.out.println("Usuário cadastrado com sucesso!");
conn.commit();
}
statement.close();
// SELECT
List<User> users = new ArrayList<>();
sql = "SELECT * FROM users";
Statement selectStatement = conn.createStatement();
ResultSet resultSet = selectStatement.executeQuery(sql);
while (resultSet.next()) {
String name = resultSet.getString("name");
String email = resultSet.getString("email");
users.add(new User(name, email));
}
resultSet.close();
selectStatement.close();
System.out.println("Lista de usuários cadastrados:");
users.forEach(System.out::println);
} catch (SQLException ex) {
System.out.println("Erro: " + ex.getMessage());
}
}
}
Desvantagem do JDBC
Como o JDBC usa o SQL para fazer as consultas no banco de dados, temos que trabalhar com dois paradigmas diferentes, relacional e orientado a objetos.
Isso torna o código mais trabalhoso a longo prazo, apesar de ter padrões de projetos que ajudam a resolver isso como o DAO e Repository.
JPA
Com a dificuldade de gerenciar dois paradigmas no código java, surgiu o JPA (Jakarta Persistence API). O JPA é uma especificação que permite você criar entidades para representar as tabelas do banco de dados. Essas entidades são classes com anotações do JPA para fazer o Mapeamento Objeto-Relacional (ORM).
Algo que deve ter atenção é que o JPA é apenas uma especificação, como uma interface do java. Ou seja, precisamos de uma biblioteca que implemente essa especificação. As mais famosas são o Hibernate e o Spring Data JPA que roda em cima do Hibernate. Mas, há outras implementações do JPA como o EclipseLink, Apache OpenJPA e DataNucleus.
Pacotes javax e jakarta
As classes que lidam com o JPA fazem parte do Java EE e fazem parte do pacote javax.*, mas em código mais moderno você pode se deparar com o pacote jakarta.*.
Depois que o Java foi comprado pela empresa Oracle, parte do código fonte do Java EE foi doado para a Eclipse Foundation, porém o nome Java ainda era legalmente da Oracle. Isso fez a Eclipse Foundation mudar o nome do projeto Java EE para Jakarta EE mudando o pacote javax.* para jakarta.*.
Por isso se você pegar código mais antigos verá importações como import javax.persistence.Entity; e import jakarta.persistence.Entity; em código modernos.
Projeto JPA
Vou usar o Hibernate v7 como biblioteca de ORM em um projeto maven junto do driver JDBC do PostgreSQL e o lombok.
Vamos começar criando a tabela tb_users no banco
CREATE TABLE tb_users(
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMP NOT NULL DEFAULT now(),
updated_at TIMESTAMP NOT NULL DEFAULT now()
);
Configurando Hibernate
No pom.xml do projeto adicionei as dependencias do hibernate, driver do postgresql e o lombok
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.13</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>7.4.7.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.40</version>
<scope>provided</scope>
</dependency>
</dependencies>
As configurações do Hibernate ficam dentro do arquivo src/main/resources/META-INF/persistence.xml. Nesse arquivo será onde fica a url de conexão, usuário e senha do banco e outras configurações.
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_1.xsd"
version="3.1">
<!-- nome da unidade de persistencia -->
<persistence-unit name="com.matheusgondra.java4db">
<!-- As entidades do JPA devem ser listadas usando <class></class> -->
<class>com.matheusgondra.User</class>
<properties>
<!-- URL do banco -->
<property name="jakarta.persistence.jdbc.url" value="jdbc:postgresql://localhost:5432/db4java" />
<!-- Credenciais do banco -->
<property name="jakarta.persistence.jdbc.user" value="dev" />
<property name="jakarta.persistence.jdbc.password" value="dev" />
<!-- Ação a ser realizada na geração do schema -->
<!-- validate verifica se as entidades estão mapeadas corretamente -->
<property name="jakarta.persistence.schema-generation.database.action" value="validate" />
</properties>
</persistence-unit>
</persistence>
Entidade User
Agora vamos criar a entidade User para mapear a tabela tb_users que foi criada no banco de dados. Uma entidade é apenas uma classe java com anotações do JPA. Usarei o lombok para deixar o Código mais enxuto.
A classe de entidade precisa de um construtor vazio (@NoArgsConstructor), um construtor com todos os campos (@AllArgsConstructor), os getters e os setters (@Data).
package com.matheusgondra;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private String email;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
Agora para tornar essa classe uma entidade JPA usamos a anotação @jakarta.persistence.Entity.
package com.matheusgondra;
import java.time.LocalDateTime;
import jakarta.persistence.Entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class User {
private Long id;
private String name;
private String email;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
Ao adicionar essa anotação o Hibernate já vê aquela classe como uma entidade JPA. Por padrão a anotação define o nome da entidade como o nome da classe. Isso pode ser alterado na anotação se necessário @Entity(name = "UserEntity"). E o mapeamento fica dessa forma
Podemos ver que temos informações errada, a tabela não é User e sim tb_users. Por padrão o nome da tabela é o mesmo nome da entidade. Quando o nome da tabela é diferente da entidade, usamos a anotação @jakarta.persistence.Table para passar o nome correto.
package com.matheusgondra;
import java.time.LocalDateTime;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "tb_users")
public class User {
private Long id;
private String name;
private String email;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
A tabela foi corrigida, mas ainda precisamos corrigir os campos da entidade. Os campos VARCHAR são de tamanho 100 e não podem ser nulo. O email deve ser único. Além disso, o campo id é uma chave primária que é auto incrementada. Os campos createdAt e updatedAt tem o nome diferente dos campos no banco que se chamam created_at e updated_at.
Para corrigir essas coisas usamos a anotação @jakarta.persistence.Column. E para o id temos o @jakarta.persistence.Id e o @jakarta.persistence.GeneratedValue para definir a estrategia de geração do id.
package com.matheusgondra;
import java.time.LocalDateTime;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "tb_users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String name;
@Column(nullable = false, length = 100, unique = true)
private String email;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
}
Outra coisa que podemos fazer é adicionar anotações do Hibernate, não do JPA, para gerar o valor do createdAt e do updatedAt.
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
@CreationTimestamp
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@UpdateTimestamp
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;
Estado da Entidade JPA
Algo que você deve saber é o ciclo de vida de uma entidade. Uma entidade JPA possui 4 estados:
- Transient
- Managed
- Detached
- Removed
Estado Transient
Nesse estado a entidade ainda não está sendo gerenciada pelo contexto de persintência (EntityManager). A entidade entra nesse estado quando você cria um novo objeto com new
public static void main(String[] args) {
// Entidade no estado Transient
User user = new User();
}
Nesse estado as modificações no objeto não são refletidas no banco de dados.
Estado Managed
Nesse estado a entidade já está associada a um contexto de persistência (EntityManager) ativo. Alterações nos campos do objetos são sincronizadas com a tabela no banco de dados.
Um objeto java entra nesse estado ao chamar o método persist(), find(), merge() da classe EntityManager.
public static void main(String[] args) {
// Entidade no estado Transient
User user = new User();
entityManager.persist(user);
// Entidade no estado Managed agora
}
Estado Detached
Nesse estado a entidade a entidade reflete um registro no banco de dados, mas o contexto de persistência (EntityManager) não está mais gerenciando a entidade.
Por exemplo, se criarmos uma nova instância da entidade (estado Transient) e criarmos um registro no banco com o método persist() (estado Managed) e fechar o contexto de persistência com o método close() a entidade irá passar para o estado Detached. É uma entidade válida que reflete um registro no banco, mas está fora do contexto de persistência e precisaria voltar para o estado Managed para refletir as mudanças no banco.
public static void main(String[] args) {
// Entidade no estado Transient
User user = new User();
entityManager.persist(user);
// Entidade no estado Managed agora
entityManager.close(); // fim do contexto
// Entidade no estado Detached agora
}
Estado Removed
Nesse estado a entidade é marcado pelo contexto de persistência (EntityManager) para ser deletada do banco de dados. Uma entidade entra nesse estado ao chamar o método remove() do EntityManager
public static void main(String[] args) {
// Entidade no estado Transient
User user = new User();
entityManager.persist(user);
// Entidade no estado Managed agora
entityManager.remove(user);
// Entidade no estado Removed agora
}
Contexto de Persistência
Todas as queries no banco de dados são feitas dentro de um contexto de persistência usando a classe jakarta.persistence.EntityManager.
Para criar o EntityManager usamos o padrão de fabrica usando a interface jakarta.persistence.EntityManagerFactory, a classe jakarta.persistence.Persistence e passando o nome da unidade persistência definido no persistence.xml
// o nome da <persistence-unit /> no persistence.xml deve ser passado para o createEntityManagerFactory
EntityManagerFactory factory = Persistence.createEntityManagerFactory("com.matheusgondra.java4db");
EntityManager entityManager = factory.createEntityManager();
CRUD
Com o contexto criado podemos fazer queries no banco de dados. Vou mostrar como é possível fazer as operações de CRUD.
Adicionei a anotação @lombok.Builder na nossa entidade User para facilitar a instanciação dos objetos.
Create
A primeira operação é o CREATE. Para criar um novo registro no banco de dados, o EntityManager possui o método persist() que recebe uma entidade JPA.
Ao chamar o método irá ser feito uma query INSERT no banco com os valores da entidade que mapeamos.
static void create(EntityManagerFactory factory) {
EntityManager entityManager = factory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
// Estado da entidade: Transient
User user = User.builder()
.name("Fulano")
.email("fulano@email.com")
.build();
System.out.println("Entidade em Trasient: " + user);
// Persiste no banco
entityManager.persist(user);
// Estado da entidaed: Managed
System.out.println("Entidade em Managed: " + user);
transaction.commit();
} catch (Exception e) {
if (transaction.isActive()) {
transaction.rollback();
}
throw e;
} finally {
// fechando o contexto de persistência
entityManager.close();
}
}
Entidade em Trasient: User(id=null, name=Fulano, email=fulano@email.com, createdAt=null, updatedAt=null)
Entidade em Managed: User(id=1, name=Fulano, email=fulano@email.com, createdAt=2026-09-19T13:59:23.564330, updatedAt=2026-09-19T13:59:23.564399)
Read
A próxima operação é o READ. Para isso, o EntityManager possui o método find().
static void find(EntityManagerFactory factory) {
try (EntityManager entityManager = factory.createEntityManager()) {
User user = entityManager.find(User.class, 1L);
if (user == null) {
throw new RuntimeException("Entidade não encontrada");
}
System.out.println("Entidade Managed: " + user);
} catch (Exception e) {
throw e;
}
}
Entidade Managed: User(id=1, name=Fulano, email=fulano@email.com, createdAt=2026-09-19T23:15:18.470144, updatedAt=2026-09-19T23:15:18.470205)
Para um findAll não temos um método pronto no Hibernate. Mas, podemos usar o método createQuery para criar um query que busca todos os registros usando JPQL (Java Persistence Query Language) que é bem parecido com o SQL.
No SQL as consultas são feitas baseada nas tabelas, no JPQL as consultas são feitas baseadas nas entidade JPA.
Adicionei outros registros para retornar mais de um registro.
static void findAll(EntityManagerFactory factory) {
try (EntityManager entityManager = factory.createEntityManager()) {
List<User> users = entityManager.createQuery("SELECT u FROM User u", User.class).getResultList();
users.forEach(System.out::println);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
User(id=1, name=Fulano, email=fulano@email.com, createdAt=2026-09-19T14:15:58.822469, updatedAt=2026-09-19T14:15:58.822552)
User(id=2, name=ciclano, email=ciclano@email.com, createdAt=2026-09-19T22:22:39.055243, updatedAt=2026-09-19T22:22:39.055243)
User(id=3, name=beltrano, email=beltrano@email.com, createdAt=2026-09-19T22:22:39.055243, updatedAt=2026-09-19T22:22:39.055243)
Update
A operação de UPDATE iremos buscar uma entidade com o método find() para termos uma entidade no estado Managed. Com isso, iremos atualizar os atributos da entidade e ao fazer o commit da transação, ou usando o método merge, o Hibernate irá atualizar o registro no banco.
static void update(EntityManagerFactory factory) {
EntityManager entityManager = factory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
User user = entityManager.find(User.class, 1L);
if (user == null) {
throw new RuntimeException("User not found");
}
System.out.println("User antes: " + user);
user.setName("Fulano Atualizado");
System.out.println("User depois: " + user);
// No commit a query UPDATE é rodada no banco
transaction.commit();
} catch (Exception e) {
if (transaction.isActive()) {
transaction.rollback();
}
throw new RuntimeException(e);
} finally {
entityManager.close();
}
}
User antes: User(id=1, name=Fulano, email=fulano@email.com, createdAt=2026-09-19T14:15:58.822469, updatedAt=2026-09-19T14:15:58.822552)
User depois: User(id=1, name=Fulano Atualizado, email=fulano@email.com, createdAt=2026-09-19T14:15:58.822469, updatedAt=2026-09-19T14:15:58.822552)
DELETE
A operação de DELETE é parecido com o UPDATE, iremos buscar uma entidade com o método find() para termos uma entidade no estado Managed. Com isso, iremos chamar o método remove() com a entidade e ao fazer o commit da transação o Hibernate irá atualizar o registro no banco.
static void delete(EntityManagerFactory factory) {
EntityManager entityManager = factory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
User user = entityManager.find(User.class, 1L);
if (user == null) {
throw new RuntimeException("User not found");
}
entityManager.remove(user);
//No commit a query DELETE é rodada no banco
transaction.commit();
} catch (Exception e) {
if (transaction.isActive()) {
transaction.rollback();
}
throw new RuntimeException(e);
} finally {
entityManager.close();
}
}




Top comments (0)