El patrón DAO separa la lógica de acceso a datos de la lógica de negocio. Define una interfaz para las operaciones CRUD y proporciona una implementación para cada base de datos. Es la base de Spring Data y JPA.
Estructura del patrón
src/main/java/com/ejemplo/
├── model/
│ └── Producto.java # Record Java 21
├── dao/
│ ├── ProductoDao.java # Interfaz
│ └── ProductoDaoJdbc.java # Implementación JDBC
└── service/
└── ProductoService.java # Lógica de negocio (usa ProductoDao)
// model/Producto.java
record Producto(Integer id, String nombre, double precio, boolean activo) {
// Constructor para inserciones (sin id)
Producto(String nombre, double precio) { this(null, nombre, precio, true); }
}
// dao/ProductoDao.java
public interface ProductoDao {
List<Producto> findAll();
Optional<Producto> findById(int id);
List<Producto> findByNombre(String patron);
int save(Producto p); // devuelve el id generado
boolean update(Producto p);
boolean delete(int id);
}
// dao/ProductoDaoJdbc.java
public class ProductoDaoJdbc implements ProductoDao {
private final DataSource ds;
public ProductoDaoJdbc(DataSource ds) { this.ds = ds; }
@Override
public List<Producto> findAll() {
List<Producto> lista = new ArrayList<>();
try (Connection c = ds.getConnection();
Statement st = c.createStatement();
ResultSet rs = st.executeQuery("SELECT * FROM productos WHERE activo=1 ORDER BY nombre")) {
while (rs.next()) lista.add(mapRow(rs));
} catch (SQLException e) { throw new RuntimeException("findAll", e); }
return lista;
}
@Override
public Optional<Producto> findById(int id) {
try (Connection c = ds.getConnection();
PreparedStatement ps = c.prepareStatement("SELECT * FROM productos WHERE id=?")) {
ps.setInt(1, id);
try (ResultSet rs = ps.executeQuery()) {
return rs.next() ? Optional.of(mapRow(rs)) : Optional.empty();
}
} catch (SQLException e) { throw new RuntimeException("findById", e); }
}
@Override
public int save(Producto p) {
String sql = "INSERT INTO productos (nombre, precio, activo) VALUES (?, ?, ?)";
try (Connection c = ds.getConnection();
PreparedStatement ps = c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, p.nombre());
ps.setDouble(2, p.precio());
ps.setBoolean(3, p.activo());
ps.executeUpdate();
try (ResultSet keys = ps.getGeneratedKeys()) {
return keys.next() ? keys.getInt(1) : -1;
}
} catch (SQLException e) { throw new RuntimeException("save", e); }
}
@Override
public boolean update(Producto p) {
String sql = "UPDATE productos SET nombre=?, precio=?, activo=? WHERE id=?";
try (Connection c = ds.getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, p.nombre()); ps.setDouble(2, p.precio());
ps.setBoolean(3, p.activo()); ps.setInt(4, p.id());
return ps.executeUpdate() > 0;
} catch (SQLException e) { throw new RuntimeException("update", e); }
}
@Override
public boolean delete(int id) {
try (Connection c = ds.getConnection();
PreparedStatement ps = c.prepareStatement("UPDATE productos SET activo=0 WHERE id=?")) {
ps.setInt(1, id);
return ps.executeUpdate() > 0;
} catch (SQLException e) { throw new RuntimeException("delete", e); }
}
private Producto mapRow(ResultSet rs) throws SQLException {
return new Producto(rs.getInt("id"), rs.getString("nombre"),
rs.getDouble("precio"), rs.getBoolean("activo"));
}
}
