✅ Mapping Java parameters to JDBC
SQL parameters are Java method parameters that appear as named markers in an SQL statement. Most parameters use one of these forms:
:namebinds one Java parameter as one JDBC value:customer.{names}expands record component names:customer.{values}or:customer.*expands record, array, or list values@JdbcConverterName("...")selects one named converter for one whole parameter
Common Cases
Section titled “Common Cases”Bind a simple value
Section titled “Bind a simple value”Use :name when one Java parameter should become one JDBC value.
This is the normal choice for String, numbers, dates, enums, and small value
objects.
@JdbcSelect("SELECT :name || ':' || :budget")String bindOneValue(String name, int budget);The marker name must match the Java method parameter name.
? placeholders are not supported.
Bind a value object
Section titled “Bind a value object”For an enum, the default converter binds the enum name as a String.
enum PricingPlan { FREE, BASIC, PRO, ENTERPRISE}
@JdbcSelect("SELECT :plan")String bindEnum(PricingPlan plan);A record with exactly one component can be bound through that component. Use this for small value types such as IDs.
record CustomerId(long value) {}
@JdbcSelect("SELECT :id")long bindCustomerId(CustomerId id);If the component is not directly JDBC-supported, it may use another converter. The conversion chain must end in a JDBC-supported value.
If the value object owns its JDBC representation, put an object converter method on the type.
record InvoiceId(long value) { @JavaToJdbc long toJdbc() { return value; }}
@JdbcSelect("SELECT :id")long bindInvoiceId(InvoiceId id);If the conversion should stay outside the value object, or the type is not yours, use a static converter.
record ExternalId(String value) {}
@JavaToJdbcstatic String externalIdToJdbc(ExternalId id) { return id.value();}
@JdbcSelect("SELECT :id")String bindExternalId(ExternalId id);Bind a family of value objects
Section titled “Bind a family of value objects”If many value objects share the same JDBC representation, define a converter for a shared interface or superclass. The Java-to-JDBC type hierarchy lookup can then use that converter for concrete subtypes.
interface RowId { long value();}
record AccountId(long value) implements RowId {}
record OrderId(long value) implements RowId {}
@JavaToJdbcstatic long rowIdToJdbc(RowId id) { return id.value();}
@JdbcSelect("SELECT :id")long bindAccountId(AccountId id);
@JdbcSelect("SELECT :id")long bindOrderId(OrderId id);This is useful for ID types such as AccountId, OrderId, or CustomerId that
all wrap the same database column type.
Insert a record
Section titled “Insert a record”Use record expansion when a record provides several SQL values.
record NewCustomer(String name, Integer budge, @JdbcName("pricing_plan") PricingPlan plan) {}
@JdbcUpdate("INSERT INTO db_customers (:customer.{names}) VALUES (:customer.*)")void insertCustomer(NewCustomer customer);For the record in this example, the SQL marker expansion produces this SQL shape:
INSERT INTO db_customers (name, budge, pricing_plan) VALUES (?, ?, ?)Record expansion supports these marker formats:
:customer.{names}expands component names in declaration order:customer.{values}expands component values in declaration order:customer.*is a short form for:customer.{values}
If a record component uses @JdbcName, :customer.{names} uses that SQL name.
Use a list in IN (...)
Section titled “Use a list in IN (...)”Use :param.{values} or :param.* when an array or list should become several
JDBC placeholders.
@JdbcSelect("SELECT x FROM (VALUES :ids.{values}) t(x)")List<Long> findIds(List<Long> ids);For a list with three values, the SQL marker expansion produces this SQL shape:
SELECT x FROM (VALUES ?, ?, ?) t(x)This is useful for IN (...) clauses and other statements that need one SQL
value per collection element.
The normal :param marker binds the list as one JDBC value instead.
Use a named converter for one parameter
Section titled “Use a named converter for one parameter”Use a named converter when one parameter needs a special binding strategy.
@JavaToJdbc("upper")static String externalIdToUpperJdbc(ExternalId id) { return id.value().toUpperCase();}
@JdbcSelect("SELECT :id")String bindExternalIdAsUpper(@JdbcConverterName("upper") ExternalId id);@JdbcConverterName applies the converter to the whole parameter.
Use it with the plain marker format, such as :id.
It is invalid with :id.{values}, :id.{names}, and :id.*.
Use a statement converter for low-level binding
Section titled “Use a statement converter for low-level binding”Use a statement converter only when returning one JDBC-supported value is not
enough, or when binding needs direct access to the PreparedStatement.
record LowerText(String value) {}
@JavaToJdbcstatic void bindLowerText(PreparedStatement stmt, int index, LowerText text) throws SQLException { stmt.setString(index, text.value().toLowerCase());}
@JdbcSelect("SELECT :text")String bindLowerText(LowerText text);The converter receives the PreparedStatement, the parameter index, and the
Java value.
It returns void and may throw SQLException.
Troubleshooting
Section titled “Troubleshooting”My list is bound as one value
Section titled “My list is bound as one value”Check the marker format.
:ids binds the list as one JDBC value and therefore needs a converter for the
whole list.
Use :ids.{values} or :ids.* when each list element should become its own JDBC
placeholder.
My record expansion fails
Section titled “My record expansion fails”Use :customer.{names} only for record parameters.
Use :customer.{values} or :customer.* for record values.
If the record uses @JdbcConverterName, expansion is invalid because the named
converter applies to the record as a whole.
My named converter is invalid with .*
Section titled “My named converter is invalid with .*”This is expected.
Named converters select a converter for the whole parameter.
Expansion markers select parameter components or elements instead.
Use either @JdbcConverterName with :param, or remove the named converter and
expand with :param.{values} or :param.*.
My custom type has no converter
Section titled “My custom type has no converter”Use an unnamed @JavaToJdbc converter for the normal binding strategy of a type.
It can be an object method on the type, a static converter for the exact type, or
a static converter for a shared interface or superclass.
Use a named converter only for special cases.
If neither exists, Kaumei JDBC can still create default converters for
enums and single-component records.
Exact Rules
Section titled “Exact Rules”For exact marker syntax, converter-name restrictions, and nullness rules, see parameter binding. For exact lookup order, scopes, hierarchy lookup, and fallback creation, see converter lookup.