author-avatar
Dominik Martyniak
JOOQ 14 minut

Jak Zintegrować JOOQ z JPA i Hibernate w Spring Boot: Przewodnik Krok po Kroku

JOOQ to potężne narzędzie w rękach dewelopera, które ułatwia komunikację i pracę z bazami danych. Wielokrotnie spotykałem się z problemem tworzenia bardziej złożonych zapytań warunkowych w Spring Boocie . Do wyboru miałem trzy rozwiązania, z których każde miało swoje wady i zalety.
Pierwsze z nich to stare, dobre JPA, które w lepszym lub gorszym stopniu zaspokajało moje potrzeby w zakresie wyciągania danych z bazy. Gdy zapytanie musiało zostać rozszerzone o dodatkowe warunki, z pomocą przychodziło QueryDSL, które w swojej prostocie i rozszerzalności JPA pozwalało na sprawniejsze zarządzanie zapytaniami do bazy. Jednak kiedy nie miałem już innego wyjścia, sięgałem po zapytania natywne. Niestety, utrzymywanie i rozwijanie ich to nie lada sztuka. Być może kiedyś podzielę się technikami, które udało mi się opanować w rozdzielaniu poszczególnych sekcji zapytań, ale na razie nie chcę wracać do tych rozważań – jest to dość uciążliwe, choć działa.
Szukając kolejnych możliwości, natrafiłem na JOOQ.


Dlaczego JOOQ?

 

JOOQ (Java Object Oriented Querying) to biblioteka, która oferuje programistom możliwość pisania zapytań SQL w sposób typowo obiektowy, co znacznie upraszcza proces ich tworzenia i zarządzania nimi. Dzięki JOOQ możemy generować zapytania SQL bezpośrednio z kodu Javy, co pozwala na lepszą kontrolę nad nimi oraz ich łatwiejsze utrzymanie i rozwijanie.


JOOQ jest bardzo dobrze rozwijanym projektem, który cieszy się dużym wsparciem społeczności oraz regularnymi aktualizacjami. Biblioteka ta integruje się doskonale ze Spring Framework, co umożliwia łatwe włączenie jej do istniejących projektów opartych na Springu. Dzięki temu możemy korzystać z dobrodziejstw JOOQ w aplikacjach zbudowanych na tej popularnej platformie.


Co więcej, JOOQ jest mocnym projektem, który ma stabilną pozycję na rynku narzędzi do pracy z bazami danych. Regularne aktualizacje, duża społeczność użytkowników oraz wsparcie ze strony twórców gwarantują, że JOOQ nie zakończy swojego żywota w najbliższym czasie. Jest to zatem bezpieczna inwestycja dla każdego dewelopera szukającego solidnych i długoterminowych rozwiązań do pracy z bazami danych.

JOOQ nie ma również problemu z współpracą z innymi narzędziami i bibliotekami, takimi jak JPA, QueryDSL czy Hibernate. Dzięki swojej elastyczności i modularności, JOOQ może być używany równolegle z tymi technologiami, co pozwala na wybór najlepszego narzędzia do konkretnego zadania. Integracja z JPA, QueryDSL czy Hibernate daje programistom dodatkową swobodę i możliwość korzystania z zalet każdej z tych technologii w jednym projekcie.


Implementacja JOOQ z użyciem klas JPA



W dzisiejszym artykule pokażę, jak na podstawie już gotowych encji JPA można zintegrować JOOQ i wygenerować odpowiednie klasy, które umożliwią korzystanie z zalet obu technologii. Dzięki temu podejściu będziemy mogli wykorzystać istniejące modele danych i jednocześnie korzystać z potężnych możliwości, jakie oferuje JOOQ do tworzenia i zarządzania zapytaniami SQL.

W projekcie używamy różnych zależności, zarówno dla głównego modułu, jak i modułu entity. Poniżej znajduje się szczegółowa konfiguracja obu modułów:

Główny moduł:

plugins {
    id "org.springframework.boot"
    id 'nu.studer.jooq' version '9.0'
}

dependencies {
    implementation project(':entity')

    // SPRING
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    runtimeOnly 'org.postgresql:postgresql'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

    // JOOQ
    implementation "org.jooq:jooq-meta-extensions-hibernate:3.19.10"
    implementation "org.jooq:jooq:3.19.10"
    implementation "org.jooq:jooq-codegen:3.19.10"

    // JOOQ Generator
    jooqGenerator "org.jooq:jooq-meta-extensions-hibernate:3.19.10"
    jooqGenerator project(':entity')
}

 

Moduł entity:

 

dependencies {
    implementation "org.springframework.boot:spring-boot-starter-data-jpa"
}

 

Zgodnie z dokumentacją JOOQ, jeśli chcemy korzystać z API JPA i Hibernate do generowania naszych klas, musimy zapewnić, że encje są w osobnym module. Jest to istotne, ponieważ JOOQ wymaga dostępu do metadanych encji, aby móc generować odpowiednie klasy. Na końcu artykułu jak zawsze znajdziecie link do repozytorium z gotowym projektem.

Więcej szczegółów na ten temat można znaleźć w dokumentacji JOOQ.


Jak działa JOOQ z JPA i Hibernate?

 

Podczas budowania projektu, JOOQ automatycznie stworzy bazę H2, która dzięki Hibernate utworzy schemat naszej bazy danych na podstawie encji JPA. To pozwoli na wygenerowanie klas JOOQ, które będą odzwierciedlały strukturę naszej bazy danych. Proces ten wygląda następująco:

  • Tworzenie encji JPA: Definiujemy encje JPA, które reprezentują tabele w bazie danych.
  • Budowanie projektu: Podczas procesu budowania, JOOQ konfiguruje tymczasową bazę H2.
  • Generowanie schematu bazy danych: Hibernate, korzystając z definicji encji JPA, tworzy schemat bazy danych w H2.
  • Generowanie klas JOOQ: JOOQ, na podstawie schematu bazy danych, generuje klasy, które mogą być używane do budowania zapytań SQL w sposób typowo obiektowy.

Przykładowa konfiguracja w pliku build.gradle dla generowania klas JOOQ może wyglądać tak:

jooq {
    version = '3.19.10'
    edition = nu.studer.gradle.jooq.JooqEdition.OSS

    configurations {
        main {
            generateSchemaSourceOnCompilation = true

            generationTool {

                generator {
                    name = 'org.jooq.codegen.DefaultGenerator'
                    database {
                        name = 'org.jooq.meta.extensions.jpa.JPADatabase'
                        properties {
                            property {
                                key = 'packages'
                                value = 'pl.devset.jooq'
                            }
                            property {
                                key = 'useAttributeConverters'
                                value = true
                            }
                            property {
                                key = 'unqualifiedSchema'
                                value = 'none'
                            }
                        }
                    }
                    generate {
                        deprecated = false
                        records = true
                        immutablePojos = true
                        fluentSetters = true
                    }
                    target {
                        packageName = 'pl.devset.generate.jooq'
                        directory = 'build/generated-src/jooq/main'
                    }
                }
            }
        }
    }
}

 

Opis konfiguracji:

  • version: Określa wersję JOOQ, która będzie używana w projekcie.
  • edition: Wybiera edycję JOOQ, w tym przypadku Open Source (OSS).

Sekcja configurations:

  • main: Definiuje główną konfigurację JOOQ.
  • generateSchemaSourceOnCompilation: Umożliwia generowanie źródła schematu podczas kompilacji.

Sekcja generationTool:

  • generator: Główna konfiguracja generatora JOOQ.
  • name: Określa używany generator, tutaj jest to domyślny generator JOOQ (org.jooq.codegen.DefaultGenerator).
    Sekcja database:
  • name: Określa typ bazy danych używanej do generowania kodu, w tym przypadku jest to rozszerzenie JPA (org.jooq.meta.extensions.jpa.JPADatabase).
  • properties: Właściwości konfiguracji bazy danych.
  • key = 'packages': Określa pakiet, w którym znajdują się encje JPA (pl.devset.jooq).
  • key = 'useAttributeConverters': Ustawienie na true umożliwia używanie konwerterów atrybutów JPA.
  • key = 'unqualifiedSchema': Ustawienie na none oznacza brak niekwalifikowanego schematu.

Sekcja generate:

  • deprecated: Ustawienie na false oznacza, że przestarzałe elementy nie będą generowane.
  • records: Ustawienie na true oznacza, że rekordy będą generowane.
  • immutablePojos: Ustawienie na true oznacza, że niezmienne POJO będą generowane.
  • fluentSetters: Ustawienie na true oznacza, że fluent settery będą generowane.

Sekcja target:

  • packageName: Określa pakiet docelowy dla wygenerowanego kodu (pl.devset.jooq).
  • directory: Określa katalog docelowy dla wygenerowanego kodu (build/generated-src/jooq/main).

 

Więcej szczegółów na temat tego procesu można znaleźć w dokumentacji JOOQ. Dzięki takiemu podejściu możemy w pełni wykorzystać potencjał JOOQ, jednocześnie korzystając z zalet JPA i Hibernate.

 

Tworzenie zapytań w JOOQ

 

package pl.devset.jooq;

import org.jooq.DSLContext;
import org.jooq.Result;
import org.springframework.stereotype.Repository;
import pl.devset.generate.jooq.tables.Users;
import pl.devset.generate.jooq.tables.records.UsersRecord;

@Repository
public class UserRepo {
    private final DSLContext dslContext;

    public UserRepo(DSLContext dslContext) {
        this.dslContext = dslContext;
    }

    void selectUsers(){

        // Przykładowe zapytanie SELECT dla tabeli users
        Result<UsersRecord> result = dslContext.selectFrom(Users.USERS)
                .fetch();

        // Przetwarzanie wyników
        for (UsersRecord user : result) {
            System.out.println("ID: " + user.getId());
            System.out.println("First Name: " + user.getFirstName());
            System.out.println("Last Name: " + user.getLastName());
        }
    }

}

 

Opis poszczególnych elementów kodu:

 

Importy:

  • org.jooq.DSLContext: Główny interfejs JOOQ do budowania i wykonywania zapytań SQL.
  • org.jooq.Result: Reprezentuje wynik zapytania JOOQ.
  • pl.devset.generate.jooq.tables.Users: Klasa wygenerowana przez JOOQ, która reprezentuje tabelę users.
  • pl.devset.generate.jooq.tables.records.UsersRecord: Klasa wygenerowana przez JOOQ, która reprezentuje rekord tabeli users.

 

DSLContext:

Główny punkt wejścia do JOOQ. Używany do budowania i wykonywania zapytań SQL.
Inicjalizowany za pomocą konstruktoru klasy, który przyjmuje DSLContext jako argument. Konstruktor jest używany do wstrzykiwania zależności przez Springa.
DSLContext jest wstrzykiwany do klasy za pomocą konstruktora, co umożliwia budowanie i wykonywanie zapytań za pomocą JOOQ.


Metoda selectUsers:

Definiuje zapytanie SELECT dla tabeli users za pomocą JOOQ.
dslContext.selectFrom(Users.USERS).fetch(): Wykonuje zapytanie SELECT i pobiera wszystkie rekordy z tabeli users. Wynik zapytania jest przechowywany w obiekcie typu Result<UsersRecord>. Ktory również został wyegenrowany porzez JOOQ.

 

Efekt wywołania metody:

2024-06-23T13:47:09.410+02:00  INFO 15968 --- [           main] o.j.i.D.logVersionSupport                : Version                  : Database version is supported by dialect POSTGRES: 15.2 (Debian 15.2-1.pgdg110+1)
ID: 1
First Name: Dominik
Last Name: DevSet

 

Jeżeli macie błąd z generowanymi nazwami:

Caused by: org.springframework.jdbc.BadSqlGrammarException: jOOQ; bad SQL grammar [select "USERS"."ID", "USERS"."FIRST_NAME", "USERS"."LAST_NAME" from "USERS"]

 

Dodajcie tego beana konfiguracji

 

package pl.devset.jooq;

import org.jooq.conf.RenderQuotedNames;
import org.jooq.impl.DefaultConfiguration;
import org.springframework.boot.autoconfigure.jooq.DefaultConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Configuring the dialect and building JOOQ classes
 */
@Configuration
public class CustomGeneratorStrategy {
    @Bean
    public DefaultConfigurationCustomizer configurationCustomiser() {
        return (DefaultConfiguration c) -> c.settings()
                .withRenderQuotedNames(
                        RenderQuotedNames.EXPLICIT_DEFAULT_UNQUOTED
                );
    }
}

 

Podsumowanie:

 

W tym artykule omówiliśmy, jak zintegrować JOOQ z JPA i Hibernate w projekcie Spring Boot, aby uprościć proces tworzenia i zarządzania złożonymi zapytaniami SQL. Przedstawiliśmy korzyści płynące z używania JOOQ, takie jak możliwość pisania zapytań SQL w sposób obiektowy, lepsza kontrola nad zapytaniami oraz łatwiejsze ich utrzymanie i rozwijanie.

Pokazaliśmy również, jak skonfigurować projekt, dodając odpowiednie zależności w głównym module oraz module entity. Wyjaśniliśmy, dlaczego ważne jest, aby encje JPA znajdowały się w osobnym module, co pozwala JOOQ na dostęp do metadanych encji i generowanie odpowiednich klas.

Omówiliśmy proces generowania schematu bazy danych za pomocą tymczasowej bazy H2 podczas budowania projektu oraz pokazaliśmy przykładową konfigurację pliku build.gradle dla JOOQ. Na końcu artykułu przedstawiliśmy prosty przykład zapytania SELECT dla tabeli users, pokazując, jak używać JOOQ do wykonywania zapytań SQL i przetwarzania wyników.

Zachęcamy do zapoznania się z dokumentacją JOOQ, aby uzyskać więcej szczegółowych informacji na temat tej potężnej biblioteki oraz do odwiedzenia repozytorium z gotowym projektem, które znajduje się na końcu artykułu. Dzięki JOOQ, JPA i Hibernate można stworzyć elastyczne, skalowalne i łatwe w utrzymaniu aplikacje, które efektywnie zarządzają danymi w bazach SQL.

Link do repo.

 

3
[spring boot, jooq, jpa]

Więcej od Dominik Martyniak

Więcej artykułów