Consultas SELECT en Java

Recuperar datos de MySQL desde Java se realiza a traves de PreparedStatement y ResultSet, las dos clases fundamentales de JDBC para ejecutar consultas parametrizadas y procesar resultados. PreparedStatement previene inyecciones SQL al separar la estructura de la consulta de sus valores, mientras que ResultSet proporciona acceso fila por fila a los datos devueltos. En este articulo aprenderas a dominar ambas clases para construir consultas seguras y eficientes.

Requisitos previos

Necesitas una conexion JDBC configurada y la tabla productos con datos de prueba:

USE tienda;
 
INSERT INTO productos (nombre, categoria, precio, stock) VALUES
('Laptop HP Pavilion', 'Computadoras', 12999.99, 25),
('Mouse Logitech MX Master', 'Perifericos', 1599.00, 150),
('Teclado Mecanico Corsair K70', 'Perifericos', 2299.50, 80),
('Monitor Samsung 27"', 'Monitores', 6499.00, 40),
('Auriculares Sony WH-1000XM5', 'Audio', 5999.99, 60),
('Webcam Logitech C920', 'Perifericos', 1299.00, 200);

Codigo completo

Este ejemplo ejecuta una consulta parametrizada y muestra los resultados:

import java.sql.*;
 
public class ConsultasMySQL {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/tienda?useSSL=false&serverTimezone=UTC";
 
        try (Connection conn = DriverManager.getConnection(url, "root", "tu_contraseña")) {
 
            String sql = "SELECT id, nombre, precio, stock FROM productos WHERE categoria = ? ORDER BY precio DESC";
 
            try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
                pstmt.setString(1, "Perifericos");
 
                try (ResultSet rs = pstmt.executeQuery()) {
                    System.out.println("Productos en categoria \"Perifericos\":");
 
                    int total = 0;
                    while (rs.next()) {
                        int id = rs.getInt("id");
                        String nombre = rs.getString("nombre");
                        double precio = rs.getDouble("precio");
                        int stock = rs.getInt("stock");
 
                        System.out.printf("  [%d] %s - $%.2f (%d en stock)%n", id, nombre, precio, stock);
                        total++;
                    }
 
                    System.out.println("Total: " + total + " productos");
                }
            }
 
        } catch (SQLException e) {
            System.err.println("Error: " + e.getMessage());
        }
    }
}

Salida esperada:

Productos en categoria "Perifericos":
  [3] Teclado Mecanico Corsair K70 - $2299.50 (80 en stock)
  [2] Mouse Logitech MX Master - $1599.00 (150 en stock)
  [6] Webcam Logitech C920 - $1299.00 (200 en stock)
Total: 3 productos

Explicacion paso a paso

El flujo de una consulta JDBC sigue tres pasos: preparar la sentencia con prepareStatement(), asignar los parametros con los metodos setXxx() y ejecutar con executeQuery() que devuelve un ResultSet. El ResultSet es un cursor que avanza fila por fila con next(), devolviendo false cuando no quedan mas filas.

Metodos getXxx del ResultSet

ResultSet ofrece metodos tipados para extraer valores de cada columna. Puedes acceder a las columnas por nombre o por indice (empezando en 1):

try (ResultSet rs = pstmt.executeQuery()) {
    while (rs.next()) {
        // Por nombre de columna (mas legible)
        String nombre = rs.getString("nombre");
        double precio = rs.getDouble("precio");
        int stock = rs.getInt("stock");
 
        // Por indice de columna (mas rapido, empieza en 1)
        String nombre2 = rs.getString(1);
        double precio2 = rs.getDouble(2);
        int stock2 = rs.getInt(3);
 
        // Tipos especiales
        java.sql.Date fecha = rs.getDate("fecha_creacion");
        java.sql.Timestamp timestamp = rs.getTimestamp("fecha_creacion");
        java.math.BigDecimal precioExacto = rs.getBigDecimal("precio");
        boolean activo = rs.getBoolean("activo");
 
        // Verificar si un valor es NULL
        int valor = rs.getInt("stock");
        if (rs.wasNull()) {
            System.out.println("El stock es NULL");
        }
    }
}

Es importante notar que getInt() devuelve 0 para valores NULL, y getDouble() devuelve 0.0. Por eso existe wasNull(), que debes llamar inmediatamente despues de extraer un valor para saber si era realmente NULL.

Consulta de un solo registro

Cuando esperas exactamente un resultado, puedes usar if en lugar de while:

public static void buscarPorId(Connection conn, int productoId) throws SQLException {
    String sql = "SELECT id, nombre, categoria, precio, stock FROM productos WHERE id = ?";
 
    try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
        pstmt.setInt(1, productoId);
 
        try (ResultSet rs = pstmt.executeQuery()) {
            if (rs.next()) {
                System.out.println("Producto encontrado:");
                System.out.println("  Nombre: " + rs.getString("nombre"));
                System.out.println("  Categoria: " + rs.getString("categoria"));
                System.out.printf("  Precio: $%.2f%n", rs.getDouble("precio"));
                System.out.println("  Stock: " + rs.getInt("stock"));
            } else {
                System.out.println("Producto con ID " + productoId + " no encontrado");
            }
        }
    }
}

Consulta con multiples parametros

public static void buscarPorRangoPrecio(Connection conn, double precioMin, double precioMax, String categoria)
        throws SQLException {
    String sql = "SELECT nombre, categoria, precio FROM productos "
               + "WHERE precio BETWEEN ? AND ? AND categoria = ? "
               + "ORDER BY precio";
 
    try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
        pstmt.setDouble(1, precioMin);
        pstmt.setDouble(2, precioMax);
        pstmt.setString(3, categoria);
 
        try (ResultSet rs = pstmt.executeQuery()) {
            System.out.printf("Productos entre $%.2f y $%.2f en '%s':%n", precioMin, precioMax, categoria);
 
            while (rs.next()) {
                System.out.printf("  %s - $%.2f%n", rs.getString("nombre"), rs.getDouble("precio"));
            }
        }
    }
}

Obtener metadatos del resultado

ResultSetMetaData proporciona informacion sobre las columnas devueltas por la consulta:

try (PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM productos LIMIT 1");
     ResultSet rs = pstmt.executeQuery()) {
 
    ResultSetMetaData meta = rs.getMetaData();
    int columnas = meta.getColumnCount();
 
    System.out.println("Estructura de la tabla productos:");
    for (int i = 1; i <= columnas; i++) {
        System.out.printf("  %-20s %-15s nullable=%s%n",
            meta.getColumnName(i),
            meta.getColumnTypeName(i),
            meta.isNullable(i) == ResultSetMetaData.columnNullable ? "si" : "no"
        );
    }
}

Mapear resultados a objetos

En aplicaciones reales, es comun mapear cada fila del ResultSet a un objeto Java:

import java.util.ArrayList;
import java.util.List;
 
public class Producto {
    private int id;
    private String nombre;
    private String categoria;
    private double precio;
    private int stock;
 
    // Constructor, getters y setters
    public Producto(int id, String nombre, String categoria, double precio, int stock) {
        this.id = id;
        this.nombre = nombre;
        this.categoria = categoria;
        this.precio = precio;
        this.stock = stock;
    }
 
    public int getId() { return id; }
    public String getNombre() { return nombre; }
    public String getCategoria() { return categoria; }
    public double getPrecio() { return precio; }
    public int getStock() { return stock; }
 
    @Override
    public String toString() {
        return String.format("[%d] %s (%s) - $%.2f, stock: %d", id, nombre, categoria, precio, stock);
    }
}
public static List<Producto> listarProductos(Connection conn, String categoria) throws SQLException {
    String sql = "SELECT id, nombre, categoria, precio, stock FROM productos WHERE categoria = ?";
    List<Producto> productos = new ArrayList<>();
 
    try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
        pstmt.setString(1, categoria);
 
        try (ResultSet rs = pstmt.executeQuery()) {
            while (rs.next()) {
                productos.add(new Producto(
                    rs.getInt("id"),
                    rs.getString("nombre"),
                    rs.getString("categoria"),
                    rs.getDouble("precio"),
                    rs.getInt("stock")
                ));
            }
        }
    }
 
    return productos;
}

Caso practico

Veamos una funcion de busqueda con paginacion y filtros dinamicos:

import java.sql.*;
import java.util.*;
 
public class BuscadorProductos {
 
    public static Map<String, Object> buscar(Connection conn, Map<String, Object> filtros,
                                              int pagina, int porPagina) throws SQLException {
        StringBuilder sql = new StringBuilder(
            "SELECT id, nombre, categoria, precio, stock FROM productos WHERE activo = 1"
        );
        List<Object> params = new ArrayList<>();
 
        // Filtro por busqueda
        if (filtros.containsKey("busqueda")) {
            sql.append(" AND nombre LIKE ?");
            params.add("%" + filtros.get("busqueda") + "%");
        }
 
        // Filtro por categoria
        if (filtros.containsKey("categoria")) {
            sql.append(" AND categoria = ?");
            params.add(filtros.get("categoria"));
        }
 
        // Filtro por rango de precio
        if (filtros.containsKey("precioMin")) {
            sql.append(" AND precio >= ?");
            params.add(filtros.get("precioMin"));
        }
        if (filtros.containsKey("precioMax")) {
            sql.append(" AND precio <= ?");
            params.add(filtros.get("precioMax"));
        }
 
        // Contar total
        String sqlCount = sql.toString().replaceFirst("SELECT .+ FROM", "SELECT COUNT(*) FROM");
        int total;
        try (PreparedStatement pstmt = conn.prepareStatement(sqlCount)) {
            for (int i = 0; i < params.size(); i++) {
                pstmt.setObject(i + 1, params.get(i));
            }
            try (ResultSet rs = pstmt.executeQuery()) {
                rs.next();
                total = rs.getInt(1);
            }
        }
 
        // Paginacion
        int offset = (pagina - 1) * porPagina;
        sql.append(" ORDER BY nombre LIMIT ? OFFSET ?");
        params.add(porPagina);
        params.add(offset);
 
        // Ejecutar consulta
        List<Producto> productos = new ArrayList<>();
        try (PreparedStatement pstmt = conn.prepareStatement(sql.toString())) {
            for (int i = 0; i < params.size(); i++) {
                pstmt.setObject(i + 1, params.get(i));
            }
            try (ResultSet rs = pstmt.executeQuery()) {
                while (rs.next()) {
                    productos.add(new Producto(
                        rs.getInt("id"),
                        rs.getString("nombre"),
                        rs.getString("categoria"),
                        rs.getDouble("precio"),
                        rs.getInt("stock")
                    ));
                }
            }
        }
 
        // Resultado
        Map<String, Object> resultado = new HashMap<>();
        resultado.put("datos", productos);
        resultado.put("total", total);
        resultado.put("pagina", pagina);
        resultado.put("totalPaginas", (int) Math.ceil((double) total / porPagina));
 
        return resultado;
    }
 
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/tienda?useSSL=false&serverTimezone=UTC";
 
        try (Connection conn = DriverManager.getConnection(url, "root", "tu_contraseña")) {
 
            Map<String, Object> filtros = new HashMap<>();
            filtros.put("categoria", "Perifericos");
            filtros.put("precioMin", 1000.0);
            filtros.put("precioMax", 3000.0);
 
            Map<String, Object> resultado = buscar(conn, filtros, 1, 5);
 
            List<Producto> datos = (List<Producto>) resultado.get("datos");
            System.out.printf("Mostrando %d de %d resultados%n", datos.size(), resultado.get("total"));
            System.out.printf("Pagina %d de %d%n", resultado.get("pagina"), resultado.get("totalPaginas"));
 
            for (Producto p : datos) {
                System.out.println("  " + p);
            }
 
        } catch (SQLException e) {
            System.err.println("Error: " + e.getMessage());
        }
    }
}

Salida esperada:

Mostrando 3 de 3 resultados
Pagina 1 de 1
  [2] Mouse Logitech MX Master (Perifericos) - $1599.00, stock: 150
  [3] Teclado Mecanico Corsair K70 (Perifericos) - $2299.50, stock: 80
  [6] Webcam Logitech C920 (Perifericos) - $1299.00, stock: 200

Manejo de errores

Los errores comunes al ejecutar consultas SELECT:

public static List<Producto> consultaSegura(Connection conn, String sql, Object... params) {
    List<Producto> productos = new ArrayList<>();
 
    try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
        for (int i = 0; i < params.length; i++) {
            pstmt.setObject(i + 1, params[i]);
        }
 
        try (ResultSet rs = pstmt.executeQuery()) {
            while (rs.next()) {
                productos.add(new Producto(
                    rs.getInt("id"),
                    rs.getString("nombre"),
                    rs.getString("categoria"),
                    rs.getDouble("precio"),
                    rs.getInt("stock")
                ));
            }
        }
    } catch (SQLException e) {
        switch (e.getErrorCode()) {
            case 1146:
                System.err.println("La tabla especificada no existe");
                break;
            case 1054:
                System.err.println("Una de las columnas especificadas no existe");
                break;
            case 1064:
                System.err.println("Error de sintaxis en la consulta SQL");
                break;
            default:
                System.err.println("Error en la consulta [" + e.getErrorCode() + "]: " + e.getMessage());
        }
    }
 
    return productos;
}

Ahora que dominas las consultas SELECT, en el siguiente articulo aprenderas a insertar datos en MySQL desde Java.

Escrito por Eduardo Lázaro