1. 简介

Jinq 为Java数据库查询提供了直观且便捷的解决方案。本教程将探讨如何在Spring项目中配置Jinq,并通过简单示例展示其核心功能。

2. Maven依赖

pom.xml 中添加 Jinq依赖

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

添加 Spring ORM依赖

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

测试时使用H2内存数据库,添加以下依赖:

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

3. 理解Jinq

Jinq通过**Java Stream API** 提供流畅的API,使数据库查询更易读、更简洁。

按车型筛选汽车的示例:

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

Jinq会将上述代码高效转换为SQL查询

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

由于使用类型安全API而非纯文本查询,这种方式更不易出错。此外,Jinq通过通用易读的表达式加速开发,但存在类型和操作限制。

3.1. 局限性

Jinq仅支持JPA基础类型和特定SQL函数。它通过将lambda操作映射为JPA数据类型和SQL函数来生成原生SQL,因此无法转换所有自定义类型或方法。

3.2. 支持的数据类型

✅ 支持的类型及方法:

  • String – 仅支持 equals(), compareTo()
  • 基本数据类型 – 算术运算
  • 枚举和自定义类 – 仅支持 == 和 != 操作
  • java.util.Collection – 支持 contains()
  • Date API – 仅支持 equals(), before(), after()

⚠️ 注意:若需自定义Java对象到数据库对象的转换,需在Jinq中注册 AttributeConverter 的具体实现。

4. Spring集成Jinq

Jinq需要 EntityManager 实例获取持久化上下文。本教程采用Spring与Hibernate提供的 EntityManager 集成方案。

4.1. 仓库接口

Spring通过仓库模式管理实体。定义 CarRepository 接口,包含按车型查询汽车的方法:

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

4.2. 抽象基础仓库

创建基础仓库提供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. 实现仓库

Jinq仅需 EntityManager 实例和实体类型类。基于基础仓库实现 Car 仓库:

@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. 装配 JinqJPAStreamProvider

添加Jinq提供者配置

@Configuration
public class JinqProviderConfiguration {

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

4.5. 配置Spring应用

配置Spring应用使用Hibernate和Jinq。参考 application.properties(使用H2内存数据库):

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

5. 查询指南

Jinq提供直观选项定制SQL查询(如 select, where, joins 等),但受限于前述约束。

5.1. Where条件

where 子句支持多重过滤。按车型和描述筛选汽车:

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

Jinq生成的SQL:

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

5.2. Select投影

仅需查询部分字段时使用 select 子句。Jinq提供最多8个值的 Tuple 类:

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

生成的SQL:

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

5.3. 关联查询

Jinq可解析实体间的一对一和多对一关系。在 Car 中添加制造商关联:

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

Manufacturer 实体包含汽车列表:

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

查询指定车型的制造商:

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

Jinq自动使用内连接

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

需控制多对多等复杂关系时,使用 join 方法

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

左外连接则使用 leftOuterJoin 方法。

5.4. 聚合操作

toListfindFirst 外,Jinq支持多种聚合方法。统计指定车型的汽车数量:

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

生成的SQL:

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

还支持 sum, average, min, max 等,可组合不同聚合操作

5.5. 分页查询

使用 skiplimit 实现分页。跳过前10条并获取20条记录:

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

生成的SQL:

select c.* from car c limit ? offset ?

6. 总结

本文介绍了Spring与Jinq的集成方案(基于Hibernate最小配置),并简要探讨了Jinq的优势及核心功能。完整代码可在 GitHub 获取。


原始标题:Introduction to Jinq with Spring