Infobip Spring Data Querydsl provides new functionality that enables the user to leverage the full power of Querydsl API on top of Spring Data repository infrastructure.
The project is divided into 2 modules: infobip-spring-data-jdbc-querydsl and infobip-spring-data-jpa-querydsl.
- News
- Note on general usage
- JDBC module:
- JPA module:
- Further reading
- Running tests
- Contributing
- License
- JPA and JDBC module extension support
- support for multiple constructors in JDBC module
infobip-spring-data-jpa-querydsl
is no longer coupled to Hibernate ORMSQLTemplates
forinfobip-spring-data-jpa-querydsl
can now be overidden - simply provide a bean of type SQLTemplates in your context.QuerydslJdbcRepository
now extendsPagingAndSortingRepository
- Breaking change:
- removed second generic parameter from QuerydslJdbcRepository
- Breaking changes:
- renamed
@EnableExtendedRepositories
to@EnableExtendedJpaRepositories
- renamed
ExtendedQueryDslJpaRepository
toExtendedQuerydslJpaRepository
- renamed
- Added new module - infobip-spring-data-jdbc-querydsl.
For the sake of brevity, all examples use repository methods directly.
In production code persistence layer (SQL) shouldn't leak to service layer. See this answer by Oliver Drotbohm (Spring Data Project Lead @ Pivotal) on how to approach encapsulating persistence logic.
- Java 8 with parameter names preserved in byte code (used to map columns to constructor parameters)
- Spring Data JDBC
- Querydsl
- entities must have an all argument constructor (
@AllArgsConstructor
), can have others as well - entity class and all argument constructor must be public (limitation of Querydsl)
- Dependency:
<dependency>
<groupId>com.infobip</groupId>
<artifactId>infobip-spring-data-jdbc-querydsl</artifactId>
<version>${infobip-spring-data-jdbc-querydsl.version}</version>
</dependency>
- Add
@EnableQuerydslJdbcRepositories
to your Main class:
@EnableQuerydslJdbcRepositories // replaces @EnableJdbcRepositories
@SpringBootApplication
public class Main {
public static void main(String[] args) {
new SpringApplicationBuilder(Main.class).run(args);
}
}
- Refactor repository interfaces to use
QuerydslJdbcRepository
instead ofCrudRepository
:
interface FooRepository extends QuerydslJdbcRepository<Foo, ID> {
}
- Done
All examples have corresponding tests in the project and can be found here.
infobip-spring-data-jdbc-annotation-processor
provides an annotation processor that automatically generates Q classes without connecting to the database.
infobip-spring-data-jdbc-querydsl
depends on infobip-spring-data-jdbc-annotation-processor
so you don't need to add explicit dependency.
In case you want to manually generate Q classes you can still exclude infobip-spring-data-jdbc-annotation-processor
and do the process manually (e.g. like this).
Inner join example:
List<Person> actual = repository.query(query -> query
.select(repository.entityProjection())
.from(person)
.innerJoin(personSettings)
.on(person.id.eq(personSettings.personId))
.where(personSettings.id.eq(johnDoeSettings.getId()))
.fetch());
);
For examples how to construct projections refer to the official documentation - section result handling.
Here is an example that uses constructor:
@Value
public static class PersonProjection {
private final String firstName;
private final String lastName;
}
...
List<PersonProjection> actual = repository.query(query -> query
.select(Projections.constructor(PersonProjection.class, person.firstName,
person.lastName))
.from(person)
.fetch());
List<Person> actual = repository.query(query -> query
.select(repository.entityProjection())
.from(person)
.where(person.firstName.in("John", "Jane"))
.orderBy(person.firstName.asc(), person.lastName.asc())
.limit(1)
.offset(1)
.fetch());
repository.update(query -> query
.set(person.firstName, "John")
.where(person.firstName.eq("Johny"))
.execute());
long numberOfAffectedRows = repository.deleteWhere(person.firstName.like("John%"));
Queries execution is always done inside the repository implementation (loan pattern) in a transaction so transactions don't have to be handled manually (like they do if you are manually managing SQLQuery and other Querydsl constructs).
To create a custom base repository interface you'll need to create:
- custom base interface
- custom annotation for enabling
- custom factory bean class and potentially factory class depending on requirements
Take a look at extension package in tests as an example on how this can be achieved.
- Java 8
- Spring Data JPA
- Querydsl
- Dependency:
<dependency>
<groupId>com.infobip</groupId>
<artifactId>infobip-spring-data-jpa-querydsl</artifactId>
<version>${infobip-spring-data-jpa-querydsl.version}</version>
</dependency>
As this project depends on querydsl-apt with jpa classifier you don't need to set up explicit Maven build phase for Q classes generation. For building Q classes without Maven, make sure your IDE has Annotation processing enabled.
- Add @EnableExtendedJpaRepositories to your Main class:
@EnableExtendedJpaRepositories // replaces @EnableJpaRepositories
@SpringBootApplication
public class Main {
public static void main(String[] args) {
new SpringApplicationBuilder(Main.class).run(args);
}
}
- Refactor repository interfaces to use
ExtendedQueryDslJpaRepository
instead ofJpaRepository
andQueryDslPredicateExecutor
(note that ExtendedQueryDslJpaRepository extends and provides the API of both):
// ExtendedQueryDslJpaRepository replaces both JpaRepository and QueryDslPredicateExecutor
interface FooRepository extends ExtendedQueryDslJpaRepository<Foo, ID> {
}
- Done
If you need other features from @EnableJpaRepositories
you can use:
@EnableJpaRepositories(repositoryBaseClass = SimpleExtendedQueryDslJpaRepository.class)
All examples have corresponding tests in the project and can be found here.
Example which uses union clause (unions aren't available in JPA):
List<Person> actual = repository.jpaSqlQuery(query -> query
.union(
repository.jpaSqlSubQuery(subQuery ->
subQuery.select(person)
.from(person)
.where(person.firstName.like("John"))),
repository.jpaSqlSubQuery(subQuery ->
subQuery.select(person)
.from(person)
.where(person.firstName.like("Jan%")))
)
.orderBy(person.firstName.asc(), person.lastName.asc())
.fetch()
);
For examples how to construct projections refer to the official documentation - section result handling.
Here is an example that uses constructor:
@Value
public class PersonProjection {
private final String firstName;
private final String lastName;
}
...
List<PersonProjection> actual = repository.query(query -> query
.select(Projections.constructor(PersonProjection.class, person.firstName, person.lastName))
.from(person)
.fetch());
Query exposes full API of JPAQuery (QueryDslPredicateExecutor only exposes where clause (Predicate) and order clause (OrderSpecifier)).
This along with Querydsl 4 API improvement can lead to code that looks more like regular SQL:
List<Person> actual = repository.query(query -> query
.select(person)
.from(person)
.where(person.firstName.in("John", "Jane"))
.orderBy(person.firstName.asc(), person.lastName.asc())
.limit(1)
.offset(1)
.fetch());
repository.update(query -> query
.set(person.firstName, "John")
.where(person.firstName.eq("Johny"))
.execute());
long numberOfAffectedRows = repository.deleteWhere(person.firstName.like("John%"));
QueryDslPredicateExecutor#findAll methods return Iterable which can be cumbersome to use. Those methods were overridden and now return a List which is easier to use and is easier to convert to Stream.
Query execution is always done inside the repository implementation (loan pattern) in a transaction so transactions don't have to be handled manually (like they do if you are manually managing JPAQuery and other Querydsl constructs).
JPA support for stored procedures is quite cumbersome and it also requires a reference to EntityManager which leads to code like this:
@PersistenceContext
private EntityManager entityManager
...
@SuppressWarnings("unchecked")
public List<Person> delete(Person personToDelete) {
return (List<Person>) entityManager
.createStoredProcedureQuery("Person_Delete")
.registerStoredProcedureParameter("FirstName", String.class, ParameterMode.IN)
.registerStoredProcedureParameter("LastName", String.class, ParameterMode.IN)
.setParameter("FirstName", personToDelete.getFirstName())
.setParameter("LastName", personToDelete.getLastName())
.getResultList(); // returns untyped List => unchecked
}
For this case, executeStoredProcedure method was added which supports Q class attributes:
public List<Person> delete(Person personToDelete) {
return repository.executeStoredProcedure(
"Person_Delete",
builder -> builder.addInParameter(person.firstName, personToDelete.getFirstName())
.addInParameter(person.lastName, personToDelete.getLastName())
.getResultList());
}
To create a custom base repository interface you'll need to create:
- custom base interface
- custom annotation for enabling
- custom factory bean class and potentially factory class depending on requirements
Take a look at extension package in tests as an example on how this can be achieved.
- Querydsl documentation
- Atlassian Querydsl examples
- Querydsl google group
- Spring Data JPA documentation
Tests require SQL Server DB.
Easies way to set it up on your machine is to use docker:
docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=<YourStrong!Passw0rd>' -p 1433:1433 -d microsoft/mssql-server-linux:2017-latest
If you have an idea for a new feature or want to report a bug please use the issue tracker.
Pull requests are welcome!
This library is licensed under the Apache License, Version 2.0.