Skip to content

Runtime API

Here is the public API in io.github.larkbatis.runtime. The runtime has zero dependencies beyond standard JDBC—no reflection, no dynamic proxies, and no runtime type inspection.

LarkBatisSession

The runtime session contract used by all generated mappers:

public interface LarkBatisSession {

    Connection conn();                                    // (1)!
    void release(Connection c);                           // (2)!
    RuntimeException translate(SQLException e, String sql);

    // Escape hatch for dynamic SQL
    default <T> List<T>   query(SqlFragment, StatementBinder, RowReader<T>);
    default <T> T         queryOne(SqlFragment, StatementBinder, RowReader<T>);
    default <T> Stream<T> queryStream(SqlFragment, StatementBinder, RowReader<T>);
    default int           update(SqlFragment, StatementBinder);

    // Internal helpers for Stream returns
    default <T> Stream<T> stream(Connection, PreparedStatement, ResultSet, RowReader<T>, String);
    default RuntimeException streamFailed(Connection, PreparedStatement, ResultSet, String, SQLException);
}
  1. conn(): Returns the connection bound to the active transaction, or opens a new auto-commit connection if running standalone.
  2. release(Connection c): Returns the connection. A no-op if part of an active transaction; closes the connection otherwise.

Implementations: JdbcLarkBatisSession (standalone Java) and SpringLarkBatisSession (larkbatis-spring).

JdbcLarkBatisSession

Standalone implementation of LarkBatisSession:

public JdbcLarkBatisSession(DataSource dataSource)

public LarkBatisTx begin()
public LarkBatisTx begin(boolean readOnly)
public boolean hasActiveTransaction()

LarkBatisTx

Transaction scope for standalone applications using try-with-resources:

try (LarkBatisTx tx = session.begin()) {
    mapper.insert(user);
    tx.commit();
}
Method Description
commit() Votes to commit. The physical commit executes when the outermost scope closes
rollbackOnly() Explicitly marks the transaction as rollback-only
close() Exiting the block without voting to commit triggers an automatic rollback

Scopes support nesting: inner scopes join the existing outer transaction. See Transactions.

SqlFragment

The safe wrapper for dynamic SQL text:

public static SqlFragment allowed(String value, String... allowed)   // (1)!
public static SqlFragment identifier(String value)                    // (2)!
public static SqlFragment unsafeRawSql(String value)                  // (3)!
public String text()
  1. allowed(...): Validates against a static whitelist. (Recommended).
  2. identifier(...): Validates SQL identifiers (alphanumeric and underscores).
  3. unsafeRawSql(...): Slices raw SQL text. Searchable across codebases via grep -rn unsafeRawSql src/.

LarkBatisSql

Static utility helpers called by generated bytecode:

Helper Description
trackVariants(statementId, sql) Monitors distinct SQL text variants per statement
maxSqlVariants(int limit) Sets threshold for dynamic SQL text variants (default 64)
failOnUnboundedVariants(boolean) Throws exception instead of logging warning when threshold exceeded
padPow2(int n) Calculates power-of-two padding length for @PadPow2
sum(int[] updateCounts) Aggregates result counts for JDBC batch execution

JdbcCodec

Static helpers for null-safe primitive conversions and enum/date mappings:

  • Reads: booleanOrNull, byteOrNull, shortOrNull, intOrNull, longOrNull, floatOrNull, doubleOrNull, instant, localDateTime, localDate, localTime, enumValue
  • Writes: setBoolean, setByte, setShort, setInt, setLong, setFloat, setDouble, setInstant, setLocalDateTime, setLocalDate, setLocalTime, setEnum
public static Long longOrNull(ResultSet rs, int column) throws SQLException {
    long v = rs.getLong(column);
    return rs.wasNull() ? null : v;
}

Standard types with built-in null handling (String, BigDecimal, byte[], Timestamp) use direct JDBC methods.

RowReader<T> and StatementBinder

@FunctionalInterface
public interface RowReader<T> {
    T read(ResultSet rs) throws SQLException;
}

@FunctionalInterface
public interface StatementBinder {
    void bind(PreparedStatement ps) throws SQLException;
}

Every generated row reader class exposes a public static final RowReader<T> READER instance. Custom escape-hatch queries reuse these static readers with zero runtime reflection.

Exceptions

All exceptions are unchecked and extend LarkBatisException:

Exception Description
LarkBatisException Base runtime exception wrapping underlying SQLExceptions
LarkBatisRejectedException Thrown when an input to SqlFragment or @OrderBy is rejected by the whitelist
LarkBatisEmptyForeachException Thrown when an un-guarded <foreach> collection is empty
LarkBatisNoKeyException Thrown when useGeneratedKeys = true is set but the driver returned no keys
LarkBatisKeyCountMismatchException Thrown when a batch insert receives fewer generated keys than inserted rows
LarkBatisUnboundedVariantsException Thrown when dynamic SQL variants exceed max-sql-variants with fail-fast enabled
LarkBatisRollbackOnlyException Thrown when attempting to commit a transaction poisoned by an inner scope error

In Spring environments, translate() automatically converts errors to Spring's DataAccessException hierarchy (e.g. DuplicateKeyException). See Errors.