Como trabalhar com interfaces no Java e Spring Boot
Veja como usar interfaces de forma eficaz em projetos com Java e Spring Boot, passando pela estrutura DAO até JPA Repository
Por que isso é importante
Interfaces são um dos pilares da programação orientada a objetos em Java. Quando aplicadas em projetos com Spring Boot, elas tornam o código mais escalável, testável e aderente ao princípio da inversão de dependência. Entender seu funcionamento vai além de decorá-las: é preciso compreender seu comportamento, especialmente no contexto de persistência com JPA Repository.
Estrutura tradicional Java sem Spring Boot
Antes do Spring Boot, era comum organizar o backend Java usando estrutura MVC dividida em pacotes como model, dao e connection factory.
Cada entidade ou tabela do sistema possuía uma classe model e uma classe DAO. Vamos ver um exemplo prático:
public class UsuarioModel {
private Long id;
private String nome;
private String email;
// Construtores
public UsuarioModel() {}
public UsuarioModel(String nome, String email) {
this.nome = nome;
this.email = email;
}
// Getters e Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getNome() { return nome; }
public void setNome(String nome) { this.nome = nome; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
public class UsuarioDAO {
private Connection connection;
public UsuarioDAO(Connection connection) {
this.connection = connection;
}
public void insert(UsuarioModel usuario) throws SQLException {
String sql = "INSERT INTO usuarios (nome, email) VALUES (?, ?)";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setString(1, usuario.getNome());
stmt.setString(2, usuario.getEmail());
stmt.executeUpdate();
}
public List<UsuarioModel> selectAll() throws SQLException {
String sql = "SELECT * FROM usuarios";
PreparedStatement stmt = connection.prepareStatement(sql);
ResultSet rs = stmt.executeQuery();
List<UsuarioModel> usuarios = new ArrayList<>();
while (rs.next()) {
UsuarioModel usuario = new UsuarioModel();
usuario.setId(rs.getLong("id"));
usuario.setNome(rs.getString("nome"));
usuario.setEmail(rs.getString("email"));
usuarios.add(usuario);
}
return usuarios;
}
public void delete(Long id) throws SQLException {
String sql = "DELETE FROM usuarios WHERE id = ?";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setLong(1, id);
stmt.executeUpdate();
}
}
⚠️Problema
O excesso de código repetido entre DAOs (insert, select, delete) fez surgir a necessidade de padronização com interfaces genéricas.
Por que criar interfaces?
Interfaces funcionam como contratos: determinam quais métodos uma classe deverá implementar. Isso evita que cada DAO crie métodos soltos e inconsistentes.
Por exemplo, em vez de duplicar métodos como selectAll(), insert() e delete() em várias classes, uma GenericDAO define a estrutura base para todos os DAOs do sistema.
Passo a passo: Implementando DAO com Interface
Reaproveitamento com interfaces genéricas
Uma interface genérica como GenericDAO<T> permite que múltiplas classes DAO compartilhem padrão sem repetir código, mantendo a consistência entre métodos de persistência de diferentes entidades.
public interface GenericDAO<T> {
void insert(T entity) throws SQLException;
List<T> selectAll() throws SQLException;
T selectById(Long id) throws SQLException;
void update(T entity) throws SQLException;
void delete(Long id) throws SQLException;
}
public class UsuarioDAO implements GenericDAO<UsuarioModel> {
private Connection connection;
public UsuarioDAO(Connection connection) {
this.connection = connection;
}
@Override
public void insert(UsuarioModel usuario) throws SQLException {
String sql = "INSERT INTO usuarios (nome, email) VALUES (?, ?)";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setString(1, usuario.getNome());
stmt.setString(2, usuario.getEmail());
stmt.executeUpdate();
}
@Override
public List<UsuarioModel> selectAll() throws SQLException {
String sql = "SELECT * FROM usuarios";
PreparedStatement stmt = connection.prepareStatement(sql);
ResultSet rs = stmt.executeQuery();
List<UsuarioModel> usuarios = new ArrayList<>();
while (rs.next()) {
UsuarioModel usuario = new UsuarioModel();
usuario.setId(rs.getLong("id"));
usuario.setNome(rs.getString("nome"));
usuario.setEmail(rs.getString("email"));
usuarios.add(usuario);
}
return usuarios;
}
@Override
public UsuarioModel selectById(Long id) throws SQLException {
String sql = "SELECT * FROM usuarios WHERE id = ?";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setLong(1, id);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
UsuarioModel usuario = new UsuarioModel();
usuario.setId(rs.getLong("id"));
usuario.setNome(rs.getString("nome"));
usuario.setEmail(rs.getString("email"));
return usuario;
}
return null;
}
@Override
public void update(UsuarioModel usuario) throws SQLException {
String sql = "UPDATE usuarios SET nome = ?, email = ? WHERE id = ?";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setString(1, usuario.getNome());
stmt.setString(2, usuario.getEmail());
stmt.setLong(3, usuario.getId());
stmt.executeUpdate();
}
@Override
public void delete(Long id) throws SQLException {
String sql = "DELETE FROM usuarios WHERE id = ?";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setLong(1, id);
stmt.executeUpdate();
}
}
✅Vantagem
Com essa estratégia, todos os DAOs seguem o mesmo padrão. Se você criar um ProdutoDAO, ele também implementará GenericDAO<ProdutoModel> com os mesmos métodos obrigatórios.
Da estrutura DAO ao Spring Boot
Spring Boot lida com persistência através do módulo Spring Data JPA. Ao estender a interface JpaRepository, ganhamos automaticamente todos os métodos CRUD e muito mais, eliminando a necessidade de escrever ou declarar esses métodos nas classes DAO manualmente.
ℹ️Atenção
Essa mágica da interface do Spring funciona por meio de recursos do Java como Proxy e Reflection.
Adicionando o Spring Boot na jogada
Ao usarmos interface UsuarioRepository extends JpaRepository<Usuario, Long>, o Spring cria automaticamente uma implementação concreta em tempo de execução. Esse processo usa as regras de instanciação definida no contêiner do Spring.
Vantagens das interfaces com Spring Boot
DAO Manual
Estrutura tradicional com implementação manual das funções CRUD
Prós
- Controle total sobre a lógica
Contras
- Muito código repetido
- Pouca escalabilidade
Interface com Spring Boot
Uso de JpaRepository para abstrair persistência
Prós
- Menos código
- Mais produtivo
- Menor chance de erro
Contras
- Menor controle personalizado
Persistência padronizada: Benefícios reais
O uso de interfaces e Spring permite foco na lógica de negócio, padronizando a persistência e entregando melhor performance de desenvolvimento. Além disso, reduz bugs em queries e facilita a escrita de testes automatizados.
✅Atenção
Ao abraçar interfaces, você acelera o desenvolvimento, reduz código duplicado e adota melhores práticas do ecossistema Java.
Ferramentas úteis na jornada
PostgreSQL
Banco de dados relacional usado com JPA
Camadas de abstração e escalabilidade
Com interfaces implementadas corretamente, é possível criar camadas intermediárias como services e facades que se comunicam com os repositórios, mantendo o baixo acoplamento e facilitando a manutenção da aplicação.
Repensando DAOs antigos
Mesmo sistemas legados podem ser refatorados a partir de interfaces. Isso permite criar um novo backend em Spring e fazer migrações parciais com segurança e previsibilidade.
Humanizando o desenvolvedor
Compartilhar histórias despretensiosas, como tentativas frustradas de outras carreiras antes da programação, ajuda a criar conexão. Essa abordagem mostra que por trás do dev existe uma trajetória e aprendizado.