Serialización y persistencia
Serializable y mapeo objeto-relacional manual
java.io.Serializable
Serializable es un mecanismo para convertir un objeto a una secuencia de bytes y reconstruirlo. El
uso principal era para RMI y caché distribuida. Hoy se prefiere JSON (Jackson, Gson) o Protobuf para serialización
porque son más interoperables.
transient excluye campos de la serialización (contraseñas, conexiones...).
import java.io.*;
// Serializable: marca que un objeto puede convertirse a bytes y recuperarse
public class Configuracion implements Serializable {
private static final long serialVersionUID = 1L; // versión para compatibilidad
private String host;
private int puerto;
private transient String password; // transient: no se serializa
public Configuracion(String host, int puerto, String password) {
this.host = host;
this.puerto = puerto;
this.password = password;
}
// getters...
}
// Serializar a fichero
Configuracion cfg = new Configuracion("localhost", 5432, "secreto");
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("config.ser"))) {
oos.writeObject(cfg);
}
// Deserializar desde fichero
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("config.ser"))) {
Configuracion recuperada = (Configuracion) ois.readObject();
System.out.println(recuperada.getHost()); // "localhost"
System.out.println(recuperada.getPassword()); // null — era transient
} serialVersionUID
// serialVersionUID: versión del esquema de serialización
// Si la clase cambia y no coincide el UID, lanza InvalidClassException al deserializar
public class Pedido implements Serializable {
private static final long serialVersionUID = 2L; // cambiar al modificar la estructura
private Long id;
private String descripcion;
// Si se añade un campo nuevo, los ficheros serializados con UID=1 fallarán al cargar
// Incrementar serialVersionUID fuerza la detección del cambio
} Mapeo objeto-relacional manual
Sin un ORM, el mapeo de relaciones entre tablas hay que hacerlo manualmente. El problema más común es el N+1: cargar una lista de entidades padre y luego hacer una consulta por entidad para cargar sus hijos. La solución es siempre un JOIN que traiga todo en una consulta.
// Mapeo objeto-relacional manual: sin ORM
// La "N+1 problem" es fácil de caer — cargar colecciones con consulta separada por entidad
// MAL: una consulta por pedido (N+1)
List<Cliente> clientes = repo.buscarTodos();
for (Cliente c : clientes) {
List<Pedido> pedidos = pedidoRepo.buscarPorClienteId(c.getId()); // N consultas
c.setPedidos(pedidos);
}
// BIEN: un JOIN para traerlo todo de una vez
String sql = """
SELECT c.id AS cliente_id, c.nombre,
p.id AS pedido_id, p.total, p.fecha
FROM clientes c
LEFT JOIN pedidos p ON c.id = p.cliente_id
ORDER BY c.id
""";
try (Connection con = dataSource.getConnection();
PreparedStatement ps = con.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
Map<Long, Cliente> clienteMap = new LinkedHashMap<>();
while (rs.next()) {
long clienteId = rs.getLong("cliente_id");
Cliente cliente = clienteMap.computeIfAbsent(clienteId, id -> {
Cliente c = new Cliente();
try { c.setId(id); c.setNombre(rs.getString("nombre")); }
catch (SQLException e) { throw new RuntimeException(e); }
c.setPedidos(new ArrayList<>());
return c;
});
long pedidoId = rs.getLong("pedido_id");
if (pedidoId != 0) { // puede ser null si el cliente no tiene pedidos (LEFT JOIN)
Pedido p = new Pedido();
try {
p.setId(pedidoId);
p.setTotal(rs.getBigDecimal("total"));
p.setFecha(rs.getDate("fecha").toLocalDate());
} catch (SQLException e) { throw new RuntimeException(e); }
cliente.getPedidos().add(p);
}
}
return new ArrayList<>(clienteMap.values());
} Serialización binaria vs JDBC vs ORM
// Serialización binaria vs persistencia en BBDD:
//
// java.io.Serializable (serialización binaria):
// + Sencillo para objetos en memoria, ficheros temporales, caché en disco
// + Sin dependencia de BBDD
// - Formato frágil: cualquier cambio en la clase puede romper la compatibilidad
// - No es consultable: no se puede hacer "SELECT" sobre objetos serializados
// - Raramente usada en aplicaciones nuevas (JSON/XML son más interoperables)
//
// Mapeo relacional manual (JDBC):
// + Control total sobre el SQL generado
// + Portable entre BBDD
// - Verbose: mucho código de plumbing (el ORM automatiza esto)
// - Gestión manual de relaciones y caché de primer nivel
//
// ORM (JPA/Hibernate):
// + Mapeo automático, consultas JPQL, caché, lazy loading
// - Caja negra: genera SQL inesperado si no se entiende el modelo
// - El conocimiento de JDBC es útil para depurar queries generadas