Jinq is a library that provides support for database queries in Java. It is inspired by .NET’s LINQ. A developer can use Java Stream API, standard functions (e.g.
String#contains
, Math#abs
), and relational, arithmetic and logical operators (e.g. ==, <, +, /, &&, ==) for filtering data.List<Book> books = jinqDataProvider.streamAll(entityManager, Book.class)
.where(book -> book.getPages() > 50 && book.isPublished()).toList();
In this example, the
Book
is not a generated metamodel but a JPA entity class. It looks like magic. Under the hood, Jinq uses lambda serialization and bytecode analysis. Let's start looking at Jinq with a Spring Boot example.Add the following dependencies in the build.gradle file:
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.jinq:jinq-jpa:1.8.32'
Consider the example with two tables in the database:
books
and authors
. Add the entity classes:@Entity(name = "books")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private boolean published;
private Integer pages;
@ManyToOne
@JoinColumn(name = "author_id")
private Author author;
// getters and setters
}
@Entity(name = "authors")
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true)
@OrderBy("id")
private final Set<Book> books = new LinkedHashSet<>();
// getters and setters
}
We need to create a
JinqJPAStreamProvider
bean for using Jinq.@Configuration
public class JinqProviderConfiguration {
@Bean
JinqJPAStreamProvider jinqDataProvider(EntityManagerFactory emf) {
return new JinqJPAStreamProvider(emf);
}
}
We need to use the
JinqJPAStreamProvider#streamAll
method for creating streams. Let's add a helper class.@Component
public class JinqHelper {
@PersistenceContext
private EntityManager entityManager;
private final JinqJPAStreamProvider jinqDataProvider;
public JinqHelper(JinqJPAStreamProvider jinqDataProvider) {
this.jinqDataProvider = jinqDataProvider;
}
public <T> JPAJinqStream<T> stream(Class<T> clazz) {
return jinqDataProvider.streamAll(entityManager, clazz);
}
}
Now we are ready to query the database.
We can use the
count
method to get the number of all authors.long authorsCount = jinqHelper.stream(Author.class).count();
Let's retrieve all published books that have more than 50 pages.
List<Book> books = jinqHelper.stream(Book.class).where(book ->
book.getPages() > 50 && book.isPublished()).toList();
If we want to read-only specific columns, we can use the
select
method and the Pair
or Tuple3
, ..., Tuple8
classes in the org.jinq.tuples
package.List<Pair<String, String>> allAuthors = jinqHelper.stream(Author.class)
.where(author -> author.getFirstName().equals("Simon"))
.select(author -> new Pair<>(author.getFirstName(), author.getLastName()))
.toList();
for (Pair<String, String> author : allAuthors) {
String firstName = author.getOne();
String lastName = author.getTwo();
// ...
}
Jinq provides aggregation methods.
Double avgPages = jinqHelper.stream(Book.class).avg(Book::getPages);
We can use the
joinList
method to perform join-queries. We also use the Pair
class in the following exampleList<Pair<Author, Book>> joinedList = jinqHelper.stream(Author.class)
.where(author -> author.getFirstName().equals("Simon"))
.joinList(Author::getBooks)
.where(pair -> pair.getTwo().isPublished())
.toList();
Jinq also provides support for grouping, sorting, different types of joins, subqueries, registering your own functions, and other useful features.
Jinq provides a convenient and natural way to build type-safe database queries. It is easier to use than JPA 1 Criteria API and does not require code generation as Query DSL or JPA 2 metamodel.