Capítulo 20: Projeto Integrador 2: Aplicação Desktop Corporativa Completa (JavaFX + JDBC + MVC + DAO)
Especialização em Sistemas com Java 21 LTS • Módulo de Banco de Dados e Projetos Integradores
🗺️ Mapa Conceitual do Tópico
flowchart TD
subgraph APRESENTACAO["Camada de Apresentação (UI / JavaFX)"]
A["Interface Visual FXML + Scene Builder"]
B["Controller MVC (@FXML Injeção e Eventos)"]
C["javafx.concurrent.Task (Execução Assíncrona em Background)"]
A <--> B
B --> C
end
subgraph NEGOCIO["Camada de Serviços & Domínio"]
D["Entidades de Domínio / Records: Vendedor, Departamento"]
E["VendedorService (Validações, Regras de Negócio, SLAs)"]
B --> E
E --> D
end
subgraph PERSISTENCIA["Camada de Persistência & Dados"]
F["VendedorDao (Interface Contrato)"]
G["VendedorDaoJdbc (PreparedStatement, SQL, Mapeamento)"]
H["Conexão JDBC / Connection Pool HikariCP"]
I["Banco de Dados Relacional (PostgreSQL / MySQL / H2)"]
E --> F
G -.->|implementa| F
G --> H --> I
end
C -.->|Platform.runLater| B
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style E fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style G fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style I fill:#ffebee,stroke:#f44336,stroke-width:2px
🏛️ 1. Arquitetura Multicamadas Corporativa Desktop
O desenvolvimento de sistemas de alta escala em Java exige a separação rigorosa de responsabilidades em camadas concêntricas e desacopladas, impedindo o surgimento de classes gigantes monolíticas (God Objects):
As Seis Camadas Canônicas da Aplicação:
- Camada de Visão (View): Arquivos
.fxmle folhas de estilo.css. Declaram a geometria, disposição e estética dos componentes visuais. - Camada de Controle (Controller): Classes anotadas com
@FXMLque capturam cliques de botões, populam tabelas e coordenam a navegação de janelas. Regra de Ouro: Não deve conter nenhuma linha de SQL ou regra pesada de negócio. - Camada de Serviço (Service): Orquestra o fluxo de dados, executa validações de regras corporativas e gerencia a demarcação transacional (
commit/rollback). - Camada de Acesso a Dados (DAO): Interfaces e implementações JDBC responsáveis pela montagem de comandos
PreparedStatemente conversão deResultSetem objetos de domínio. - Camada de Modelo (Model / Domain): Entidades ricas e Records imutáveis que representam as tabelas e relacionamentos do negócio.
- Camada de Infraestrutura (DB / Connection Factory): Gerenciador de ciclo de vida de conexões físicas com o banco de dados.
⚡ 2. Operações Assíncronas e Prevenção de Travamento da UI (UI Freezing)
Se uma consulta SQL de relatório demorar 5 segundos e for executada diretamente na JavaFX Application Thread, a interface gráfica congelará completamente, o sistema operacional reportará “O programa não está respondendo” e animações serão interrompidas.
A Solução: javafx.concurrent.Task<T>
JavaFX fornece abstrações elegantes para delegar tarefas lentas para threads secundárias (ou Virtual Threads) mantendo a interface reativa:
Task<List<Vendedor>> tarefaCarga = new Task<>() {
@Override
protected List<Vendedor> call() throws Exception {
return vendedorService.listarTodos(); // Executa em background!
}
};
tarefaCarga.setOnSucceeded(e -> {
// Retorna automaticamente para a JavaFX Application Thread com segurança
tabelaVendedores.setItems(FXCollections.observableArrayList(tarefaCarga.getValue()));
barraProgresso.setVisible(false);
});
tarefaCarga.setOnFailed(e -> {
AlertaUtil.exibirErro("Falha de Comunicação", tarefaCarga.getException().getMessage());
});
Thread.ofVirtual().start(tarefaCarga); // Executa em Virtual Thread!
🛡️ 3. Transações e Integridade Referencial no CRUD
No cadastro de entidades dependentes (ex: Vendedor associado obrigatoriamente a um Departamento):
- Integridade Referencial: O DAO deve validar se o
departamento_idexiste antes de persistir o registro, tratando violações de chave estrangeira (FK constraint violation). - Operações de Deleção com Confirmação: Deleções devem apresentar caixas modais de diálogo (
Alert(AlertType.CONFIRMATION)) antes de enviar comandosDELETEao banco.
📊 4. Matriz Comparativa: Padrões de Projeto Desktop
| Padrão | Separação da Lógica Visual | Acoplamento View-Model | Complexidade Arquitetural | Adoção no JavaFX |
|---|---|---|---|---|
| MVC Clássico | Média (Controller manipula View diretamente) | Baixo | Baixa | Padrão Nativo FXML |
| MVP (Model-View-Presenter) | Alta (View expõe interface ao Presenter) | Quase nulo | Média | Utilizado em aplicações Swing/JavaFX |
| MVVM (Model-View-ViewModel) | Máxima (Data Binding bidirecional) | Nulo (ViewModel desconhece a View) | Alta | Altamente ergonômico com Properties API |
💻 5. Código de Demonstração Corporativo (Java 21 LTS)
package com.empresa.projeto;
import java.math.BigDecimal;
import java.sql.*;
import java.time.LocalDate;
import java.util.*;
/**
* Projeto Integrador Corporativo: Arquitetura completa MVC + DAO + JDBC.
*/
public class SistemaGestaoCorporativaApp {
// 1. Entidades de Domínio
public record Departamento(Integer id, String nome) {}
public record Colaborador(Integer id, String nome, String email, LocalDate dataAdmissao, BigDecimal salario, Departamento departamento) {}
// 2. Camada DAO (Contrato e Implementação JDBC)
public interface ColaboradorDao {
void inserir(Colaborador c, Connection conn) throws SQLException;
List<Colaborador> listarTodos(Connection conn) throws SQLException;
}
public static class ColaboradorDaoJdbc implements ColaboradorDao {
@Override
public void inserir(Colaborador c, Connection conn) throws SQLException {
String sql = "INSERT INTO colaboradores (nome, email, data_admissao, salario, departamento_id) VALUES (?, ?, ?, ?, ?)";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, c.nome());
ps.setString(2, c.email());
ps.setDate(3, java.sql.Date.valueOf(c.dataAdmissao()));
ps.setBigDecimal(4, c.salario());
ps.setInt(5, c.departamento().id());
ps.executeUpdate();
}
}
@Override
public List<Colaborador> listarTodos(Connection conn) throws SQLException {
String sql = """
SELECT c.id, c.nome, c.email, c.data_admissao, c.salario, d.id as dep_id, d.nome as dep_nome
FROM colaboradores c
INNER JOIN departamentos d ON c.departamento_id = d.id
ORDER BY c.nome
""";
List<Colaborador> lista = 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_nome"));
lista.add(new Colaborador(
rs.getInt("id"),
rs.getString("nome"),
rs.getString("email"),
rs.getDate("data_admissao").toLocalDate(),
rs.getBigDecimal("salario"),
dep
));
}
}
return lista;
}
}
// 3. Camada de Serviço (Regras de Negócio)
public static class ColaboradorService {
private final ColaboradorDao dao = new ColaboradorDaoJdbc();
private final String dbUrl;
public ColaboradorService(String dbUrl) { this.dbUrl = dbUrl; }
public void cadastrar(Colaborador c) throws SQLException {
if (c.salario() == null || c.salario().compareTo(new BigDecimal("1412.00")) < 0) {
throw new IllegalArgumentException("Salário não pode ser inferior ao piso nacional!");
}
try (Connection conn = DriverManager.getConnection(dbUrl, "sa", "")) {
dao.inserir(c, conn);
}
}
public List<Colaborador> recuperarQuadroGeral() throws SQLException {
try (Connection conn = DriverManager.getConnection(dbUrl, "sa", "")) {
return dao.listarTodos(conn);
}
}
}
// 4. Execução Integrada
public static void main(String[] args) {
System.out.println("================================================================================");
System.out.println(" PROJETO INTEGRADOR: ARQUITETURA MULTICAMADAS JAVA 21 ");
System.out.println("================================================================================");
String urlMemoria = "jdbc:h2:mem:empresadb;DB_CLOSE_DELAY=-1";
try (Connection conn = DriverManager.getConnection(urlMemoria, "sa", "")) {
// Provisionamento DDL
try (Statement st = conn.createStatement()) {
st.execute("""
CREATE TABLE departamentos (id INT PRIMARY KEY, nome VARCHAR(50) NOT NULL);
CREATE TABLE colaboradores (
id INT AUTO_INCREMENT PRIMARY KEY,
nome VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL,
data_admissao DATE NOT NULL,
salario DECIMAL(15,2) NOT NULL,
departamento_id INT NOT NULL,
FOREIGN KEY (departamento_id) REFERENCES departamentos(id)
);
INSERT INTO departamentos VALUES (1, 'Engenharia de Software'), (2, 'Operações Financeiras');
""");
}
System.out.println("✅ Banco de dados corporativo estruturado com chaves estrangeiras.");
ColaboradorService service = new ColaboradorService(urlMemoria);
// Cadastro de Colaboradores via Camada de Serviço
Departamento depTI = new Departamento(1, "Engenharia de Software");
service.cadastrar(new Colaborador(null, "Mariana Ramos", "mariana@empresa.com", LocalDate.now(), new BigDecimal("9800.00"), depTI));
service.cadastrar(new Colaborador(null, "Lucas Albuquerque", "lucas@empresa.com", LocalDate.now(), new BigDecimal("12500.00"), depTI));
// Listagem de Produção
List<Colaborador> equipe = service.recuperarQuadroGeral();
System.out.printf("%nQuadro de Colaboradores Atualizado (%d registros):%n", equipe.size());
System.out.printf("%-20s | %-25s | %-22s | %-12s%n", "NOME", "EMAIL", "DEPARTAMENTO", "SALÁRIO");
System.out.println("---------------------+---------------------------+------------------------+-------------");
for (Colaborador colab : equipe) {
System.out.printf("%-20s | %-25s | %-22s | R$ %9.2f%n",
colab.nome(), colab.email(), colab.departamento().nome(), colab.salario());
}
} catch (Exception e) {
System.err.println("Erro na execução corporativa: " + e.getMessage());
}
System.out.println("================================================================================");
}
}