jOOQ
Was ist jOOQ?⚑
jOOQ bietet eine typesichere, DSL-aehnliche API, mit der SQL-Abfragen direkt umgesetzt werden koennen. Anders als andere ORM-Frameworks wie zB Hibernate, setzt es hier darauf SQL auf eine praegnaten und saubere Weise direkt in den Code zu integrieren, anstatt SQL zu abstrahieren.
Vorteile⚑
-
Typesicherheit: Jede Abfrage wird kompiliert, sodass Fehler wie falsche Spaltennamen oder ungueltige Datentypen waehrend der Kompilierung erkannt werden.
-
SQL-Unterstuetzung: Unterstuetzt die meisten SQL-Dialekte und erlaubt SQL nativ zu schreiben.
-
Erzeugung von Code: Generiert Klassen fuer Tabellen, Spalten und anderen Elemente. DAdurch kann das Objekt direkt verwendet werden, anstatt strings fuer die SQL-Abfragen zu schreiben.
-
Flexibilitaet: Man muss sich sich nicht an ORM-Konventionen wie Entities und LazyLoading halten.
-
Leichte Integration: Funktioniert mit bestehenden JDBC-Treibern und mit anderen Frameworks wie SpringBoot.
Nachteile⚑
-
SQL-Lastig: Erfordert SQL-Kenntnisse und direkte SQL-Integration in den Code.
-
Kein automatisches Caching: Benoetigt man ein Caching muss dieses selbst implementiert oder durch ein separates Framework wie Redis eingefuehrt werden. Im Vergleich: Hibernate bietet ein integriertes First- und Second-Level-Caching an.
-
Codegenerierung: Die benoetigten Klassen muessen kompiliert werden und ist damit nicht im NoSQL-Bereich geeginet oder wenn dynamische Datenbankstrukturen notwendig sind.
-
Potenzielle SQL-Injektions: Auch wenn qOOQ typsicher ist, kann man durch die Implementierung roher SQL-Abfragen das Risiko fuer SQL-Injektions erhoehen - hier ist vorsicht bei der Entwicklung geboten.
Wann setzte ich jOOQ ein?⚑
Es ist ideal wenn:
- SQL direkt und typesicher eingesetet werden soll
- man komplexe und massgeschneiderte Abfragen benoetigt
- man die kontrolle ueber die Datenbank-Interaktionen behalten moechte
Es ist nicht ideal wenn:
- man nur einfache CRUD-Operationen benoetigt
- Caching oder ORM-Funktionen wie LazyLoading benoetigt werden
- eine dynamische DAtenbankstruktur vorliegt
Wie funktioniert jOOQ?⚑
jOOQ generiert die Klassen (z.B. fuer Tabellen, Views, Sequenzen) basierend auf der Datenbankstruktur. Dieser Prozess funktioniert wie folgt:
- Verbindung zur Datenbank
- Analyse der Struktur (Tabellen, Spalten, Typen, Constraints)
- Codegenerierung basierend auf den Metadaten, darunter Tabellen- und Record-Klassen
- Speicherung in
target/generated-sources/jooq
Was wird fuer die Generierung benoetigt?⚑
- Maven-Plugin
- Datenbankverbindung - Details wie JDBC-Url, Benutzername, Passwort
- Generator-Konfiguration - Welche Datenbank wird verwendet, Ausschluss von Tabellen und Schemas, Zielort
Beispiel:⚑
Folgende Tabelle wurde erzeugt:
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL
);
jOOQ generiert⚑
Tabellenklasse:Users
package com.example.jooqdemo.generated.tables
import org.jooq.impl.TableImpl
import org.jooq.TableField
import org.jooq.impl.DSL
import com.example.jooqdemo.generated.tables.records.UserRecord
object Users : TableImpl<UserRecord>("users") {
val ID: TableField<UserRecord, Long> = createField("id", DSL.bigint(), this)
val NAME: TableField<UserRecord, String> = createField("name", DSL.varchar(100), this)
val EMAIL: TableField<UserRecord, String> = createField("email", DSL.varchar(100), this)
}
POJO:User
package com.example.jooqdemo.generated.tables.pojos
data class User(
var id: Long? = null,
var name: String? = null,
var email: String? = null
)
Record:UserRecord
package com.example.jooqdemo.generated.tables.records
import org.jooq.impl.UpdatableRecordImpl
class UserRecord : UpdatableRecordImpl<UserRecord>(Users) {
var id: Long? = null
var name: String? = null
var email: String? = null
}
Nutzung⚑
package com.example.jooqdemo.config
import org.jooq.DSLContext
import org.jooq.impl.DSL
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import javax.sql.DataSource
@Configuration
class JooqConfig(private val dataSource: DataSource) {
@Bean
fun dslContext(): DSLContext {
return DSL.using(dataSource.connection)
}
}
package com.example.jooqdemo.repository
import com.example.jooqdemo.generated.tables.Users
import com.example.jooqdemo.generated.tables.Orders
import com.example.jooqdemo.generated.tables.pojos.User
import org.jooq.DSLContext
import org.springframework.stereotype.Repository
@Repository
class UserRepository(private val dsl: DSLContext) {
// INSERT: Benutzer hinzufügen
fun addUser(name: String, email: String): Int {
return dsl.insertInto(Users.USERS)
.columns(Users.USERS.NAME, Users.USERS.EMAIL)
.values(name, email)
.execute()
}
// UPDATE: Benutzer-E-Mail aktualisieren
fun updateUserEmail(userId: Long, newEmail: String): Int {
return dsl.update(Users.USERS)
.set(Users.USERS.EMAIL, newEmail)
.where(Users.USERS.ID.eq(userId))
.execute()
}
// DELETE: Benutzer löschen
fun deleteUserById(userId: Long): Int {
return dsl.deleteFrom(Users.USERS)
.where(Users.USERS.ID.eq(userId))
.execute()
}
// JOIN: Benutzer mit Bestellungen abrufen
fun findUsersWithOrders(): List<Pair<User, Int>> {
return dsl.select(Users.USERS.asterisk(), Orders.ORDERS.ID.count())
.from(Users.USERS)
.join(Orders.ORDERS).on(Users.USERS.ID.eq(Orders.ORDERS.USER_ID))
.groupBy(Users.USERS.ID)
.fetch { record ->
val user = record.into(User::class.java)
val orderCount = record.get(Orders.ORDERS.ID.count())
user to orderCount
}
}
// Raw SQL: Manuelle SQL-Abfrage
fun rawQuery(): List<User> {
return dsl.fetch("SELECT * FROM users WHERE email LIKE ?", "%example.com%")
.into(User::class.java) // Mappe das Ergebnis auf das `User`-POJO
}
}
package com.example.jooqdemo.service
import com.example.jooqdemo.repository.UserRepository
import com.example.jooqdemo.generated.tables.pojos.User
import org.springframework.stereotype.Service
@Service
class UserService(private val userRepository: UserRepository) {
fun addUser(name: String, email: String): Int {
return userRepository.addUser(name, email)
}
fun updateUserEmail(userId: Long, newEmail: String): Int {
return userRepository.updateUserEmail(userId, newEmail)
}
fun deleteUserById(userId: Long): Int {
return userRepository.deleteUserById(userId)
}
fun findUsersWithOrders(): List<Pair<User, Int>> {
return userRepository.findUsersWithOrders()
}
fun rawQuery(): List<User> {
return userRepository.rawQuery()
}
}