3
respostas

DAO genérica

Oi pessoal, estou finalizando o curso de JDBC e decidi tentar criar uma DAO genérica da qual as classes DAO de Objetos específicos herdam, segue o código com uma explicação mais bem detalhada:

Essa é o DAO genérico que basicamente contém um CRUD já implementado

package br.caelum.models;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;

public abstract class DAO {

    private Connection con;
    protected String tabela;
    private String insertSQL;
    private String updateSQL;

    public DAO(Connection con, String tabela) {
        this.con = con;
        this.tabela = tabela;
    }

    protected abstract void setStatementValues(PreparedStatement stmt, Object object) throws SQLException;
    protected abstract Object createObject(ResultSet rs) throws SQLException;

    protected void setInsertSQL(String sql) {
        this.insertSQL = sql;
    }

    protected void setUpdateSQL(String sql) {
        this.updateSQL = sql;
    }

    public void insert(Object object) {
        System.out.println(this.insertSQL);
        try (PreparedStatement stmt = this.con.prepareStatement(this.insertSQL)) {
            this.setStatementValues(stmt, object);
            stmt.execute();
        } catch (SQLException ex) {
            this.treatException(ex);
        }
    }

    public void update(Object object) {
        try (PreparedStatement stmt = this.con.prepareStatement(this.updateSQL)) {
            this.setStatementValues(stmt, object);
            stmt.execute();
        } catch (SQLException ex) {
            this.treatException(ex);
        }
    }

    public void delete(ObjectWithID object) {
        String sql = String.format("DELETE FROM %s WHERE id = ?", this.tabela);
        try (PreparedStatement stmt = this.con.prepareStatement(sql)) {
            stmt.setInt(1, object.getId());
            stmt.execute();
        } catch (SQLException ex) {
            this.treatException(ex);
        }
    }

    public Object find(int id) {
        String sql = String.format("SELECT * FROM %s WHERE id = ?", this.tabela);
        try (PreparedStatement stmt = this.con.prepareStatement(sql)) {
            stmt.setInt(1, id);
            stmt.execute();
            return executeResultSet(stmt);
        } catch (SQLException ex) {
            this.treatException(ex);
            return null;
        }

    }

    @SuppressWarnings("rawtypes")
    public ArrayList findAll() {
        String sql = String.format("SELECT * FROM %s", this.tabela);
        try (Statement stmt = this.con.createStatement()) {
            stmt.execute(sql);
            ArrayList list = new ArrayList<>();
            return executeResultSet(stmt, list);
        } catch (SQLException ex) {
            this.treatException(ex);
            return null;
        }
    }

    private Object executeResultSet(PreparedStatement stmt) {
        try (ResultSet rs = stmt.getResultSet()) {
            rs.next();
            return this.createObject(rs);
        } catch (Exception ex) {
            this.treatException(ex);
            return null;
        }
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private ArrayList executeResultSet(Statement stmt, ArrayList list) {
        try (ResultSet rs = stmt.getResultSet()) {
            while (rs.next()) {
                Object object = this.createObject(rs);
                list.add(object);
            }
        } catch (Exception ex) {
            this.treatException(ex);
        }
        return list;
    }

    private void treatException(Exception ex) {
        System.out.println(ex.getMessage());
        ex.printStackTrace();
    }

}

Há a opção de herdar diretamente desse DAO, ou herdar da classe ArrayListDAO (ainda não achei um nome melhor) que consiste apenas em uma camada entre o DAO genérico e o DAO de uma especificação que guarda os elementos do DB num array para que não seja necessário ficar dando "Select * " toda hora

package br.caelum.models;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;

public abstract class ArrayListDAO extends DAO {

    protected ArrayList<Object> objects;

    public ArrayListDAO(Connection con, String tabela) {
        super(con, tabela);
        this.loadObjects();
    }

    protected abstract void setStatementValues(PreparedStatement stmt, Object object) throws SQLException;
    protected abstract Object createObject(ResultSet rs) throws SQLException;
    protected abstract Object convertObject(Object object);

    @SuppressWarnings("unchecked")
    protected void loadObjects() throws NullPointerException {
        this.objects = super.findAll();
    }

    @SuppressWarnings("rawtypes")
    protected ArrayList getObjects() {
        return this.objects;
    }

    @Override
    public void update(Object object) {
        super.update(object);
        this.loadObjects();
    }

    @Override
    public void delete(ObjectWithID object) {
        super.delete(object);
        this.loadObjects();
    }

    @Override
    public void insert(Object object) {
        super.insert(object);
        this.objects.add(this.convertObject(object));
    }


}

Gastaria de saber formas de melhorar esse código, agradeço desde já a ajuda

3 respostas

PS: esse é um exemplo do DAO de um produto que herda da classe ArrayListDAO

package br.caelum.models;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.sql.ResultSet;

import br.caelum.jdbc.DBConnection;

public class ProdutoDao extends ArrayListDAO {

    public ProdutoDao() {
        super(DBConnection.getConnection(), "produtos");
        super.setInsertSQL(String.format("INSERT INTO %s (nome, descricao) VALUES (?, ?)", this.tabela ));
        super.setUpdateSQL(String.format("UPDATE %s SET nome = ?, descricao = ? WHERE id = ?", this.tabela));
    }

    @SuppressWarnings("unchecked")
    public ArrayList<Produto> findAll(){
        return  (ArrayList<Produto>) super.getObjects();
    }

    @Override
    public Produto find(int id) {
        return this.convertObject(super.find(id));
    }

    @Override
    protected Produto convertObject(Object object) {
        return (Produto) object;
    }

    @Override
    protected void setStatementValues(PreparedStatement stmt, Object object) throws SQLException {
        Produto produto = (Produto) object;
        stmt.setString(1, produto.getNome());
        stmt.setString(2, produto.getDescricao());
        if (produto.getId() != 0) stmt.setInt(3, produto.getId());
    }

    @Override
    protected Produto createObject(ResultSet rs) throws SQLException{
        return ProdutoFactory.createProdutoUsingResultSet(rs);
    }

}

Olá Gabriel!

Ficou bem legal essa sua implementação de DAO Genérico, gostei da idéia do ArrayListDAO, temos que sempre pensar em reduzir as conexões com o banco pra melhorar a performance.

Você conseguiu usar bem o que foi passado no curso, está bem redondo o código. Algumas coisas mais que acho interessante pensar quando fazemos este tipo de código.

  1. Temos que ter cuidado com os recursos que estamos gerenciando, por exemplo, temos Connection, ResultSets, etc, quando fazemos algo genérico, temos que nos preocupar com a forma que gerenciamos estes recursos, na classe mãe ou classe filha? Onde precisamos dar close ? Quem é responsável pro liberar os recursos? Será que a nossa classe deve criar um Connection e o ResultSet, ou recebemos pronto? Isto tudo impacta na forma que vamos lidar com mudanças, se quisermos no futuro usar hibernate, qual será o custo de alterar.

  2. Entendo que foi pra exercitar o que foi aprendido no curso ;). Mas só pra não deixar de falar, as vezes é importante avaliarmos usar algo pronto, no caso uma solução para mapeamento de dados poderia isolar toda a sua aplicação da parte do banco, te deixando livre pra pensar em Java, sem ter trabalho com SQL, mas ainda assim precisamos pesar e entender os benefícios e problemas em usar algo pronto.

  3. Concluindo, estou colocando os comentários aqui, não como críticas, mas pra você pensar em possíveis decisões que temos que tomar quando o sistema vai crescendo.

Um abraço!

E parabéns pela implementação!

Luan

Obrigado pelas dicas Luan, a ideia do projeto era justamente consolidar o aprendizado