4.2. Datos#
Hasta ahora no nos hemos detenido en las particularidades de los tipos de datos, ya que los que muestran nuestro ejemplo son bastante simples. Sin embargo, la correspondencia entre tipos de datos de Java y de SQL no es exacta, por lo que JDBC implementa los métodos setXXXX de PreparedStatement para guardar datos en la base de datos y los métodos getXXXX de ResultSet para recuperarlos.
En principio (aunque hay excepciones), podemos usar estos métodos y abstraernos de cómo el SGBD lo traduce a un tipo SQL.
4.2.1. Datos simples#
Por datos simples entendemos los que representan caracteres, enteros, números reales (flotantes o de precisión fija) o booleanos y tienen, por lo general, un equivalente propio de Java.
Tipo SQL |
Tipo Java[1] |
Método JDBC de PreparedStatement |
---|---|---|
CHAR(n)
VARCHAR(n)
|
setString(int idx, String v) |
|
SMALLINT
INTEGER
BIGINT
|
setShort(int idx, short v)
setInt(int idx, int v)
setLong(int idx, long v)
|
|
BOOLEAN |
setBoolean(int idx, boolean v)
|
|
NUMERIC/DECIMAL[2] |
setBigDecimal(int idx, BigDecimal)
|
|
FLOAT, REAL[3]
FLOAT, DOUBLE PRECISION
|
setFloat(int idx, float v)
setDouble(int idx, double v)
|
Nota
No hay más que sustituir set por get para obtener los métodos de PreparedStatement necesarios para guardar datos.
Ejemplos de cómo obtener o escribir datos de este tipo ya se han dejado escritos en los ejemplos de apartados anteriores. Sólo BigDecimal no es un tipo primitivo en Java, pero su uso es trivial:
BigDecimal comaFija = new BigDecimal("13.456");
Aclaración
Dado que hemos escogido SQLite para desarrollar la unidad, nos conviene aclarar sus particularidades. Su característica fundamental en lo referente a tipos es que tiene tipado dinámico y cuál sea el tipo que declaremos al crear la tabla es irrelevante, porque el sistema gestor aceptará el dato con independencia de su tipo. Por ejemplo:
CREATE TABLE Persona (
nombre VARCHAR(255);
);
INSERT INTO Persona VALUES
("Manolo"), // Consecuente con la definición: no da problemas.
(4356); // Inconsecuente, pero da igual: el dato se almacena como entero.
De hecho, SQLite ni siquiera atiende a qué palabra usamos para definir el tipo y creará la tabla, incluso aunque nos inventemos el nombre del tipo:
CREATE TABLE Persona (
nombre TIPOINVENTADO // No da error.
);
Internamente, SQLite sólo dispone de datos de tipo texto, entero (de
diverso tamaño), doble y BLOB; y dependiendo del valor que se suministre usará
un tipo u otro para el dato. Así, CHAR
y VARCHAR
se asimilan a texto,
INTEGER
, BIGINT
, SMALLINT
y BOOLEAN
a enteros, FLOAT
,
DOBLE
y NUMERIC
/DECIMAL
a dobles (por tanto, se perderá la precisión
de este último tipo).
4.2.2. Datos complejos#
Los datos complejos se caracterizan porque el paquete java.sql
tiene
definidos tipos específicos que se corresponden con los definidos en el estándar
SQL.
Tipo SQL |
Tipo Java |
Método JDBC de PreparedStatement |
---|---|---|
DATE
TIME
TIMESTAMP
|
setDate(int idx, java.sql.Date v)
setTime(int idx, java.sql.Time v)
setTimestamp(int idx, java.sql.Timestamp v)
|
|
BLOB |
setBlob(int idx, java.sql.Blob v)
setBinaryStream(int idx, InputStream v)
|
|
CLOB |
setClob(int idx, java.sql.Clob v)
setCharacterStream(int idx, Reader v)
|
|
JSON |
setString(int idx, String v) |
|
ARRAY
STRUCT
|
setArray(int idx, java.sql.Array v)
setStruct(int idx, java.sql.Struct v)
|
4.2.2.1. Fechas y tiempos#
El estándar SQL define cinco tipos de datos para la expresión de tiempos:
DATE
que sirve para definir fechas (p.e. '2014-01-08').TIME
que sirve para definir horas con precisión de segundos (p.e. '08:30:21'), aunque también podría incluirse precisión de hasta el microsegundo, añadiendo decimales al segundo.TIMESTAMP
que combina en un mismo tipo fecha y hora (p.e. '2014-01-08 08:30:21').TIMESTAMP WITH TIME ZONE
que permite almacenar, además, el huso horario (p.e. '2014-01-08 08:30:21+01:00').INTERVAL
para almacenar periodos de tiempo (p.e. 'INTERVAL 2 DAYS' o 'INTERVAL 2 DAYS 10 HOURS').
A través de JDBC sólo se soportan directamente los tres primeros tipos y, además, se requiere saber cómo convertir entre java.sql.Date, java.sql.Time, java.sql.Timestamp y los tipos con los que frecuentemente se trabaja en Java:
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
java.util.Date udate = new java.util.Date(); // Almacena fecha y hora.
// Date --> Date (SQL)
Date date = new Date(udate.getTime());
// Date (SQL) --> Date
udate = new Date(date.getTime());
// Date --> Time (SQL)
Time time = new Time(date.getTime());
// Time (SQL) --> Date
date = new Date(time.getTime());
// Date --> Timestamp (SQL)
Timestamp timestamp = new Timestamp(date.getTime());
// Timestamp (SQL) --> Date
date = new Date(timestamp.getTime());
LocalDate localDate = LocalDate.now();
// LocalDate --> Date (SQL)
date = Date.valueOf(localDate);
// Date (SQL) --> LocalDate
localDate = date.toLocalDate();
LocalTime localTime = LocalTime.now();
// LocalTime --> Time (SQL)
sqltime = Time.valueOf(localTime);
// Time (SQL) --> Localtime
localTime = Time.toLocalTime();
LocalDateTime localDateTime = LocalDateTime.now();
// LocalDateTime --> Timestamp (SQL)
timestamp = Timestamp.valueOf(localDateTime);
// Timestamp (SQL) --> LocalDateTime
localDateTime = timestamp.toLocalDateTime();
4.2.2.2. BLOB y CLOB#
Ambos tipos representan datos de tamaño considerable, BLOB
datos binarios
(p.e. una imagen) y CLOB
un conjunto de caracteres, o sea, un texto mayor
que el que se podría almacenar con VARCHAR
(cuyo límite depende del
SGBD). Al margen de esa diferencia, explicado uno, explicado el otro.
Por ejemplo, si tuviéramos un archivo con una foto que quisiéramos guardar en una base de datos podríamos hacer:
try (
Connection conn = DriverManager.getConnection(dbUrl);
) {
try(
PreparedStatement pstmt = conn.preparedStatement("INSERT INTO Persona (nombre, avatar) VALUES (?, ?)")
) {
Path archivo = Path.of("ruta", "al", "archivo", "jpg");
try(InputStream st = Files.newInputStream(archivo)) {
pstmt.setString(1, "Manolito");
pstmt.setBinaryStream(2, st);
pstmt.executeUpdate();
}
}
}
También podríamos querer guardar un archivo binaro ya cargado en memoria:
byte[] archivo = new byte[] {10, 20, 5, 50, 12, 221, 13}
Blob blob = conn.createBlob();
blob.setBytes(1, archivo); // Agregamos la secuencia de bytes al principio del Blob.
try (
Connection conn = DriverManager.getConnection(dbUrl);
) {
try(
PreparedStatement pstmt = conn.preparedStatement("INSERT INTO Persona (nombre, avatar) VALUES (?, ?)")
) {
pstmt.setString(1, "Manolito");
pstmt.setBlob(2, blob);
pstmt.executeUpdate();
}
finally {
blob.free(); // Vaciamos el blob para liberar la memoria.
}
}
4.2.2.3. JSON#
Desde SQL:2023 el estándar soporta de forma nativa el tipo JSON. Sin embargo, JDBC aún no tiene soporte alguno para ello, así que el único modo de tratarlo es a través de String.
4.2.2.4. ARRAY y STRUCT#
El tipo de dato ARRAY
es, simplemente, una secuencia de datos de un mismo
tipo, o sea, lo que entenderíamos como array en cualquier lenguaje de
programación:
CREATE TABLE Trabaja (
profesor INTEGER,
claustro INTEGER,
departamento INTEGER,
-- Para poder asignar varios casilleros a un mismo profesor
casillero INTEGER ARRAY NOT NULL,
/* Restricciones */
);
STRUCT
, en cambio, es un tipo de dato que permite incluir
como valor de un campo una estructura de datos al modo de las estructuras C o
los mapas de Python o Java:
// No pueden definirse restricciones en la definición, así que estas
// (p.e. tipo_via debería incluir un CHECK con varios valores)
// deben definirse en la tabla en la que se incluya este tipo struct.
CREATE TYPE domicilio AS (
tipo_via VARCHAR(40),
nombre_via VARCHAR(150),
numero INTEGER,
bloque CHAR(1),
escalera CHAR(1),
piso INTEGER,
letra CHAR(2)
);
Aclaración
SQLite no soporta de forma nativa los datos complejos (ya explicamos al tratar los datos simples cómo funcionan en realidad los tipos en él). En particular:
DATE
/TIME
/TIMESTAMP
Puede almacenarlos como una cadena (“2024-12-12”), un entero(el tiempo UNIX 1733961600) o un flotante (fecha juliana usada en Astronomía). Para darles soporte añade funciones específicas.
En el caso particular de JDBC,
setDate
almacenará la fecha como un entero, lo cual nos es indiferente si leemos los campos congetDate
, pero quizás no nos guste tanto, si la lectura la hacemos por otros medios (p.e. usando directamente el cliente de SQLite sin echar mano de funciones específicas).BLOB
Es el único dato complejo que realmente soporta SQLite, así que no tendremos problemas con él.
CLOB
SQLite no le da un tratamiento especial y se trata como cualquier otra cadena, ya que internamente SQLite sólo tiene un tipo para datos que son cadenas. Sin embargo,
setClob
no está implementado para él, por lo que tendremos que usarsetString
.JSON
No tiene soporte nativo sino a través de funciones específicas. En cualquier caso, JDBC tampoco lo tiene con lo que tendrá que usarse
setString
igual que para el resto de SGBD.ARRAY
/STRUCT
No tienen soporte en SQLite y, además, los métodos
setArray
ysetStruct
no están implementados para el driver.
Notas al pie