Este artículo presenta un enfoque puramente algorítmico en Java para recuperar y estructurar datos jerárquicos desde una base de datos, usando menús dinámicos como caso de uso. La metodología destaca por realizar una única consulta a la base de datos, optimizando el rendimiento, y soporta niveles de anidamienot ilimitados, aunque el ejemplo se centra en dos niveles por simplicidad.
Escenarios de ejemplo
En una aplicación típica, diferentes roles de usuario tienen asignados menús específicos. Por ejemplo, un administrador podría acceder a todos los menús, mientras que un agente solo ve una selección limitada. Los datos de menú incluyen campos como identificador, padre, nivel, orden, tipo, ruta, título e ícono, con una relación padre-hijo que forma la jerarquía.
Estructura de entidades
Definimos clases de entidad para representar menús y roles en la base de datos, adaptadas para trabajar con MyBatis-Plus y manejar relaciones jerárquicas.
package com.example.modelo;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.List;
@Data
@TableName("tabla_menu")
public class ElementoMenu {
private String id;
private String idPadre;
private int nivelProfundidad;
private int ordenVisualizacion;
private int tipoMenu;
private String ruta;
private String titulo;
private String icono;
@TableField(exist = false)
private List<ElementoMenu> elementosHijos;
}
package com.example.modelo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("tabla_rol")
public class RolEntidad {
@TableId(type = IdType.AUTO)
private Integer idRol;
private String nombreRol;
private String idsMenusAsignados; // Almacena un JSON array de identificadores de menú
private int porcentajeDescuento;
}
Algoritmo de construcción jerárquica
El algoritmo siguiente recupera los menús asignados a un rol mediante una consulta única, los organiza por nivel y construye iterativamente la estructura jerárquica en memoria, desde los nodos más profundos hacia la raíz.
public List<ElementoMenu> obtenerMenuJerarquico(String idRol) {
RolEntidad rol = rolMapper.selectById(idRol);
if (rol == null || rol.getIdsMenusAsignados() == null) {
return Collections.emptyList();
}
List<String> listaIdsMenu = JSONArray.parseArray(rol.getIdsMenusAsignados(), String.class);
if (listaIdsMenu.isEmpty()) {
return Collections.emptyList();
}
// Consulta única ordenada por nivel
LambdaQueryWrapper<ElementoMenu> consulta = new LambdaQueryWrapper<>();
consulta.in(ElementoMenu::getId, listaIdsMenu);
consulta.orderByAsc(ElementoMenu::getNivelProfundidad);
List<ElementoMenu> todosLosMenus = menuMapper.selectList(consulta);
if (todosLosMenus.isEmpty()) {
return Collections.emptyList();
}
// Determinar el nivel máximo de profundidad
int nivelMaximo = todosLosMenus.stream()
.mapToInt(ElementoMenu::getNivelProfundidad)
.max()
.orElse(0);
List<ElementoMenu> menuFinal = new ArrayList<>();
// Iterar desde el nivel más profundo hacia arriba
for (int nivelActual = nivelMaximo; nivelActual > 0; nivelActual--) {
List<ElementoMenu> elementosNivel = filtrarPorNivel(todosLosMenus, nivelActual);
if (nivelActual == nivelMaximo) {
menuFinal = elementosNivel;
} else {
// Asignar elementos hijos a sus padres
for (ElementoMenu padre : elementosNivel) {
List<ElementoMenu> hijos = menuFinal.stream()
.filter(hijo -> padre.getId().equals(hijo.getIdPadre()))
.collect(Collectors.toList());
padre.setElementosHijos(hijos);
}
menuFinal = elementosNivel;
}
}
return menuFinal;
}
// Método auxiliar para filtrar y ordenar elementos por nivel
private List<ElementoMenu> filtrarPorNivel(List<ElementoMenu> lista, int nivel) {
return lista.stream()
.filter(item -> item.getNivelProfundidad() == nivel)
.sorted(Comparator.comparingInt(ElementoMenu::getOrdenVisualizacion))
.collect(Collectors.toList());
}
Este enfoque garantiza que la jerarquía se construya de manera eficiente en memoria, con la ventaja de reducir el número de consultas a la base de datos y manejar estructuras anidadas de forma flexible.