Generated Keys¶
@Options(useGeneratedKeys = true, ...) retrieves database-generated primary keys after an INSERT and sets them directly on your parameter object.
@Insert("INSERT INTO users (name, email, created_at) VALUES (#{name}, #{email}, #{createdAt})")
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
int insert(User u);
private static final String[] KEYS_insert = { "id" };
@Override
public int insert(User u) {
Connection c = s.conn();
try (PreparedStatement ps = c.prepareStatement(SQL_insert, KEYS_insert)) { // (1)!
ps.setString(1, u.getName());
ps.setString(2, u.getEmail());
JdbcCodec.setInstant(ps, 3, u.getCreatedAt());
int n = ps.executeUpdate();
try (ResultSet gk = ps.getGeneratedKeys()) {
if (gk.next()) {
u.setId(gk.getLong(1)); // (2)!
}
}
return n;
} catch (SQLException e) {
throw s.translate(e, SQL_insert);
} finally {
s.release(c);
}
}
- Explicit key column names:
RETURN_GENERATED_KEYSbehaves inconsistently across database drivers (see below). - The property accessor is chosen at build time based on
keyPropertyand its declared Java type.
Always name keyColumn¶
keyColumn is practically mandatory in production
Using prepareStatement(sql, Statement.RETURN_GENERATED_KEYS) is not portable across drivers:
- Oracle returns the database
ROWID, not sequence IDs. - PostgreSQL returns all columns of the inserted row.
Specifying explicit column names with keyColumn is the only way to get consistent behavior across all databases. If keyColumn is omitted, the generator falls back to RETURN_GENERATED_KEYS and prints a compiler warning.
keyProperty is mandatory: omitting it is a compile error because the generator must know where to store the returned key.
For composite keys, provide comma-separated lists of matching length:
keyProperty with multiple parameters¶
When a method accepts multiple arguments, prefix keyProperty with the parameter name:
@Insert("INSERT INTO users (name) VALUES (#{u.name})")
@Options(useGeneratedKeys = true, keyProperty = "u.id", keyColumn = "id")
int insert(@Param("u") User u, @Param("audit") String audit);
Invalid property paths are caught as compile-time errors.
Batch inserts and generated keys¶
Batch inserts taking a List<T> compile to JDBC addBatch() / executeBatch() calls:
int n = LarkBatisSql.sum(ps.executeBatch());
try (ResultSet gk = ps.getGeneratedKeys()) {
int i = 0;
while (gk.next() && i < orders.size()) {
orders.get(i).setId(gk.getLong(1));
i++;
}
if (i != orders.size()) {
throw new LarkBatisKeyCountMismatchException(STMT_insertAll, orders.size(), i);
}
}
Some database drivers return fewer generated keys than inserted rows. Instead of silently leaving trailing list elements with null or zero IDs, LarkBatis throws LarkBatisKeyCountMismatchException immediately.
<selectKey> is not supported¶
MyBatis used <selectKey> to fetch sequence values before running an insert. In LarkBatis, write the two statements explicitly:
@Select("SELECT user_seq.NEXTVAL FROM dual")
long nextUserId();
@Insert("INSERT INTO users (id, name) VALUES (#{id}, #{name})")
int insert(User u);
try (LarkBatisTx tx = session.begin()) {
u.setId(mapper.nextUserId());
mapper.insert(u);
tx.commit();
}
When no key is returned¶
If a statement declares useGeneratedKeys = true but the driver returns an empty ResultSet, LarkBatis throws LarkBatisNoKeyException to prevent uninitialized default IDs from propagating silently.