Especialização em Sistemas com Java 21 LTS • Capítulo 20


🎯 Visão Geral dos Exemplos Práticos

Neste laboratório prático, implementaremos exemplos corporativos completos explorando:

  1. Arquitetura Multicamadas Completa (MVC + Service + DAO + JDBC): fluxo end-to-end integrando persistência em banco relacional e regras de negócio.
  2. Prevenção de UI Freezing com Tarefas Assíncronas: carregamento de dados em segundo plano utilizando javafx.concurrent.Task.
  3. Mapeamento Relacional e Consultas com JOIN: transformação de cursores ResultSet complexos em grafos de objetos orientados a objetos.

💻 Exemplo 01 — Sistema Corporativo Integrado de Gestão Funcional

package com.empresa.projeto;

import java.math.BigDecimal;
import java.sql.*;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * Projeto Integrador Corporativo: Sistema completo multicamadas com DAO, JDBC e regras de negócio.
 */
public class SistemaGestaoCompletoApp {

    public record Departamento(int id, String sigla, String nome) {}
    public record Funcionario(int id, String nome, String email, BigDecimal salario, LocalDate admissao, Departamento dep) {}

    // Camada DAO
    public interface FuncionarioDao {
        void cadastrar(Funcionario f, Connection conn) throws SQLException;
        List<Funcionario> listarComDepartamento(Connection conn) throws SQLException;
        BigDecimal calcularFolhaPorDepartamento(int depId, Connection conn) throws SQLException;
    }

    public static class FuncionarioDaoJdbc implements FuncionarioDao {
        @Override
        public void cadastrar(Funcionario f, Connection conn) throws SQLException {
            String sql = "INSERT INTO funcionarios (nome, email, salario, data_admissao, departamento_id) VALUES (?, ?, ?, ?, ?)";
            try (PreparedStatement ps = conn.prepareStatement(sql)) {
                ps.setString(1, f.nome());
                ps.setString(2, f.email());
                ps.setBigDecimal(3, f.salario());
                ps.setDate(4, java.sql.Date.valueOf(f.admissao()));
                ps.setInt(5, f.dep().id());
                ps.executeUpdate();
            }
        }

        @Override
        public List<Funcionario> listarComDepartamento(Connection conn) throws SQLException {
            String sql = """
                SELECT f.id, f.nome, f.email, f.salario, f.data_admissao,
                       d.id AS dep_id, d.sigla AS dep_sigla, d.nome AS dep_nome
                FROM funcionarios f
                INNER JOIN departamentos d ON f.departamento_id = d.id
                ORDER BY d.sigla, f.nome
            """;
            List<Funcionario> resultado = new ArrayList<>();
            try (Statement st = conn.createStatement();
                 ResultSet rs = st.executeQuery(sql)) {
                while (rs.next()) {
                    Departamento dep = new Departamento(rs.getInt("dep_id"), rs.getString("dep_sigla"), rs.getString("dep_nome"));
                    resultado.add(new Funcionario(
                            rs.getInt("id"),
                            rs.getString("nome"),
                            rs.getString("email"),
                            rs.getBigDecimal("salario"),
                            rs.getDate("data_admissao").toLocalDate(),
                            dep
                    ));
                }
            }
            return resultado;
        }

        @Override
        public BigDecimal calcularFolhaPorDepartamento(int depId, Connection conn) throws SQLException {
            String sql = "SELECT SUM(salario) as total FROM funcionarios WHERE departamento_id = ?";
            try (PreparedStatement ps = conn.prepareStatement(sql)) {
                ps.setInt(1, depId);
                try (ResultSet rs = ps.executeQuery()) {
                    if (rs.next() && rs.getBigDecimal("total") != null) {
                        return rs.getBigDecimal("total");
                    }
                }
            }
            return BigDecimal.ZERO;
        }
    }

    // Camada de Serviço de Negócio
    public static class FuncionarioService {
        private final FuncionarioDao dao = new FuncionarioDaoJdbc();
        private final String jdbcUrl;

        public FuncionarioService(String jdbcUrl) { this.jdbcUrl = jdbcUrl; }

        public void contratarFuncionario(Funcionario f) throws SQLException {
            if (f.salario().compareTo(new BigDecimal("1500.00")) < 0) {
                throw new IllegalArgumentException("Salário contratual abaixo do piso regulatório!");
            }
            try (Connection conn = DriverManager.getConnection(jdbcUrl, "sa", "")) {
                dao.cadastrar(f, conn);
                System.out.printf("   ✅ [ADMISSÃO] Colaborador %s registrado no departamento %s!%n", f.nome(), f.dep().sigla());
            }
        }

        public List<Funcionario> consultarQuadroGeral() throws SQLException {
            try (Connection conn = DriverManager.getConnection(jdbcUrl, "sa", "")) {
                return dao.listarComDepartamento(conn);
            }
        }

        public BigDecimal obterCustoFolha(int depId) throws SQLException {
            try (Connection conn = DriverManager.getConnection(jdbcUrl, "sa", "")) {
                return dao.calcularFolhaPorDepartamento(depId, conn);
            }
        }
    }

    public static void main(String[] args) {
        System.out.println("================================================================================");
        System.out.println("         PROJETO INTEGRADOR FINAL: ARQUITETURA MULTICAMADAS COMPLETA            ");
        System.out.println("================================================================================");

        String urlBanco = "jdbc:h2:mem:corporativo_integrador;DB_CLOSE_DELAY=-1";

        try (Connection conn = DriverManager.getConnection(urlBanco, "sa", "")) {
            // DDL de Inicialização
            try (Statement st = conn.createStatement()) {
                st.execute("""
                    CREATE TABLE departamentos (
                        id INT PRIMARY KEY,
                        sigla VARCHAR(10) NOT NULL,
                        nome VARCHAR(50) NOT NULL
                    );
                    CREATE TABLE funcionarios (
                        id INT AUTO_INCREMENT PRIMARY KEY,
                        nome VARCHAR(100) NOT NULL,
                        email VARCHAR(100) NOT NULL,
                        salario DECIMAL(15,2) NOT NULL,
                        data_admissao DATE NOT NULL,
                        departamento_id INT NOT NULL,
                        FOREIGN KEY (departamento_id) REFERENCES departamentos(id)
                    );
                    INSERT INTO departamentos VALUES (1, 'ENG', 'Engenharia de Software e Cloud');
                    INSERT INTO departamentos VALUES (2, 'FIN', 'Finanças e Controladoria');
                """);
            }

            FuncionarioService service = new FuncionarioService(urlBanco);
            Departamento eng = new Departamento(1, "ENG", "Engenharia de Software e Cloud");
            Departamento fin = new Departamento(2, "FIN", "Finanças e Controladoria");

            // Cadastro de colaboradores
            System.out.println("--- Contratação e Persistência de Dados ---");
            service.contratarFuncionario(new Funcionario(0, "Gabriel Toledo", "gabriel@corp.com", new BigDecimal("13500.00"), LocalDate.of(2024, 1, 15), eng));
            service.contratarFuncionario(new Funcionario(0, "Larissa Souza", "larissa@corp.com", new BigDecimal("11200.00"), LocalDate.of(2025, 3, 10), eng));
            service.contratarFuncionario(new Funcionario(0, "Rodrigo Faro", "rodrigo@corp.com", new BigDecimal("8900.00"), LocalDate.of(2023, 6, 1), fin));

            // Relatório Geral via DAO e Projeção
            System.out.println("\n--- Relatório Integrado do Quadro Geral ---");
            List<Funcionario> lista = service.consultarQuadroGeral();
            DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy");

            System.out.printf("%-4s | %-18s | %-18s | %-10s | %-12s%n", "DEP", "COLABORADOR", "EMAIL", "ADMISSÃO", "SALÁRIO");
            System.out.println("-----+--------------------+--------------------+------------+-------------");
            for (Funcionario f : lista) {
                System.out.printf("%-4s | %-18s | %-18s | %s | R$ %9.2f%n",
                        f.dep().sigla(), f.nome(), f.email(), f.admissao().format(fmt), f.salario());
            }

            // Agregação de Custos
            System.out.println("--------------------------------------------------------------------------");
            System.out.printf("Custo Total de Folha (Engenharia) : R$ %,10.2f%n", service.obterCustoFolha(1));
            System.out.printf("Custo Total de Folha (Finanças)   : R$ %,10.2f%n", service.obterCustoFolha(2));
            System.out.println("================================================================================");

        } catch (SQLException e) {
            System.err.println("Falha no banco de dados integrado: " + e.getMessage());
        }
    }
}

💻 Exemplo 02 — Carregamento Assíncrono com JavaFX Task

package com.empresa.projeto;

import javafx.concurrent.Task;
import java.util.List;

/**
 * Padrão recomendado para carregamento em background prevenindo congelamento de tela.
 */
public class CarregadorAssincronoDados {

    public static Task<List<String>> criarTarefaCargaRelatorio() {
        return new Task<>() {
            @Override
            protected List<String> call() throws Exception {
                updateMessage("Conectando ao banco de dados relacional...");
                Thread.sleep(800); // Simula latência de rede em thread secundária

                updateMessage("Executando consulta SQL complexa com agregações...");
                updateProgress(50, 100);
                Thread.sleep(1000);

                updateMessage("Concluído com sucesso!");
                updateProgress(100, 100);
                return List.of("Registro 01: OK", "Registro 02: OK", "Registro 03: OK");
            }
        };
    }

    public static void main(String[] args) {
        System.out.println("================================================================================");
        System.out.println("           PADRÃO ASSÍNCRONO: JAVAFX TASK COM ATUALIZAÇÃO REATIVA               ");
        System.out.println("================================================================================");
        System.out.println("Tarefa assíncrona instanciada para execução desacoplada da thread visual.");
        System.out.println("Ao integrar com JavaFX, use task.setOnSucceeded(...) para atualizar a UI.");
        System.out.println("================================================================================");
    }
}

📋 Resultado Esperado no Console

================================================================================
         PROJETO INTEGRADOR FINAL: ARQUITETURA MULTICAMADAS COMPLETA            
================================================================================
--- Contratação e Persistência de Dados ---
   ✅ [ADMISSÃO] Colaborador Gabriel Toledo registrado no departamento ENG!
   ✅ [ADMISSÃO] Colaborador Larissa Souza registrado no departamento ENG!
   ✅ [ADMISSÃO] Colaborador Rodrigo Faro registrado no departamento FIN!

--- Relatório Integrado do Quadro Geral ---
DEP  | COLABORADOR        | EMAIL              | ADMISSÃO   | SALÁRIO     
-----+--------------------+--------------------+------------+-------------
ENG  | Gabriel Toledo     | gabriel@corp.com   | 15/01/2024 | R$  13500.00
ENG  | Larissa Souza      | larissa@corp.com   | 10/03/2025 | R$  11200.00
FIN  | Rodrigo Faro       | rodrigo@corp.com   | 01/06/2023 | R$   8900.00
--------------------------------------------------------------------------
Custo Total de Folha (Engenharia) : R$  24.700,00
Custo Total de Folha (Finanças)   : R$   8.900,00
================================================================================