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.


📚 Referências