Backend API. Server-side code: this runs in a Codename One backend, not in the app on the device.

public final class Db

  1. Object
  2. 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.WorkA unit of work run inside transaction.

Methods

public static Db open(String path) throws IOExceptionOpens (or creates) the database at the given path.
public synchronized int execute(String sql, Object[] params) throws IOExceptionRuns a statement that returns no rows.
public synchronized List query(String sql, Object[] params) throws IOExceptionRuns a query and returns every row as a column-name to value map.
public synchronized Object transaction(Db.Work body) throws ExceptionSynchronized because a TRANSACTION is not one call.
public void enableWriteAheadLog() throws IOExceptionSwitches the database to write-ahead logging, which is what lets readers run while a writer is active.
public void setBusyTimeout(int millis) throws IOExceptionHow 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 IOException
Opens (or creates) the database at the given path. “:memory:” gives a process-lifetime database, which is what a stateless function usually wants for a cache.

execute

public synchronized int execute(String sql, Object[] params) throws IOException
Runs a statement that returns no rows. Returns the number of rows changed.

query

public synchronized List query(String sql, Object[] params) throws IOException
Runs a query and returns every row as a column-name to value map. Values are String, Long, Double or null, which is exactly what the JSON writer accepts.

transaction

public synchronized Object transaction(Db.Work body) throws Exception

Synchronized 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

Exception

enableWriteAheadLog

public void enableWriteAheadLog() throws IOException
Switches the database to write-ahead logging, which is what lets readers run while a writer is active. Worth doing once after open for anything that serves concurrent requests; pointless for :memory:.

setBusyTimeout

public void setBusyTimeout(int millis) throws IOException
How long a blocked writer waits for a competing one before giving up. Without this, two connections writing at once produce SQLITE_BUSY immediately rather than queueing.

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.