Transacciones con base de datos

De ChuWiki

He metido todos los artículos de esta wiki y de http://chuidiang.org relativos a Java y JDBC en un pdf Libro JDBC Java. A través del enlace puedes comprarlo si tienes interés.

Para realizar una inserción, consulta, modificación o borrado de datos en la base de datos, necesitamos primero de todo pedirle a la conexión un Statement o cualquiera de sus derivados. Esto se consigue con los métodos createStatement(), prepareStatement() o prepareCall(). Vamos a ver brevemente para qué se usa cada una de ellas, sus ventajas e inconvenientes. Luego pasaremos al detalle.

createStatement()[editar]

Con createStatement() obtenemos una Statement que nos permite ejecutar sin más sentencias SQL pasándolas como un String. Es un método sencillo, pero que tiene una pega. En caso de que los datos que se quieren insertar en la base de datos hayan sido introducidos por el usuario, se deben revisar primero antes de construir el String. Imagina por ejemplo que le hemos pedido al usuario su nombre y construimos el String sql de esta manera

String sql = "insert into usuario (nombre) values ('" + nombreIntroducido + "')";

Puede suceder que al usuario le de por poner una comilla simple en su nombre (se llama O'hara, por ejemplo). Esta comilla, introducida ahí, dará lugar a la siguiente SQL

String sql = "insert into usuario (nombre) values ('O'hara')

Que nos dará un error porque pensará que el nombre acaba después de la O, en la segunda comilla, y el resto deja de tener sentido.

Otra segunda pega es que si tenemos, por ejemplo, una fecha en un Date de java, tendremos que convertirla con nuestro código a un String en un formato que le guste a la sentencia SQL y a nuestra base de datos. Un date.toString() normalmente no vale. Por ejemplo, para MySQL, estos formatos son válidos

mysql> INSERT INTO nombre_de_tabla (idate) VALUES (19970505);
mysql> INSERT INTO nombre_de_tabla (idate) VALUES ('19970505');
mysql> INSERT INTO nombre_de_tabla (idate) VALUES ('97-05-05');
mysql> INSERT INTO nombre_de_tabla (idate) VALUES ('1997.05.05');
mysql> INSERT INTO nombre_de_tabla (idate) VALUES ('1997 05 05');
mysql> INSERT INTO nombre_de_tabla (idate) VALUES ('0000-00-00');

Desgraciadamente, un toString() de un Date de java devuelve por defecto algo como esto

Thu May 20 15:30:27 CEST 2010

que no vale ni de casualidad.

prepareStatement()[editar]

En base de datos, una PreparedStatement es una sentencia SQL precompilada, de forma que si vamos a usarla muchas veces con distintos datos, la ejecución es más rápida que si usamos una SQL normal.

Desde java tenemos posibilidad de crear y usar estas PreparedStatement. Tienen dos ventajas sobre el createStatement() que hemos mencionado en el punto anterior.

La primera ventaja ya la hemos mencionado, son más rápidas en ejecución. Debemos asegurarnos que tanto nuestra base de datos como el conector que usemos las soportan. Si no es así, podemos hacer el código java igualmente, pero no obtendremos mejora en eficiencia.

La segunda ventaja es que no debemos preocuparnos de revisar los datos que introduzca el usuario, como en el caso de las Statement, cuando al usuario le daba por llamarse O'hara, con una comilla simple. Tampoco nos tenemos que preocupar de formatear adecuadamente los Date u otros tipos de datos. A una PreparedStatement le pasamos directamente las clases java que representan nuestros datos (String, Number, Integer, Date, etc) y ella solita se encarga de ponerlo todo correctamente.

Un pequeño ejemplo de cómo se usa PreparedStatement

PreparedStatement psInsertar = conexion.prepareStatement(
   "insert into person values (null,?,?,?)");
psInsertar.setInt(1, 23); // La edad, el primer interrogante, es un entero. 
psInsertar.setString(2, "Pedro"); // El String nombre es el segundo interrogante
psInsertar.setString(3, "Perez"); // Y el tercer interrogante, un String apellido.
psInsertar.exequteUpdate(); // Se ejecuta la inserción.

Básicamente ponemos la SQL dejando interrogantes en lo que serán parámetros. Hay que fijarse que no ponemos comillas simples ni nada. Luego, son los métodos setInt(), setString() vamos poniendo los valores de los parámetros, indicando el número de interrogante (el primero, el segundo, etc) y el dato.

callableStatement()[editar]

Este hereda de PrepearedStatement, por lo que tiene todo lo que tiene PreparedStatement. Sin embargo, se especializa en llamdas a un procedimiento o función previamente almacenado en base de datos. Admite una sintaxis propia, no SQL, que traduce a la llamada SQL específica de cada base de datos. De esta forma, desde Java, siempre usaremos la misma sintaxis. Si tenemos una función una_funcion() definida en la base de datos que admite un parámetro NUMBER y devuelve un NUMBER, podríamos llamarla así

// Se prepara el Statement
CallableStatement st = conexion.prepareCall( "{?=call una_funcion(?)}");
// Se indica que el primer interrogante es de salida.

st.registerOutParameter(1,Types.NUMERIC);

// Se pasa un parámetro en el segundo interrogante.
st.setInteger(2,parametro);

// Se hace la llamada a la función.
st.execute( );

// Se recoge el resultado del primer interrogante.
resultado = st.getInt( 1 );

Si te fijas, hemos puesto entre llaves el String que pasamos en la llamada a prepareCall(). Estas llaves indican que lo que hay dentro es sintaxis propia de JDBC y no de SQL. Si no ponemos las llaves, se podría poner la SQL específica de la base de datos para llamadas a procedimientos o funciones.

Veremos en los siguientes apartado estas tres opciones con más detalle.