Spring

JOOQ를 사용한 Spring Boot와 Spring Data JPA의 기술적 차이점 물어보다

기록만이살길 2022. 11. 3. 12:40
반응형

JOOQ를 사용한 Spring Boot와 Spring Data JPA의 기술적 차이점 물어보다

1. 질문(문제점):

JOOQ와 함께 Spring Boot를 통해 Spring Data JPA를 언제 사용하고 그 반대의 경우도 마찬가지입니까?

Spring Data JPA는 기본 CRUD 쿼리를 완료하는 데 사용할 수 있지만 JOOQ를 사용하면 더 쉽게 만드는 동안 복잡한 조인 쿼리에는 실제로 사용할 수 없다는 것을 알고 있습니까?

편집: jooq와 함께 Spring 데이터 jpa를 모두 사용할 수 있습니까?

2. 해결방안:

귀하의 질문에 쉬운 대답은 없습니다. 나는 그 주제에 대해 몇 가지 연설을 했습니다. 때로는 프로젝트에 둘 다 있어야 하는 합당한 이유가 있습니다.

편집: 방언 및 데이터 유형과 관련하여 데이터베이스에 대한 IMHO 추상화는 여기서 요점이 아닙니다!! jOOQ는 주어진 대상 언어에 대해 SQL을 생성하는 데 꽤 좋은 일을 합니다. JPA/Hibernate도 마찬가지입니다. jOOQ는 Postgres 또는 Oracle과 같은 모든 기능이 없는 데이터베이스의 기능을 에뮬레이트하기 위해 추가 마일을 사용한다고 말할 수 있습니다. 여기서 질문은 " SQL 이 제공하는 모든 것으로 쿼리를 직접 표현할 수 있기를 원합니까, 아니면 JPA가 표현할 수 있는 것에 만족합니까?"입니다.

다음은 둘 다 함께 실행하는 예입니다. 여기에 사용자 정의 확장이 있는 Spring Data JPA 제공 저장소가 있습니다(인터페이스와 구현이 필요함). Spring 컨텍스트가 JPA EntityManager와 jOOQ 컨텍스트를 모두 주입하도록 했습니다. 그런 다음 jOOQ를 사용하여 쿼리를 만들고 JPA를 통해 실행합니다. 왜요? JPA에서는 해당 쿼리를 표현하는 것이 불가능하기 때문입니다("가장 많이 들어본 것 주세요"는 가장 많은 개수가 아니라 여러 개일 수 있음).

JPA를 통해 쿼리를 실행하는 이유는 간단합니다. 다운스트림 사용 사례에서는 JPA 엔터티를 전달해야 할 수 있습니다. jOOQ는 물론 이 쿼리 자체를 실행할 수 있으며 원하는 대로 레코드 작업을 하거나 항목을 매핑할 수 있습니다. 그러나 두 기술을 모두 사용할 수 있는지에 대해 구체적으로 물었으므로 이것이 좋은 예라고 생각했습니다.

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import org.jooq.DSLContext;
import org.jooq.Field;
import org.jooq.Record;
import org.jooq.SelectQuery;
import org.jooq.conf.ParamType;
import org.jooq.impl.DSL;
import org.springframework.data.repository.CrudRepository;

import static ac.simons.bootiful_databases.db.tables.Genres.GENRES;
import static ac.simons.bootiful_databases.db.tables.Plays.PLAYS;
import static ac.simons.bootiful_databases.db.tables.Tracks.TRACKS;
import static org.jooq.impl.DSL.count;
import static org.jooq.impl.DSL.rank;
import static org.jooq.impl.DSL.select;

public interface GenreRepository extends 
        CrudRepository<GenreEntity, Integer>, GenreRepositoryExt {

    List<GenreEntity> findAllByOrderByName();
}

interface GenreRepositoryExt {
    List<GenreWithPlaycount> findAllWithPlaycount();

    List<GenreEntity> findWithHighestPlaycount();
}

class GenreRepositoryImpl implements GenreRepositoryExt {

    private final EntityManager entityManager;

    private final DSLContext create;

    public GenreRepositoryImpl(EntityManager entityManager, DSLContext create) {
        this.entityManager = entityManager;
        this.create = create;
    }

    @Override
    public List<GenreWithPlaycount> findAllWithPlaycount() {
        final Field<Integer> cnt = count().as("cnt");
        return this.create
                .select(GENRES.GENRE, cnt)
                .from(PLAYS)
                .join(TRACKS).onKey()
                .join(GENRES).onKey()
                .groupBy(GENRES.GENRE)
                .orderBy(cnt)
                .fetchInto(GenreWithPlaycount.class);
    }

    @Override
    public List<GenreEntity> findWithHighestPlaycount() {
        /*
        select id, genre 
        from (
          select g.id, g.genre, rank() over (order by count(*) desc) rnk 
            from plays p
            join tracks t on p.track_id = t.id
            join genres g on t.genre_id = g.id
           group by g.id, g.genre
        ) src
        where src.rnk = 1;
        */
        final SelectQuery<Record> sqlGenerator = 
        this.create.select()
                .from(
                        select(
                                GENRES.ID, GENRES.GENRE, 
                                rank().over().orderBy(count().desc()).as("rnk")
                        ).from(PLAYS)
                        .join(TRACKS).onKey()
                        .join(GENRES).onKey()
                        .groupBy(GENRES.ID, GENRES.GENRE)
                ).where(DSL.field("rnk").eq(1)).getQuery();

         // Retrieve sql with named parameter
        final String sql = sqlGenerator.getSQL(ParamType.NAMED);
        // and create actual hibernate query
        final Query query = this.entityManager.createNativeQuery(sql, GenreEntity.class);
        // fill in parameter
        sqlGenerator.getParams().forEach((n, v) -> query.setParameter(n, v.getValue()));
        // execute query
        return query.getResultList();
    }
}

나는 이것에 대해 두 번 말했습니다. 이러한 기술에는 은색 총알이 없으며 때로는 매우 얇은 판단입니다.

전체 이야기는 여기: https://speakerdeck.com/michaelsimons/live-with-your-sql-fetish-and-choose-the-right-tool-for-the-job

녹화된 버전: https://www.youtube.com/watch?v=NJ9ZJstVL9E

전체 작업 예제는 https://github.com/michael-simons/bootiful-databases 입니다.

62055237
반응형