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

Technical difference between Spring Boot with JOOQ and Spring Data JPA

什么时候使用 Spring Data JPA 而不是 Spring 使用 JOOQ 启动,反之亦然?

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

编辑:你能同时使用 Spring 数据 jpa 和 jooq 吗?

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

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

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

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

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

编辑:恕我直言,关于方言和数据类型的数据库抽象不是这里的重点!! jOOQ 在为给定的目标方言生成 SQL 方面做得非常好——JPA/Hibernate 也是如此。我什至会说 jOOQ 在模拟没有像 Postgres 或 Oracle 这样的所有花里胡哨的数据库的功能方面更进一步。 这里的问题是"Do I want to be able to express a query myself with everything SQL has to offer or am I happy with what JPA can express?"

这里有一个 运行 两者结合的例子。我有一个 Spring Data JPA 提供的存储库,这里有一个自定义扩展(接口和实现是必需的)。我让 Spring 上下文同时注入 JPA EntityManager 和 jOOQ 上下文。然后我使用 jOOQ 创建查询并通过 JPA 运行 它们。 为什么?因为用 JPA 不可能表达有问题的查询("Give me the thing I listened the most" 这不是计数最高的那个,但可能是几个)。

我 运行 通过 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

JOOQ适配时的官方解释:

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

Just because you're using jOOQ doesn't mean you have to use it for everything!

When introducing jOOQ into an existing application that uses JPA, the common question is always: "Should we replace JPA by jOOQ?" and "How do we proceed doing that?"

Beware that jOOQ is not a replacement for JPA. Think of jOOQ as a complement. JPA (and ORMs in general) try to solve the object graph persistence problem. In short, this problem is about

Loading an entity graph into client memory from a database Manipulating that graph in the client Storing the modification back to the database As the above graph gets more complex, a lot of tricky questions arise like:

What's the optimal order of SQL DML operations for loading and storing entities? How can we batch the commands more efficiently? How can we keep the transaction footprint as low as possible without compromising on ACID? How can we implement optimistic locking? jOOQ only has some of the answers. While jOOQ does offer updatable records that help running simple CRUD, a batch API, optimistic locking capabilities, jOOQ mainly focuses on executing actual SQL statements.

SQL is the preferred language of database interaction, when any of the following are given:

You run reports and analytics on large data sets directly in the database You import / export data using ETL You run complex business logic as SQL queries Whenever SQL is a good fit, jOOQ is a good fit. Whenever you're operating and persisting the object graph, JPA is a good fit.

And sometimes, it's best to combine both

Spring Data JPA 为您提供以下内容:

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

jOOQ 是一个数据库映射库,它为此目的实现了 active record pattern. It takes an SQL-centric approach to database operations, and uses a domain-specific language

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

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

Spring Data JPA 确实支持 @Query 习语,能够 运行 本机查询(通过设置 nativeQuery 标志),我们可以在其中编写和查看查询(简单和复杂,有连接或其他)就在存储库中并轻松重用它们。

鉴于以上情况,

When would you use Spring Data JPA over Spring Boot with JOOQ and vice versa?

我只会使用 Spring Data JPA,除非我不使用 Spring 生态系统本身。其他原因可能是我更喜欢流畅的风格..

I know that Spring Data JPA can be used for completing basic CRUD queries, but not really for complex join queries

正如我上面提到的,Spring Data JPA 确实提供了使用复杂 and/or 连接查询的能力。此外,通过 custom repository 机制(使用 JOOQ 的 @Michael Simons post 中已有示例)提供了更大的灵活性。所以它本身就是一个完整的解决方案。

Can you use both Spring data jpa with jooq?

@Michael Simons 已经很好地回答了