1.1 Antecedentes del problema

Cuando comience a trabajar con bases de datos reales, recordará de inmediato la frase “La optimización prematura es la raíz de todos los males”. Solo que ahora la recuerdas de forma negativa. Cuando se trabaja con una base de datos, la optimización es indispensable. Y necesita trabajar con él ya en la etapa de diseño.

Hibernate hace que trabajar con la base de datos sea muy conveniente. Puede obtener fácilmente cualquier objeto secundario simplemente anotando correctamente @OneToManyy @ManyToMany. Ejemplo:


@Entity
@Table(name="user")
class User {
   @Column(name="id")
   public Integer id;
 
   @OneToMany(cascade = CascadeType.ALL)
   @JoinColumn(name = "user_id")
   public List<Comment> comments;
}

Y lo fácil que es obtener los comentarios de los usuarios:


User user = session.get(User.class, 1);
List<Comment> comments = user.getComments();

Y te llevarás una gran sorpresa. El usuario tiene varios miles de comentarios. Si escribe un código como este, Hibernate, por supuesto, cargará todos los comentarios del usuario. Pero será muy lento, los comentarios ocuparán mucha memoria, etc.

¡Por eso no puedes escribir así! En teoría sí, pero en la práctica no.

1.2 Empeorando las cosas con las colecciones

El problema es aún más interesante. Después de todo, normalmente nunca necesitas todos los comentarios de los usuarios. Incluso si los muestra en algún lugar del cliente, prefiere hacerlo en partes: páginas.

Entonces necesitas métodos como este:


public class CommentsManager {
    private static final PAGE_SIZE = 50;
 
    public List<Comment> getCommentsPage(int userId, int pageIndex){
     	User user = session.get(User.class, userId);
     	List<Comment> comments = user.getComments();
     	return comments.subList(pageIndex * PAGE_SIZE, PAGE_SIZE);
    }
 
   public int getCommentsPageCount(int userId)   {
     	User user = session.get(User.class, userId);
     	List<Comment> comments = user.getComments();
     	return Math.ceil(  comments.size()/PAGE_SIZE);
   }
 
}

El primer método devuelve solo una página de comentarios: 50 piezas. El segundo método devuelve el número de páginas de comentarios. Y esto es lo peor. ¡Para simplemente averiguar la cantidad de comentarios, tenía que descargar todos los comentarios de la base de datos!

1.3 Luz al final del túnel

Por lo tanto, nadie usa nuestras maravillosas colecciones infantiles. No, por supuesto que se usan, pero solo como parte de consultas HQL. Por ejemplo como este:


public class CommentsManager {
      private static final PAGE_SIZE = 50;
 
       public List<Comment> getCommentsPage(int userId, int pageIndex){
           	String hql = "select comments from User where id = :id";
           	Query<Comment> query = session.createQuery( hql, Comment.class);
           	query.setParametr("id", userId);
           	query.setOffset(pageIndex * PAGE_SIZE);
           	query.setLimit(PAGE_SIZE);
           	return query.list();
      }
 
      public int getCommentsPageCount(int userId)   {
           	String hql = "select count(comments) from User where id = :id";
           	Query<Integer> query = session.createQuery( hql, Integer.class);
           	query.setParametr("id", userId);
           	return Math.ceil(query.singleResult()/PAGE_SIZE);
     }
 
}

La buena noticia es que podemos implementar nuestros métodos de tal manera que no necesitemos cargar datos adicionales de la base de datos. La mala noticia es que no es fácil trabajar con nuestras colecciones.