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
