Backend API. Server-side code: this runs in a Codename One backend, not in the app on the device.
public final class Database
- Object
- Database
One database API over SQLite, PostgreSQL and MySQL, chosen by URL.
Database.open("/var/lib/app/app.db")
Database.open(":memory:")
Database.open("postgres://user:[email protected]:5432/app?sslmode=require")
Database.open("mysql://user:[email protected]/app?sslmode=require"
+ "&sslrootcert=/etc/ssl/rds-ca.pem")
The point of the single type is that a handler cannot tell which engine answered it. Rows come back as column-name to value maps whose values are always Long, Double, String, byte[] or null, whichever engine produced them – so the same code developed against a local SQLite file runs against a managed PostgreSQL without a branch. Parameters are always bound, never interpolated, on all three.
TLS has three settings, and none of them is “encrypted but unverified”.
sslmode=require demands TLS whose certificate chains to a trusted root AND
carries the host’s name; sslmode=disable is plaintext, deliberately;
sslmode=prefer uses TLS when the server offers it and fails loudly rather
than silently downgrading when verification does not hold. A managed instance
or a development container that presents a private CA is reached by naming it:
sslrootcert=/path/to/ca.pem.
SQLite goes through Db, which is per-target: the engine is linked into
the binary on the translated side and reached through a JDBC driver on the
local Java SE side. PostgreSQL and MySQL are the SAME code on both targets –
they speak the wire protocol over Tcp, so there is no driver to install
and nothing that can behave differently between the two.
Statements are written ONCE, in the portable form: ? for every parameter and
plain unquoted names. PostgreSQL binds $1 rather than ?, and that difference
stops inside execute and query – see Dialect.bind –
rather than at every call site. SQL already written for one engine keeps
working: a statement carrying no ? at all is passed through untouched, so
hand-written $1 is left alone.
What differs between engines and used to be papered over by the caller:
lastInsertId is meaningful for SQLite and MySQL and always 0 for
PostgreSQL, which has no such concept. insert is the portable form of
that question – it asks whichever way this engine answers – and
dialect exposes the rest of the differences for code that generates
schema.
Nested types
interface Database.Work | A unit of work run inside transaction. |
Methods
Inherited methods
Method details
open
public static Database open(String url)
throws IOExceptionThrows
of
public static Database of(Db db)
throws IOExceptionThrows
execute
public int execute(String sql, Object[] params)
throws IOExceptionSynchronized, like the two below, because a shared session is not safe to interleave – and for the network engines it is worse than interleaved transactions.
Postgres and MySql each own a Wire, and a Wire owns ONE 16KB buffer with a position and a limit, plus one output stream it builds every message in. MySql also carries the packet sequence number. Two handlers calling at once therefore write into the same message buffer, move each other’s parse position and desynchronize the sequence: that is protocol corruption, not merely one request’s work committed by another’s COMMIT.
The SQLite path delegates to Db, which is synchronized on its own monitor. Holding this one first is safe – the order is always Database then Db, never the reverse – and Db’s monitor is reentrant for the callbacks.
Throws
query
public List query(String sql, Object[] params)
throws IOExceptionThrows
queryOne
public Map queryOne(String sql, Object[] params)
throws IOExceptionThe one row a query is expected to return, or null when it returns none.
Its own method because the alternative is written at every call site and is wrong in the same way each time: reading get(0) off a list without looking at its size, which is an IndexOutOfBoundsException on the day the row is missing rather than the null the code above it is already written to handle. More than one row is a bug in the statement, and it is reported as one rather than silently discarded.
Throws
insert
public long insert(String sql, Object[] params, String idColumn)
throws IOExceptionRuns an INSERT and answers the key the database generated for it.
This is the operation the engines disagree about most and the one an
application needs most often. SQLite and MySQL assign the key and hold it
until asked – lastInsertId – while PostgreSQL has no such
concept at all, and the only way to learn the key there is to ask the
INSERT itself for it with a RETURNING clause. Written by hand that is a
branch on the engine at every insert; here the dialect knows which it is.
idColumn names the generated column, which is what RETURNING
needs. It is quoted for the engine, so a column named “order” or one whose
case matters is spelled correctly rather than folded.
ONE ROW. A statement whose VALUES clause names more than one tuple is refused before it runs, because the three engines key a multi-row insert differently and there is no answer that means the same thing on all of them. A statement whose row count its text does NOT show – an INSERT … SELECT – is refused on PostgreSQL, where RETURNING counts the rows for certain, and answers with the engine’s last-insert-id on the other two, where asking would mean trusting MySQL’s affected-row count, which says two for an upsert that touched one row. Use execute() for a statement that inserts an unknown number of rows.
Parameters
sqlString- an INSERT in the portable form, with no RETURNING of its own
paramsObject[]- Not documented.
idColumnString- Not documented.
Returns
Throws
dialect
public Dialect dialect()How this connection’s engine spells what the three of them spell differently: parameter placeholders, identifier quoting, column types, the declaration of a generated key.
Statements passed to execute and query are already
rendered through it, so ordinary code never needs this. Schema generation
does – it has to ask what this engine calls a 64-bit integer.
beginExclusiveTransaction
public void beginExclusiveTransaction()
throws IOExceptiontransaction holds the monitor around its callback.
The owning thread must complete this transaction; do not hand it to another thread.Throws
isInTransaction
public boolean isInTransaction()beginTransaction
public void beginTransaction()
throws IOExceptionThrows
beginTransaction
public void beginTransaction(boolean readOnly)
throws IOExceptionBegins a transaction like beginTransaction, telling the engine
when it will only read.
A read-only one is a real promise on two of the engines: PostgreSQL and MySQL refuse a write inside it, and SQLite takes no write lock up front (BEGIN DEFERRED rather than IMMEDIATE), so it does not queue other writers behind a transaction that will never write.
Throws
savepoint
public void savepoint(String name)
throws IOExceptionrollbackToSavepoint can return to without ending the transaction.
The name must be a plain identifier; it is written into the statement.Throws
rollbackToSavepoint
public void rollbackToSavepoint(String name)
throws IOExceptionThrows
releaseSavepoint
public void releaseSavepoint(String name)
throws IOExceptionThrows
commitTransaction
public void commitTransaction()
throws IOExceptionThrows
rollbackTransaction
public void rollbackTransaction()
throws IOExceptionThrows
transaction
public Object transaction(Database.Work body)
throws ExceptionThrows
lastInsertId
public long lastInsertId()The id the most recent insert produced, or 0 where the engine has no such concept. PostgreSQL is the case that has none: use INSERT … RETURNING.
INSIDE THE GATE like every other operation on this class, which is the point: it was the one that was not. A close on another thread – a pool shutting down, a reconnect – could therefore run between this reading the engine and the engine reading its own state.
tuneForConcurrency
public void tuneForConcurrency(int busyTimeoutMillis)
throws IOExceptionThrows
asSqlite
public Db asSqlite()close
public void close()Inside the connection gate, like execute, query and transaction.
Without it a shutdown or a reconnect could close the engine while another handler was inside an operation, so Postgres.close or MySql.close wrote its termination packet into a Wire another thread was mid-exchange on – and on the packaged TLS path freed the native session while that thread was reading through it. The engines’ own close() methods were given this lock already; the facade that fronts them was not, which left the same race one level up.
isOpen
public boolean isOpen()toString
public String toString()