Spring Boot with JOOQ 和 Spring Data JPA 之间的技术差异

Posted

技术标签:

【中文标题】Spring Boot with JOOQ 和 Spring Data JPA 之间的技术差异【英文标题】:Technical difference between Spring Boot with JOOQ and Spring Data JPA 【发布时间】:2020-09-15 05:01:57 【问题描述】:

您何时将 Spring Data JPA 而非 Spring Boot 与 JOOQ 一起使用,反之亦然?

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

编辑:你可以将 Spring data jpa 与 jooq 一起使用吗?

【问题讨论】:

之所以使用JPA是为了独立于底层的Database。使用 JOOQ 的原因是利用底层数据库的全部功能。这两种方法都各有利弊。 可以同时使用吗?如果是这样,这样做的利弊是什么? 如果您关心性能和高级数据库功能,请使用 jooq。如果您想抽象出您的数据库并仅处理业务代码,请使用 JPA 好主。有人还在用吗?我一直不明白 @SeanPatrickFloyd:人们使用 jOOQ 是为了独立于底层的数据库产品,而且人们确实同时使用这两种产品,这在大型项目中并不少见 【参考方案1】:

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

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

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

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

【讨论】:

你觉得spring data jpa在某种程度上降低了使用/理解JPA的难度吗? Spring Data (JPA) 添加了各种很棒的功能,例如您可以注释的存储库、审计、查询您可能需要或不需要的 DSL,这取决于您要构建的内容。我不认为它降低了难度,因为您仍在使用 JPA,但顶部有一个额外的 Spring 层/意见。如果你想学习 JPA,我的建议是从一个简单的 JPA 应用程序开始。这样您就可以学习 JPA 范式,如果您需要 Spring Data 功能,您可以轻松添加它。 问题是关于Spring Data JPA,而不仅仅是JPA/Hibernate。 Spring Data 正在 JPA 上应用 domain-driven design 原则,特别是存储库,并将所有内容集成到 Spring 生态系统中。如果您不了解 DDD,我建议您先阅读它,然后再对 JPA 做出假设。并回答“你为什么要这样做?”这个问题。因为通常,我不会为我的数据库是什么样子而烦恼。【参考方案2】:

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

编辑:恕我直言,关于方言和数据类型的数据库抽象不是这里的重点!! 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。

【讨论】:

【参考方案3】:

以下是适合 JOOQ 时的官方解释:

https://www.jooq.org/doc/latest/manual/getting-started/jooq-and-jpa/

仅仅因为你在使用 jOOQ 并不意味着你必须在所有事情上都使用它!

将 jOOQ 引入使用 JPA 的现有应用程序时, 常见的问题总是:“我们应该用 jOOQ 替换 JPA 吗?”如何 我们继续这样做吗?”

请注意,jOOQ 不是 JPA 的替代品。将 jOOQ 视为 补充。 JPA(和一般的 ORM)尝试解决对象图 持久性问题。总之,这个问题是关于

将实体图从数据库加载到客户端内存中 在客户端中操作该图 将修改存储回 数据库由于上图变得越来越复杂,很多棘手 出现这样的问题:

用于加载和存储的 SQL DML 操作的最佳顺序是什么 实体?我们如何更有效地批处理命令?我们怎么能 在不妥协的情况下保持尽可能低的交易足迹 上酸?我们如何实现乐观锁定? jOOQ 只有一些 的答案。虽然 jOOQ 确实提供了可更新的记录来帮助 运行简单的 CRUD、批处理 API、乐观锁定功能、 jOOQ 主要侧重于执行实际的 SQL 语句。

SQL 是数据库交互的首选语言,当任何 下面给出:

您可以直接在 数据库 您使用 ETL 导入/导出数据 您运行复杂的业务 逻辑作为 SQL 查询只要 SQL 非常适合,jOOQ 就非常适合。 每当您操作和持久化对象图时,JPA 都是 很合身。

有时,最好将两者结合起来

【讨论】:

【参考方案4】:

Spring Data JPA 为您提供以下功能:

    ORM 层,允许您将数据库表视为 Java 对象。它允许您编写很大程度上与数据库无关的代码(您可以使用 mysql、Oracle、SQL Server 等),并且可以避免编写裸 SQL 时遇到的大部分容易出错的代码。 Unit of Work 模式。您看到这么多关于 C# 的文章解释什么是工作单元,而几乎没有关于 Java 的文章的一个原因是因为 JPA。 Java 已经有 15 年了。 C#,好吧,你永远不会知道。 Domain-driven designrepositories。 DDD 是一种面向对象软件的方法,它消除了您在数据库驱动的应用程序中经常看到的贫血域模型,实体对象只有数据和访问器方法(贫血模型),以及服务类中的所有业务逻辑.还有更多内容,但这是与 Spring Data JPA 相关的最重要的部分。 集成到 Spring 生态系统中,具有控制反转、依赖注入等功能。

另一方面,jOOQ 是一个实现active record pattern 的数据库映射库。它采用以 SQL 为中心的数据库操作方法,并为此使用 domain-specific language。

正如经常发生的那样,没有一个正确或更好的选择。如果您不关心您的数据库,Spring Data JPA 可以很好地工作。如果您乐于不做任何复杂的查询,那么 Spring Data JPA 就足够了。但是,一旦您需要在表之间进行连接,您会注意到 Spring Data JPA repository 对于某些操作确实不是很好的匹配。

正如@michael-simons 所提到的,有时将两者结合起来可能是最好的解决方案。

【讨论】:

以上是关于Spring Boot with JOOQ 和 Spring Data JPA 之间的技术差异的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 JOOQ 和 Spring-boot 2.0 进行手动事务管理?

带有 testcontainers 和 jOOQ 的 Spring Boot 不会注入 DSL 上下文

使用 Spring Boot 进行 JOOQ SQL 语法转换

jooQ spring boot Multiple Schema(读写拆分)

如何使用 Spring-Boot config 配置 JOOQ 设置?

带有 JOOQ 的 Spring Boot 收到一条消息“需要一个找不到的 'org.jooq.DSLContext' 类型的 bean”