PostgreSQL
Модуль database-jdbc-postgres добавляет отображатели столбцов для представлений, специфичных для PostgreSQL:
массивов, interval, range-типов и json / jsonb.
Репозитории, отображения и конфигурация jdbc описаны в документации JDBC;
модуль лишь добавляет отображатели в граф приложения.
Подключение¶
Зависимость build.gradle:
Модуль:
Зависимость build.gradle.kts:
Модуль:
PostgresJdbcDatabaseModule наследует JdbcDatabaseModule, поэтому подключается вместо него, а не рядом с ним.
Модуль приносит драйвер PostgreSQL org.postgresql:postgresql транзитивной зависимостью.
PostgresJdbcDatabaseModule объединяет пять модулей отображателей из io.koraframework.database.jdbc.postgres.mapper:
PgCollectionJdbcMappersModule, PgArrayJdbcMappersModule, PgIntervalJdbcMappersModule, PgRangeJdbcMappersModule и PgJsonJdbcMappersModule.
Если нужна только часть отображателей, унаследуйте JdbcDatabaseModule вместе с нужными модулями:
Отображатели для обычных типов JDK регистрируются с тегом, поэтому никогда не меняют отображение поля неявно:
отображатели коллекций, массивов, Duration и Period помечены тегом @Pg,
а отображатели JSON — тегом @PgJson или @PgJsonb (все из io.koraframework.database.jdbc.postgres.annotation).
Чтобы выбрать отображатель PostgreSQL, поставьте аннотацию на поле отображения или на параметр метода репозитория.
Отображателям PgRange тег не нужен, потому что PgRange сам по себе специфичен для PostgreSQL.
Каждый отображатель объявлен как @DefaultComponent, поэтому ваш собственный отображатель того же типа и с тем же тегом заменяет его.
@EntityJdbc
@Table("users")
public record User(@Id long id,
@Pg List<String> roles, //(1)!
@Pg Duration sessionTtl, //(2)!
PgRange<LocalDate> validity, //(3)!
@PgJsonb Settings settings) { //(4)!
@Json
public record Settings(String theme, boolean notifications) { }
}
@Repository
public interface UserRepository extends JdbcRepository {
@Query("SELECT %{return#selects} FROM %{return#table} WHERE id = ANY(:ids)")
List<User> findAllByIds(@Pg List<Long> ids); //(5)!
@Query("INSERT INTO %{entity#inserts}")
UpdateCount insert(User entity);
}
- Столбец типа
varchar[] - Столбец типа
interval - Столбец типа
daterange, тег не нужен - Столбец типа
jsonb,Settingsзаписывается и читается черезJsonWriter/JsonReader, сгенерированные для типа с@Json - Список связывается как один параметр
int8[], поэтому для= ANY(:ids)не нужен собственный отображатель
@EntityJdbc
@Table("users")
data class User(
@field:Id val id: Long,
@Pg val roles: List<String>, //(1)!
@Pg val sessionTtl: Duration, //(2)!
val validity: PgRange<LocalDate>, //(3)!
@PgJsonb val settings: Settings //(4)!
) {
@Json
data class Settings(val theme: String, val notifications: Boolean)
}
@Repository
interface UserRepository : JdbcRepository {
@Query("SELECT %{return#selects} FROM %{return#table} WHERE id = ANY(:ids)")
fun findAllByIds(@Pg ids: List<Long>): List<User> //(5)!
@Query("INSERT INTO %{entity#inserts}")
fun insert(entity: User): UpdateCount
}
- Столбец типа
varchar[] - Столбец типа
interval - Столбец типа
daterange, тег не нужен - Столбец типа
jsonb,Settingsзаписывается и читается черезJsonWriter/JsonReader, сгенерированные для типа с@Json - Список связывается как один параметр
int8[], поэтому для= ANY(:ids)не нужен собственный отображатель
Массивы и коллекции¶
С @Pg коллекции и массивы связываются как массив PostgreSQL и читаются из него:
| Тип элемента | Элемент массива PostgreSQL |
|---|---|
Boolean |
bool |
Short |
int2 |
Integer |
int4 |
Long |
int8 |
Float |
float4 |
Double |
float8 |
BigDecimal |
numeric |
String |
varchar |
UUID |
uuid |
- Параметр может быть
List<T>,Set<T>илиCollection<T>, поэтому коллекцию не нужно копировать ради вызова. Результат всегдаList<T>; уникальность задается запросом, например черезDISTINCT. - Массивы поддерживаются как
T[]для всех типов элементов выше (Array<T>вKotlin) и как примитивные массивыboolean[],short[],int[],long[],float[],double[](BooleanArray,ShortArray,IntArray,LongArray,FloatArray,DoubleArrayвKotlin). - Элементы
NULLпередаются как есть для коллекций и массивов объектов. Примитивный массив не может содержатьNULL, поэтому чтение в него массива с элементомNULLзавершаетсяSQLException. - Столбец
NULLчитается какnull, а значениеnullсвязывается какNULL.
Интервалы¶
С @Pg типы java.time.Duration и java.time.Period связываются со столбцом interval и читаются из него.
Duration записывается как дни, часы, минуты и секунды с дробной частью, а Period — как годы, месяцы и дни.
У месяцев и лет нет фиксированной длины, поэтому чтение interval с годами или месяцами в Duration завершается SQLException;
для таких значений используйте Period.
Точно так же чтение interval с временной частью в Period завершается ошибкой; для него используйте Duration.
Диапазоны¶
PgRange<T> из io.koraframework.database.jdbc.postgres представляет значение range-типа, и тег ему не нужен:
Тип Java |
Тип PostgreSQL |
|---|---|
PgRange<Integer> |
int4range |
PgRange<Long> |
int8range |
PgRange<BigDecimal> |
numrange |
PgRange<LocalDate> |
daterange |
PgRange<LocalDateTime> |
tsrange |
PgRange<OffsetDateTime> |
tstzrange |
PgRange — это record из lower, upper, lowerInclusive, upperInclusive и isEmpty; граница null означает, что с этой стороны диапазон не ограничен.
Фабричные методы покрывают привычные формы: closed [a,b], closedOpen [a,b), openClosed (a,b], open (a,b) и empty().
@Repository
public interface UserRepository extends JdbcRepository {
@Query("SELECT %{return#selects} FROM %{return#table} WHERE validity && :period")
List<User> findValidWithin(PgRange<LocalDate> period);
}
var users = repository.findValidWithin(PgRange.closedOpen(LocalDate.of(2026, 1, 1), null)); //(1)!
[2026-01-01,): с 1 января 2026 года без верхней границы
@Repository
interface UserRepository : JdbcRepository {
@Query("SELECT %{return#selects} FROM %{return#table} WHERE validity && :period")
fun findValidWithin(period: PgRange<LocalDate>): List<User>
}
val users = repository.findValidWithin(PgRange.closedOpen(LocalDate.of(2026, 1, 1), null)) //(1)!
[2026-01-01,): с 1 января 2026 года без верхней границы
PostgreSQL приводит дискретные range-типы (int4range, int8range, daterange) к форме [), а вырожденные диапазоны — к empty,
поэтому прочитанное значение может отличаться от записанного: PgRange.closed(1, 5) читается обратно как [1,6).
Границы tstzrange возвращаются в часовом поясе сессии, поэтому обозначают тот же момент времени, но могут иметь другое смещение.
JSON¶
@PgJson отображает значение в столбец json, а @PgJsonb — в столбец jsonb через JsonWriter<T> / JsonReader<T>
модуля JSON для типа значения, поэтому у типа значения они должны быть, например за счет аннотации @Json.
Теги различаются типом связываемого параметра: операторы jsonb, такие как @>, ? и jsonb_path_query, требуют, чтобы параметр был именно jsonb,
поэтому для столбцов jsonb используйте @PgJsonb. Приведение ::jsonb в запросе не нужно.
Столбец NULL читается как null, а значение null связывается как NULL.
Чтобы отличать NULL в столбце от литерала JSON null, объявите поле как JsonNullable<T>:
JsonNullable.undefined() соответствует NULL в столбце, JsonNullable.nullValue() — литералу JSON null,
а определенное значение — сериализованному значению.