Visão Arquitetural: Acesso a Dados com JDBC e o Padrão DAO
O JDBC (Java Database Connectivity) é a API fundamental do Java que nos permite executar comandos SQL em um banco de dados relacional. Embora poderosa, a utilização direta do JDBC pode levar a um código repetitivo e acoplado à sua infraestrutura.
Para resolver isso, aplicamos o padrão de projeto DAO (Data Access Object). A estratégia é criar uma camada de abstração que isola completamente a lógica de negócio das particularidades da persistência de dados. Cada entidade do nosso modelo de domínio (como Vendedor ou Departamento) terá um “objeto de acesso a dados” correspondente, responsável por todas as operações de CRUD (Create, Retrieve, Update, Delete) para aquela entidade.
Estrutura do Projeto: Organização para Escalabilidade
Uma estrutura de pacotes bem definida é crucial para a manutenibilidade. Para este projeto, adotaremos uma organização que separa claramente as responsabilidades:
src
└── com
└── yourdomain
├── application
│ └── Program.java // Classe principal para testar a aplicação
│
├── db
│ ├── DB.java // Classe utilitária para conexão com o banco
│ ├── DbException.java // Exceção personalizada para erros de DB
│ └── DbIntegrityException.java // Exceção para violação de integridade
│
└── model
├── entities
│ ├── Department.java // Classe da entidade Departamento
│ └── Seller.java // Classe da entidade Vendedor
│
├── dao
│ ├── DaoFactory.java // Fábrica para criar os objetos DAO
│ ├── DepartmentDao.java // Interface para o DAO de Departamento
│ └── SellerDao.java // Interface para o DAO de Vendedor
│
└── dao
└── impl
├── DepartmentDaoJDBC.java // Implementação do DAO com JDBC
└── SellerDaoJDBC.java // Implementação do DAO com JDBC
Fase 1: Configuração da Conexão com o Banco de Dados
O primeiro passo é criar uma base sólida e centralizada para gerenciar a conexão com o banco de dados.
1. Arquivo db.properties
Crie este arquivo na raiz do projeto. Ele externaliza as credenciais e a URL de conexão, permitindo alterá-las sem recompilar o código.
user=root
password=your_password
dburl=jdbc:mysql://localhost:3306/coursejdbc?useSSL=false&serverTimezone=UTC
2. Classe Utilitária DB.java
Esta classe será responsável por carregar as propriedades, obter e fechar conexões. O uso de métodos estáticos a torna um ponto de acesso único e conveniente.
// package com.yourdomain.db;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class DB {
private static Connection conn = null;
// Carrega as propriedades do arquivo db.properties
private static Properties loadProperties() {
try (FileInputStream fs = new FileInputStream("db.properties")) {
Properties props = new Properties();
props.load(fs);
return props;
} catch (IOException e) {
throw new DbException(e.getMessage());
}
}
// Obtém uma nova conexão com o banco de dados
public static Connection getConnection() {
if (conn == null) {
try {
Properties props = loadProperties();
String url = props.getProperty("dburl");
conn = DriverManager.getConnection(url, props);
} catch (SQLException e) {
throw new DbException(e.getMessage());
}
}
return conn;
}
// Fecha a conexão
public static void closeConnection() {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
throw new DbException(e.getMessage());
}
}
}
// Métodos auxiliares para fechar Statement e ResultSet de forma segura
public static void closeStatement(Statement st) {
if (st != null) {
try {
st.close();
} catch (SQLException e) {
throw new DbException(e.getMessage());
}
}
}
public static void closeResultSet(ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
throw new DbException(e.getMessage());
}
}
}
}
Fase 2: Modelagem das Entidades
As classes de entidade são um espelho das tabelas do banco de dados.
Classe Department.java
// package com.yourdomain.model.entities;
import java.io.Serializable;
public class Department implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
public Department() {}
public Department(Integer id, String name) {
this.id = id;
this.name = name;
}
// Getters, Setters, hashCode, equals e toString
// ...
}
Fase 3: Projetando e Implementando o Padrão DAO
Aqui criamos a camada de abstração para o acesso a dados.
1. Interface SellerDao.java
A interface define o “contrato”, ou seja, quais operações de persistência estarão disponíveis para a entidade Seller, sem se preocupar com a tecnologia (JDBC, JPA, etc.).
// package com.yourdomain.model.dao;
import com.yourdomain.model.entities.Department;
import com.yourdomain.model.entities.Seller;
import java.util.List;
public interface SellerDao {
void insert(Seller obj);
void update(Seller obj);
void deleteById(Integer id);
Seller findById(Integer id);
List<Seller> findAll();
List<Seller> findByDepartment(Department department);
}
2. Fábrica DaoFactory.java
Esta classe usa o Padrão de Fábrica (Factory Pattern) para desacoplar a implementação do DAO do código cliente. O programa principal não precisará saber qual implementação concreta está sendo usada.
// package com.yourdomain.model.dao;
import com.yourdomain.db.DB;
import com.yourdomain.model.dao.impl.SellerDaoJDBC;
public class DaoFactory {
// O método expõe a interface, mas internamente instancia a implementação
public static SellerDao createSellerDao() {
return new SellerDaoJDBC(DB.getConnection());
}
}
3. Implementação SellerDaoJDBC.java
Esta é a classe concreta que contém o código JDBC para executar as operações SQL.
// package com.yourdomain.model.dao.impl;
import com.yourdomain.db.DbException;
import com.yourdomain.model.dao.SellerDao;
import com.yourdomain.model.entities.Department;
import com.yourdomain.model.entities.Seller;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SellerDaoJDBC implements SellerDao {
private Connection conn;
public SellerDaoJDBC(Connection conn) {
this.conn = conn;
}
@Override
public Seller findById(Integer id) {
PreparedStatement st = null;
ResultSet rs = null;
try {
st = conn.prepareStatement(
"SELECT seller.*, department.Name as DepName "
+ "FROM seller INNER JOIN department "
+ "ON seller.DepartmentId = department.Id "
+ "WHERE seller.Id = ?");
st.setInt(1, id);
rs = st.executeQuery();
// O ResultSet pode retornar um ou nenhum resultado
if (rs.next()) {
Department dep = instantiateDepartment(rs);
Seller obj = instantiateSeller(rs, dep);
return obj;
}
return null;
} catch (SQLException e) {
throw new DbException(e.getMessage());
} finally {
// Fechamento dos recursos
}
}
@Override
public void insert(Seller obj) {
PreparedStatement st = null;
try {
st = conn.prepareStatement(
"INSERT INTO seller "
+ "(Name, Email, BirthDate, BaseSalary, DepartmentId) "
+ "VALUES (?, ?, ?, ?, ?)",
Statement.RETURN_GENERATED_KEYS); // Pede para retornar o ID gerado
st.setString(1, obj.getName());
st.setString(2, obj.getEmail());
st.setDate(3, new java.sql.Date(obj.getBirthDate().getTime()));
st.setDouble(4, obj.getBaseSalary());
st.setInt(5, obj.getDepartment().getId());
int rowsAffected = st.executeUpdate();
if (rowsAffected > 0) {
ResultSet rs = st.getGeneratedKeys();
if (rs.next()) {
int id = rs.getInt(1); // O ID gerado está na primeira coluna
obj.setId(id);
}
} else {
throw new DbException("Unexpected error! No rows affected!");
}
} catch (SQLException e) {
throw new DbException(e.getMessage());
} finally {
// Fechamento dos recursos
}
}
// Métodos auxiliares para evitar duplicação de código
private Department instantiateDepartment(ResultSet rs) throws SQLException {
Department dep = new Department();
dep.setId(rs.getInt("DepartmentId"));
dep.setName(rs.getString("DepName"));
return dep;
}
private Seller instantiateSeller(ResultSet rs, Department dep) throws SQLException {
Seller obj = new Seller();
obj.setId(rs.getInt("Id"));
obj.setName(rs.getString("Name"));
obj.setEmail(rs.getString("Email"));
obj.setBaseSalary(rs.getDouble("BaseSalary"));
obj.setBirthDate(rs.getDate("BirthDate"));
obj.setDepartment(dep);
return obj;
}
// ... Implementação dos outros métodos (update, delete, findAll, etc.)
}
Fase 4: Utilizando o DAO na Aplicação
Finalmente, a classe Program pode usar a fábrica para obter uma instância do DAO e realizar as operações de forma limpa e desacoplada.
Program.java
// package com.yourdomain.application;
import com.yourdomain.model.dao.DaoFactory;
import com.yourdomain.model.dao.SellerDao;
import com.yourdomain.model.entities.Department;
import com.yourdomain.model.entities.Seller;
import java.util.Date;
import java.util.List;
public class Program {
public static void main(String[] args) {
// Obter o DAO através da fábrica
SellerDao sellerDao = DaoFactory.createSellerDao();
System.out.println("=== TEST 1: seller findById ===");
Seller seller = sellerDao.findById(3);
System.out.println(seller);
System.out.println("\n=== TEST 2: seller findByDepartment ===");
Department department = new Department(2, null);
List<Seller> list = sellerDao.findByDepartment(department);
for (Seller obj : list) {
System.out.println(obj);
}
System.out.println("\n=== TEST 3: seller insert ===");
Seller newSeller = new Seller(null, "Greg", "greg@gmail.com", new Date(), 4000.0, department);
sellerDao.insert(newSeller);
System.out.println("Inserted! New id = " + newSeller.getId());
}
}
Seguindo esta estrutura, você terá uma aplicação Java com acesso a dados robusta, organizada e fácil de manter, onde a lógica de negócio permanece completamente isolada da tecnologia de persistência.