Spring Boot with JOOQ 和 Spring Data JPA 的技术区别

ph-*_*ett 14 spring jooq spring-data spring-data-jpa spring-boot

您什么时候将 Spring Data JPA 用于 Spring Boot 和 JOOQ,反之亦然?

我知道 Spring Data JPA 可用于完成基本的 CRUD 查询,但不适用于复杂的连接查询,而使用 JOOQ 使其更容易?

编辑:您可以将 Spring 数据 jpa 与 jooq 一起使用吗?

San*_*erg 8

恕我直言,如果你想要一个以数据库为核心的执行和可维护的应用程序,你不想抽象出你正在使用数据库的事实。JOOQ 为您提供完全控制,因为您可以在代码中读取和编写实际查询,但具有类型安全性。

JPA 包含 OO 模型,这与数据库在所有情况下的工作方式都不匹配,这可能会导致意外查询,例如 N+1,因为您在字段上放置了错误的注释。如果您没有给予足够的关注,这将导致在扩展您的应用程序时出现性能问题。JPA Criteria 有点帮助,但它仍然难以编写和阅读。

因此,使用 JPA,您首先用 SQL 编写查询,然后用半天时间将其转换为 Criteria。在使用这两个框架多年后,即使是简单的 CRUD 应用程序,我也会使用 JOOQ(因为没有简单的 CRUD 应用程序这样的东西:-))。

编辑:我认为您不能将 JPA 与 JOOQ 混合使用,问题是,您为什么要这样做?他们都使用不同的方法,所以只需选择一个。学习一个框架的复杂性已经够难的了。

  • 问题是关于 [Spring Data JPA](https://spring.io/projects/spring-data-jpa),而不仅仅是 JPA/Hibernate。Spring Data 正在 JPA(特别是存储库)上应用领域驱动设计原则,并将整个过程集成到 Spring 生态系统中。如果您不了解 DDD,我建议您在对 JPA 做出假设之前先阅读它。并回答“你为什么想要这样做?”的问题。因为通常情况下,我不会关心我的数据库是什么样子。 (2认同)

Mic*_*ons 8

你的问题没有简单的答案。我已经就该主题进行了几次演讲。有时有充分的理由在一个项目中同时拥有两者。

编辑:恕我直言,关于方言和数据类型的数据库抽象不是这里的重点!!jOOQ 在为给定的目标方言生成 SQL 方面做得非常好 - JPA / Hibernate 也是如此。我什至会说 jOOQ 为模拟没有像 Postgres 或 Oracle 那样的所有花里胡哨的数据库的函数做了额外的努力。这里的问题是“我是否希望能够用SQL提供的所有内容自己表达查询,或者我对 JPA 可以表达的内容感到满意?”

这是一个同时运行两者的示例。我有一个 Spring Data JPA 提供的存储库,这里有一个自定义扩展(需要接口和实现)。我让 Spring 上下文同时注入 JPAEntityManager和 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我多次谈到这个问题。在这些技术中没有灵丹妙药,有时这是一个非常薄弱的​​判断:

完整的演讲在这里: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

  • @lmk 我认为使用 jOOQ 你可以获得类型检查的额外信心,我相信这是在“nativeQuery”中所缺少的。 (2认同)