Kihagyás

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

pool size = (core_count * 2) + effective_spindle_count

4 magos szerver, 1 DB: 4 * 2 + 1 = 9maximum-pool-size: 10 teljesen rendben.


Query típusok – mikor mit használj

JPA Repository  →  EntityManager  →  JdbcTemplate  →  dataSource.getConnection() (szinte soha)
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 countQuery kö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.