@UK indicate that the corresponding column contains unique values, making them
suitable for single-result lookups and for use as keyset pagination cursors.
The @PK annotation is meta-annotated with @UK, so primary key fields are automatically
recognized as unique without needing an explicit @UK annotation.
NULL Handling
In standard SQL, NULL != NULL, so a UNIQUE constraint typically allows multiple rows with NULL
values. This breaks the uniqueness guarantee that scrolling (scroll) relies on, because
WHERE key > cursor silently excludes NULL rows.
Some databases treat NULLs as equal for uniqueness purposes: PostgreSQL 15+ supports
NULLS NOT DISTINCT, and SQL Server allows only one NULL by default. Since Storm cannot determine the
database constraint behavior from the code alone, you can declare it explicitly via nullsDistinct().
When a nullable field is annotated with @UK and nullsDistinct is true (the
default), the metamodel processor emits a compile-time warning, and scroll methods throw a
PersistenceException at runtime. To suppress the warning and enable keyset pagination, either make
the field non-nullable (use a primitive type or add @Nonnull), or set
@UK(nullsDistinct = false) to indicate that the database constraint prevents duplicate NULLs.
Usage example (Java):
record User(@PK Integer id,
@UK String email,
String name
) implements Entity<Integer> {}
Usage example (Kotlin):
data class User(@PK val id: Int?,
@UK val email: String,
val name: String
) : Entity<Int>
Compound Unique Keys
For compound unique constraints that need a metamodel key (e.g., for keyset pagination or type-safe lookups),
use an inline record annotated with @UK:
Java:
record UserEmailUk(int userId, String email) {}
record SomeEntity(@PK Integer id,
@FK User user,
String email,
@UK @Persist(insertable = false, updatable = false) UserEmailUk uniqueKey
) implements Entity<Integer> {}
The @Persist(insertable = false, updatable = false) annotation prevents the inline record's columns
from being persisted separately when they overlap with other fields on the entity.
Compound unique constraints that do not require a metamodel key do not need to be modeled in the entity. Schema validation does not warn about unmodeled compound constraints.
The metamodel processor generates Metamodel.Key instances for fields annotated with @UK,
enabling type-safe keyset pagination and unique field lookups via repository methods like
findBy(Metamodel.Key, value) and getBy(Metamodel.Key, value).
- Since:
- 1.9
- See Also:
-
Optional Element Summary
Optional ElementsModifier and TypeOptional ElementDescriptionbooleanIndicates whether a corresponding unique constraint is expected to exist in the database.booleanIndicates whether NULL values are considered distinct for the purpose of the UNIQUE constraint.
-
Element Details
-
nullsDistinct
boolean nullsDistinctIndicates whether NULL values are considered distinct for the purpose of the UNIQUE constraint.When
true(the default, matching the SQL standard), the database allows multiple rows with NULL values in the unique column. This means the uniqueness guarantee is broken for nullable fields, which makes keyset pagination unsafe.Set to
falsewhen the database treats NULLs as equal (e.g., PostgreSQL 15+ withNULLS NOT DISTINCT, or SQL Server which allows only one NULL by default). This tells Storm that the nullable field is safe for keyset pagination.This attribute has no effect on fields that are already non-nullable (primitives,
@PK, or fields annotated with@Nonnull).- Returns:
trueif NULLs are distinct (SQL standard),falseif the database prevents duplicate NULLs.- Since:
- 1.9
- Default:
true
-
constraint
boolean constraintIndicates whether a corresponding unique constraint is expected to exist in the database.When
true(the default), schema validation will warn if no matching unique constraint is found in the database. Set tofalsewhen the database intentionally omits the unique constraint, for example because uniqueness is enforced at the application level.Setting this to
falseonly suppresses the constraint check during schema validation. The field is still fully functional as a unique key for keyset pagination and unique lookups.- Returns:
trueif the unique constraint is expected in the database,falseto skip the check.- Since:
- 1.10
- Default:
true
-