SQLite es un archivo, no un servidor: no hay nada que instalar ni que arrancar. Con un solo jar y java.sql, que ya viene en el JDK, tienes una base de datos de verdad dentro del programa.
También te puede interesar
El controlador
curl -LO https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc/3.53.4.0/sqlite-jdbc-3.53.4.0.jar
salidacontrolador: SQLite JDBC 3.53.4.0
Es el único añadido: la API —Connection, PreparedStatement, ResultSet— está en el JDK desde siempre. Desde Java 6 ni siquiera hace falta Class.forName: el controlador se registra solo si está en el classpath.
sin la banderaWARNING: A restricted method in java.lang.System has been called WARNING: java.lang.System::load has been called by org.sqlite.SQLiteJDBCLoader in an unnamed module WARNING: Restricted methods will be blocked in a future release unless native access is enabled
Se quita añadiendo --enable-native-access=ALL-UNNAMED al lanzar. Y conviene hacerlo: el último mensaje avisa de que en una versión futura esto no será un aviso, será un error.
Crear la tabla
st.execute("""
CREATE TABLE libros (
id INTEGER PRIMARY KEY AUTOINCREMENT,
titulo TEXT NOT NULL,
autor TEXT NOT NULL,
anio INTEGER NOT NULL,
notas TEXT,
UNIQUE(titulo, autor))""");
Insertar y recuperar la clave generada
try (var ps = cn.prepareStatement(
"INSERT INTO libros (titulo, autor, anio) VALUES (?, ?, ?)",
Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, "Rayuela"); ps.setString(2, "Julio Cortázar"); ps.setInt(3, 1963);
ps.executeUpdate();
try (var k = ps.getGeneratedKeys()) { if (k.next()) IO.println("id generado: " + k.getLong(1)); }
}
salidaid generado: 1
Los parámetros se numeran desde 1, no desde 0. Es un despiste clásico y da un error poco claro.
Muchos de golpe: lote y transacción
cn.setAutoCommit(false);
try (var ps = cn.prepareStatement("INSERT INTO libros (titulo, autor, anio) VALUES (?, ?, ?)")) {
for (Object[] l : mas) {
ps.setString(1, (String) l[0]); ps.setString(2, (String) l[1]); ps.setInt(3, (Integer) l[2]);
ps.addBatch();
}
int[] n = ps.executeBatch();
cn.commit();
} catch (SQLException e) { cn.rollback(); throw e; }
finally { cn.setAutoCommit(true); }
salidainsertados 3 en lote
En SQLite esto no es una cuestión de estilo. Con autoCommit activado, cada INSERT es una transacción con su sincronización a disco: meter mil filas una a una frente a hacerlo en una transacción son dos órdenes de magnitud de diferencia. El finally que restaura autoCommit importa si la conexión se reutiliza.
Consultar
try (var ps = cn.prepareStatement(
"SELECT id, titulo, autor, anio, notas FROM libros WHERE anio < ? ORDER BY anio")) {
ps.setInt(1, 1990);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
String notas = rs.getString("notas");
if (rs.wasNull()) notas = "(sin notas)";
...
}
}
}
salida3 Ficciones Jorge Luis Borges 1944 (sin notas) 2 Pedro Páramo Juan Rulfo 1955 (sin notas) 1 Rayuela Julio Cortázar 1963 (sin notas) 4 La casa de los espíritus Isabel Allende 1982 (sin notas)
wasNull() es la rareza de JDBC que hay que conocer. getInt de una columna nula devuelve 0 y getString devuelve null, así que con los números no puedes distinguir un cero de verdad de un nulo. La única forma es llamar a wasNull() justo después del get: se refiere a la última columna leída.El error de la restricción
insertando el mismo libro otra vez[SQLITE_CONSTRAINT_UNIQUE] A UNIQUE constraint failed (UNIQUE constraint failed: libros.titulo, libros.autor) código 19
El 19 es el código de SQLite para «violación de restricción». Para distinguir «ya existe» de «error interno» sin buscar texto dentro del mensaje, se compara e.getErrorCode(), o mejor e.getSQLState(), que es el código estándar y no depende del motor.
Los interrogantes no son concatenación
ps.setString(1, "x'; DROP TABLE libros; --");
salidafilas: 0 y la tabla sigue con 4 libros
El valor viaja aparte de la consulta y la base de datos nunca lo interpreta como SQL: para ella es un nombre de autor larguísimo y raro que no existe. Esto deja de ser cierto en cuanto construyes la consulta con + o con String.format, y ahí es donde aparecen las inyecciones.
Lo que no se puede parametrizar son los nombres de tabla y de columna. Si tienen que ser variables, valídalos contra una lista blanca.
| Para… | Usa | Y no olvides |
|---|---|---|
| Conectar | DriverManager.getConnection("jdbc:sqlite:ruta") |
try con recursos |
| Insertar o actualizar | ps.executeUpdate() |
los parámetros empiezan en 1 |
| Muchas filas | addBatch + executeBatch |
dentro de una transacción |
| Consultar | ps.executeQuery() |
cerrar el ResultSet |
| Columnas nulas | rs.wasNull() |
justo después del get |
| Valores del usuario | siempre ? |
nunca concatenar |
| Varios escritores | ?journal_mode=WAL en la URL |
Todo el código se compiló y se ejecutó con Java 25 LTS en un contenedor limpio antes de publicar esta página; las salidas están copiadas de esa ejecución.