JDBC
Модуль предоставляет реализацию репозиториев на основе JDBC протокола работы с базами данных и с использованием Hikari для управления набором соединений.
Подключение¶
Зависимость build.gradle
:
Модуль:
Зависимость build.gradle.kts
:
Модуль:
Также требуется предоставить реализацию драйвера базы данных как зависимость.
Конфигурация¶
Пример полной конфигурации, описанной в классе JdbcDatabaseConfig
(указаны примеры значений или значения по умолчанию):
db {
jdbcUrl = "jdbc:postgresql://localhost:5432/postgres" //(1)!
username = "postgres" //(2)!
password = "postgres" //(3)!
schema = "public" //(4)!
poolName = "kora" //(5)!
maxPoolSize = 10 //(6)!
minIdle = 0 //(7)!
connectionTimeout = "10s" //(8)!
validationTimeout = "5s" //(9)!
idleTimeout = "10m" //(10)!
maxLifetime = "15m" //(11)!
leakDetectionThreshold = "0s" //(12)!
initializationFailTimeout = "0s" //(13)!
readinessProbe = false //(14)!
dsProperties { //(15)!
"hostRecheckSeconds": "2"
}
telemetry {
logging {
enabled = false //(16)!
}
metrics {
enabled = true //(17)!
slo = [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] //(18)!
}
tracing {
enabled = true //(19)!
}
}
}
- JDBC URL подключения к базе данных (обязательный)
- Имя пользователя для подключения (обязательный)
- Пароль пользователя для подключения (обязательный)
- Схема базы данных для подключения (по умолчанию отсутвует)
- Имя набора соединений к базе данных в Hikari (обязательный)
- Максимальный размер набора соединений к базе данных в Hikari
- Минимальный размер набора готовых соединений к базе данных в Hikari в режиме ожидания
- Максимальное время на установку соединения в Hikari
- Максимальное время на проверку соединения в Hikari
- Максимальное время на простой соединения в Hikari
- Максимальное время жизни соединения в Hikari
- Максимальное время соединение может отстуствовать в Hikari до того как будет считаться утечкой (по умолчанию отсутвует)
- Максимальное время ожидания инициализации соединения при старте сервиса (по умолчанию отсутвует)
- Включить ли пробу готовности для соединения базы данных
- Дополнительные атрибуты JDBC соединения
dataSourceProperties
(ниже примерhostRecheckSeconds
параметра) (по умолчанию отсутвует) - Включает логгирование модуля (по умолчанию
false
) - Включает метрики модуля (по умолчанию
true
) - Настройка SLO для DistributionSummary метрики
- Включает трассировку модуля (по умолчанию
true
)
db:
jdbcUrl: "jdbc:postgresql://localhost:5432/postgres" #(1)!
username: "postgres" #(2)!
password: "postgres" #(3)!
schema: "public" #(4)!
poolName: "kora" #(5)!
maxPoolSize: 10 #(6)!
minIdle: 0 #(7)!
connectionTimeout: "10s" #(8)!
validationTimeout: "5s" #(9)!
idleTimeout: "10m" #(10)!
maxLifetime: "15m" #(11)!
leakDetectionThreshold: "0s" #(12)!
initializationFailTimeout: "0s" //(13)!
readinessProbe: false //(14)!
dsProperties: #(15)!
hostRecheckSeconds: "1"
telemetry:
logging:
enabled: false #(16)!
metrics:
enabled: true #(17)!
slo: [ 2, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] #(18)!
tracing:
enabled: true #(19)!
}
- JDBC URL подключения к базе данных (обязательный)
- Имя пользователя для подключения (обязательный)
- Пароль пользователя для подключения (обязательный)
- Схема базы данных для подключения (по умолчанию отсутвует)
- Имя набора соединений к базе данных в Hikari (обязательный)
- Максимальный размер набора соединений к базе данных в Hikari
- Минимальный размер набора готовых соединений к базе данных в Hikari в режиме ожидания
- Максимальное время на установку соединения в Hikari
- Максимальное время на проверку соединения в Hikari
- Максимальное время на простой соединения в Hikari
- Максимальное время жизни соединения в Hikari
- Максимальное время соединение может отстуствовать в Hikari до того как будет считаться утечкой (по умолчанию отсутвует)
- Максимальное время ожидания инициализации соединения при старте сервиса (по умолчанию отсутвует)
- Включить ли пробу готовности для соединения базы данных
- Дополнительные атрибуты JDBC соединения
dataSourceProperties
(ниже примерhostRecheckSeconds
параметра) (по умолчанию отсутвует) - Включает логгирование модуля (по умолчанию
false
) - Включает метрики модуля (по умолчанию
true
) - Настройка SLO для DistributionSummary метрики
- Включает трассировку модуля (по умолчанию
true
)
Использование¶
Конвертация¶
Возможно переопределять преобразование различных частей сущности и параметров запроса, для этого Kora предоставляет специальные интерфейсы.
Результат¶
Если требуется преобразовать результат в ручную, предлагается использовать JdbcResultSetMapper
:
final class ResultMapper implements JdbcResultSetMapper<UUID> {
@Override
public UUID apply(ResultSet rs) throws SQLException {
// код преобразования
}
}
@Repository
public interface EntityRepository extends JdbcRepository {
@Mapping(ResultMapper.class)
@Query("SELECT id FROM entities")
List<UUID> getIds();
}
Сущность¶
Для оптимального преобразование сущности предполагается использовать аннотацию @EntityJdbc
для создания обработчиками аннотаций преобразователя результата.
Для всех вложенных сущностей также предполагается использовать эту аннотацию
Строка¶
Если требуется преобразовать строку в ручную, предлагается использовать JdbcRowMapper
:
final class RowMapper implements JdbcRowMapper<UUID> {
@Override
public UUID apply(ResultSet rs) throws SQLException {
return UUID.fromString(rs.getString(0));
}
}
@Repository
public interface EntityRepository extends JdbcRepository {
@Mapping(RowMapper.class)
@Query("SELECT id FROM entities")
List<UUID> findAll();
}
class RowMapper : JdbcRowMapper<UUID> {
@Throws(SQLException::class)
override fun apply(rs: ResultSet): UUID {
return UUID.fromString(rs.getString(0))
}
}
@Repository
interface EntityRepository : JdbcRepository {
@Mapping(RowMapper::class)
@Query("SELECT id FROM entities")
fun findAll(): List<UUID>
}
Колонка¶
Если требуется преобразовать значение колонки в ручную, предлагается использовать JdbcResultColumnMapper
:
public final class ColumnMapper implements JdbcResultColumnMapper<UUID> {
@Override
public UUID apply(ResultSet row, int index) throws SQLException {
return UUID.fromString(row.getString(index));
}
}
@EntityJdbc
@Table("entities")
public record Entity(@Mapping(ColumnMapper.class) @Id UUID id, String name) { }
@Repository
public interface EntityRepository extends JdbcRepository {
@Query("SELECT id, name FROM entities")
List<Entity> findAll();
}
class ColumnMapper : JdbcResultColumnMapper<UUID> {
@Throws(SQLException::class)
override fun apply(row: ResultSet, index: Int): UUID {
return UUID.fromString(row.getString(index))
}
}
@EntityJdbc
@Table("entities")
data class Entity(
@Id @Mapping(ColumnMapper::class) val id: UUID,
val name: String
)
@Repository
interface EntityRepository : JdbcRepository {
@Query("SELECT id, name FROM entities")
fun findAll(): List<Entity>
}
Параметр¶
Если требуется преобразовать значение параметра запроса в ручную, предлагается использовать JdbcParameterColumnMapper
:
public final class ParameterMapper implements JdbcParameterColumnMapper<UUID> {
@Override
public void set(PreparedStatement stmt, int index, @Nullable UUID value) throws SQLException {
if (value != null) {
stmt.setString(index, value.toString());
}
}
}
@Repository
public interface EntityRepository extends JdbcRepository {
@Query("SELECT id, name FROM entities WHERE id = :id")
List<Entity> findById(@Mapping(ParameterMapper.class) UUID id);
}
class ParameterMapper : JdbcParameterColumnMapper<UUID?> {
@Throws(SQLException::class)
override fun set(stmt: PreparedStatement, index: Int, value: UUID?) {
if (value != null) {
stmt.setString(index, value.toString())
}
}
}
@Repository
interface EntityRepository : JdbcRepository {
@Query("SELECT id, name FROM entities WHERE id = :id")
fun findById(@Mapping(ParameterMapper::class) id: UUID): List<Entity>
}
Поддерживаемые типы¶
Список поддерживаемых типов для аргументов/возвращаемых значений из коробки
- void
- boolean / Boolean
- short / Short
- int / Integer
- long / Long
- double / Double
- float / Float
- byte[]
- String
- BigDecimal
- UUID
- LocalDate
- LocalTime
- LocalDateTime
- OffsetTime
- OffsetDateTime
Выборка по списку¶
Иногда требуется выборка по списку значений из базы, на уровне драйвера все эти параметры должны быть отдельно проставлены, так как длина списка не известна это не самая очевидная задача так как Kora старается делать все преобразования во время компиляции и убирать любые преобразования строк особенно в SQL во время выполнения, для такой функциональности потребуется добавить самостоятельный преобразователь параметров.
На данный момент точно известно, что можно легко добавить поддержку таких параметров без ручного управления в такие популярные базы данных как Postgres/Oracle.
Из коробки Kora не предоставляет конвертацию таких параметров, но его легко добавить самостоятельно, ниже показан пример для Postgres
:
@Component
class ListOfStringJdbcParameterMapper implements JdbcParameterColumnMapper<List<String>> {
@Override
public void set(PreparedStatement stmt, int index, List<String> value) throws SQLException {
String[] typedArray = value.toArray(String[]::new);
Array sqlArray = stmt.getConnection().createArrayOf("VARCHAR", typedArray);
stmt.setArray(index, sqlArray);
}
}
@Repository
public interface EntityRepository extends JdbcRepository {
@Query("SELECT id, name FROM entities WHERE id = ANY(:ids)")
List<Entity> findAllByIds(@Mapping(ListOfStringJdbcParameterMapper.class) List<String> ids);
}
@Component
class ListOfStringJdbcParameterMapper : JdbcParameterColumnMapper<List<String>> {
@Throws(SQLException::class)
override fun set(stmt: PreparedStatement, index: Int, value: List<String>) {
val typedArray = value.toTypedArray()
val sqlArray = stmt.connection.createArrayOf("VARCHAR", typedArray)
stmt.setArray(index, sqlArray)
}
}
@Repository
interface EntityRepository : JdbcRepository {
@Query("SELECT id, name FROM entities WHERE id = ANY(:ids)")
fun findAllByIds(@Mapping(ListOfStringJdbcParameterMapper::class) ids: List<String>): List<Entity>
}
Созданный идентификатор¶
Если необходимо получить в качестве результата созданные базой данных первичные ключи сущности,
предлагается использовать аннотацию @Id
над методом, где тип возвращаемого значения является идентификаторами.
Такой подход работает и для @Batch
запросов.
Транзакции¶
Для выполнения блокирующих запросов в Kora есть интерфейс JdbcConnectionFactory
,
который предоставляется в методе в рамках контракта JdbcRepository
.
Все методы репозитория вызванные в рамках лямбды транзакции будут выполнены в этой самой транзакции.
Для того чтобы выполнять запросы транзакционно, можно использовать контракт inTx
:
@Component
public final class SomeService {
private final EntityRepository repository;
public SomeService(EntityRepository repository) {
this.repository = repository;
}
public List<Entity> saveAll(Entity one, Entity two) {
return repository.getJdbcConnectionFactory().inTx(() -> {
repository.insert(one); //(1)!
// do some work
repository.insert(two); //(2)!
return List.of(one, two);
});
}
}
- Будет выполнено в рамках транзакции либо откатится если вся лямбра выкинет исключение
- Будет выполнено в рамках транзакции либо откатится если вся лямбра выкинет исключение
@Component
class SomeService(private val repository: EntityRepository) {
fun saveAll(one: List<Entity>, two: List<Entity>): List<Entity> {
return repository.jdbcConnectionFactory.inTx(SqlFunction1 {
repository.insert(one) //(1)!
// do some work
repository.insert(two) //(2)!
one + two
})
}
}
- Будет выполнено в рамках транзакции либо откатится если вся лямбра выкинет исключение
- Будет выполнено в рамках транзакции либо откатится если вся лямбра выкинет исключение
Уровень изоляции берется из конфигурации dsProperties
пула Hikari,
либо можно самостоятельно поменять его через java.sql.Connection
перед выполнением запросов.
Ручное управление¶
Если для запроса нужна какая-то более сложная логика, либо запросы вне репозитория, можно использовать java.sql.Connection
:
@Component
public final class SomeService {
private final EntityRepository repository;
public SomeService(EntityRepository repository) {
this.repository = repository;
}
public List<Entity> saveAll(Entity one, Entity two) {
return repository.getJdbcConnectionFactory().inTx(connection -> {
// do some work
return List.of(one, two);
});
}
}
Сигнатуры¶
Доступные сигнатуры для методов репозитория из коробки:
Под T
подразумевается тип возвращаемого значения, либо List<T>
, либо Void
, либо UpdateCount
.
T myMethod()
@Nullable T myMethod()
Optional<T> myMethod()
CompletionStage<T> myMethod()
CompletionStage (надо предоставитьExecutor
)Mono<T> myMethod()
Project Reactor (надо предоставитьExecutor
и подключить зависимость)
Под T
подразумевается тип возвращаемого значения, либо T?
, либо List<T>
, либо Unit
, либо UpdateCount
.
myMethod(): T
suspend myMethod(): T
Kotlin Coroutine (надо предоставитьExecutor
и подключить зависимость какimplementation
)