UPDATE en Java

Actualizar datos en MySQL desde Java sigue el mismo patron que las inserciones: PreparedStatement con placeholders y executeUpdate(). La diferencia clave es que las actualizaciones requieren una clausula WHERE precisa para evitar modificar registros no deseados, y el manejo de transacciones cobra mayor importancia cuando multiples tablas se ven afectadas. En este articulo aprenderas a realizar actualizaciones seguras, manejar transacciones y construir consultas de actualizacion dinamicas.

Requisitos previos

Necesitas una conexion JDBC configurada y la tabla productos con datos de prueba. Si no los tienes, consulta los articulos anteriores.

Codigo completo

Este ejemplo actualiza el precio y stock de un producto por su ID:

import java.sql.*;
 
public class UpdateMySQL {
    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 = "UPDATE productos SET precio = ?, stock = ? WHERE id = ?";
 
            try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
                pstmt.setDouble(1, 13499.99);
                pstmt.setInt(2, 30);
                pstmt.setInt(3, 1);
 
                int filasAfectadas = pstmt.executeUpdate();
 
                if (filasAfectadas > 0) {
                    System.out.println("Producto actualizado correctamente");
                    System.out.println("Filas afectadas: " + filasAfectadas);
                } else {
                    System.out.println("No se encontro el producto con ID 1");
                }
            }
 
        } catch (SQLException e) {
            System.err.println("Error: " + e.getMessage());
        }
    }
}

Salida esperada:

Producto actualizado correctamente
Filas afectadas: 1

Explicacion paso a paso

El metodo executeUpdate() devuelve el numero de filas afectadas por la operacion. Un valor de 0 significa que ninguna fila coincidio con la condicion WHERE, no que hubo un error. Esto es importante para distinguir entre "no encontrado" y "encontrado pero sin cambios".

Por defecto, MySQL reporta como "afectadas" solo las filas cuyo valor realmente cambio. Si actualizas un producto con los mismos valores que ya tiene, executeUpdate() devuelve 0 aunque la fila exista. Si necesitas saber cuantas filas coincidieron independientemente de si cambiaron, puedes agregar useAffectedRows=false a la URL de conexion, aunque el comportamiento por defecto (useAffectedRows=true en Connector/J 8+) es generalmente el deseado.

Actualizacion con verificacion previa

public static boolean actualizarProducto(Connection conn, int id, double nuevoPrecio, int nuevoStock)
        throws SQLException {
    // Verificar que existe
    String sqlVerificar = "SELECT nombre, precio, stock FROM productos WHERE id = ?";
 
    try (PreparedStatement pstmt = conn.prepareStatement(sqlVerificar)) {
        pstmt.setInt(1, id);
 
        try (ResultSet rs = pstmt.executeQuery()) {
            if (!rs.next()) {
                System.out.println("Producto con ID " + id + " no encontrado");
                return false;
            }
 
            System.out.printf("Antes: %s - $%.2f, stock: %d%n",
                rs.getString("nombre"), rs.getDouble("precio"), rs.getInt("stock"));
        }
    }
 
    // Actualizar
    String sqlUpdate = "UPDATE productos SET precio = ?, stock = ? WHERE id = ?";
 
    try (PreparedStatement pstmt = conn.prepareStatement(sqlUpdate)) {
        pstmt.setDouble(1, nuevoPrecio);
        pstmt.setInt(2, nuevoStock);
        pstmt.setInt(3, id);
 
        int filas = pstmt.executeUpdate();
        System.out.printf("Despues: $%.2f, stock: %d (filas: %d)%n", nuevoPrecio, nuevoStock, filas);
        return filas > 0;
    }
}

Actualizacion dinamica

Cuando solo quieres actualizar los campos que el usuario modifico, necesitas construir la consulta dinamicamente:

import java.util.*;
 
public static int actualizarCampos(Connection conn, int id, Map<String, Object> campos)
        throws SQLException {
    if (campos.isEmpty()) {
        System.out.println("No hay campos para actualizar");
        return 0;
    }
 
    StringBuilder sql = new StringBuilder("UPDATE productos SET ");
    List<Object> params = new ArrayList<>();
 
    int i = 0;
    for (Map.Entry<String, Object> campo : campos.entrySet()) {
        if (i > 0) sql.append(", ");
        sql.append(campo.getKey()).append(" = ?");
        params.add(campo.getValue());
        i++;
    }
 
    sql.append(" WHERE id = ?");
    params.add(id);
 
    try (PreparedStatement pstmt = conn.prepareStatement(sql.toString())) {
        for (int j = 0; j < params.size(); j++) {
            pstmt.setObject(j + 1, params.get(j));
        }
 
        int filas = pstmt.executeUpdate();
        System.out.println("Campos actualizados: " + campos.keySet());
        System.out.println("Filas afectadas: " + filas);
        return filas;
    }
}
 
// Uso
Map<String, Object> cambios = new HashMap<>();
cambios.put("precio", 14999.99);
cambios.put("stock", 20);
actualizarCampos(conn, 1, cambios);

Actualizacion por lotes

Similar a la insercion por lotes, puedes actualizar multiples registros de forma eficiente con addBatch():

public static int actualizarPrecios(Connection conn, Map<Integer, Double> nuevosPrecios)
        throws SQLException {
    String sql = "UPDATE productos SET precio = ? WHERE id = ?";
 
    conn.setAutoCommit(false);
 
    try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
        for (Map.Entry<Integer, Double> entry : nuevosPrecios.entrySet()) {
            pstmt.setDouble(1, entry.getValue());
            pstmt.setInt(2, entry.getKey());
            pstmt.addBatch();
        }
 
        int[] resultados = pstmt.executeBatch();
        conn.commit();
 
        int totalActualizados = 0;
        for (int r : resultados) {
            if (r >= 0) totalActualizados += r;
        }
 
        System.out.println("Productos actualizados: " + totalActualizados + " de " + nuevosPrecios.size());
        return totalActualizados;
 
    } catch (SQLException e) {
        conn.rollback();
        throw e;
    } finally {
        conn.setAutoCommit(true);
    }
}

Actualizacion con CASE para multiples valores

Cuando necesitas actualizar diferentes valores para diferentes registros en una sola consulta, CASE es mas eficiente que multiples UPDATEs:

public static void actualizarPreciosPorCategoria(Connection conn) throws SQLException {
    String sql = "UPDATE productos SET precio = precio * CASE "
               + "WHEN categoria = ? THEN ? "
               + "WHEN categoria = ? THEN ? "
               + "WHEN categoria = ? THEN ? "
               + "ELSE 1.0 END "
               + "WHERE categoria IN (?, ?, ?)";
 
    try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
        // Incrementos: Computadoras +5%, Perifericos +10%, Audio +8%
        pstmt.setString(1, "Computadoras");
        pstmt.setDouble(2, 1.05);
        pstmt.setString(3, "Perifericos");
        pstmt.setDouble(4, 1.10);
        pstmt.setString(5, "Audio");
        pstmt.setDouble(6, 1.08);
 
        pstmt.setString(7, "Computadoras");
        pstmt.setString(8, "Perifericos");
        pstmt.setString(9, "Audio");
 
        int filas = pstmt.executeUpdate();
        System.out.println("Precios actualizados para " + filas + " productos");
    }
}

Caso practico

Veamos un sistema de transferencia de stock entre productos con bloqueo optimista y transaccion:

import java.sql.*;
 
public class TransferenciaStock {
 
    public static boolean transferir(Connection conn, int origenId, int destinoId, int cantidad)
            throws SQLException {
        conn.setAutoCommit(false);
 
        try {
            // Verificar stock del origen con FOR UPDATE para bloquear la fila
            String sqlVerificar = "SELECT nombre, stock FROM productos WHERE id = ? FOR UPDATE";
 
            String nombreOrigen;
            int stockOrigen;
 
            try (PreparedStatement pstmt = conn.prepareStatement(sqlVerificar)) {
                pstmt.setInt(1, origenId);
                try (ResultSet rs = pstmt.executeQuery()) {
                    if (!rs.next()) {
                        throw new SQLException("Producto origen " + origenId + " no encontrado");
                    }
                    nombreOrigen = rs.getString("nombre");
                    stockOrigen = rs.getInt("stock");
                }
            }
 
            if (stockOrigen < cantidad) {
                throw new SQLException(
                    String.format("Stock insuficiente en '%s': tiene %d, se necesitan %d",
                        nombreOrigen, stockOrigen, cantidad)
                );
            }
 
            // Verificar que el destino existe
            String nombreDestino;
            try (PreparedStatement pstmt = conn.prepareStatement(sqlVerificar)) {
                pstmt.setInt(1, destinoId);
                try (ResultSet rs = pstmt.executeQuery()) {
                    if (!rs.next()) {
                        throw new SQLException("Producto destino " + destinoId + " no encontrado");
                    }
                    nombreDestino = rs.getString("nombre");
                }
            }
 
            // Descontar del origen
            String sqlDescontar = "UPDATE productos SET stock = stock - ? WHERE id = ?";
            try (PreparedStatement pstmt = conn.prepareStatement(sqlDescontar)) {
                pstmt.setInt(1, cantidad);
                pstmt.setInt(2, origenId);
                pstmt.executeUpdate();
            }
 
            // Agregar al destino
            String sqlAgregar = "UPDATE productos SET stock = stock + ? WHERE id = ?";
            try (PreparedStatement pstmt = conn.prepareStatement(sqlAgregar)) {
                pstmt.setInt(1, cantidad);
                pstmt.setInt(2, destinoId);
                pstmt.executeUpdate();
            }
 
            // Registrar movimiento
            String sqlLog = "INSERT INTO movimientos_stock (producto_origen, producto_destino, cantidad, fecha) "
                          + "VALUES (?, ?, ?, NOW())";
            try (PreparedStatement pstmt = conn.prepareStatement(sqlLog)) {
                pstmt.setInt(1, origenId);
                pstmt.setInt(2, destinoId);
                pstmt.setInt(3, cantidad);
                pstmt.executeUpdate();
            }
 
            conn.commit();
 
            System.out.printf("Transferencia completada: %d unidades%n", cantidad);
            System.out.printf("  De: %s (stock restante: %d)%n", nombreOrigen, stockOrigen - cantidad);
            System.out.printf("  A: %s%n", nombreDestino);
 
            return true;
 
        } catch (SQLException e) {
            conn.rollback();
            System.err.println("Error en transferencia: " + e.getMessage());
            return false;
        } finally {
            conn.setAutoCommit(true);
        }
    }
 
    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")) {
            transferir(conn, 1, 2, 5);
        } catch (SQLException e) {
            System.err.println("Error de conexion: " + e.getMessage());
        }
    }
}

Salida esperada:

Transferencia completada: 5 unidades
  De: Laptop HP Pavilion (stock restante: 20)
  A: Mouse Logitech MX Master

Manejo de errores

Los errores comunes al actualizar registros y como manejarlos:

public static Map<String, Object> actualizarSeguro(Connection conn, int id,
                                                     Map<String, Object> campos) {
    Map<String, Object> resultado = new java.util.HashMap<>();
 
    try {
        int filas = actualizarCampos(conn, id, campos);
 
        if (filas == 0) {
            resultado.put("exito", false);
            resultado.put("mensaje", "Producto no encontrado o sin cambios");
        } else {
            resultado.put("exito", true);
            resultado.put("filasAfectadas", filas);
        }
 
    } catch (SQLException e) {
        resultado.put("exito", false);
 
        switch (e.getErrorCode()) {
            case 1062:
                resultado.put("mensaje", "El valor duplica una clave unica existente");
                break;
            case 1048:
                resultado.put("mensaje", "No se puede establecer NULL en un campo obligatorio");
                break;
            case 1406:
                resultado.put("mensaje", "El valor excede la longitud maxima del campo");
                break;
            case 1205:
                resultado.put("mensaje", "Tiempo de espera agotado (registro bloqueado por otra operacion)");
                break;
            case 1213:
                resultado.put("mensaje", "Deadlock detectado. Reintenta la operacion");
                break;
            default:
                resultado.put("mensaje", "Error [" + e.getErrorCode() + "]: " + e.getMessage());
        }
    }
 
    return resultado;
}

Ahora que dominas las actualizaciones, en el siguiente articulo aprenderas a eliminar registros de MySQL desde Java.

Escrito por Eduardo Lázaro