Apuntes DAM
Tema 13 de 14PROG · 1º DAM/DAW

Programación

Conexión a bases de datos

Aprende a conectar aplicaciones Java a bases de datos usando JDBC, incluyendo manejo de conexiones, sentencias SQL y transacciones.

120 min lecturaAvanzadoRevisado el

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ísticaFicherosBase de Datos
BúsquedaManual, lentaSQL optimizado, rápida
Relaciones entre datosComplicadoNativo (claves foráneas)
Acceso concurrenteProblemáticoGestionado por el SGBD
IntegridadResponsabilidad del programadorGarantizada por el SGBD
Volumen de datosLimitadoMillones de registros

¿Qué es JDBC?

JDBC (Java Database Connectivity) es la API estándar de Java para conectar aplicaciones Java con cualquier base de datos relacional. Se encuentra en el paquete java.sql y permite ejecutar sentencias SQL desde Java de forma independiente del SGBD.
Java Database Connectivity | JDBC — Telusko


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 / InterfazPaqueteFunción
DriverManagerjava.sqlGestiona los drivers y crea conexiones
Connectionjava.sqlRepresenta la conexión activa con la BD
Statementjava.sqlEjecuta sentencias SQL simples
PreparedStatementjava.sqlEjecuta sentencias SQL parametrizadas (más seguro)
ResultSetjava.sqlTabla con los resultados de una consulta SELECT
SQLExceptionjava.sqlExcepción producida por errores de BD
imports-jdbc.java
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:

arquitectura-jdbc.java
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:

SGBDDriver (JAR)URL de conexión
MySQLmysql-connector-java-x.x.xx.jarjdbc:mysql://servidor/basedatos
PostgreSQLpostgresql-x.x.jarjdbc:postgresql://servidor/basedatos
Oracleojdbc11.jarjdbc:oracle:thin:@servidor:1521:sid
SQLitesqlite-jdbc-x.x.jarjdbc:sqlite:ruta/archivo.db

Añadir el driver en NetBeans

Para añadir el driver en NetBeans: clic derecho sobre Libraries en el proyecto → Add JAR/Folder → seleccionar el fichero .jar descargado de la web del fabricante. Para Maven/Gradle, añadir la dependencia en pom.xml o build.gradle.
pom.xml (Maven)
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.

conexion-basica.java
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)

url-conexion.java
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:

conexion-try-resources.java
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

El método getConnection() puede lanzar:
  • SQLException: credenciales incorrectas, BD no existe, error general
  • SQLTimeoutException: tiempo de espera excedido (servidor no responde)
Siempre hay que capturar al menos SQLException.
Como Conectar Java con MySQL | JDBC MySQL Connector NetBeans 12 — Ing. René Domínguez


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étodoDevuelvePara
executeQuery(sql)ResultSetSELECT (consultas que devuelven datos)
executeUpdate(sql)int (filas afectadas)INSERT, UPDATE, DELETE (modifican datos)
execute(sql)booleanCualquier sentencia SQL (uso general)

Consulta SELECT con executeQuery()

select-basico.java
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()

insert-update-delete.java
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

Nunca construyas sentencias SQL concatenando directamente datos del usuario:// ✗ 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
prepared-statement.java
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étodoTipo JavaTipo SQL
setInt(pos, valor)intINTEGER, INT
setString(pos, valor)StringVARCHAR, CHAR, TEXT
setDouble(pos, valor)doubleDOUBLE, FLOAT, DECIMAL
setDate(pos, valor)java.sql.DateDATE
setBoolean(pos, valor)booleanBOOLEAN, TINYINT(1)
setNull(pos, tipo)—NULL

Reutilizar PreparedStatement con múltiples registros

prepared-reutilizar.java
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

prepared-select.java
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

Al crear un 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

resultset-patron.java
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 filas

Métodos get del ResultSet

MétodoDevuelveUso
getString("campo")StringVARCHAR, CHAR, TEXT
getInt("campo")intINT, INTEGER, SMALLINT
getDouble("campo")doubleDOUBLE, FLOAT, DECIMAL
getDate("campo")java.sql.DateDATE
getBoolean("campo")booleanBOOLEAN, TINYINT(1)
getLong("campo")longBIGINT
getObject("campo")ObjectCualquier tipo
wasNull()booleantrue 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():

resultset-tipos.java
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)

resultset-scroll.java
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}
6. Cómo usar Statement y ResultSet – Curso de JDBC — makigas


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

Las transacciones garantizan las propiedades ACID:
  • 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
transacciones.java
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
modelo-alumno.java
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}
alumno-dao.java
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}
main-dao.java
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}
Patrón de Acceso a Datos, DAO, JDBC parte 5 de conexiones a bases de datos con Java — JESUS HERNANDEZ CABRERA


Repaso de SQL con JDBC

JDBC ejecuta sentencias SQL estándar. Repasamos las más usadas con ejemplos completos:

CREATE TABLE

crear-tabla.java
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

select-join.java
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

agregacion.java
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)

get-generated-key.java
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

Ejemplo Guiado
Fácil

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

Ejercicio Práctico
Medio

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.

Ejercicio Práctico
Difícil

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.

Ejercicio Práctico
Difícil

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.

Ejemplo Guiado
Medio

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()

¿Has encontrado un error, algo desactualizado o una explicación que no se entiende? Avísanos y lo corregimos.

¿Has terminado este tema?

Crea una cuenta gratis para guardar qué temas has terminado, subir de nivel y ganar medallas.

Guardar mi progreso