Padrão DAO e relacionamento 1 - N, qual a maneira mais elegante?

Em vários pontos de elaboração da arquitetura de projetos, sempre me questionei sobre uma possível melhor implementação com o padrão DAO sobre tabelas no banco que se relacionam no modelo 1-N.

Imagine o seguinte contexto:

No banco de dados eu tenho as tabelas “student” e “book” (um student tem muitos books, logo um book pertence unicamente a um student).

Logo posso listar todos os students e seus respectivos books através da query:
select * from student, book where student.student_id = book.student;
ou
select * from student inner join(book) on (student.student_id = book.student);

No meu projeto, tenho a seguinte classe para representar um student:

class Student{
long id;
List<Book> books;
//getters and setters
}

(aqui vem o conflito)
Logo no meu DAO, para implementar um método que liste todos os students e seus respectivos books, e itera-los em um List, a única maneira possívelmente elegante que vejo é a seguinte:

Classe StudentDAO

//imports statements necessários
class StudentDAO{

//constructor para definir uma variável java.sql.Connection

List<Student> getList(){
PreparedStatement ps = con.prepareStatement("select * from students");
ResultSet rs = ps.executeQuery();
Student aluno = null;
List lista = new ArrayList<Student>;
while(rs.next()){
aluno = new Student();
aluno.setId(rs.getLong("student_id"));
aluno.setBooks(new BookDAO().getList(aluno.getId(), con));
lista.add(aluno);
}
return lista;
}
}

Classe BookDAO

//imports statements necessários
class BookDAO{

List<Book> getList(long studentId, Connection con){
PreparedStatement ps = con.prepareStatement("select * from book where book.student = ?");
ps.setLong(1, studentId);
ResultSet rs = ps.executeQuery();
Book livro = null;
List lista = new ArrayList<Livro>;
while(rs.next()){
livro= new Book();
livro.setId(rs.getLong("book_id"));
lista.add(livro);
}
return lista;
}
}

No seguinte contexto, pequenas queries estão sendo executadas para cada Student que venha a ser listado no primeiro ResultSet. Assim, não promovendo o uso do join. Porém, imaginem caso eu usasse apenas uma query com o join proposto no topo deste post nesse contexto, como iteraria de maneira elegante?

bem, eu não sei se eh tão elegante, mas sei q é 100% funcional.

public class Cliente{
    private String nome;
    private String sobreNome;
    private Endereco endereco;
}

public class Endereco{
   private String rua;
   private Integer numero;
}

public class ClienteDAO{
    private Connection con;
    ClienteDAO(){
          this.con = Conexao.getConnection();
    }
    
    public List<Cliente> retornaCliente(){
         List<Cliente> cli = new ArrayList();
         PreparedStatement stm = this.con.prepareStatement("select * from cliente");
         ResultSet rs = stm.executeQuery();          
         while(rs.next()){
               Cliente cliente = new Cliente();
               cliente.setNome(rs.getString("nome");
               
               Endereco endereco = new Endereco();
               endereco.setRua(rs.getString("rua"));

               cliente.setEndereco(endereco);
               cli.add(cliente);
         } 
        rs.close();
        stm.close();
        return cli;
    }

}

eh isso…

t+ e boa sorte.

Acho q da primeira maneira que vc ja postou é mais elegante… Pelo menso creio q assim vc esta trabalhando corretamente sua OO.

Se vc criar uma senteça so vc tera q controlar de alguma forma o momento em q é mudado o estudante. Assim creio q seria menos interessante…

[quote=fernandopaiva]bem, eu não sei se eh tão elegante, mas sei q é 100% funcional.

public class Cliente{
    private String nome;
    private String sobreNome;
    private Endereco endereco;
}

public class Endereco{
   private String rua;
   private Integer numero;
}

public class ClienteDAO{
    private Connection con;
    ClienteDAO(){
          this.con = Conexao.getConnection();
    }
    
    public List<Cliente> retornaCliente(){
         List<Cliente> cli = new ArrayList();
         PreparedStatement stm = this.con.prepareStatement("select * from cliente");
         ResultSet rs = stm.executeQuery();          
         while(rs.next()){
               Cliente cliente = new Cliente();
               cliente.setNome(rs.getString("nome");
               
               Endereco endereco = new Endereco();
               endereco.setRua(rs.getString("rua"));

               cliente.setEndereco(endereco);
               cli.add(cliente);
         } 
        rs.close();
        stm.close();
        return cli;
    }

}

eh isso…

t+ e boa sorte.[/quote]

Este é um relacionamento 1-1, a dúvida é sobre relacionamentos 1-N.

[quote=Lucas Abbatepaolo]Acho q da primeira maneira que vc ja postou é mais elegante… Pelo menso creio q assim vc esta trabalhando corretamente sua OO.

Se vc criar uma senteça so vc tera q controlar de alguma forma o momento em q é mudado o estudante. Assim creio q seria menos interessante…

[/quote]

Sim, até certo ponto ela é elegante.

Neste exemplo, ele está retornando como um full join retornaria.

No caso se eu quisesse um resultado similar a inner join, teria que adicionar uma clausula na primeira sql especificando que quero apenas os students que estão relacionados na foreign key de books… e se eu quisesse um left ou right join, seria a mesma coisa. Na minha opinião, é nesse ponto que fica deselegante.

Uma das vantagens de um framework como o Hibernate é que no relacionamento 1-N, quando você localizar o Student automaticamente vai ter todos os seus Books na lista sem precisar montar uma consulta especial para isso.

Mas por trás do JPA existe uma lógica automatizada para se fazer essas consultas, e é exatamente o que eu me questiono, como seria esse algoritmo?

JPA fica elegante para o lado do programador, mas como seria por trás do framework?

Mas por trás do JPA existe uma lógica automatizada para se fazer essas consultas, e é exatamente o que eu me questiono, como seria esse algoritmo?

JPA fica elegante para o lado do programador, mas como seria por trás do framework?[/quote]

Dai não sei te dizer, mas você pode baixar os fontes do Hibernate e tentar entender como ele faz isso (http://sourceforge.net/projects/hibernate/files/hibernate3/3.6.7.Final/).

Em questão de reutilização é uma boa pratica…mas em questão de performance é uma pessima pratica. Vc ta fazendo 2 acessos ao banco para resolver uma querie!!
Na pratica não funciona não - http://fernandofranzini.wordpress.com/2009/12/16/praticas-de-aplicativos-web/
Vai minha opinião, para seu caso:
-Cada DAO faz o acesso ao banco e tem um método para transformar o resultset em objetos.
-Cada DAO deve ir no banco e pegar todos dados + outros via JOIN e montar objetos. Caso um DAO faça JOIN com outra tabela, Vc pode chamar a metodo de montagem do outro DAO, para não duplicar a transformação do objetos.

[quote=FernandoFranzini]Em questão de reutilização é uma boa pratica…mas em questão de performance é uma pessima pratica. Vc ta fazendo 2 acessos ao banco para resolver uma querie!!
Na pratica não funciona não - http://fernandofranzini.wordpress.com/2009/12/16/praticas-de-aplicativos-web/
Vai minha opinião, para seu caso:
-Cada DAO faz o acesso ao banco e tem um método para transformar o resultset em objetos.
-Cada DAO deve ir no banco e pegar todos dados + outros via JOIN e montar objetos. Caso um DAO faça JOIN com outra tabela, Vc pode chamar a metodo de montagem do outro DAO, para não duplicar a transformação do objetos.
[/quote]

Muito obrigado pela resposta construtiva, mas me levantou mais algumas dúvidas:

Tá, estou fazendo 2 acessos ao banco para resolver apenas 1 lista final, mas isso realmente é uma crítica péssima pratica em questão de performance? Realmente acredito que se performance for o mais importante do sistema, seria um problema. Mas considerando que um sistema “regular” tem outras preocupações, como reuso de código, estensabilidade e fácil manutenção, me questiono se essa abordagem não seria viável. Você tem alguma fonte sobre algum teste já feito sobre isso?

Considerando que cada DAO deve ir no banco pegar todas suas relações com o uso do JOIN na query. Essa situação me retorna à dúvida inicial: como fazer isso de maneira elegante?
Supondo que eu tenha o mesmo contexto especificado no primeiro post e uma relação de 1-N. como faria pra iterar, de maneira elegante, uma List<Student>, sendo que cada Student tem um List<Livro>, executando apenas uma simples e elegante query:
select * from student inner join(book) on (student.student_id = book.student);

Ninguém tem uma solução mais elegante… estou concluindo que a proposta DAO acaba deixando a desejar no modelo 1-N.

Olá,
estava lendo sobre isto e resolvi responder esta mensagem.

Já ouviu falar do Padrão Observer?