GithubHelp home page GithubHelp logo

isabella232 / infobip-spring-data-querydsl Goto Github PK

View Code? Open in Web Editor NEW

This project forked from infobip/infobip-spring-data-querydsl

0.0 0.0 0.0 595 KB

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.

License: Apache License 2.0

Java 98.47% TSQL 0.48% Kotlin 1.05%

infobip-spring-data-querydsl's Introduction

Infobip Spring Data Querydsl

Maven Central Coverage Status Known Vulnerabilities

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.

Contents

  1. Changelog
  2. Note on general usage
  3. JDBC module:
  4. R2DBC module:
  5. JPA module:
  6. Annotation processor
  7. Further reading
  8. Running tests
  9. Contributing
  10. License

Changelog

For changes check the changelog.

Note on general usage

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.

JDBC module:

Requirements:

  • Java 8 with parameter names preserved in byte code (used to map columns to constructor parameters)
  • Spring Data JDBC
  • 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)

Setup:

  1. Dependency:
<dependency>
   <groupId>com.infobip</groupId>
   <artifactId>infobip-spring-data-jdbc-querydsl-boot-starter</artifactId>
   <version>${infobip-spring-data-jdbc-querydsl.version}</version>
</dependency>
  1. Refactor repository interfaces to either use new base repository or fragments approach:
  • new base repository approach:
interface TRepository extends QuerydslJdbcRepository<T, ID> {
}
  • fragments:
interface TRepository extends PagingAndSortingRepository<T, ID>, QuerydslPredicateExecutor<T>, QuerydslJdbcFragment<T> {
}
  1. Done

Features and examples:

All examples have corresponding tests in the project and can be found here.

Inner Join:

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());
);

Projections

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());

Query

Optional<Person> actual = repository.queryOne(query -> query
        .select(repository.entityProjection())
        .from(person)
        .where(person.firstName.in("John", "Jane"))
        .orderBy(person.firstName.asc(), person.lastName.asc())
        .limit(1)
        .offset(1));
List<Person> actual = repository.queryMany(query -> query
        .select(repository.entityProjection())
        .from(person)
        .where(person.firstName.in("John", "Jane"))
        .orderBy(person.firstName.asc(), person.lastName.asc())
        .limit(1)
        .offset(1));

Update

repository.update(query -> query
        .set(person.firstName, "John")
        .where(person.firstName.eq("Johny"))
        .execute());

Delete

long numberOfAffectedRows = repository.deleteWhere(person.firstName.like("John%"));

Transactional support

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).

Embedded support

Entity fields marked with @org.springframework.data.relational.core.mapping.Embedded are inlined in Q classes:

Model:

@Table("Person")
@Value
public class PersonWithEmbeddedFirstAndLastName {

   @With
   @Id
   private final Long id;

   @Embedded(onEmpty = Embedded.OnEmpty.USE_EMPTY)
   private final FirstAndLastName firstAndLastName;
   ...
}
@Value
public class FirstAndLastName {

    private final String firstName;
    private final String lastName;
}

Query (note the missing .personWithEmbeddedFirstAndLastName field in Q instance):

repository.findAll(personWithEmbeddedFirstAndLastName.firstName.in("John", "Johny"));

Streaming

streamAll is a new method added to repository for more convenient use.

@Transactional
public void transactionalAnnotatedMethodRequiredForConsumingStream() {
   try (Stream<Person> stream = repository.streamAll()) {
      // use stream
   }
}

Extension:

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.

R2DBC module:

Requirements:

  • Java 8 with parameter names preserved in byte code (used to map columns to constructor parameters)
  • Spring Data R2DBC
  • 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)
  • if you're not using Flyway, you need to provide a SQLTemplates bean

Setup:

  1. Dependency:
<dependency>
   <groupId>com.infobip</groupId>
   <artifactId>infobip-spring-data-r2dbc-querydsl-boot-starter</artifactId>
   <version>${infobip-spring-data-r2dbc-querydsl.version}</version>
</dependency>
  1. Refactor repository interfaces to either use new base repository or fragments approach:
  • new base repository approach:
interface TRepository extends QuerydslR2dbcRepository<T, ID> {
}
  • fragments:
interface TRepository extends ReactiveSortingRepository<T, ID>, ReactiveQuerydslPredicateExecutor<T>, QuerydslR2dbcFragment<T> {
}
  1. Done

Features and examples:

All examples have corresponding tests in the project and can be found here.

Inner Join:

Inner join example:

Flux<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())))
                                     .all();

Projections

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;
}
...

Flux<PersonProjection> actual = repository.query(query -> query
        .select(constructor(PersonProjection.class, person.firstName, person.lastName))
        .from(person))
                                          .all();

Query

Flux<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))
                                .all();

Update

Mono<Integer> numberOfAffectedRows = repository.update(query -> query.set(person.firstName, "John")
                                                                     .where(person.firstName.eq("Johny")));

Delete

Mono<Integer> numberOfAffectedRows = repository.deleteWhere(person.firstName.like("John%"));

Transactional support

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).

Extension:

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.

JPA module:

Requirements:

  • Java 8
  • Spring Data JPA

Setup:

  1. Dependency:
<dependency>
   <groupId>com.infobip</groupId>
   <artifactId>infobip-spring-data-jpa-querydsl-boot-starter</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.

  1. Refactor repository interfaces to either use new base repository or fragments approach:
  • new base repository approach:
interface TRepository extends ExtendedQueryDslJpaRepository<T, ID> {
}
  • fragments:
interface TRepository extends JpaRepository<T, ID>, QuerydslPredicateExecutor<T>, QuerydslJpaFragment<T> {
}
  1. Done

If you need other features from @EnableJpaRepositories you can use:

@EnableJpaRepositories(repositoryFactoryBeanClass = ExtendedQuerydslJpaRepositoryFactoryBean.class)

Features and examples:

All examples have corresponding tests in the project and can be found here.

Native queries with Querydsl:

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()
);

Projections

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

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());

Update

repository.update(query -> query
        .set(person.firstName, "John")
        .where(person.firstName.eq("Johny"))
        .execute());

Delete

long numberOfAffectedRows = repository.deleteWhere(person.firstName.like("John%"));

List instead of Iterable return type

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.

Transactional support

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).

Stored procedure builder

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());
}

Streaming

streamAll is a new method added to repository for more convenient use.

@Transactional
public void transactionalAnnotatedMethodRequiredForConsumingStream() {
   try (Stream<Person> stream = repository.streamAll()) {
      // use stream
   }
}

Extension:

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.

Annotation processor

Annotation processor infobip-spring-data-jdbc-annotation-processor is used by R2DBC and JDBC modules to generate Querydsl Q classes. Without annotation processor this process can be quite cumbersome as connecting to database would be required during the build phase.

Current implementation of Annotation Processor uses pascal casing based naming strategy for table and column names.

To customize this behavior across whole project add following annotation to one of your classes:

@ProjectTableCaseFormat(CaseFormat.LOWER_UNDERSCORE)
@ProjectColumnCaseFormat(CaseFormat.LOWER_UNDERSCORE)
public class SomeClassOnCompilationPath {
...
}

SomeClassOnCompilationPath can be any class that is being compiled in the project.

Note that for customizing single table/column mapping Table and Column can be used.

If this behavior needs to be changed across multiple projects, or you simply wish to customize annotation processor following steps can be taken:

  1. create a new Maven module (or a Maven project if you want to reuse across multiple projects)
  2. add dependency to infobip-spring-data-jdbc-annotation-processor-common
  3. create implementation of com.querydsl.sql.codegen.NamingStrategy
  4. create annotation processor that extends the base one:
@AutoService(Processor.class)
public class CustomSpringDataJdbcAnnotationProcessor extends SpringDataJdbcAnnotationProcessorBase {

    public CustomSpringDataJdbcAnnotationProcessor() {
        super(CustomNamingStrategy.class);
    }
}
  1. in module (or project) that needs to use this new processor exclude the default annotation processor dependency and include your own:
<dependency>
	<groupId>com.infobip</groupId>
    <!-- infobip-spring-data-jdbc-querydsl-boot-starter is used as an example here, same pattern applies for other modules --> 
	<artifactId>infobip-spring-data-jdbc-querydsl-boot-starter</artifactId>
	<version>${infobip-spring-data-jdbc-querydsl-boot-starter.version}</version>
	<exclusions>
		<exclusion>
			<groupId>com.infobip</groupId>
			<artifactId>infobip-spring-data-jdbc-annotation-processor</artifactId>
		</exclusion>
	</exclusions>
</dependency>

<!-- include dependency to custom annotation processor -->

infobip-spring-data-jdbc-annotation-processor can be used as an example codebase for custom annotation processor. It includes tests that can be used for custom annotation processor as well.

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).

Further reading

Running tests

To run tests you need to have docker installed. Containers are automatically started using testcontainers.

Contributing

If you have an idea for a new feature or want to report a bug please use the issue tracker.

Pull requests are welcome!

License

This library is licensed under the Apache License, Version 2.0.

infobip-spring-data-querydsl's People

Contributors

dirkluijk avatar lpandzic avatar snyk-bot avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.