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 de SGBD, basta con cambiar el controlador JDBC. La capa [DAO] no cambia.
6.3. Los pasos para operar una base de datos
![]() |
En la arquitectura anterior, la operació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;
- emisió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 una conexión abierta. Se cierra tan pronto como 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) {
// procesar 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 genera ningún error. La clase del controlador JDBC se busca en el Classpath del proyecto. Por lo tanto, en el proyecto de Eclipse, el archivo [jar], que contiene la clase del controlador JDBC, debe haber sido incluido en el Classpath del proyecto.
6.3.2. Paso 2: apertura de una conexión
Una vez instalado el controlador JDBC, se le solicita 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 {
// abrir conexión
connexion = DriverManager.getConnection(url, user, passwd);
...
} catch (SQLException e1) {
// se procesa la excepción
...
} finally {
// cerrar la conexión
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e2) {
// procesar 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 conoce como una excepción controlada: es obligatorio utilizar un try/catch para manejarla o, como alternativa, no manejarla e indicar que el método permite que la excepción se propague 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 si hay 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 procesa 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, 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 cancelarán. Para una operación de lectura, no es necesaria una transacción. Sin embargo, crear una transacción de solo lectura puede permitir que ciertos SGBD realicen ciertas optimizaciones;
- línea 12: uso de un [PreparedStatement]. Un [PreparedStatement] normalmente tiene parámetros indicados por el carácter ?. Aquí no los tiene. Un [PreparedStatement] es una orden preparada por el SGBD. Esta preparación tiene un costo y solo se realiza una vez. Posteriormente, esta orden preparada es ejecutada por el SGBD con diferentes parámetros efectivos que reemplazarán a los parámetros formales «?». Cabe señalar que es preferible nombrar las columnas deseadas en lugar de usar 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, llamada fila actual. Al crear inicialmente el [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 siguiente línea del [ResultSet] y devuelve true si tiene éxito, false en caso contrario. Si tiene éxito, la siguiente línea se convierte en la nueva línea actual. La línea anterior se pierde y no se podrá retroceder para recuperarla.
La tabla de [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] llamada [myId];
- la columna [NOM] se transferirá a una columna de la tabla [ResultSet] llamada [myNom];
- ...
En el ejemplo anterior, los identificadores [myCol] se denominan etiquetas de columna. Si no se utilizan estas etiquetas, los nombres de las columnas de [ResultSet] dependerán de SGBD. Cuando el [SELECT] opera sobre una sola 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 se encuentran 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], representada 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. Para la portabilidad entre SGBD, es necesario utilizar aquí etiquetas de columnas, 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 aprovechar los distintos campos de la línea actual de [ResultSet], se dispone de los siguientes métodos:
para obtener la columna denominada «labelColi» de la línea actual y, por lo 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 utilizado 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) {
// procesar 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 como están ([http://stackoverflow.com/questions/4507440/must-jdbc-resultsets-and-statements-be-closed-separately-although-the-connection]).
- línea 22: la excepción se maneja mediante el siguiente método [doCatchException]:
private static void doCatchException(Connection connexion, Throwable th) {
// anular transacción
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
// procesar la excepción
}
}
- líneas 4-6: se cancela la transacción. Esto la da por terminada y SGBD podrá liberar los recursos utilizados para 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 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 procesa 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 1 parámetro (representado 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 es 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], que está 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
Una conexión debe cerrarse lo más rápido posible en un contexto multiusuario, 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 para que se cerrara independientemente de si hubo 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 siguientes ejemplos utilizan la siguiente base de datos MySQL:
![]() |
- [ID]: clave primaria en modo AUTO_INCREMENT (si no se proporciona 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 manejar jSON (Notación de Objetos de JavaScript) (véase el párrafo 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;
}
// getters y setters
...
// 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. Esto da como resultado 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 agregan o eliminan campos de la clase, su método [toString] sigue siendo válido. Además, 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 throwable
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 porque aquí es la única que lo utiliza. Nos encontramos aquí en 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 llena
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 verifica
select();
// fin
System.out.println("Travail terminé");
}
// lista de productos
private static void select() {
...
}
// eliminación de productos
public static void delete() {
...
}
// agregar productos
public static void insert() {
...
}
// agregar 2 productos
public static void insert2() {
...
}
// actualización de ciertos 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 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));
// cancelació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();
// regreso al modo predeterminado
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// se procesa 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 debe conocer:
- inicio de una transacción: [connexion.setAutoCommit(false)];
- Finalización exitosa de una transacción: [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, todas las operaciones realizadas en BD durante la transacción se cancelan;
- regreso al modo [auto-commit], que es el modo predeterminado 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 (fue 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, cancelamos 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:
// Agregar productos
public static void insert() {
Connection connexion = null;
PreparedStatement ps = null;
try {
// Inicio de sesión
connexion = DriverManager.getConnection(url, user, passwd);
// Inicio de transacción
connexion.setAutoCommit(false);
// se llena 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();
// regreso al modo predeterminado
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// se procesa 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();
// regreso al modo predeterminado
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// se procesa 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 ciertos productos:
// actualización de ciertos productos
public static void update() {
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 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();
// regreso al modo predeterminado
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// se procesa 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. Como estamos en una transacción, la primera inserción se anulará.
// se agrega n 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 agrega 1 fila
ps = connexion.prepareStatement(insert2);
// ejecución
ps.executeUpdate();
// se agrega la misma fila por segunda vez, es decir, con 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();
// regreso al modo predeterminado
connexion.setAutoCommit(true);
} catch (SQLException e1) {
// se procesa 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 es proporcionada por el grupo;
- cuando el código Java cierra una conexión, esta se devuelve al grupo;
Al final, 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 migrado 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, que aún no está configurada;
- líneas 26-29: la información que permite que la fuente de datos se conecte a la base de datos;
- línea 31: crea un grupo de 1 conexión. Aquí no se necesita 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 transacción
connexion.setAutoCommit(false);
...
} catch (SQLException e1) {
// se procesa 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 señalar que es de tipo [javax.sql.DataSource], que es una interfaz;
- línea 18: instanciación de los objetos de 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 realmente se utiliza. Por lo tanto, aquí nada sugiere que se esté utilizando 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
Se puede encontrar más información sobre la administración de bases de datos en el documento [Exploiter une base relationnelle avec l'écosystème Spring].












