Introducción a las Bases de Datos
La mayoría de las aplicaciones, tras recoger y procesar datos, necesitan almacenarlos de forma permanente para su posterior uso. Entre las técnicas de persistencia hemos visto los ficheros. Otra alternativa, mucho más potente y escalable, consiste en utilizar los servicios de un Sistema Gestor de Base de Datos (SGBD).
| Característica | Ficheros | Base de Datos |
|---|---|---|
| Búsqueda | Manual, lenta | SQL optimizado, rápida |
| Relaciones entre datos | Complicado | Nativo (claves foráneas) |
| Acceso concurrente | Problemático | Gestionado por el SGBD |
| Integridad | Responsabilidad del programador | Garantizada por el SGBD |
| Volumen de datos | Limitado | Millones de registros |
¿Qué es JDBC?
java.sql y permite ejecutar sentencias SQL desde Java de forma independiente del SGBD.API JDBC: Clases Principales
La API JDBC está compuesta por varias clases e interfaces que trabajan conjuntamente. No es necesario conocerlas todas, solo las principales:
| Clase / Interfaz | Paquete | Función |
|---|---|---|
| DriverManager | java.sql | Gestiona los drivers y crea conexiones |
| Connection | java.sql | Representa la conexión activa con la BD |
| Statement | java.sql | Ejecuta sentencias SQL simples |
| PreparedStatement | java.sql | Ejecuta sentencias SQL parametrizadas (más seguro) |
| ResultSet | java.sql | Tabla con los resultados de una consulta SELECT |
| SQLException | java.sql | Excepción producida por errores de BD |
1import java.sql.*; // Importa todas las clases de JDBC
2
3// O de forma individual:
4import java.sql.Connection;
5import java.sql.DriverManager;
6import java.sql.Statement;
7import java.sql.PreparedStatement;
8import java.sql.ResultSet;
9import java.sql.SQLException;Arquitectura JDBC
JDBC actúa como intermediario entre la aplicación Java y el SGBD. El driver es el componente específico de cada SGBD que hace posible esta comunicación:
1/*
2 * Flujo de comunicación con la BD:
3 *
4 * [Aplicación Java]
5 * ↓ (API JDBC - java.sql)
6 * [DriverManager]
7 * ↓ (selecciona el driver correcto)
8 * [Driver MySQL / PostgreSQL / Oracle...]
9 * ↓ (protocolo específico del SGBD)
10 * [Servidor de Base de Datos]
11 * ↓ (resultados)
12 * [ResultSet]
13 * ↓
14 * [Aplicación Java]
15 */Driver JDBC
Cada fabricante de SGBD desarrolla su propio driver JDBC que implementa los protocolos de comunicación específicos de su producto. Sin el driver correcto, JDBC no puede comunicarse con el SGBD. Los drivers más comunes son:
| SGBD | Driver (JAR) | URL de conexión |
|---|---|---|
| MySQL | mysql-connector-java-x.x.xx.jar | jdbc:mysql://servidor/basedatos |
| PostgreSQL | postgresql-x.x.jar | jdbc:postgresql://servidor/basedatos |
| Oracle | ojdbc11.jar | jdbc:oracle:thin:@servidor:1521:sid |
| SQLite | sqlite-jdbc-x.x.jar | jdbc:sqlite:ruta/archivo.db |
Añadir el driver en NetBeans
.jar descargado de la web del fabricante. Para Maven/Gradle, añadir la dependencia en pom.xml o build.gradle.1<!-- Dependencia para MySQL en Maven -->
2<dependency>
3 <groupId>com.mysql</groupId>
4 <artifactId>mysql-connector-j</artifactId>
5 <version>8.3.0</version>
6</dependency>
7
8<!-- Dependencia para PostgreSQL en Maven -->
9<dependency>
10 <groupId>org.postgresql</groupId>
11 <artifactId>postgresql</artifactId>
12 <version>42.7.1</version>
13</dependency>Conexión a la Base de Datos
Antes de trabajar con la base de datos hay que crear una conexión: un canal de comunicación que permanece abierto mientras se necesite. Una vez terminado el trabajo, debe cerrarsepara liberar recursos del SGBD.
1import java.sql.*;
2
3// Datos de conexión
4String url = "jdbc:mysql://localhost/Instituto";
5String usuario = "Rodrigo";
6String password = "12345";
7
8// Crear la conexión
9Connection con = DriverManager.getConnection(url, usuario, password);
10
11// Trabajar con la BD...
12
13// Cerrar siempre la conexión al terminar
14con.close();Formato de la URL de Conexión (MySQL)
1// Formato: jdbc:mysql://<servidor>/<base_de_datos>[?opciones]
2
3// Servidor local (misma máquina)
4String url = "jdbc:mysql://localhost/MiBD";
5
6// Servidor por IP
7String url = "jdbc:mysql://192.168.1.100/MiBD";
8
9// Con opciones adicionales (recomendado para MySQL 8+)
10String url = "jdbc:mysql://localhost/Instituto" +
11 "?useSSL=false" +
12 "&serverTimezone=Europe/Madrid" +
13 "&allowPublicKeyRetrieval=true";
14
15// PostgreSQL
16String url = "jdbc:postgresql://localhost:5432/MiBD";
17
18// SQLite (fichero local, sin servidor)
19String url = "jdbc:sqlite:datos/mibasedatos.db";Conexión con try-with-resources (Recomendado)
La forma moderna y segura de gestionar conexiones JDBC es contry-with-resources, que cierra automáticamente todos los recursos al terminar:
1import java.sql.*;
2
3public class ConexionEjemplo {
4
5 // Constantes de conexión (en producción, usar fichero de configuración)
6 private static final String URL = "jdbc:mysql://localhost/Instituto";
7 private static final String USUARIO = "Rodrigo";
8 private static final String PASSWORD = "12345";
9
10 public static void main(String[] args) {
11 // try-with-resources: cierra la conexión automáticamente
12 try (Connection con = DriverManager.getConnection(URL, USUARIO, PASSWORD)) {
13
14 System.out.println("¡Conexión establecida!");
15 System.out.println("SGBD: " + con.getMetaData().getDatabaseProductName());
16 System.out.println("Versión: " + con.getMetaData().getDatabaseProductVersion());
17
18 // Aquí se realizarían las operaciones con la BD
19
20 }
21 catch (SQLException e) {
22 System.out.println("Error de conexión: " + e.getMessage());
23 System.out.println("Código SQL: " + e.getSQLState());
24 }
25 // La conexión se cierra automáticamente al salir del try
26 }
27}Excepciones al conectar
getConnection() puede lanzar:SQLException: credenciales incorrectas, BD no existe, error generalSQLTimeoutException: tiempo de espera excedido (servidor no responde)
SQLException.Ejecución de Sentencias SQL
Para ejecutar sentencias SQL se usa un objeto Statement, creado desde la conexión. Hay dos métodos principales según el tipo de sentencia SQL:
| Método | Devuelve | Para |
|---|---|---|
| executeQuery(sql) | ResultSet | SELECT (consultas que devuelven datos) |
| executeUpdate(sql) | int (filas afectadas) | INSERT, UPDATE, DELETE (modifican datos) |
| execute(sql) | boolean | Cualquier sentencia SQL (uso general) |
Consulta SELECT con executeQuery()
1String sql = "SELECT num, nombre, media FROM Alumnos WHERE fnac > '2009-01-01'";
2
3try (Connection con = DriverManager.getConnection(URL, USER, PASS);
4 Statement stmt = con.createStatement();
5 ResultSet rs = stmt.executeQuery(sql)) {
6
7 // Recorrer los resultados fila a fila
8 while (rs.next()) {
9 int num = rs.getInt("num");
10 String nombre = rs.getString("nombre");
11 double media = rs.getDouble("media");
12
13 System.out.printf("Alumno %d: %-20s → %.2f%n", num, nombre, media);
14 }
15}
16catch (SQLException e) {
17 System.out.println("Error en consulta: " + e.getMessage());
18}INSERT, UPDATE y DELETE con executeUpdate()
1try (Connection con = DriverManager.getConnection(URL, USER, PASS);
2 Statement stmt = con.createStatement()) {
3
4 // INSERT: insertar un nuevo alumno
5 String sqlInsert = "INSERT INTO Alumnos (num, nombre, media, fnac) " +
6 "VALUES (10, 'Pedro García', 7.5, '2008-03-15')";
7 int filasInsertadas = stmt.executeUpdate(sqlInsert);
8 System.out.println("Insertadas: " + filasInsertadas + " fila(s)");
9
10 // UPDATE: actualizar la nota de un alumno
11 String sqlUpdate = "UPDATE Alumnos SET media = 8.0 WHERE num = 10";
12 int filasActualizadas = stmt.executeUpdate(sqlUpdate);
13 System.out.println("Actualizadas: " + filasActualizadas + " fila(s)");
14
15 // DELETE: eliminar un alumno
16 String sqlDelete = "DELETE FROM Alumnos WHERE num = 10";
17 int filasEliminadas = stmt.executeUpdate(sqlDelete);
18 System.out.println("Eliminadas: " + filasEliminadas + " fila(s)");
19}
20catch (SQLException e) {
21 System.out.println("Error: " + e.getMessage());
22}Problema de seguridad: SQL Injection
// ✗ PELIGROSO: vulnerable a SQL InjectionString sql = "SELECT * FROM Alumnos WHERE nombre = '" + nombreIntroducido + "'";Si el usuario escribe '; DROP TABLE Alumnos; --, ¡se destruiría la tabla! La solución es usar PreparedStatement.PreparedStatement: Sentencias Parametrizadas
PreparedStatement es la forma recomendada de ejecutar sentencias SQL con datos variables. Usa marcadores de posición (?) en lugar de concatenar los valores directamente:
- Seguridad: Previene SQL Injection automáticamente
- Rendimiento: La sentencia se compila una vez y se reutiliza
- Legibilidad: Código más claro y mantenible
1// PreparedStatement: los ? son marcadores de posición
2String sql = "INSERT INTO Alumnos (num, nombre, media, fnac) VALUES (?, ?, ?, ?)";
3
4try (Connection con = DriverManager.getConnection(URL, USER, PASS);
5 PreparedStatement pstmt = con.prepareStatement(sql)) {
6
7 // Asignar valores a los marcadores (posiciones empiezan en 1)
8 pstmt.setInt(1, 11); // num = 11
9 pstmt.setString(2, "Laura Pérez"); // nombre = 'Laura Pérez'
10 pstmt.setDouble(3, 8.5); // media = 8.5
11 pstmt.setDate(4, Date.valueOf("2008-06-20")); // fnac = '2008-06-20'
12
13 int filasInsertadas = pstmt.executeUpdate();
14 System.out.println("Insertadas: " + filasInsertadas);
15}
16catch (SQLException e) {
17 System.out.println("Error: " + e.getMessage());
18}Métodos set para Asignar Valores
| Método | Tipo Java | Tipo SQL |
|---|---|---|
| setInt(pos, valor) | int | INTEGER, INT |
| setString(pos, valor) | String | VARCHAR, CHAR, TEXT |
| setDouble(pos, valor) | double | DOUBLE, FLOAT, DECIMAL |
| setDate(pos, valor) | java.sql.Date | DATE |
| setBoolean(pos, valor) | boolean | BOOLEAN, TINYINT(1) |
| setNull(pos, tipo) | — | NULL |
Reutilizar PreparedStatement con múltiples registros
1// Una ventaja clave: se compila UNA vez y se ejecuta MUCHAS veces
2String sql = "INSERT INTO Productos (nombre, precio, stock) VALUES (?, ?, ?)";
3
4String[][] productos = {
5 {"Laptop", "1299.99", "10"},
6 {"Ratón", "29.99", "50"},
7 {"Teclado", "89.99", "30"},
8 {"Monitor", "449.99", "15"}
9};
10
11try (Connection con = DriverManager.getConnection(URL, USER, PASS);
12 PreparedStatement pstmt = con.prepareStatement(sql)) {
13
14 for (String[] p : productos) {
15 pstmt.setString(1, p[0]);
16 pstmt.setDouble(2, Double.parseDouble(p[1]));
17 pstmt.setInt(3, Integer.parseInt(p[2]));
18 pstmt.executeUpdate(); // Ejecutar para cada producto
19 }
20 System.out.println("Insertados " + productos.length + " productos.");
21}
22catch (SQLException e) {
23 System.out.println("Error: " + e.getMessage());
24}SELECT con PreparedStatement
1// SELECT con parámetro: buscar alumnos por rango de nota
2String sql = "SELECT nombre, media FROM Alumnos WHERE media BETWEEN ? AND ? ORDER BY media DESC";
3
4double notaMin = 5.0;
5double notaMax = 10.0;
6
7try (Connection con = DriverManager.getConnection(URL, USER, PASS);
8 PreparedStatement pstmt = con.prepareStatement(sql)) {
9
10 pstmt.setDouble(1, notaMin);
11 pstmt.setDouble(2, notaMax);
12
13 try (ResultSet rs = pstmt.executeQuery()) {
14 System.out.println("Aprobados:");
15 while (rs.next()) {
16 System.out.printf(" %-20s → %.2f%n",
17 rs.getString("nombre"), rs.getDouble("media"));
18 }
19 }
20}
21catch (SQLException e) {
22 System.out.println("Error: " + e.getMessage());
23}Clase ResultSet: Trabajar con Resultados
Cuando se ejecuta un SELECT, los resultados llegan encapsulados en un objetoResultSet, que representa una tabla con los datos. Funciona de forma similar a un iterador: dispone de un cursor que apunta en cada momento a una única fila activa.
Posición inicial del cursor
ResultSet, el cursor se coloca antes de la primera fila. Hay que llamar a next() al menos una vez para colocarlo en la primera fila y poder leer datos.Patrón Básico de uso del ResultSet
1ResultSet rs = stmt.executeQuery("SELECT * FROM Alumnos");
2
3while (rs.next()) { // Avanza al siguiente y devuelve true/false
4 // Leer datos de la fila activa por nombre de columna
5 int num = rs.getInt("num");
6 String nombre = rs.getString("nombre");
7 double media = rs.getDouble("media");
8 Date fnac = rs.getDate("fnac");
9
10 // O leer por posición (empezando en 1)
11 int num2 = rs.getInt(1);
12 String nombre2 = rs.getString(2);
13
14 System.out.printf("%3d | %-20s | %.2f | %s%n", num, nombre, media, fnac);
15}
16// Al salir: hemos procesado TODAS las filasMétodos get del ResultSet
| Método | Devuelve | Uso |
|---|---|---|
| getString("campo") | String | VARCHAR, CHAR, TEXT |
| getInt("campo") | int | INT, INTEGER, SMALLINT |
| getDouble("campo") | double | DOUBLE, FLOAT, DECIMAL |
| getDate("campo") | java.sql.Date | DATE |
| getBoolean("campo") | boolean | BOOLEAN, TINYINT(1) |
| getLong("campo") | long | BIGINT |
| getObject("campo") | Object | Cualquier tipo |
| wasNull() | boolean | true si el último valor leído fue NULL |
Tipos de ResultSet
Por defecto, el ResultSet es de solo lectura y el cursor solo avanza hacia adelante. Se pueden crear ResultSet con más capacidades pasando parámetros acreateStatement():
1// ResultSet por defecto: solo avanza, solo lectura
2Statement stmt = con.createStatement();
3
4// ResultSet con scroll (puede avanzar y retroceder) + solo lectura
5Statement stmt = con.createStatement(
6 ResultSet.TYPE_SCROLL_INSENSITIVE, // Puede moverse en cualquier dirección
7 ResultSet.CONCUR_READ_ONLY // Solo lectura
8);
9
10// ResultSet con scroll + actualizable
11Statement stmt = con.createStatement(
12 ResultSet.TYPE_SCROLL_SENSITIVE, // Sensible a cambios en la BD
13 ResultSet.CONCUR_UPDATABLE // Se pueden modificar los datos
14);Métodos de Movimiento del Cursor (Scroll)
1Statement stmt = con.createStatement(
2 ResultSet.TYPE_SCROLL_INSENSITIVE,
3 ResultSet.CONCUR_READ_ONLY
4);
5ResultSet rs = stmt.executeQuery("SELECT * FROM Alumnos ORDER BY num");
6
7// Métodos de movimiento
8rs.next(); // Avanza a la siguiente fila → true si existe
9rs.previous(); // Retrocede a la fila anterior → true si existe
10rs.first(); // Va a la primera fila → true si existe
11rs.last(); // Va a la última fila → true si existe
12
13// Movimiento absoluto (filas numeradas desde 1)
14rs.absolute(3); // Va a la fila 3
15rs.absolute(-1); // Va a la última fila (negativo = desde el final)
16
17// Movimiento relativo (desde la posición actual)
18rs.relative(2); // Avanza 2 filas
19rs.relative(-1); // Retrocede 1 fila
20
21// Posicionamiento fuera de los datos
22rs.beforeFirst(); // Antes de la primera fila (posición inicial)
23rs.afterLast(); // Después de la última fila
24
25// Comprobaciones de posición
26rs.isFirst(); // ¿Está en la primera fila?
27rs.isLast(); // ¿Está en la última fila?
28rs.isBeforeFirst(); // ¿Está antes de la primera?
29rs.isAfterLast(); // ¿Está después de la última?
30
31// Ejemplo: mostrar el último alumno
32rs.last();
33System.out.println("Último alumno: " + rs.getString("nombre"));
34
35// Ejemplo: mostrar todos en orden inverso
36rs.afterLast();
37while (rs.previous()) {
38 System.out.println(rs.getString("nombre"));
39}Transacciones
Por defecto, JDBC ejecuta cada sentencia en modo auto-commit: cada operación se confirma automáticamente en la BD. En ocasiones, necesitamos que varias operaciones se ejecuten como una unidad atómica: o todas se ejecutan correctamente, o ninguna. A esto se le llama transacción.
Propiedades ACID de las transacciones
- Atomicidad: todo o nada
- Consistencia: la BD pasa de un estado válido a otro
- Isolamiento: las transacciones no interfieren entre sí
- Durabilidad: los cambios confirmados son permanentes
1// Ejemplo: transferencia bancaria (debe ser atómica)
2String sqlRetirar = "UPDATE Cuentas SET saldo = saldo - ? WHERE id = ?";
3String sqlDepositar = "UPDATE Cuentas SET saldo = saldo + ? WHERE id = ?";
4
5try (Connection con = DriverManager.getConnection(URL, USER, PASS)) {
6
7 // Desactivar auto-commit para gestionar la transacción manualmente
8 con.setAutoCommit(false);
9
10 try (PreparedStatement pstmtRetirar = con.prepareStatement(sqlRetirar);
11 PreparedStatement pstmtDepositar = con.prepareStatement(sqlDepositar)) {
12
13 // Operación 1: Retirar de la cuenta origen
14 pstmtRetirar.setDouble(1, 500.0);
15 pstmtRetirar.setInt(2, 1); // cuenta origen
16 pstmtRetirar.executeUpdate();
17
18 // Operación 2: Depositar en la cuenta destino
19 pstmtDepositar.setDouble(1, 500.0);
20 pstmtDepositar.setInt(2, 2); // cuenta destino
21 pstmtDepositar.executeUpdate();
22
23 // Si todo fue bien → CONFIRMAR la transacción
24 con.commit();
25 System.out.println("Transferencia realizada con éxito.");
26 }
27 catch (SQLException e) {
28 // Si algo falló → REVERTIR todos los cambios
29 con.rollback();
30 System.out.println("Error en la transferencia. Operación revertida.");
31 System.out.println(e.getMessage());
32 }
33 finally {
34 // Restaurar auto-commit
35 con.setAutoCommit(true);
36 }
37}
38catch (SQLException e) {
39 System.out.println("Error de conexión: " + e.getMessage());
40}Patrón DAO: Organizar el Código de BD
El patrón DAO (Data Access Object) es la forma recomendada de organizar el código de acceso a base de datos. Separa la lógica de negocio del acceso a datos, haciendo el código más limpio, reutilizable y mantenible.
- Clase Modelo (POJO): Representa un registro de la tabla
- Clase DAO: Contiene todos los métodos de acceso a BD (CRUD: Create, Read, Update, Delete)
- Main / Lógica: Usa el DAO sin conocer los detalles de la BD
1// Clase Modelo: representa un Alumno (POJO - Plain Old Java Object)
2public class Alumno {
3 private int num;
4 private String nombre;
5 private double media;
6 private java.sql.Date fnac;
7
8 // Constructor vacío
9 public Alumno() {}
10
11 // Constructor completo
12 public Alumno(int num, String nombre, double media, java.sql.Date fnac) {
13 this.num = num;
14 this.nombre = nombre;
15 this.media = media;
16 this.fnac = fnac;
17 }
18
19 // Getters y setters
20 public int getNum() { return num; }
21 public String getNombre() { return nombre; }
22 public double getMedia() { return media; }
23 public java.sql.Date getFnac() { return fnac; }
24
25 public void setNum(int num) { this.num = num; }
26 public void setNombre(String n) { this.nombre = n; }
27 public void setMedia(double m) { this.media = m; }
28 public void setFnac(java.sql.Date f) { this.fnac = f; }
29
30 @Override
31 public String toString() {
32 return String.format("Alumno[%d | %-20s | %.2f | %s]",
33 num, nombre, media, fnac);
34 }
35}1import java.sql.*;
2import java.util.ArrayList;
3import java.util.List;
4
5// Clase DAO: gestiona TODAS las operaciones de BD sobre Alumnos
6public class AlumnoDAO {
7
8 private static final String URL = "jdbc:mysql://localhost/Instituto";
9 private static final String USER = "Rodrigo";
10 private static final String PASS = "12345";
11
12 // CREATE - Insertar un alumno
13 public boolean insertar(Alumno a) {
14 String sql = "INSERT INTO Alumnos (num, nombre, media, fnac) VALUES (?, ?, ?, ?)";
15 try (Connection con = DriverManager.getConnection(URL, USER, PASS);
16 PreparedStatement pstmt = con.prepareStatement(sql)) {
17
18 pstmt.setInt(1, a.getNum());
19 pstmt.setString(2, a.getNombre());
20 pstmt.setDouble(3, a.getMedia());
21 pstmt.setDate(4, a.getFnac());
22
23 return pstmt.executeUpdate() > 0;
24 }
25 catch (SQLException e) {
26 System.out.println("Error al insertar: " + e.getMessage());
27 return false;
28 }
29 }
30
31 // READ - Obtener todos los alumnos
32 public List<Alumno> obtenerTodos() {
33 List<Alumno> lista = new ArrayList<>();
34 String sql = "SELECT * FROM Alumnos ORDER BY nombre";
35
36 try (Connection con = DriverManager.getConnection(URL, USER, PASS);
37 Statement stmt = con.createStatement();
38 ResultSet rs = stmt.executeQuery(sql)) {
39
40 while (rs.next()) {
41 Alumno a = new Alumno(
42 rs.getInt("num"),
43 rs.getString("nombre"),
44 rs.getDouble("media"),
45 rs.getDate("fnac")
46 );
47 lista.add(a);
48 }
49 }
50 catch (SQLException e) {
51 System.out.println("Error al leer: " + e.getMessage());
52 }
53 return lista;
54 }
55
56 // READ - Obtener un alumno por su número
57 public Alumno obtenerPorNum(int num) {
58 String sql = "SELECT * FROM Alumnos WHERE num = ?";
59 try (Connection con = DriverManager.getConnection(URL, USER, PASS);
60 PreparedStatement pstmt = con.prepareStatement(sql)) {
61
62 pstmt.setInt(1, num);
63 try (ResultSet rs = pstmt.executeQuery()) {
64 if (rs.next()) {
65 return new Alumno(
66 rs.getInt("num"),
67 rs.getString("nombre"),
68 rs.getDouble("media"),
69 rs.getDate("fnac")
70 );
71 }
72 }
73 }
74 catch (SQLException e) {
75 System.out.println("Error al buscar: " + e.getMessage());
76 }
77 return null; // No encontrado
78 }
79
80 // READ - Buscar alumnos por rango de nota
81 public List<Alumno> obtenerPorRangoMedia(double min, double max) {
82 List<Alumno> lista = new ArrayList<>();
83 String sql = "SELECT * FROM Alumnos WHERE media BETWEEN ? AND ? ORDER BY media DESC";
84
85 try (Connection con = DriverManager.getConnection(URL, USER, PASS);
86 PreparedStatement pstmt = con.prepareStatement(sql)) {
87
88 pstmt.setDouble(1, min);
89 pstmt.setDouble(2, max);
90
91 try (ResultSet rs = pstmt.executeQuery()) {
92 while (rs.next()) {
93 lista.add(new Alumno(
94 rs.getInt("num"),
95 rs.getString("nombre"),
96 rs.getDouble("media"),
97 rs.getDate("fnac")
98 ));
99 }
100 }
101 }
102 catch (SQLException e) {
103 System.out.println("Error al buscar: " + e.getMessage());
104 }
105 return lista;
106 }
107
108 // UPDATE - Actualizar la media de un alumno
109 public boolean actualizarMedia(int num, double nuevaMedia) {
110 String sql = "UPDATE Alumnos SET media = ? WHERE num = ?";
111 try (Connection con = DriverManager.getConnection(URL, USER, PASS);
112 PreparedStatement pstmt = con.prepareStatement(sql)) {
113
114 pstmt.setDouble(1, nuevaMedia);
115 pstmt.setInt(2, num);
116
117 return pstmt.executeUpdate() > 0;
118 }
119 catch (SQLException e) {
120 System.out.println("Error al actualizar: " + e.getMessage());
121 return false;
122 }
123 }
124
125 // DELETE - Eliminar un alumno
126 public boolean eliminar(int num) {
127 String sql = "DELETE FROM Alumnos WHERE num = ?";
128 try (Connection con = DriverManager.getConnection(URL, USER, PASS);
129 PreparedStatement pstmt = con.prepareStatement(sql)) {
130
131 pstmt.setInt(1, num);
132 return pstmt.executeUpdate() > 0;
133 }
134 catch (SQLException e) {
135 System.out.println("Error al eliminar: " + e.getMessage());
136 return false;
137 }
138 }
139
140 // Estadísticas
141 public double calcularMediaGeneral() {
142 String sql = "SELECT AVG(media) FROM Alumnos";
143 try (Connection con = DriverManager.getConnection(URL, USER, PASS);
144 Statement stmt = con.createStatement();
145 ResultSet rs = stmt.executeQuery(sql)) {
146
147 if (rs.next()) return rs.getDouble(1);
148 }
149 catch (SQLException e) {
150 System.out.println("Error: " + e.getMessage());
151 }
152 return 0.0;
153 }
154
155 public int contarAlumnos() {
156 String sql = "SELECT COUNT(*) FROM Alumnos";
157 try (Connection con = DriverManager.getConnection(URL, USER, PASS);
158 Statement stmt = con.createStatement();
159 ResultSet rs = stmt.executeQuery(sql)) {
160
161 if (rs.next()) return rs.getInt(1);
162 }
163 catch (SQLException e) {
164 System.out.println("Error: " + e.getMessage());
165 }
166 return 0;
167 }
168}1// Main: usa el DAO sin conocer los detalles de la BD
2public class Main {
3 public static void main(String[] args) {
4 AlumnoDAO dao = new AlumnoDAO();
5
6 // CREATE: insertar nuevos alumnos
7 dao.insertar(new Alumno(1, "Ana García", 8.5, Date.valueOf("2008-03-15")));
8 dao.insertar(new Alumno(2, "Juan López", 4.2, Date.valueOf("2009-07-20")));
9 dao.insertar(new Alumno(3, "María Pérez", 9.1, Date.valueOf("2008-11-05")));
10
11 // READ: mostrar todos
12 System.out.println("=== TODOS LOS ALUMNOS ===");
13 for (Alumno a : dao.obtenerTodos()) {
14 System.out.println(a);
15 }
16
17 // READ: buscar uno específico
18 Alumno encontrado = dao.obtenerPorNum(2);
19 System.out.println("
20Buscado: " + encontrado);
21
22 // READ: alumnos aprobados
23 System.out.println("
24=== APROBADOS (>= 5) ===");
25 for (Alumno a : dao.obtenerPorRangoMedia(5.0, 10.0)) {
26 System.out.println(a);
27 }
28
29 // UPDATE: actualizar nota
30 boolean ok = dao.actualizarMedia(2, 5.5);
31 System.out.println("
32Actualizado: " + ok);
33
34 // Estadísticas
35 System.out.printf("
36Total alumnos: %d%n", dao.contarAlumnos());
37 System.out.printf("Media general: %.2f%n", dao.calcularMediaGeneral());
38
39 // DELETE
40 dao.eliminar(3);
41 System.out.println("Alumno eliminado. Total ahora: " + dao.contarAlumnos());
42 }
43}Repaso de SQL con JDBC
JDBC ejecuta sentencias SQL estándar. Repasamos las más usadas con ejemplos completos:
CREATE TABLE
1String sql = """
2 CREATE TABLE IF NOT EXISTS Alumnos (
3 num INT PRIMARY KEY AUTO_INCREMENT,
4 nombre VARCHAR(100) NOT NULL,
5 media DOUBLE DEFAULT 0.0,
6 fnac DATE
7 )
8 """;
9
10try (Connection con = DriverManager.getConnection(URL, USER, PASS);
11 Statement stmt = con.createStatement()) {
12 stmt.execute(sql);
13 System.out.println("Tabla creada correctamente.");
14}
15catch (SQLException e) {
16 System.out.println("Error: " + e.getMessage());
17}SELECT con JOIN
1// Tablas: Alumnos(num, nombre) y Notas(alumno_num, asignatura, nota)
2String sql = """
3 SELECT a.nombre, n.asignatura, n.nota
4 FROM Alumnos a
5 INNER JOIN Notas n ON a.num = n.alumno_num
6 WHERE n.nota >= ?
7 ORDER BY a.nombre, n.asignatura
8 """;
9
10try (Connection con = DriverManager.getConnection(URL, USER, PASS);
11 PreparedStatement pstmt = con.prepareStatement(sql)) {
12
13 pstmt.setDouble(1, 5.0); // Solo aprobados
14
15 try (ResultSet rs = pstmt.executeQuery()) {
16 String alumnoActual = "";
17 while (rs.next()) {
18 String alumno = rs.getString("nombre");
19 String asignatura = rs.getString("asignatura");
20 double nota = rs.getDouble("nota");
21
22 if (!alumno.equals(alumnoActual)) {
23 System.out.println("
24" + alumno + ":");
25 alumnoActual = alumno;
26 }
27 System.out.printf(" %-20s → %.1f%n", asignatura, nota);
28 }
29 }
30}
31catch (SQLException e) {
32 System.out.println("Error: " + e.getMessage());
33}Funciones de Agregación
1String sql = """
2 SELECT
3 COUNT(*) AS total,
4 AVG(media) AS media_general,
5 MAX(media) AS nota_max,
6 MIN(media) AS nota_min,
7 SUM(media) AS suma
8 FROM Alumnos
9 """;
10
11try (Connection con = DriverManager.getConnection(URL, USER, PASS);
12 Statement stmt = con.createStatement();
13 ResultSet rs = stmt.executeQuery(sql)) {
14
15 if (rs.next()) {
16 System.out.println("Total alumnos: " + rs.getInt("total"));
17 System.out.printf("Media general: %.2f%n", rs.getDouble("media_general"));
18 System.out.printf("Nota máxima: %.2f%n", rs.getDouble("nota_max"));
19 System.out.printf("Nota mínima: %.2f%n", rs.getDouble("nota_min"));
20 }
21}
22catch (SQLException e) {
23 System.out.println("Error: " + e.getMessage());
24}Obtener ID Generado (AUTO_INCREMENT)
1String sql = "INSERT INTO Alumnos (nombre, media) VALUES (?, ?)";
2
3try (Connection con = DriverManager.getConnection(URL, USER, PASS);
4 PreparedStatement pstmt = con.prepareStatement(sql,
5 Statement.RETURN_GENERATED_KEYS)) { // Solicitar la clave generada
6
7 pstmt.setString(1, "Nuevo Alumno");
8 pstmt.setDouble(2, 7.0);
9 pstmt.executeUpdate();
10
11 // Obtener el ID generado automáticamente
12 try (ResultSet keys = pstmt.getGeneratedKeys()) {
13 if (keys.next()) {
14 int idGenerado = keys.getInt(1);
15 System.out.println("ID del nuevo alumno: " + idGenerado);
16 }
17 }
18}
19catch (SQLException e) {
20 System.out.println("Error: " + e.getMessage());
21}Ejercicios Prácticos
Primera Conexión y Consulta Básica
Crea un programa que se conecte a una BD MySQL llamada "Instituto" y trabaje con la tabla Alumnos: Tabla Alumnos: num (PK), nombre (VARCHAR), media (DOUBLE), fnac (DATE) El programa debe: 1. Crear la tabla si no existe (executeUpdate con CREATE TABLE IF NOT EXISTS) 2. Insertar 4 alumnos con PreparedStatement 3. Consultar todos los alumnos ordenados por media descendente 4. Mostrarlos en formato tabla formateada 5. Mostrar cuántos alumnos hay con media >= 5 (consulta con WHERE) 6. Cerrar correctamente con try-with-resources Captura y muestra el mensaje de cualquier SQLException
CRUD Completo con PreparedStatement
Implementa un CRUD completo para gestionar una tabla Productos en BD: Tabla: Productos (id INT AUTO_INCREMENT PK, nombre VARCHAR, precio DOUBLE, stock INT, categoria VARCHAR) Menú interactivo con Scanner: 1. Añadir producto (INSERT con PreparedStatement) 2. Ver todos (SELECT ordenado por categoria y nombre) 3. Buscar por nombre (SELECT con LIKE ?) 4. Actualizar precio (UPDATE con PreparedStatement) 5. Reponer stock (UPDATE stock = stock + ?) 6. Eliminar producto (DELETE) 7. Estadísticas (total, media precio, producto más caro, stock total) 0. Salir Usa PreparedStatement en todas las operaciones con parámetros. Captura SQLException en cada método.
DAO con Relación entre Tablas
Implementa un sistema con dos tablas relacionadas: Tabla Departamentos: id (PK AUTO_INCREMENT), nombre VARCHAR, presupuesto DOUBLE Tabla Empleados: id (PK AUTO_INCREMENT), nombre VARCHAR, salario DOUBLE, dept_id INT (FK → Departamentos) Clase DepartamentoDAO: - insertar(dept), obtenerTodos(), obtenerPorId(id), actualizar(dept), eliminar(id) Clase EmpleadoDAO: - insertar(emp), obtenerTodos(), obtenerPorDepartamento(deptId) - obtenerEmpleadosConDepartamento(): hace JOIN y devuelve lista de Strings formateados - calcularSalarioMedioPorDepartamento(): usa GROUP BY, devuelve Map<String, Double> - eliminar(id) En Main: crear 3 departamentos, insertar 6 empleados (al menos 2 por departamento), mostrar empleados con su departamento (JOIN), mostrar el salario medio de cada depto.
Transacción: Sistema de Pedidos
Implementa un sistema de pedidos con transacciones: Tablas: - Productos (id, nombre, precio, stock) - Pedidos (id, fecha, total) - DetallePedido (id, pedido_id FK, producto_id FK, cantidad, precio_unitario) Clase PedidoService con el método realizarPedido(): - Recibe una lista de pares (producto_id, cantidad) - Dentro de UNA transacción: 1. Verifica que hay stock suficiente para TODOS los productos 2. Si alguno no tiene stock → rollback y lanzar excepción 3. Descuenta el stock de cada producto 4. Crea un registro en Pedidos con el total calculado 5. Crea los registros en DetallePedido 6. Si todo fue bien → commit En Main: intentar 2 pedidos, uno con stock suficiente y otro sin él. Usar transacciones correctamente con setAutoCommit y rollback.
ResultSet con Scroll y Metadatos
Crea un programa que explore la tabla Alumnos usando ResultSet con scroll y metadatos: 1. Crear un Statement con TYPE_SCROLL_INSENSITIVE y CONCUR_READ_ONLY 2. Ejecutar SELECT * FROM Alumnos 3. Obtener y mostrar los METADATOS de la consulta: - Número de columnas - Nombre y tipo de cada columna 4. Usar rs.last() para ir al último alumno y mostrar su nombre 5. Usar rs.first() para ir al primero y mostrarlo 6. Usar rs.absolute(2) para ir al segundo registro 7. Mostrar si el cursor está en la primera o última fila con isFirst(), isLast() 8. Recorrer en orden inverso con rs.previous() mostrando todos 9. Contar cuántos hay con rs.last() + getRow()