Transacciones
En párrafos anteriores se ha tratado de la creación y uso de sentencias SQL, que siempre se obtenían llamando a un método de un objeto de tipo Connection, como createStatement() o prepareStatement(). El uso de transacciones, también se controla mediante métodos del objeto Connection. Como ya se ha dicho, Connection representa una conexión a una Base de datos dada, luego representa el lugar adecuado para el manejo de transacciones, dado que estas afectan a todas las sentencias ejecutadas sobre una conexión a la base de datos.
Por defecto, una conexión funciona en modo autocommit, es decir, cada vez que se ejecuta una sentencia SQL se abre y se cierra automáticamente una transacción, que sólo afecta a dicha sentencia. Es posible modificar esta opción mediante setAutoCommit(), mientras que getAutoCommit() indica si se está en modo autocommit o no. Si no se está trabajando en modo autocommit será necesario que se cierren explícitamente las transacciones mediante commit() si tienen éxito, o rollback(), si fallan; nótese que, tras cerrar una transacción, la próxima vez que se ejecute una sentencia SQL se abrirá automáticamente una nueva, por lo que no existe ningún método del tipo que permita iniciar una transacción.
Es posible también especificar el nivel de aislamiento de una transacción, mediante setTransactionIsolation(), así como averiguar cuál es el nivel de aislamiento de la actual mediante getTransactionIsolation(). Los niveles de aislamiento se representan mediante las constantes que se muestran en la lista siguiente, en la cual se explica muy básicamente el efecto de cada nivel de aislamiento.
TRANSACTION_NONE
No se pueden utilizar transacciones.
TRANSACTION_READ_UNCOMMITTED
Desde esta transacción se pueden llegar a ver registros que han sido modificados por otra transacción, pero no guardados, por lo que podemos llegar a trabajar con valores que nunca llegan a guardarse realmente.
TRANSACTION_READ_COMMITTED
Se ven solo las modificaciones ya guardadas hechas por otras transacciones.
TRANSACTION_REPEATABLE_READ
Si se leyó un registro, y otra transacción lo modifica, guardándolo, y lo volvemos a leer, seguiremos viendo la información que había cuando lo leímos por primera vez. Esto proporciona un nivel de consistencia mayor que los niveles de aislamiento anteriores.
TRANSACTION_SERIALIZABLE
Se verán todos los registros tal y como estaban antes de comenzar la transacción, no importa las modificaciones que otras transacciones hagan, ni que lo hayamos leído antes o no. Si se añadió algún nuevo registro, tampoco se verá.
Además de manejar transacciones, el objeto Connection también proporciona algunos otros métodos que permiten especificar características de una conexión a una base de datos; por ejemplo, los métodos isReadOnly() y setReadOnly() permiten averiguar si una conexión a una base de datos es de sólo lectura, o hacerla de sólo lectura. El método isClosed() permite averiguar si una conexión está cerrada o no, y nativeSQL() permite obtener la cadena SQL que el driver mandaría a la base de datos si se tratase de ejecutar la cadena SQL especificada, permitiendo averiguar qué es exactamente lo que se le envía a la base de datos.
Información de la Base de Datos
Falta aún una pieza importante a la hora de trabajar con la conexión a la base de datos mediante Connection, y es la posibilidad de poder interrogar sobre las características de una base de datos; por ejemplo, puede se interesante saber si la base de datos soporta cierto nivel de aislamiento en una transacción, como la TRANSACTION_SERIALIZABLE, que muchos gestores no soportan. Para esto está otro de los interfaces que proporciona JDBC, DatabaseMetaData, al que es posible interrogar sobre las características de la base de datos con la que se está trabajando. Es posible obtener un objeto de tipo DatabaseMetaData mediante el método getMetaData() de Connection.
DatabaseMetaData proporciona diversa información sobre una base de datos, y cuenta con varias docenas de métodos, a través de los cuales es posible obtener gran cantidad de información acerca de una tabla; por ejemplo, getColumns() devuelve las columnas de una tabla, getPrimaryKeys() devuelve la lista de columnas que forman la clave primaria, getIndexInfo() devuelve información acerca de sus índices, mientras que getExportedKeys() devuelve la lista de todas las claves ajenas que utilizan la clave primaria de esta tabla, y getImportedKeys() las claves ajenas existentes en la tabla. El método getTables() devuelve la lista de todas las tablas en la base de datos, mientras que getProcedures() devuelve la lista de procedimientos almacenados. Muchos de los métodos de DatabaseMetaData devuelven un objeto de tipo ResultSet que contiene la información deseada. El listado que se presenta a continuación, muestra el código necesario para obtener todas las tablas de una base de datos.
String nombreTablas = "%"; // Listamos todas las tablas
String tipos[] = new String[1]; // Listamos sólo tablas
tipos[0] = "TABLE";
DatabaseMetaData dbmd = conexion.getMetaData();
ResultSet tablas = dbmd.getTables( null,null,nombreTablas,tipos );
boolean seguir = tablas.next();
while( seguir ) {
// Mostramos sólo el nombre de las tablas, guardado
// en la columna "TABLE_NAME"
System.out.println(
tablas.getString( tablas.findColumn( "TABLE_NAME" ) ) );
seguir = tablas.next();
};
Hay todo un grupo de métodos que permiten averiguar si ciertas características están soportadas por la base de datos; entre ellos, destacan supportsGroupBy() indica si se soporta el uso de GROUP BY en un SELECT, mientras que supportsOuterJoins() indica si se pueden llevar a cabo outer-joins. El método supportsTransactions(), comentado antes, indica si cierto tipo de transacciones está soportado o no. Otros métodos de utilidad son getUserName(), que devuelve el nombre del usuario actual; getURL(), que devuelve el URL de la base de datos actual.
DatabaseMetaData proporciona muchos otros métodos que permiten averiguar cosas tales como el máximo número de columnas utilizable en un SELECT, etc. En general, casi cualquier pregunta sobre las capacidades de la base de datos se puede contestar llamando a los distintos métodos del objeto DatabaseMetaData, que merece la pena que el lector consulte cuando no sepa si cierta característica está soportada.
Tipos SQL en Java
Muchos de los tipos de datos estándar de SQL ’92, no tienen un equivalente nativo en Java. Para superar esta deficiencia, se deben mapear los tipos de datos SQL en Java, utilizando las clases JDBC para acceder a los tipos de datos SQL. Es necesario saber cómo recuperar adecuadamente tipos de datos Java; como int, long, o string, a partir de sus contrapartidas SQL almacenadas en base de datos. Esto puede ser especialmente importante si se está trabajando con datos numéricos, que necesiten control decimal con precisión, o con fechas SQL, que tienen un formato muy bien definido.
El mapeo de los tipos de datos Java a SQL es realmente sencillo, tal como se muestra en la tabla que acompaña a este párrafo. Observe el lector que los tipos que comienzan por "java" no son tipos básicos, sino clases que tienen métodos para trasladar los datos a formatos utilizables, y son necesarias estas clases porque no hay un tipo de datos básico que mapee directamente su contrapartida SQL. La creación de estas clases debe hacerse siempre que se necesite almacenar un tipo de dato SQL en un programa Java, para poder utilizar directamente el dato desde la base de datos.
Java |
SQL |
String |
VARCHAR |
boolean |
BIT |
byte |
TINYINT |
short |
SMALLINT |
int |
INTEGER |
long |
BIGINT |
float |
REAL |
double |
DOUBLE |
byte[]-byte array: imagenes, sonidos... |
VARBINARY (BLOBs) |
java.sql.Date |
DATE |
java.sql.Time |
TIME |
java.sql.Timestamp |
TIMESTAMP |
java.math.BigDecimal |
NUMERIC |
El tipo de dato byte[], es un array de bytes de tamaño variable. Esta estructura de datos guarda datos binarios, que en SQL son VARBINARY y LONG-VARBINARY. Estos tipos se utilizan para almacenar imágenes, ficheros de documentos, y cosas parecidas. Para almacenar y recuperar este tipo de información de la base de datos, se deben utilizarlos métodos para streams que proporciona JDBC: setBinaryStream() y getBinaryStream().
La conversión de tipos en el sentido contrario puede no estar tan clara, ya que hay tipos SQL cuya tipo Java correspondiente puede no ser evidente, como VARBINARY, o DECIMAL, etc. La tabla siguiente muestra los tipos Java correspondientes a cada tipo SQL.
SQL |
Java |
CHAR |
String |
VARCHAR |
String |
LONGVARCHAR |
String |
NUMERIC |
java.math.BigDecimal |
DECIMAL |
java.math.BigDecimal |
BIT |
boolean |
TINYINT |
byte |
SMALLINT |
short |
INTEGER |
int |
BIGINT |
long |
REAL |
float |
FLOAT |
double |
DOUBLE |
double |
BINARY |
byte[] |
VARBINARY |
byte[] |
LONGVARBINARY |
byte[] |
DATE |
java.sql.Date |
TIME |
java.sql.Time |
TIMESTAMP |
java.sql.Timestamp |
Existe una constante para cada tipo de dato SQL, declarada en java.sql.Types; por ejemplo, el tipo al tipo TIMESTAMP le corresponde la constante java.sql.Types.TIMESTAMP.
Además, JDBC proporciona clases Java nuevas para representar varios tipos de datos SQL: estas son java.sql.Date, java.sql.Time y java.sql.Timestamp.
|