HikariCP Spring Bootban – Összefoglaló
Hogyan működik?
A HikariCP egy connection pool – ahelyett hogy minden DB-kéréshez új JDBC kapcsolatot nyitna (ami lassú), előre létrehoz N darab kapcsolatot, és ezeket adja kölcsön a szálaknak, majd visszaveszi őket.
Spring Bootban automatikusan ez az alapértelmezett pool (2.x óta), nem kell külön dependency. Ha spring-boot-starter-data-jpa vagy spring-boot-starter-jdbc van a projektben, a HikariCP már benne van.
Application Thread
│
▼
HikariPool (pl. 10 connection)
[conn1] [conn2] ... [conn10]
│
▼
PostgreSQL / MySQL
Fontos beállítások
✅ Érdemes beállítani
spring:
datasource:
hikari:
maximum-pool-size: 10 # hány connection legyen a poolban
minimum-idle: 10 # legyen egyenlő a maximum-pool-size-zal (fixed pool)
connection-timeout: 30000 # max várakozás connection megszerzésére (ms)
idle-timeout: 600000 # meddig lehet idle egy connection (10 perc)
max-lifetime: 1800000 # max életkor (30 perc) – legyen KEVESEBB mint a DB timeout!
pool-name: MyAppPool # logokban azonosítható
leak-detection-threshold: 2000 # fejlesztésben mindig kapcsold be!
❌ Amit kerülj
| Beállítás | Miért kerüld |
|---|---|
connection-test-query minden esetben | Modern driverek (isValid()) gyorsabbak – csak legacy driverhez kell |
minimum-idle < maximum-pool-size | Elastic pool – felesleges overhead, Hikari maga is ellenzi |
max-lifetime > DB szerver wait_timeout | Stale connection exception production-ben |
Nagyon nagy maximum-pool-size | "Too many connections" anti-pattern |
connection-timeout: 0 | Végtelen várakozás, thread starvation |
Pool méretezés
4 magos szerver, 1 DB: 4 * 2 + 1 = 9 → maximum-pool-size: 10 teljesen rendben.
Query típusok – mikor mit használj
Repository @Query | EntityManager | JdbcTemplate | |
|---|---|---|---|
| Fix query | ✅ ideális | felesleges | felesleges |
| Dinamikus query | ❌ nem tud | ✅ ideális | alternatíva |
| Olvashatóság | legjobb | több boilerplate | közepes |
| Projection/DTO | ✅ interface projection | macerásabb | RowMapper |
| Tranzakcióban részt vesz | ✅ | ✅ | ✅ |
SELECT – jó gyakorlatok
// Repository native query – fix, egyszerű esetekre
@Query(
value = "SELECT * FROM users WHERE status = :status AND created_at > :since",
nativeQuery = true
)
fun findActiveUsersSince(
@Param("status") status: String,
@Param("since") since: LocalDateTime
): List<User>
// EntityManager – dinamikus query esetén
fun findByFilter(status: String, limit: Int): List<User> {
return em.createNativeQuery(
"SELECT * FROM users WHERE status = :status LIMIT :limit",
User::class.java
)
.setParameter("status", status)
.setParameter("limit", limit)
.resultList as List<User>
}
✅ SELECT best practice-ek
@Transactional(readOnly = true)– read query esetén mindig; Hibernate optimalizál, flush skip- Named paraméter (
:param) ne positional (?1) – olvashatóbb - Projection / DTO ha nem kell az egész entitás:
interface UserSummary {
val id: Long
val email: String
}
@Query(value = "SELECT id, email FROM users WHERE active = true", nativeQuery = true)
fun findSummaries(): List<UserSummary>
- Pagination esetén
countQuerykötelező:
@Query(
value = "SELECT * FROM users WHERE active = :active",
countQuery = "SELECT COUNT(*) FROM users WHERE active = :active",
nativeQuery = true
)
fun findAll(@Param("active") active: Boolean, pageable: Pageable): Page<User>
UPDATE / DELETE – jó gyakorlatok
// Repository – @Modifying kötelező!
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Transactional
@Query(value = "UPDATE users SET status = :status WHERE id = :id", nativeQuery = true)
fun updateStatus(@Param("id") id: Long, @Param("status") status: String): Int
// EntityManager – dinamikus esetben
@Transactional
fun deactivateOldUsers(before: LocalDateTime): Int {
return em.createNativeQuery(
"DELETE FROM users WHERE created_at < :before AND status = 'INACTIVE'"
)
.setParameter("before", before)
.executeUpdate() // nem getResultList()!
}
SELECT vs UPDATE/DELETE összehasonlítás
| SELECT | UPDATE/DELETE | |
|---|---|---|
@Modifying | nem kell | kötelező repo-nál |
@Transactional | readOnly = true ajánlott | sima @Transactional kell |
| cache invalidálás | nem gond | clearAutomatically = true |
| visszatérési érték | entitás / lista | Int (érintett sorok száma) |
Amit kerülj – native query-nél
❌ String concatenation – SQL injection
// SOHA
val query = "SELECT * FROM users WHERE name = '$name'"
// Mindig binding
"SELECT * FROM users WHERE name = :name"
❌ Native UPDATE/DELETE után Hibernate cache inkonzisztencia
// Veszélyes: native UPDATE után a Hibernate cache nem frissül
em.createNativeQuery("UPDATE users SET status = 'BANNED' WHERE id = :id")
.setParameter("id", userId)
.executeUpdate()
// Az ugyanabban a tranzakcióban betöltött User entitás még a régi státuszt látja!
// Megoldás: @Modifying(clearAutomatically = true, flushAutomatically = true)
❌ N+1 native query-vel
Ha az entitásnak lazy relation-jei vannak és native query adja vissza, a Hibernate szépen egyenként tölti be a kapcsolatokat. JOIN FETCH nem megy native-ban → vagy DTO-t adj vissza, vagy JPQL-t használj.
❌ dataSource.getConnection() közvetlen hívása
// Kerülendő – manuális connection kezelés, kikerüli a Spring tranzakciókezelést
val conn = dataSource.getConnection()
// ha exception → leak!
// Helyette: JdbcTemplate
jdbcTemplate.update("UPDATE users SET status = ? WHERE id = ?", status, id)
Connection leak detektálás
spring:
datasource:
hikari:
leak-detection-threshold: 2000 # ms – WARNING logot ír ha connection nem kerül vissza
Fejlesztésben mindig kapcsold be. Ha látod a logban, valahol hiányzik a @Transactional vagy nem záródik be a connection.