Backend API. Server-side code: this runs in a Codename One backend, not in the app on the device.
public final class Db
- Object
- Db
SQLite persistence for server-side binaries, on the engine the translator already bundles. Not com.codename1.db.Database, which needs a CodenameOneImplementation for every call.
Parameters are always bound, never interpolated: string concatenation into SQL is how injection happens, and a server parses input it did not write.
Nested types
interface Db.Work | A unit of work run inside transaction. |
Methods
public static Db open(String path)
throws IOException | Opens (or creates) the database at the given path. |
public synchronized int execute(String sql, Object[] params)
throws IOException | Runs a statement that returns no rows. |
public synchronized List query(String sql, Object[] params)
throws IOException | Runs a query and returns every row as a column-name to value map. |
public synchronized Object transaction(Db.Work body)
throws Exception | Synchronized because a TRANSACTION is not one call. |
public void enableWriteAheadLog()
throws IOException | Switches the database to write-ahead logging, which is what lets readers run while a writer is active. |
public void setBusyTimeout(int millis)
throws IOException | How long a blocked writer waits for a competing one before giving up. |
public synchronized long lastInsertId() | The rowid the most recent insert produced. |
public synchronized void close() | SYNCHRONIZED, like execute, query and transaction, and it keeps the handle when the close does not take. |
Inherited methods
Method details
open
public static Db open(String path)
throws IOExceptionThrows
execute
public synchronized int execute(String sql, Object[] params)
throws IOExceptionThrows
query
public synchronized List query(String sql, Object[] params)
throws IOExceptionThrows
transaction
public synchronized Object transaction(Db.Work body)
throws ExceptionSynchronized because a TRANSACTION is not one call.
SQLite serializes each API call on a connection, which is what made “one shared connection is correct, and SQLite serializes it” look true. It serializes the calls, not the BEGIN/body/COMMIT sequence around them: a second handler sharing this Db can execute between another’s BEGIN and COMMIT and have its write committed – or rolled back – by a request that knows nothing about it, or meet “cannot start a transaction within a transaction” and fail for a reason its own code cannot explain.
The monitor is reentrant, which is what makes this work: transaction() holds it for the whole callback and the execute() calls inside it re-enter freely. A pooled connection is used by one thread at a time anyway, so the cost there is an uncontended lock; a shared one is serialized, which is exactly what correctness requires of it.
Throws
enableWriteAheadLog
public void enableWriteAheadLog()
throws IOExceptionThrows
setBusyTimeout
public void setBusyTimeout(int millis)
throws IOExceptionThrows
lastInsertId
public synchronized long lastInsertId()The rowid the most recent insert produced.
SYNCHRONIZED, like execute, query, transaction and close – it was the one operation here that was not, and the only one that hands the native handle over without holding the lock that guards it. close() frees the sqlite3 connection and then zeroes the field, so a reader that had already loaded the handle called sqlite3_last_insert_rowid on freed memory. The native’s own null check cannot help with that: the pointer it is given is not null, it is dangling.
close
public synchronized void close()SYNCHRONIZED, like execute, query and transaction, and it keeps the handle when the close does not take.
sqlite3_close answers SQLITE_BUSY when a prepared statement is still alive on the connection, and does NOT destroy it. Zeroing the handle regardless threw away the only reference to a connection that was still open, so nothing could ever close it again – the native connection and everything it owns leaked, once per shutdown that raced a borrower.
The lock is what makes that rare rather than merely recoverable: a DbPool.close() arriving while a borrowed connection is mid-query now waits for the query instead of closing underneath it.