Sistema de Gestión Bibliotecaria con Arquitectura JavaWeb y Persistencia MySQL

Visión General del Sistema

Esta solución tecnológica se fundamenta en una arquitectura JavaWeb clásica, diseñada para administrar los procesos operativos de una biblioteca institucional. El sistema permite la interacción entre tres roles principales: usuarios finales (estudiantes o docentes), bibliotecarios operativos y administradores del sistema. La persistencia de datos se gestiona mediante MySQL, mientras que la lógica de negocio reside en Servlets y la presentación se construye con JSP complementado por frameworks de interfaz como Layui y Bootstrap.

Arquitectura y Stack Tecnológico

El backend está desarrollado utilizando Java JDK 8 o superior, ejecutándose sobre un contenedor Servlet como Apache Tomcat (versión 9.x recomendada). La comunicación con la base de datos se realiza mediante JDBC estándar. Para la capa de presentación, se combina JSP con librerías frontend que facilitan el diseño responsivo y la interactividad asíncrona.

  • Lenguaje: Java 8+
  • Servidor: Tomcat 9.0+
  • Base de Datos: MySQL 5.7 o 8.0
  • Frontend: JSP, Layui, Bootstrap, jQuery
  • IDE: IntelliJ IDEA, Eclipse o MyEclipse

Desglose de Módulos Funcionales

La aplicación se segmenta lógicamente en tres áreas de acceso diferenciadas por permisos:

1. Módulo de Usuario (Lector)

Esta interfaz está orientada a la consulta pública y gestión personal. Permite visualizar anuncios institucionales, buscar catálogo de libros mediante filtros difusos (título, autor, ubicación) y consultar el estado de借阅 (préstamos). Adicionalmente, los usuarios autenticados pueden gestionar su perfil, revisar historial de infracciones y enviar sugerancias al personal bibliotecario.

2. Módulo de Bibliotecario

Diseñado para la operación diaria. Incluye funcionalidades para registrar préstamos y devoluciones, validando automáticamente las reglas de negocio (límites de tiempo, multas por retraso). También gestiona la publicación de anuncios y genera reportes exportables a formato Excel sobre el movimiento de libros.

3. Módulo de Administrador

Proporciona control total sobre la configuración del sistema. Permite la administración CRUD (Crear, Leer, Actualizar, Borrar) de libros, categorías, tarjetas de借阅 (carnets), reglas de préstamo y cuentas de bibliotecarios. Incluye un dashboard estadístico con gráficos de tendencia de préstamos últimos 30 días.

Esquema de Persistencia

El modelo de datos relacional consta de diez tablas principales que normalizan la información:

  • books: Almacena metadatos del libro (título, autor, estado, ubicación).
  • borrow_records: Historial de transacciones de préstamo y devolución.
  • messages: Tabla para sugerencias y comentarios de usuarios.
  • admin_users: Credenciales de administradores del sistema.
  • borrow_cards: Información de los carnets de los lectores.
  • librarians: Credenciales y datos del personal operativo.
  • categories: Clasificación temática de los libros.
  • libraries: Datos de las sucursales o bibliotecas físicas.
  • rules: Configuración de límites de préstamo y costos por mora.
  • announcements: Contenido de las notificaciones públicas.

Configuración del Entorno de Desarrollo

Para desplegar el proyecto correctamente, se deben cumplir los siguientes requisitos de infraestructura:

  1. Instalar Java JDK 1.8 y configurar la variable de entorno JAVA_HOME.
  2. Configurar una instancia de MySQL y crear la base de datos importando el script SQL provisto.
  3. Modificar los archivos de configuración de conexión (usualmente ubicados en utilidades de JDBC) con las credenciales locales.
  4. Importar el proyecto en el IDE selecccionado y configurar el runtime de Tomcat.
  5. Si el proyecto utiliza Maven, ejecutar clean e install antes del despliegue.

Implementación de Controladores Servlet

A continuación, se presentan ejemplos refactorizados de la lógica de backend. Se ha optimizado la gestión de recursos y se han mejorado las prácticas de seguridad en la manipulación de entradas.

Gestión de Actualización de Libros

Este controlador maneja la modificación de metadatos de un libro existente. Se utiliza una sentencia preparada para evitar inyecciones SQL y se gestiona la conexión mediante un bloque try-with-resources simulado para asegurar el cierrre de recursos.


@WebServlet("/api/books/update")
public class BookUpdateController extends HttpServlet {
    
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        
        response.setContentType("application/json; charset=UTF-8");
        PrintWriter writer = response.getWriter();
        JSONObject jsonResponse = new JSONObject();
        
        // Extracción de parámetros
        String bookId = request.getParameter("id");
        String title = request.getParameter("name");
        String author = request.getParameter("author");
        String location = request.getParameter("position");
        String status = request.getParameter("status");
        
        int rowsAffected = 0;
        String query = "UPDATE books SET title=?, author=?, library_id=?, sort_id=?, " +
                       "position=?, status=?, description=? WHERE id=?";
        
        try (Connection conn = DataSourceUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement(query)) {
            
            stmt.setString(1, title);
            stmt.setString(2, author);
            stmt.setString(3, request.getParameter("library_id"));
            stmt.setString(4, request.getParameter("sort_id"));
            stmt.setString(5, location);
            stmt.setString(6, status);
            stmt.setString(7, request.getParameter("description"));
            stmt.setString(8, bookId);
            
            rowsAffected = stmt.executeUpdate();
            
        } catch (SQLException e) {
            jsonResponse.put("code", "500");
            jsonResponse.put("msg", "Error en la base de datos");
        }
        
        if (rowsAffected == 1) {
            jsonResponse.put("code", "200");
            jsonResponse.put("msg", "Actualización exitosa");
        } else {
            jsonResponse.put("code", "400");
            jsonResponse.put("msg", "Fallo en la actualización");
        }
        
        writer.write(jsonResponse.toString());
    }
}

Listado Paginado de Préstamos

La siguiente clase demuestra cómo implementar paginación y filtrado dinámico para el historial de préstamos. Se calcula el offset basado en la página actual y se construye la cláusula WHERE dinámicamente según los filtros del usuario.


@WebServlet("/admin/api/borrow-history")
public class BorrowHistoryServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        resp.setContentType("application/json; charset=UTF-8");
        String pageNum = req.getParameter("page") != null ? req.getParameter("page") : "1";
        String pageSize = req.getParameter("limit") != null ? req.getParameter("limit") : "10";
        String searchField = req.getParameter("condition");
        String searchValue = req.getParameter("conditionValue");
        
        JSONArray dataRows = new JSONArray();
        int totalRecords = 0;
        String message = "Sin resultados";
        int statusCode = 1;
        
        StringBuilder sqlBuilder = new StringBuilder("SELECT * FROM borrow_books WHERE 1=1");
        StringBuilder countSqlBuilder = new StringBuilder("SELECT COUNT(*) as total FROM borrow_books WHERE 1=1");
        
        if (searchField != null && searchValue != null && !searchValue.isEmpty()) {
            String filterClause = " AND " + searchField + " LIKE ?";
            sqlBuilder.append(filterClause);
            countSqlBuilder.append(filterClause);
        }
        
        sqlBuilder.append(" ORDER BY id DESC LIMIT ?, ?");
        
        try (Connection conn = DataSourceUtil.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sqlBuilder.toString());
             PreparedStatement countStmt = conn.prepareStatement(countSqlBuilder.toString())) {
            
            // Configuración de parámetros de búsqueda
            int paramIndex = 1;
            if (searchField != null && searchValue != null && !searchValue.isEmpty()) {
                stmt.setString(paramIndex, "%" + searchValue + "%");
                countStmt.setString(paramIndex, "%" + searchValue + "%");
                paramIndex++;
            }
            
            // Paginación
            int offset = (Integer.parseInt(pageNum) - 1) * Integer.parseInt(pageSize);
            stmt.setInt(paramIndex, offset);
            stmt.setInt(paramIndex + 1, Integer.parseInt(pageSize));
            
            ResultSet rs = stmt.executeQuery();
            while (rs.next()) {
                JSONObject row = new JSONObject();
                row.put("record_id", rs.getString("id"));
                row.put("card_id", rs.getString("card_id"));
                row.put("book_id", rs.getString("book_id"));
                row.put("borrow_date", rs.getString("borrow_date"));
                row.put("due_date", rs.getString("end_date"));
                dataRows.add(row);
            }
            
            // Conteo total
            ResultSet countRs = countStmt.executeQuery();
            if (countRs.next()) {
                totalRecords = countRs.getInt("total");
            }
            
            if (!dataRows.isEmpty()) {
                statusCode = 0;
                message = "Consulta completada";
            }
            
        } catch (Exception e) {
            message = "Error interno del servidor";
            e.printStackTrace();
        }
        
        JSONObject result = new JSONObject();
        result.put("code", statusCode);
        result.put("count", totalRecords);
        result.put("msg", message);
        result.put("data", dataRows);
        
        resp.getWriter().print(result.toString());
    }
}

Filtro de Seguridad XSS

Para mitigar riesgos de Cross-Site Scripting, se implementa un filtro que sanitiza las entradas HTML antes de ser procesadas o almacenadas, eliminando etiquetas peligrosas como script y style.


public class SecuritySanitizer {
    
    public String sanitizeInput(String input) {
        if (input == null || input.isEmpty()) {
            return null;
        }
        
        String cleaned = input;
        
        // Eliminación de etiquetas script
        cleaned = cleaned.replaceAll("<script[^>]*>.*?</script>", "");
        // Eliminación de etiquetas style
        cleaned = cleaned.replaceAll("<style[^>]*>.*?</style>", "");
        // Eliminación de etiquetas HTML generales
        cleaned = cleaned.replaceAll("<[^>]+>", "");
        
        return cleaned.trim();
    }
}

Etiquetas: JavaWeb servlet jsp MySQL Layui

Publicado el 9-12 13:37