6. [Cours]: Introducción a API JDBC
Palabras clave: bases de datos relacionales, API JDBC, SQLException.
6.1. Support
![]() | ![]() | ![]() |
La carpeta [support / chap-06] contiene los proyectos de Eclipse de este capítulo.
6.2. Architecture
![]() |
La capa JDBC (Java DataBase Connectivity) es una interfaz de acceso universal a bases de datos. Siempre presenta la misma interfaz hacia la capa [DAO]. Si se cambia a SGBD, basta con cambiar el controlador JDBC. La capa [DAO] no cambia.
6.3. Pasos para el funcionamiento de una base de datos
![]() |
En la arquitectura anterior, la ejecución de una base de datos mediante el programa de consola comprende los siguientes pasos:
- carga del controlador JDBC de la base de datos;
- apertura de una conexión con la base de datos;
- ejecución de una orden SQL en la base de datos y procesamiento de los resultados de la orden SQL;
- cierre de la conexión;
El paso 1 solo se realiza una vez. Los pasos 2 a 4 se repiten. Cabe destacar que no se deja ninguna conexión abierta. Se cierra en cuanto ya no se necesita.
6.3.1. paso 1: carga en memoria del controlador JDBC
El código
// carga del controlador JDBC
try {
Class.forName(nom de la classe du pilote JDBC);
} catch (ClassNotFoundException e1) {
// gestionar la excepción
}
La operación de la línea 3 tiene como objetivo cargar en memoria el controlador JDBC de la base de datos. Esta operación solo es necesario realizarla una vez. Sin embargo, repetirla no provoca ningún error. La clase del controlador JDBC se busca en la ruta de clases (Classpath) del proyecto. Por lo tanto, es necesario que, en el proyecto de Eclipse, el archivo [jar], que contiene la clase del controlador JDBC, se haya incluido en la ruta de clases del proyecto.
6.3.2. Paso 2: apertura de una conexión
Una vez instalado el controlador JDBC, se le pide que abra una conexión con el BD:
El código
package spring.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class IntroJdbc01 {
...
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// apertura de la conexión
connexion = DriverManager.getConnection(url, user, passwd);
...
} catch (SQLException e1) {
// se gestiona la excepción
...
} finally {
// Cerrar la conexión
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e2) {
// gestionar la excepción
...
}
}
}
- líneas 3-7: las clases de implementación de la interfaz JDBC se encuentran todas en el paquete [java.sql]. Además, en caso de error, todas lanzan una excepción de tipo [SQLException] (líneas 19 y 27). Esta excepción deriva de la clase [Exception] y es lo que se denomina una excepción controlada: es obligatorio utilizar un bloque try/catch para gestionarla o, como alternativa, no gestionarla e indicar que el método permite que se produzca la excepción completando la firma del método con [throws SQLException];
- línea 17, [DriverManager.getConnection] es un método estático que espera tres parámetros:
- [url]: el URL de la base de datos. Es una cadena de caracteres que depende del BD utilizado. Para MySQL, tiene el formato [jdbc:mysql://localhost:3306/nom_de_la_bd];
- [user]: el propietario de la conexión;
- [passwd]: su contraseña;
- líneas 24-30: la conexión debe cerrarse en la cláusula [finally] para que se cierre independientemente de que se produzca una excepción o no.
6.3.3. Paso 3: emisión de órdenes SQL y [SELECT]
Una vez obtenida la conexión, se pueden emitir órdenes SQL. La forma de gestionar las órdenes de lectura [SELECT] difiere de la utilizada para las operaciones de actualización [UPDATE, INSERT, DELETE]. Comenzamos con las órdenes SQL y [SELECT]:
El código
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// inicio de sesión
connexion = DriverManager.getConnection(url, user, passwd);
// inicio de transacción
connexion.setAutoCommit(false);
// en modo de solo lectura
connexion.setReadOnly(true);
// se lee la tabla [PRODUITS]
ps = connexion.prepareStatement("SELECT ID, NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS");
rs = ps.executeQuery();
System.out.println("Liste des produits : ");
while (rs.next()) {
System.out.println(new Produit(rs.getInt(1), rs.getString(2), rs.getInt(3), rs.getDouble(4), rs.getString(5)));
}
// confirmación de la transacción
connexion.commit();
} catch (SQLException e1) {
// se gestiona la excepción
doCatchException(connexion,e1);
} finally {
// se procesa el «finally»
doFinally(rs, ps, connexion);
}
private void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
....
}
- líneas 8 y 10: apertura de una transacción (línea 8) en modo de solo lectura (línea 10). Una transacción es una secuencia de órdenes SQL que o bien se ejecutan todas con éxito o bien fallan todas. Así, en una transacción que contenga N órdenes SQL, si la orden I+1 falla, las I anteriores se anularán. Para una operación de lectura, no es necesaria una transacción. No obstante, crear una transacción de solo lectura puede permitir que algunos SGBD realicen ciertas optimizaciones;
- línea 12: uso de un [PreparedStatement]. Un [PreparedStatement] suele tener parámetros indicados con el carácter «?». En este caso, no los tiene. Un [PreparedStatement] es una orden preparada por el SGBD. Esta preparación tiene un coste y solo se realiza una vez. A continuación, esta orden preparada es ejecutada por el SGBD con diferentes parámetros efectivos que sustituirán a los parámetros formales «?». Cabe señalar que es preferible nombrar las columnas deseadas en lugar de utilizar el símbolo * para obtener todas las columnas. Al especificar el nombre de las columnas, se pueden obtener sus valores a partir de su posición en la consulta SELECT;
- línea 13: ejecución de [PreparedStatement]. Se recupera un objeto de tipo [ResultSet];
Un objeto de tipo [ResultSet] representa una tabla, es decir, un conjunto de filas y columnas. En un momento dado, solo se tiene acceso a una fila de la tabla, denominada fila actual. Durante la creación inicial del [ResultSet], no hay ninguna fila actual. Es necesario realizar una operación [ResultSet.next()] para obtenerla. La firma del método next es la siguiente:
Este método intenta pasar a la línea siguiente del [ResultSet] y devuelve true si tiene éxito, y false en caso contrario. Si tiene éxito, la línea siguiente se convierte en la nueva línea actual. La línea anterior se pierde y no se podrá volver atrás para recuperarla.
La tabla [ResultSet] tiene columnas denominadas labelCol1, labelCol2, ... especificadas en la consulta [SELECT] ejecutada. Con la consulta:
SELECT ID as myId, NOM as myNom, CATEGORIE as myCategorie, PRIX as myPrix, DESCRIPTION as myDescription FROM PRODUITS
- la columna [ID] se transferirá a una columna de [ResultSet] denominada [myId];
- la columna [NOM] pasará a una columna de la tabla [ResultSet] denominada [myNom];
- ...
En el ejemplo anterior, los identificadores [myCol] se denominan etiquetas de columna. A falta de estas etiquetas, los nombres de las columnas de [ResultSet] dependen de SGBD. Cuando el [SELECT] opera sobre una única tabla, las etiquetas de las columnas serán, por defecto, los nombres de las columnas solicitadas por el SELECT. El problema surge cuando el [SELECT] opera sobre varias tablas y en estas hay nombres de columnas idénticos, como en el siguiente ejemplo:
SELECT PRODUITS.NOM, CATEGORIES.NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID
suponiendo que la tabla [PRODUITS] tenga una clave externa hacia la tabla [CATEGORIES], simbolizada por la relación [Produits].CATEGORIE_ID --> [CATEGORIES].ID, y que las tablas [PRODUITS] y [CATEGORIES] tengan ambas un campo [NOM]. En este caso, los nombres asignados en la tabla [ResultSet] a las columnas [PRODUITS.NOM] y [CATEGORIES.NOM] dependen de la tabla SGBD. Por lo tanto, para garantizar la portabilidad con el archivo SGBD, es necesario utilizar aquí etiquetas de columna, por lo que se escribirá:
SELECT PRODUITS.NOM as p_NOM, CATEGORIES.NOM as c_NOM FROM PRODUITS, CATEGORIES WHERE PRODUITS.CATEGORIE_ID=CATEGORIES.ID
Para explotar los distintos campos de la línea actual de [ResultSet], disponemos de los siguientes métodos:
para obtener la columna denominada «labelColi» de la línea actual y, por tanto, la columna del [SELECT] que tiene esa etiqueta. Type designa el tipo del campo coli. Se pueden utilizar los siguientes métodos [getType]: getInt, getLong, getString, getDouble, getFloat, getDate, ... En lugar de utilizar el nombre de la columna, se puede utilizar su posición en la consulta [SELECT] ejecutada:
donde i es el índice de la columna deseada (i>=1).
- líneas 15-17: recuperación de los valores leídos en la consulta BD;
- línea 19: la transacción se valida (también se dice que se «confirma»). Esto la finaliza y libera los recursos que la transacción SGBD había movilizado para ella;
- línea 25: se liberan los recursos en la transacción [finally]. Esta llama al siguiente método [doFinally]:
private void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
// cierre de ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// cierre [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
if (connexion != null) {
try {
// cerrar la conexión
connexion.close();
} catch (SQLException e3) {
// gestionar la excepción
}
}
}
- líneas 3-9: cierre del [ResultSet];
- líneas 11-17: cierre de [PreparedStatement];
- líneas 18-27: cierre de la conexión;
Los cierres de las líneas 3-17 parecen redundantes, ya que la conexión se cierra en las líneas 18-25. De hecho, en algunos casos no lo son y se recomienda dejarlos ([http://stackoverflow.com/questions/4507440/must-jdbc-resultsets-and-statements-be-closed-separately-although-the-connection]).
- línea 22: la excepción se gestiona mediante el siguiente método [doCatchException]:
private static void doCatchException(Connection connexion, Throwable th) {
// anulación de la transacción
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// gestionar la excepción
}
}
- líneas 4-6: se cancela la transacción. Esto la da por terminada y el SGBD podrá liberar los recursos asignados a ella;
6.3.4. paso 3: emisión de órdenes SQL y [INSERT, UPDATE, DELETE]
Las órdenes SQL y [INSERT, UPDATE, DELETE] son operaciones de actualización: modifican la base de datos, pero no devuelven ninguna línea. La única información que se proporciona es el número de líneas afectadas por la operación de actualización.
El código
Connection connexion = null;
PreparedStatement ps = null;
try {
// abrir conexión
connexion = DriverManager.getConnection(url, user, passwd);
// inicio de la transacción
connexion.setAutoCommit(false);
// en modo lectura/escritura
connexion.setReadOnly(false);
// se actualiza la tabla
ps = connexion.prepareStatement("UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?");
// categoría 1
ps.setInt(1, 10);
// ejecución
int nbLignes=ps.executeUpdate();
// confirmación de la transacción
connexion.commit();
} catch (SQLException e1) {
// se gestiona la excepción
doCatchException(connexion, e1);
} finally {
// se procesa el «finally»
doFinally(null, ps, connexion);
}
}
- línea 9: la conexión se utiliza para lectura y escritura;
- línea 11: un [PreparedStatement] con un parámetro (simbolizado por ?). Puede haber varios parámetros. Se numeran a partir del 1;
- línea 13: se asigna su valor al único parámetro. El primer parámetro de [setType] es la posición del parámetro en el [PreparedStatement] (1, 2, ...) y el segundo, el valor que se le asigna. Se pueden utilizar los métodos [setInt, setLong, setFloat, setDouble, setString, setDate, ...];
- línea 15: se utiliza el método [executeUpdate] y no el [executeQuery], reservado para las órdenes SELECT. El método devuelve el número de líneas afectadas por la operación. Puede ser 0.
- línea 17: se valida la transacción;
6.3.5. paso 4: cierre de la conexión
En un entorno multiusuario, una conexión debe cerrarse lo antes posible, ya que un SGBD admite un número limitado de conexiones abiertas. En los ejemplos anteriores, se cerraba en la cláusula [finally] de las operaciones SQL, de modo que se cerrara independientemente de si se había producido una excepción o no.
6.4. Un proyecto de ejemplo
6.4.1. Soporte
![]() |
La carpeta [support / chap5] contiene los proyectos de Eclipse de este capítulo [1, 2]. La carpeta [database] contiene el script SQL que permite crear la base de datos de ejemplo de este capítulo, MySQL.
6.4.2. La base de datos utilizada
Los ejemplos siguientes utilizan la siguiente base de datos MySQL:
![]() |
- [ID]: clave primaria en modo AUTO_INCREMENT (si no se especifica una clave primaria, SGBD la genera);
- [NOM]: nombre de un producto —único—;
- [CATEGORIE]: número de su categoría;
- [PRIX]: su precio;
- [DESCRIPTION]: una descripción del producto;
La crearemos con la herramienta [WampServer] de la siguiente manera: [1-9]:
![]() |
![]() |
![]() |
![]() |
6.4.3. El proyecto Eclipse
![]() |
El proyecto es un proyecto Maven definido por el siguiente archivo [pom.xml]:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.jdbc</groupId>
<artifactId>intro-jdbc-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.1</version>
</dependency>
</dependencies>
</project>
- líneas 8-12: el controlador JDBC de SGBD MySQL5;
- líneas 13-17: una biblioteca capaz de gestionar jSON (Javascript Object Notation) (véase el apartado 22.6). La utilizaremos para mostrar los productos de la base de datos en formato jSON;
6.4.4. La clase de productos
La clase [Produit] es la siguiente:
package istia.st.jdbc;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Produit {
// campos
private int id;
private String nom;
private int categorie;
private double prix;
private String description;
// constructores
public Produit() {
}
public Produit(int id, String nom, int categorie, double prix, String description) {
this.id = id;
this.nom = nom;
this.categorie = categorie;
this.prix = prix;
this.description = description;
}
// getter y setter
...
// a String
public String toString() {
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
}
- línea 34: se utiliza la biblioteca jSON para mostrar la cadena jSON del producto. El resultado es una visualización similar a la siguiente:
Liste des produits :
{"id":1,"nom":"NOM1","categorie":1,"prix":100.0,"description":"DESC1"}
{"id":2,"nom":"NOM2","categorie":1,"prix":101.0,"description":"DESC2"}
La ventaja del método [toString] anterior es que, si se añaden o se eliminan campos de la clase, su método [toString] sigue siendo válido. Por otra parte, si los campos son a su vez objetos (listas, matrices, diccionarios, objetos de usuario), las bibliotecas jSON saben transformarlos a su vez en cadenas jSON;
6.4.5. La clase [Static]
La clase [Static] agrupa en métodos el código que se utiliza con frecuencia en la clase principal:
package istia.st.jdbc;
import java.util.ArrayList;
import java.util.List;
public class Static {
public static List<String> getErreursFromThrowable(Throwable th) {
// se recupera la lista de mensajes de error de la excepción
List<String> erreurs = new ArrayList<String>();
while (th != null) {
// mensaje de error del throwable
erreurs.add(th.getMessage());
// se pasa a la causa del objeto lanzable
th = th.getCause();
}
// resultado
return erreurs;
}
public static void show(String title, List<String> messages){
// título
System.out.println(String.format("%s : ",title));
// mensajes
for(String message : messages){
System.out.println(String.format("- %s",message));
}
}
}
- líneas 8-19: permite obtener la lista de errores encapsulados en un objeto de tipo [Throwable], que es la clase padre de la clase [Exception];
- líneas 21-28: muestra en pantalla una lista de mensajes;
Este código podría estar en la clase principal, ya que aquí es la única que lo utiliza. Nos encontramos aquí ante un caso más amplio en el que otras clases necesitarían este código.
6.4.6. El esqueleto de la clase principal
package istia.st.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class IntroJdbc01 {
// constantes
final static String url = "jdbc:mysql://localhost:3306/dbIntroJdbc";
final static String user = "root";
final static String passwd = "";
final static String insert = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (?, ?, ?, ?, ?)";
final static String delete = "DELETE FROM PRODUITS";
final static String select = "SELECT ID, NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS";
final static String update = "UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?";
final static String insert2 = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (100,'X',1,1,'x')";
public static void main(String[] args) {
// carga del controlador JDBC de MySQL
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e1) {
doCatchException("Pilote JDBC introuvable", null, e1);
return;
}
// se vacía la tabla [PRODUITS]
delete();
// se rellena
insert();
// se lee
select();
// actualización
update();
// visualización
select();
// inserción de dos elementos idénticos
// la inserción debe fallar y ninguno de los dos elementos se inserta debido a la transacción
insert2();
// se comprueba
select();
// fin
System.out.println("Travail terminé");
}
// lista de productos
private static void select() {
...
}
// eliminación de productos
public static void delete() {
...
}
// Añadir productos
public static void insert() {
...
}
// Añadir 2 productos
public static void insert2() {
...
}
// actualización de algunos productos
public static void update() {
..
}
private static void doFinally(ResultSet rs, PreparedStatement ps, Connection connexion) {
// cierre ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// cierre [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
// cerrar la sesión
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e3) {
// se muestran los mensajes de error
Static.show("Les erreurs suivantes se sont produites lors de la fermeture de la connexion",
Static.getErreursFromThrowable(e3));
}
}
}
private static void doCatchException(String title, Connection connexion, Throwable th) {
// se muestran los mensajes de error
Static.show(title, Static.getErreursFromThrowable(th));
// anulación de la transacción
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// se muestran los mensajes de error
Static.show(title, Static.getErreursFromThrowable(e2));
}
}
}
6.4.7. Eliminación del contenido de la tabla de productos
El método [delete] elimina el contenido de la tabla:
// eliminación de productos
public static void delete() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// Inicio de sesión
connexion = DriverManager.getConnection(url, user, passwd);
// Inicio de la transacción
connexion.setAutoCommit(false);
// se vacía la tabla [PRODUITS]
ps = connexion.prepareStatement(delete);
ps.executeUpdate();
// confirmación de la transacción
connexion.commit();
// vuelta al modo predeterminado
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// se gestiona la excepción
doCatchException("Les erreurs suivantes se sont produites à la suppression du contenu de la table", connexion, e1);
} finally {
// se procesa el «finally»
doFinally(null, null, connexion);
}
}
Este ejemplo utiliza transacciones. Una transacción permite agrupar órdenes SQL que deben completarse todas con éxito o cancelarse todas. Hay cuatro operaciones que hay que conocer:
- inicio de una transacción: [connexion.setAutoCommit(false)];
- Fin de una transacción con éxito: [connexion.commit()]. En este caso, se validan todas las operaciones realizadas en BD durante la transacción;
- fin de una transacción fallida: [connexion.rollback()]. En este caso, se anulan todas las operaciones realizadas en BD durante la transacción;
- vuelta al modo [auto-commit], que es el modo por defecto de API JDBC: [connexion.setAutoCommit(true)]. En este modo, cada orden SQL es objeto de una transacción. Así, si se realizan dos inserciones y la segunda falla:
- en el modo [AutoCommit=true], la primera inserción se mantiene (ha sido validada por el primer AutoCommit);
- en el modo [AutoCommit=false], la primera inserción se cancela;
En nuestros ejemplos, cada vez que se produce una excepción, anulamos la transacción en el método [doCatchException]:
private static void doCatchException(String title, Connection connexion, Throwable th) {
// se muestran los mensajes de error
Static.show(title, Static.getErreursFromThrowable(th));
// anulación de la transacción
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// se muestran los mensajes de error
Static.show("Erreur lors de l'annulation de la transaction", Static.getErreursFromThrowable(e2));
}
}
6.4.8. Creación del contenido de la tabla de productos
El método [insert] crea el contenido de la tabla:
// Añadir productos
public static void insert() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// Inicio de sesión
connexion = DriverManager.getConnection(url, user, passwd);
// Inicio de la transacción
connexion.setAutoCommit(false);
// se rellena la tabla
ps = connexion.prepareStatement(insert);
for (int i = 0; i < 10; i++) {
// preparación
int n = i + 1;
ps.setInt(1, n);
ps.setString(2, String.format("NOM%s", n));
ps.setInt(3, n / 5 + 1);
ps.setDouble(4, 100 * (1 + (double) i / 100));
ps.setString(5, String.format("DESC%s", n));
// ejecución
ps.executeUpdate();
}
// confirmación de la transacción
connexion.commit();
// vuelta al modo predeterminado
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// se gestiona la excepción
doCatchException("Les erreurs suivantes se sont produites à la création du contenu de la table", connexion, e1);
} finally {
// se procesa el «finally»
doFinally(null, null, connexion);
}
}
6.4.9. Visualización del contenido de la tabla de productos
El método [select] muestra el contenido de la tabla:
// lista de productos
private static void select() {
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// apertura de conexión
connexion = DriverManager.getConnection(url, user, passwd);
// Inicio de la transacción
connexion.setAutoCommit(false);
// se lee la tabla [PRODUITS]
ps = connexion.prepareStatement(select);
rs = ps.executeQuery();
System.out.println("Liste des produits : ");
while (rs.next()) {
System.out.println(new Produit(rs.getInt(1), rs.getString(2), rs.getInt(3), rs.getDouble(4), rs.getString(5)));
}
// confirmación de la transacción
connexion.commit();
// vuelta al modo predeterminado
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// se gestiona la excepción
doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
} finally {
// se procesa el «finally»
doFinally(null, null, connexion);
}
}
6.4.10. Actualización del contenido de la tabla
El método [update] actualiza determinados productos:
// actualización de algunos productos
public static void update() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// apertura de la conexión
connexion = DriverManager.getConnection(url, user, passwd);
// inicio de la transacción
connexion.setAutoCommit(false);
// se actualiza la tabla
ps = connexion.prepareStatement(update);
// categoría 1
ps.setInt(1, 1);
// ejecución
ps.executeUpdate();
// confirmación de la transacción
connexion.commit();
// vuelta al modo predeterminado
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// se gestiona la excepción
doCatchException("Les erreurs suivantes se sont produites à la mise à jour du contenu de la table", connexion, e1);
} finally {
// se procesa el «finally»
doFinally(null, null, connexion);
}
}
6.4.11. Función de la transacción
El método [insert2] inserta dos productos con la misma clave primaria en la tabla, lo cual no es posible. Al tratarse de una transacción, la primera inserción se anulará.
// se añaden 2 productos con las mismas claves primarias
public static void insert2() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// apertura de conexión
connexion = DriverManager.getConnection(url, user, passwd);
// Inicio de la transacción
connexion.setAutoCommit(false);
// se añade una línea
ps = connexion.prepareStatement(insert2);
// ejecución
ps.executeUpdate();
// se añade la misma línea por segunda vez, por lo que tiene la misma clave primaria
// la inserción debe fallar y ninguno de los dos elementos debe insertarse debido a la transacción
ps.executeUpdate();
// confirmar transacción
connexion.commit();
// vuelta al modo predeterminado
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// se gestiona la excepción
doCatchException("Les erreurs suivantes se sont produites lors de l'ajout", connexion, e1);
} finally {
// se procesa el «finally»
doFinally(null, null, connexion);
}
}
6.4.12. Resultados
La ejecución del método [main] arroja los siguientes resultados:
6.5. Uso de una fuente de datos de tipo [DataSource]
Vamos a retomar la aplicación anterior utilizando una fuente de datos de tipo [javax.sql.DataSource]:

Vamos a utilizar una fuente de datos implementada por la clase [org.apache.tomcat.jdbc.pool.DataSource]. Esta clase utiliza un grupo de conexiones, es decir, un conjunto de conexiones abiertas:
- cuando se instancia el grupo, se abre un número determinado de conexiones con la base de datos. Este número es configurable;
- cuando el código Java abre una conexión, esta la proporciona el grupo;
- cuando el código Java cierra una conexión, esta se devuelve al grupo;
En definitiva, las conexiones solo se abren una vez, lo que mejora el rendimiento del acceso a la base de datos. La fuente de datos se definirá en una clase de configuración de Spring
6.5.1. El proyecto Eclipse
![]() |
El proyecto es un proyecto Maven definido por el siguiente archivo [pom.xml]:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.jdbc</groupId>
<artifactId>intro-jdbc-02</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<!-- biblioteca jSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.1</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
<!-- Tomcat JDBC -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
<version>8.0.20</version>
</dependency>
</dependencies>
</project>
- líneas 21-25: dependencia de Spring;
- líneas 27-31: dependencia de la biblioteca que proporciona la fuente de datos;
La clase de configuración de Spring [AppConfig] es la siguiente:
package istia.st.jdbc;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
// constantes
final static String URL = "jdbc:mysql://localhost:3306/dbIntroJdbc";
final static String USER = "root";
final static String PASSWD = "";
final static String INSERT = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (?, ?, ?, ?, ?)";
final static String DELETE = "DELETE FROM PRODUITS";
final static String SELECT = "SELECT ID, NOM, CATEGORIE, PRIX, DESCRIPTION FROM PRODUITS";
final static String UPDATE = "UPDATE PRODUITS SET PRIX=PRIX*1.1 WHERE CATEGORIE=?";
final static String INSERT2 = "INSERT INTO PRODUITS(ID, NOM, CATEGORIE, PRIX, DESCRIPTION) VALUES (100,'X',1,1,'x')";
final static String DRIVER_CLASSNAME = "com.mysql.jdbc.Driver";
@Bean
public DataSource dataSource() {
// fuente de datos TomcatJdbc
DataSource dataSource = new DataSource();
// configuración de acceso JDBC
dataSource.setDriverClassName(DRIVER_CLASSNAME);
dataSource.setUsername(USER);
dataSource.setPassword(PASSWD);
dataSource.setUrl(URL);
// una conexión abierta inicialmente
dataSource.setInitialSize(1);
// resultado
return dataSource;
}
}
- líneas 11-19: las constantes definidas anteriormente en [IntroJdbc01] se han trasladado a [AppConfig];
- líneas 31-34: el bean de Spring que define la fuente de datos;
- línea 24: creación de la fuente de datos, aún sin configurar;
- líneas 26-29: la información que permite a la fuente de datos conectarse a la base de datos;
- línea 31: crea un grupo de 1 conexión. Aquí no se necesitan más. Nunca hay varias conexiones simultáneas;
6.5.2. La clase principal
La clase principal [IntroJdbc02] es la siguiente:
package istia.st.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IntroJdbc02 {
// fuente de datos
private static DataSource dataSource;
public static void main(String[] args) {
// recuperación del contexto de Spring
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
// recuperación de la fuente de datos
dataSource = ctx.getBean(DataSource.class);
// se vacía la tabla [PRODUITS]
delete();
...
// fin
ctx.close();
System.out.println("Travail terminé");
}
// lista de productos
private static void select() {
Connection connexion = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// Inicio de sesión
connexion = dataSource.getConnection();
// Inicio de la transacción
connexion.setAutoCommit(false);
...
} catch (SQLException e1) {
// se gestiona la excepción
doCatchException("Les erreurs suivantes se sont produites à la lecture de la table", connexion, e1);
} finally {
// se procesa el «finally»
doFinally(null, null, connexion);
}
}
...
}
- línea 14: la fuente de datos. Cabe destacar que es de tipo [javax.sql.DataSource], que es una interfaz;
- línea 18: instanciación de los objetos Spring;
- línea 20: obtención de una referencia a la fuente de datos. Cabe señalar que en ningún momento se menciona la clase que se utiliza realmente. Así pues, aquí nada permite suponer que se utilice una implementación [TomcatJdbc];
- línea 36: obtención de una conexión abierta;
- el resto del código es idéntico al de la clase [IntroJdbc01];
6.6. Conclusion
Encontrará más información sobre la gestión de bases de datos en el documento [Exploiter une base relationnelle avec l'écosystème Spring].












