Introduction to Jinq with Spring – 使用Spring的Jinq简介

最后修改: 2018年 2月 13日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

Jinq provides an intuitive and handy approach for querying databases in Java. In this tutorial, we’ll explore how to configure a Spring project to use Jinq and some of its features illustrated with simple examples.

Jinq为在 Java 中查询数据库提供了直观而方便的方法。在本教程中,我们将探讨如何配置Spring项目以使用Jinq,并通过简单的示例说明其部分功能。

2. Maven Dependencies

2.Maven的依赖性

We’ll need to add the Jinq dependency in the pom.xml file:

我们需要在pom.xml文件中添加Jinq依赖项

<dependency>
    <groupId>org.jinq</groupId>
    <artifactId>jinq-jpa</artifactId>
    <version>1.8.22</version>
</dependency>

For Spring, we’ll add the Spring ORM dependency in the pom.xml file:

对于Spring,我们将在pom.xml文件中添加Spring ORM依赖性

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>5.3.3</version>
</dependency>

Finally, for testing, we’ll use an H2 in-memory database, so let’s also add this dependency, along with spring-boot-starter-data-jpa to the pom.xml file:

最后,为了进行测试,我们将使用一个H2内存数据库,所以我们也将这个依赖项,以及spring-boot-starter-data-jpa添加到pom.xml文件。

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.200</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>2.7.2</version>
</dependency>

3. Understanding Jinq

3.了解金克

Jinq helps us to write easier and more readable database queries by exposing a fluent API that’s internally based on the Java Stream API.

Jinq通过暴露一个内部基于Java Stream API.的流畅的API,帮助我们编写更容易、更可读的数据库查询。

Let’s see an example where we’re filtering cars by model:

让我们看一个例子,我们要按车型过滤汽车。

jinqDataProvider.streamAll(entityManager, Car.class)
  .where(c -> c.getModel().equals(model))
  .toList();

Jinq translates the above code snippet into a SQL query in an efficient way, so the final query in this example would be:

Jinq以一种有效的方式将上述代码片断翻译成SQL查询,因此本例中的最终查询将是。

select c.* from car c where c.model=?

Since we’re not using plain-text for writing queries and use a type-safe API instead this approach is less prone to errors.

由于我们没有使用纯文本来编写查询,而是使用类型安全的API,这种方法不容易出错。

Plus, Jinq aims to allow faster development by using common, easy-to-read expressions.

此外,Jinq旨在通过使用常见的、易于阅读的表达式来实现更快的开发。

Nevertheless, it has some limitations in the number of types and operations we can use, as we’ll see next.

尽管如此,它在我们可以使用的类型和操作的数量上有一些限制,我们接下来会看到。

3.1. Limitations

3.1.局限性

Jinq supports only the basic types in JPA and a concrete list of SQL functions. It works by translating the lambda operations into a native SQL query by mapping all objects and methods into a JPA data type and a SQL function.

Jinq只支持JPA中的基本类型和SQL函数的具体列表。它的工作原理是通过将所有对象和方法映射为JPA数据类型和SQL函数,将lambda操作转化为本地SQL查询。

Therefore, we can’t expect the tool to translate every custom type or all methods of a type.

因此,我们不能期望该工具能翻译每个自定义类型或一个类型的所有方法。

3.2. Supported Data Types

3.2.支持的数据类型

Let’s see the supported data types and methods supported:

让我们看看支持的数据类型和支持的方法。

  • Stringequals(), compareTo() methods only
  • Primitive Data Types – arithmetic operations
  • Enums and custom classes – supports == and != operations only
  • java.util.Collection – contains()
  • Date API – equals(), before(), after() methods only

Note: if we wanted to customize the conversion from a Java object to a database object, we’d need to register our concrete implementation of an AttributeConverter in Jinq.

注意:如果我们想定制从Java对象到数据库对象的转换,我们需要在Jinq中注册我们的AttributeConverter的具体实现。

4. Integrating Jinq With Spring

4.将Jinq与Spring相结合

Jinq needs an EntityManager instance to get the persistence context. In this tutorial, we’ll introduce a simple approach with Spring to make Jinq work with the EntityManager provided by Hibernate.

Jinq需要一个EntityManager实例来获取持久化上下文。在本教程中,我们将介绍一种使用Spring的简单方法,使Jinq与Hibernate>提供的EntityManager共同工作。

4.1. Repository Interface

4.1.存储库接口

Spring uses the concept of repositories to manage entities. Let’s look at our CarRepository interface where we have a method to retrieve a Car for a given model:

Spring使用存储库的概念来管理实体。让我们看看我们的CarRepository接口,我们有一个方法来检索一个给定模型的Car

public interface CarRepository {
    Optional<Car> findByModel(String model);
}

4.2. Abstract Base Repository

4.2.抽象基础存储库

Next, we’ll need a base repository to provide all the Jinq capabilities:

接下来,我们需要一个基础资源库来提供所有的Jinq功能。

public abstract class BaseJinqRepositoryImpl<T> {
    @Autowired
    private JinqJPAStreamProvider jinqDataProvider;

    @PersistenceContext
    private EntityManager entityManager;

    protected abstract Class<T> entityType();

    public JPAJinqStream<T> stream() {
        return streamOf(entityType());
    }

    protected <U> JPAJinqStream<U> streamOf(Class<U> clazz) {
        return jinqDataProvider.streamAll(entityManager, clazz);
    }
}

4.3. Implementing the Repository

4.3.实施存储库

Now, all we need for Jinq is an EntityManager instance and the entity type class.

现在,我们对Jinq所需要的只是一个EntityManager实例和实体类型类。

Let’s see the Car repository implementation using our Jinq base repository that we just defined:

让我们看看Car资源库的实现,使用我们刚刚定义的Jinq基础资源库。

@Repository
public class CarRepositoryImpl 
  extends BaseJinqRepositoryImpl<Car> implements CarRepository {

    @Override
    public Optional<Car> findByModel(String model) {
        return stream()
          .where(c -> c.getModel().equals(model))
          .findFirst();
    }

    @Override
    protected Class<Car> entityType() {
        return Car.class;
    }
}

4.4. Wiring the JinqJPAStreamProvider

4.4.为JinqJPAStreamProvider布线

In order to wire the JinqJPAStreamProvider instance, we’ll add the Jinq provider configuration:

为了连接JinqJPAStreamProvider实例,我们将添加Jinq提供者配置:

@Configuration
public class JinqProviderConfiguration {

    @Bean
    @Autowired
    JinqJPAStreamProvider jinqProvider(EntityManagerFactory emf) {
        return new JinqJPAStreamProvider(emf);
    }
}

4.5. Configuring the Spring Application

4.5.配置Spring应用程序

The final step is to configure our Spring application using Hibernate and our Jinq configuration. As a reference, see our application.properties file, in which we use an in-memory H2 instance as the database:

最后一步是使用Hibernate和Jinq配置我们的Spring应用程序。作为参考,请参阅我们的application.properties文件,其中我们使用内存中的H2实例作为数据库。

spring.datasource.url=jdbc:h2:~/jinq
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create-drop

5. Query Guide

5 查询指南

Jinq provides many intuitive options to customize the final SQL query with select, where, joins and more. Note that these have the same limitations that we have already introduced above.

Jinq提供了许多直观的选项来定制最终的SQL查询,包括select, where, joins等等。注意,这些都有我们上面已经介绍过的限制。

5.1. Where

5.1.在哪里

The where clause allows applying multiple filters to a data collection.

where子句允许对一个数据集合应用多个过滤器。

In the next example, we want to filter cars by model and description:

在下一个例子中,我们想按车型和描述来过滤汽车。

stream()
  .where(c -> c.getModel().equals(model)
    && c.getDescription().contains(desc))
  .toList();

And this is the SQL that Jinq translates:

而这是金克翻译的SQL。

select c.model, c.description from car c where c.model=? and locate(?, c.description)>0

5.2. Select

5.2.选择

In case we want to retrieve only a few columns/fields from the database, we need to use the select clause.

如果我们只想从数据库中检索一些列/字段,我们需要使用select子句。

In order to map multiple values, Jinq provides a number of Tuple classes with up to eight values:

为了映射多个值,Jinq提供了一些Tuple类,最多有八个值。

stream()
  .select(c -> new Tuple3<>(c.getModel(), c.getYear(), c.getEngine()))
  .toList()

And the translated SQL:

还有翻译后的SQL。

select c.model, c.year, c.engine from car c

5.3. Joins

5.3.连接

Jinq is able to resolve one-to-one and many-to-one relationships if the entities are properly linked.

如果实体被正确链接,Jinq能够解决一对一和多对一关系

For example, if we add the manufacturer entity in Car:

例如,如果我们在Car中添加制造商实体。

@Entity(name = "CAR")
public class Car {
    //...
    @OneToOne
    @JoinColumn(name = "name")
    public Manufacturer getManufacturer() {
        return manufacturer;
    }
}

And the Manufacturer entity with the list of Cars:

Manufacturer实体有Cars的列表。

@Entity(name = "MANUFACTURER")
public class Manufacturer {
    // ...
    @OneToMany(mappedBy = "model")
    public List<Car> getCars() {
        return cars;
    }
}

We’re now able to get the Manufacturer for a given model:

我们现在能够获得某一型号的Manufacturer

Optional<Manufacturer> manufacturer = stream()
  .where(c -> c.getModel().equals(model))
  .select(c -> c.getManufacturer())
  .findFirst();

As expected, Jinq will use an inner join SQL clause in this scenario:

正如预期的那样,Jinq在这种情况下会使用一个内联SQL子句

select m.name, m.city from car c inner join manufacturer m on c.name=m.name where c.model=?

In case we need to have more control over the join clauses in order to implement more complex relationships over the entities, like a many-to-many relation, we can use the join method:

如果我们需要对join子句有更多的控制,以便在实体上实现更复杂的关系,如多对多的关系,我们可以使用join方法:

List<Pair<Manufacturer, Car>> list = streamOf(Manufacturer.class)
  .join(m -> JinqStream.from(m.getCars()))
  .toList()

Finally, we could use a left outer join SQL clause by using the leftOuterJoin method instead of the join method.

最后,我们可以通过使用leftOuterJoin方法而不是join方法来使用左外连接SQL子句。

5.4. Aggregations

5.4.聚合

All the examples we have introduced so far are using either the toList or the findFirst methods – to return the final result of our query in Jinq.

到目前为止,我们介绍的所有例子都是使用toListfindFirst方法–来返回我们在Jinq中查询的最终结果。

Besides these methods, we also have access to other methods to aggregate results.

除了这些方法,我们还可以使用其他方法来汇总结果

For example, let’s use the count method to get the total count of the cars for a concrete model in our database:

例如,让我们使用count方法来获得我们数据库中一个具体模型的汽车总计数。

long total = stream()
  .where(c -> c.getModel().equals(model))
  .count()

And the final SQL is using the count SQL method as expected:

而最后的SQL是使用count SQL方法,正如预期的那样。

select count(c.model) from car c where c.model=?

Jinq also provides aggregation methods like sum, average, min, max, and the possibility to combine different aggregations.

Jinq还提供聚合方法,如sum, average, min, max,结合不同聚合的可能性

5.5. Pagination

5.5.分页

In case we want to read data in batches, we can use the limit and skip methods.

如果我们想分批读取数据,我们可以使用limitskip方法。

Let’s see an example where we want to skip the first 10 cars and get only 20 items:

让我们看一个例子,我们想跳过前10辆车,只得到20个项目。

stream()
  .skip(10)
  .limit(20)
  .toList()

And the generated SQL is:

而生成的SQL是。

select c.* from car c limit ? offset ?

6. Conclusion

6.结论

There we go. In this article, we’ve seen an approach for setting up a Spring application with Jinq using Hibernate (minimally).

我们走吧。在这篇文章中,我们已经看到了一种使用Hibernate(最小化)在Jinq中设置Spring应用程序的方法。

We’ve also briefly explored Jinq’s benefits and some of its main features.

我们还简要地探讨了Jinq的好处和它的一些主要功能。

As always, the sources can be found over on GitHub.

一如既往,可以在GitHub上找到源代码