Spring Data JPA Tutorial: Creating Database Queries With Querydsl

The previous part of this tutorial described how we can create dynamic database queries with the JPA Criteria API. Although it is easy to create simple criteria queries, the JPA Criteria API has one major flaw:

It is very hard to implement complex queries and even harder to read them.

This blog post describes how we can solve these problems by using Querydsl. We will also implement a search function that has two requirements:

  • It must return todo entries whose title or description contains the given search term.
  • The search must be case-insensitive.

Let’s start by getting the required dependencies.

Additional Reading:

If you are not familiar with Spring Data JPA, you should read the following blog posts before you continue reading this blog post:

Getting the Required Dependencies

Before we can use Querydsl with Spring Data JPA, we have to add the Querydsl JPA module into our classpath. We can do this by adding the following dependency declaration into the dependencies section of our pom.xml file:

<dependency>
	<groupId>com.mysema.querydsl</groupId>
	<artifactId>querydsl-jpa</artifactId>
	<version>3.4.3</version>
</dependency>

After we have added the Querydsl JPA dependency into our pom.xml file, we have to ensure that Maven creates the Querydsl query types when we compile our project.

Creating the Querydsl Query Types

Querydsl query types are classes that:

  • Describe the structure of our domain model classes such as entities and @Embeddable classes.
  • Provide us a way to create the conditions of our database queries.

We can create these classes automatically by using the Maven APT Plugin. We can configure the Maven APT Plugin by following these steps:

  1. Add the Maven APT Plugin (version 1.1.3) declaration to the plugins section of the pom.xml file.
  2. Configure the dependencies of this plugin and add the querydsl-apt dependency (version 3.4.3) to the plugin’s dependencies section.
  3. Create an execution that invokes the plugin’s process goal when our project is compiled.
  4. Ensure that the Querydsl query types are created to the target/generated-sources/apt directory.
  5. Ensure that the plugin runs only the com.mysema.query.apt.jpa.JPAAnnotationProcessor. This annotation processor scans our entities and embeddable classes, and creates the Querydsl query types.

The configuration of the Maven APT plugin looks as follows:

<plugin>
	<groupId>com.mysema.maven</groupId>
	<artifactId>apt-maven-plugin</artifactId>
	<version>1.1.3</version>
	<executions>
		<execution>
			<goals>
				<goal>process</goal>
			</goals>
			<configuration>
				<outputDirectory>target/generated-sources/apt</outputDirectory>
				<processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
			</configuration>
		</execution>
	</executions>
	<dependencies>
		<dependency>
			<groupId>com.mysema.querydsl</groupId>
			<artifactId>querydsl-apt</artifactId>
			<version>3.4.3</version>
		</dependency>
	</dependencies>
</plugin>

When we compile our project, the invoked annotation processor creates the Querydsl query types to the target/generated-sources/apt directory. Because our domain model has only one entity, the annotation processor creates only one class called QTodo. The source code of the QTodo class looks as follows:

package net.petrikainulainen.springdata.jpa.todo;

import static com.mysema.query.types.PathMetadataFactory.*;

import com.mysema.query.types.path.*;

import com.mysema.query.types.PathMetadata;
import javax.annotation.Generated;
import com.mysema.query.types.Path;


@Generated("com.mysema.query.codegen.EntitySerializer")
public class QTodo extends EntityPathBase<Todo> {

    private static final long serialVersionUID = -797939782L;

    public static final QTodo todo = new QTodo("todo");

    public final StringPath createdByUser = createString("createdByUser");

    public final DateTimePath<java.time.ZonedDateTime> creationTime = createDateTime("creationTime", java.time.ZonedDateTime.class);

    public final StringPath description = createString("description");

    public final NumberPath<Long> id = createNumber("id", Long.class);

    public final DateTimePath<java.time.ZonedDateTime> modificationTime = createDateTime("modificationTime", java.time.ZonedDateTime.class);

    public final StringPath modifiedByUser = createString("modifiedByUser");

    public final StringPath title = createString("title");

    public final NumberPath<Long> version = createNumber("version", Long.class);

    public QTodo(String variable) {
        super(Todo.class, forVariable(variable));
    }

    public QTodo(Path<Todo> path) {
        super(path.getType(), path.getMetadata());
    }

    public QTodo(PathMetadata<?> metadata) {
        super(Todo.class, metadata);
    }

}
Every Querydsl query type is generated to the same package than the corresponding entity.

Let’s move on and find out how we can create database queries with Querydsl.

Creating Database Queries With Querydsl

We can create database queries with Querydsl by following these steps:

  1. Modify the repository interface to support queries that use Querydsl.
  2. Specify the conditions of the invoked database query.
  3. Invoke the database query.

Let’s get started.

Modifying the Repository Interface

The QueryDslPredicateExecutor<T> interface declares the methods that can be used to invoke database queries that use Querydsl. This interface has one type parameter T that describes the type of the queried entity.

In other words, if we need to modify our repository interface to support database queries that use Querydsl, we have to follow these steps:

  1. Extend the QueryDslPredicateExecutor<T> interface.
  2. Set the type of the queried entity.

Example:
The only Spring Data JPA repository of our example application (TodoRepository) manages Todo objects. After we have modified this repository to support Querydsl, its source code looks as follows:

import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.repository.Repository;

interface TodoRepository extends Repository<Todo, Long>, QueryDslPredicateExecutor<Todo> {

}

After we have extended the QueryDslPredicateExecutor<T> interface, the classes that use our repository interface get access to the following methods:

  • The long count(Predicate predicate) method returns the number of objects that fulfil the conditions specified by the Predicate object given as a method parameter.
  • The boolean exists(Predicate predicate) method checks if there are objects that fulfil the conditions specified by the Predicate object given as a method parameters. If such objects are found, this method returns true. Otherwise this method returns false.
  • The Iterable<T> findAll(Predicate predicate) returns objects that fulfil the conditions specified by the Predicate object given as a method parameter.
  • The T findOne(Predicate predicate) method returns an object that fulfils the conditions specified by the Predicate object given as a method parameter. If no object is found, this method returns null.
The QueryDslPredicateExecutor<T> interface declares also other methods that are used to sort and paginate objects that fulfil the conditions specified by the Predicate object. We will talk more about these methods when we learn to sort and paginate our query results.

Additional Reading:

Let’s find out how we can specify the conditions of the invoked database query.

Specifying the Conditions of the Invoked Database Query

We can specify the conditions of the invoked database query by using the generated Querydsl query types. To be more specific, we have to follow these steps:

  1. Get a reference to the query object that describes the queried entity.
  2. Create the Predicate object that specifies the conditions of the invoked database query.

First, we can get a reference to the query object that describes the queried entity by following these steps:

  1. Locate the query type that describes the queried entity.
  2. Get the reference from a static field.

If we need to query Todo objects, we can the required reference from the todo field of the QTodo class. The relevant part of the QTodo class looks follows:

public class QTodo extends EntityPathBase<Todo> {

    public static final QTodo todo = new QTodo("todo");
}

The source code of our query generation code looks as follows:

QTodo todo = QTodo.todo;

Second, We have to create the Predicate object that specifies the conditions of the invoked database query.

We can create a condition that limits the values of a single field by following these steps:

  1. Select the target field of the condition.
  2. Specify the condition.

If we want to create a Predicate object that returns Todo objects whose title is 'Foo', we can create the Predicate object by using the following code:

Predicate titleIs = QTodo.todo.title.eq("Foo");

We can also combine multiple Predicate objects by using the BooleanExpression class. The following examples demonstrates how we can use that class:

Example 1:
If we want to select todo entries whose title is 'Foo' and description is 'Bar', we can create the Predicate object by using the following code:

Predicate titleAndDescriptionAre = QTodo.todo.title.eq("Foo")
				.and(QTodo.todo.description.eq("Bar"));

Example 2:
If we want select todo entries whose title is 'Foo' or 'Bar', we can create the Predicate object by using the following code:

Predicate titleIs = QTodo.todo.title.eq("Foo")
                .or(QTodo.todo.title.eq("Bar"));

Example 3:
If we want to select todo entries whose title is 'Foo' and description is not 'Bar', we can create the Predicate object by using the following code:

Predicate titleIsAndDescriptionIsNot = QTodo.todo.title.eq("Foo")
				.and(QTodo.todo.description.eq("Bar").not());

The obvious next question is:

Where should we create these Predicate objects?

I argue that we should create these objects by using predicate builder classes because this way we will put our query generation logic into one place. In other words, we don’t litter the source code of our service classes (or other components) with the query generation logic.

Example:

We can create a predicate builder class by following these steps:

  1. Create a final TodoPredicates class. The name of this class isn’t important, but I like to use the naming convention: [The name of the queried entity class]Predicates.
  2. Add a private constructor the created class. This ensures that no one can instantiate our predicate builder class.
  3. Add static predicate builder methods to this class. In our case, we will add only one predicate builder method (hasTitle(String title)) to this class and implement it by returning a new Predicate object.

The source code of the TodoPredicates class looks as follows:

import com.mysema.query.types.Predicate;

final class TodoPredicates {

    private TodoPredicates() {}

    static Predicate hasTitle(String title) {
        return QTodo.todo.title.eq(title);
    }
}

Let's move on and find out how we can invoke the created database query.

Invoking the Created Database Query

After we have specified the conditions of the invoked query by creating a new Predicate object, we can invoke the database query by using the methods provided by the QueryDslPredicateExecutor<T> interface.

The following examples demonstrates how we can invoke different database queries:

Example 1:
If we want to get the number of Todo objects that have the title 'foo', we have to create and invoke our database query by using this code:

Predicate pred = TodoPredicates.hasTitle("foo");
long count = repository.count(pred);

Example 2:
If we want to know if the database contains Todo objects that have the title 'foo', we have to create and invoke our database query by using this code:

Predicate pred = TodoPredicates.hasTitle("foo");
boolean exists = repository.exists(pred);

Example 3:
If we want to the get all Todo objects that have the title 'foo', we have to create and invoke our database query by using this code:

Predicate pred = TodoPredicates.hasTitle("foo");
Iterable<Todo> todoEntries = repository.findAll(pred);

Example 4:
If we want to get the Todo object whose title is 'foo', we have to create and invoke our database query by using this code:

Predicate pred = TodoPredicates.hasTitle("foo");
Todo todoEntry = repository.findOne(pred);

Let’s move on and find out how we can implement the search function.

Implementing the Search Function

We can implement our search function by following these steps:

  1. Modify our repository interface to support Querydsl.
  2. Create the predicate builder class that creates Predicate objects.
  3. Implement the service method that uses our predicate builder class and invokes the created database query by using our repository interface.

Let’s start by modifying our repository interface.

Modifying Our Repository Interface

We can make the necessary modifications to our repository interface by following these steps:

  1. Extend the QueryDslPredicateExecutor<T> interface.
  2. The type of the queried entity to Todo.

The source code of our repository interface looks as follows:

import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.repository.Repository;

import java.util.List;
import java.util.Optional;

interface TodoRepository extends Repository<Todo, Long>, QueryDslPredicateExecutor<Todo> {

    void delete(Todo deleted);

    List<Todo> findAll();

    Optional<Todo> findOne(Long id);

    void flush();

    Todo save(Todo persisted);
}

Let’s move on and create the predicate builder class.

Creating the Predicate Builder Class

We can create the predicate builder class that fulfils the requirements of our search function by following these steps:

  1. Create the predicate builder class and ensure that it cannot be instantiated.
  2. Add a static titleOrDescriptionContainsIgnoreCase(String searchTerm) method to the predicate builder class and set its return type to Predicate.
  3. Implement the titleOrDescriptionContainsIgnoreCase(String searchTerm) method by following these rules:
    • If the searchTerm is null or empty, return a Predicate object that returns all todo entries.
    • If the searchTerm is not null, return a Predicate object that ignores case and returns todo entries whose title or description contains the given search term.

The source code of our predicate builder class looks as follows:

import com.mysema.query.types.Predicate;

final class TodoPredicates {

    private TodoPredicates() {}

    static Predicate titleOrDescriptionContainsIgnoreCase(String searchTerm) {
        if (searchTerm == null || searchTerm.isEmpty()) {
            return QTodo.todo.isNotNull();
        }
        else {
            return QTodo.todo.description.containsIgnoreCase(searchTerm)
                    .or(QTodo.todo.title.containsIgnoreCase(searchTerm));
        }
    }
}

Let's find out how we can implement the service method that creates and invokes our database query.

Implementing the Service Method

The first thing that we have to do is to create an interface called TodoSearchService. This interface declares one method called findBySearchTerm(). This method takes the search term as a method parameter and returns a list of TodoDTO objects. The source code of the TodoSearchService interface looks as follows:

import java.util.List;

public interface TodoSearchService {

    List<TodoDTO> findBySearchTerm(String searchTerm);
}

We can implement this interface by following these steps:

  1. Create a RepositoryTodoSearchService class, implement the TodoSearchService interface, and annotate the class with the @Service annotation.
  2. Add a private final TodoRepository field to the created class.
  3. Create a constructor that injects a TodoRepository object to the created field by using constructor injection.
  4. Override the findBySearchTerm() method. Annotate the method with the @Transactional annotation and ensure that the transaction is read-only.
  5. Implement the findBySearchTerm() method by following these steps:
    1. Get the Predicate object by invoking the static titleOrDescriptionContainsIgnoreCase() method of the TodoPredicates class.
    2. Get the todo entries whose title or description contains the given search term by invoking the findAll() method of the QueryDslPredicateExecutor<T> interface. Pass the created Predicate object as a method parameter.
    3. Transform the Iterable<Todo> object into a list of TodoDTO objects and return the created list.

The source code of our service class looks as follows:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

import static net.petrikainulainen.springdata.jpa.todo.TodoPredicates.titleOrDescriptionContainsIgnoreCase;

@Service
final class RepositoryTodoSearchService implements TodoSearchService {

    private final TodoRepository repository;

    @Autowired
    public RepositoryTodoSearchService(TodoRepository repository) {
        this.repository = repository;
    }

    @Transactional(readOnly = true)
    @Override
    public List<TodoDTO> findBySearchTerm(String searchTerm) {
		Predicate searchPred = titleOrDescriptionContainsIgnoreCase(searchTerm);
        Iterable<Todo> searchResults = repository.findAll(searchPred);
        return TodoMapper.mapEntitiesIntoDTOs(searchResults);
    }
}

Let’s move on and find out when we should create our database queries by using Querydsl.

When Should We Use Querydsl?

The previous parts of this tutorial described how we can create static database queries by using the method names of our query methods, the @Query annotation, and named queries. Although these query generation methods are very useful, and they help us to get rid of boilerplate code, we cannot use them if we have to create dynamic queries (i.e queries that don’t have a constant number of conditions).

If we have to create dynamic database queries, we can use either the JPA Criteria API or Querydsl. The pros of using Querydsl are:

  • It supports dynamic queries.
  • It has a very clean API. In other words, it is easy to create complex queries with Querydsl, and the query generation code is easy to read.
  • It also supports JDO, Lucene, and MongoDB.

The only "problem" of Querydsl is that it isn't the standard way to create dynamic queries with the Java Persistence API. In my opinion this isn't a real problem, but if you can use only standard APIs, it is definitely a problem to you.

If you read my the previous part of this tutorial, you probably remember that we should use the JPA Criteria API only when we don't have a choice. That is why my opinion shouldn't be a surprise to you.

If we need to create dynamic database queries, we should use Querydsl.

Let's summarize what we learned from this blog post.

Summary

This blog post has taught us five things:

  • We can create Querydsl query types by using the Maven APT Plugin.
  • If we want to invoke queries that use Querydsl, our repository interface must extend the QueryDslPredicateExecutor<T> interface.
  • We can specify the conditions of our database queries by creating new Predicate objects.
  • We should create our Predicate objects by using predicate builder classes.
  • If we have to create dynamic database queries, we should use Querydsl.

The next part of this tutorial describes how we can sort the query results of our database queries.

P.S. You can get the example application of this blog post from Github.

If you want to learn how to use Spring Data JPA, you should read my Spring Data JPA tutorial.
102 comments… add one
  • neo Apr 14, 2012 @ 15:41

    hi,petri,why in code didn't find QPerson class

    • Petri Apr 14, 2012 @ 15:56

      Hi Neo,

      Did you remember to generate the Querydsl query type and add the target/generated-sources directory as a source directory for your project?

    • Manolosoft Sep 18, 2012 @ 17:34

      Hi neo, in the root project right click, in the maven option click in maven-generate-sources after process go to the folder target/generated-sources/your package, in this folder you find the class QPerson.java...

      Greetings.

      Petri, Thanks you, is the best of the best in tutorials, You helped me a lot.

      PD: Sorry for my bad english... Im from Colombia... xD

      • Petri Sep 20, 2012 @ 22:36

        Manolosoft,

        Great to hear that I could help you out.

  • neo Apr 15, 2012 @ 6:56

    Yes, got it, but don't understand why. Thank you very much!

  • Tiago Apr 19, 2012 @ 12:30

    Hi Petry, nice tutorial!

    I have one question concerning this aproach:

    Suppose I declared some methods that will be parsed into queries by Spring Data JPA in the interface that extends both JpaRepository and QueryDslPredicateExecutor (PersonRepository). "findByFirstname" for instance.

    In the service class (RepositoryPersonService) I implement the complex queries using QueryDsl.

    In the classes that use the service I want to call the simple methods (parsed by JPA) and also the complex methods (QueryDsl). For that I'd have to inject both repositories in my classes? One of type "PersonRepository" and other of type "PersonService".

    I don't think it's a good idea to have two injections that has the same objective. A solution would be copping every relevant method signature to the "PersonService" interface, and implement then at the "RepositoryPersonService" class, making use of an injected "PersonRepository". But it seems to me too much ctrl+c ctrl+v. Do you know a better approch?

    • Petri Apr 19, 2012 @ 19:31

      Hi Tiago,

      Thank you for your comment. You made a good point concerning the problems related to a situation where the PersonRepository is injected to the RepositoryPersonService class. It is indeed true that some methods of the PersonRepositoryService class are just delegating the method call forward to person repository.

      This sucks, but I still prefer this approach because this way I can use the service layer as transaction boundary and provide a decent separation between the controller "logic" and the application "logic".

      Adam Biem has some interesting ideas concerning the architecture of Java EE applications. He actually provides a solution for the problem you mentioned. Unfortunately, you cannot use his solution if you are using Spring MVC. If you are interested, you should definitely check out his blog: http://www.adam-bien.com/roller/abien/

  • Thomas Struntz Oct 26, 2012 @ 9:32

    Hi Petri,
    thanks for your tutorials. I have a question regarding querying with querydsl. Is it possible to create a predicate that contains (multiple) Joins?

    I have a many-to-many relationship with a link table. That link table contains an additional field and is also represented by a Entity class.

    I can create findBy methods that span tables. However my actual comparison is not just String or numbers but an actual function call, eg in SQL: where myFunction(:param1, :param2) = true.

    Is his possible?

    • Timo Westkämper Oct 26, 2012 @ 22:04

      Hi Thomas.

      Is your question how to create multiple joins in Spring Data with Querydsl or directly in a Querydsl query?

      You can also use custom expressions in Querydsl, but you need to register your functions in some way on the JPA provider level.

      Br,
      Timo Westkämper

  • Eric B Nov 14, 2012 @ 18:09

    Hi Petri,

    I'm using Eclipse 3.7 with m2e 1.1. I tried loading your code and it complains that the maven-apt-plugin lifecycle is not covered. I know I can manually generate the sources using maven generate-sources everytime something changes, but that is a bit of a nuissance. I tried instaling the m2e-querydsl plugin (https://github.com/ilx/m2e-querydsl) and that removed the error in the pom, but did nothing to generate the sources in the outputDirectory nor add the generated-sources to the Deployment Assembly.

    Are you aware of a functional m2e connector that works with querydsl? I am hoping that you encountered some while writing your blog and/or book.

    Thanks,

    Eric

    • Petri Nov 14, 2012 @ 19:31

      Hi Eric,

      Unfortunately the only advice that I was able to found was to generate the Querydsl query types by running the mvn generate-sources command at command prompt every time when something changes. This is indeed really irritating for Eclipse users and this is one drawback of Querydsl (at least at the moment).

  • Eric B Nov 15, 2012 @ 19:12

    Hi Petri,

    After a lot of stuggling and searching, I did find the following. Upgrading to m2e 1.2 and using the following in my pom.xml worked to compile your code. Also enables auto-regen of querydsl classes:

    
    <plugin>
        <!-- Requires mysema m2e plugin (http://ilx.github.com/m2e-querydsl/repository/0.0.5/) -->
        <groupId>com.mysema.maven</groupId>
        <artifactId>maven-apt-plugin</artifactId>
        <version>1.0.4</version>
        <executions>
            <execution>
                <goals>
                    <goal>process</goal>
                </goals>
                <configuration>
                    <logOnlyOnError>true</logOnlyOnError>
                    <outputDirectory>target/generated-sources/apt</outputDirectory>
                    <processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
                </configuration>
            </execution>
        </executions>
        <dependencies>
            <dependency>
                <groupId>com.mysema.querydsl</groupId>
                <artifactId>querydsl-apt</artifactId>
                <version>${querydsl.version}</version>
            </dependency>
            <dependency>
                <groupId>com.mysema.querydsl</groupId>
                <artifactId>querydsl-jpa</artifactId>
                <classifier>apt</classifier>
                <version>${querydsl.version}</version>
            </dependency>
        </dependencies>
    </plugin>
    <!-- right now this seems needed -->
    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>build-helper-maven-plugin</artifactId>
        <executions>
            <execution>
                <id>add-source</id>
                <phase>generate-sources</phase>
                <goals>
                    <goal>add-source</goal>
                </goals>
                <configuration>
                    <sources>
                        <source>target/generated-sources/apt</source>
                    </sources>
                </configuration>
            </execution>
        </executions>
    </plugin>
    
    

    This also required the m2e connector for build-helper-maven-plugin which was easily found via the m2e catalog.

    According to the querydsl plugin site (https://github.com/mysema/maven-apt-plugin/issues/9), the apt plugin is supposed to work without the querydsl connector/plugin, but I have been unable to get it to work without that plugin.

    Hopefully this info can help you or someone else in the future.

    Thanks,

    Eric

    • Petri Nov 15, 2012 @ 19:34

      Wow, that is valuable information. Thank you so much for posting it to my blog.

    • alxo Jul 20, 2018 @ 15:40

      I have used above plugin but still now not working in my pom.
      main problem is getting error of .
      plz any help for me.

      • Petri Jul 21, 2018 @ 19:50

        Hi,

        Unfortunately I don't use Eclipse => I am sorry but I cannot help you out :(

  • hari Nov 25, 2012 @ 18:07

    Hi Petri,
    Can you suggest on how to construct query by joining 2 tables?
    Thanks,
    Hari

    • Petri Nov 26, 2012 @ 20:09

      Hi Hari,

      If you are using the Spring Data JPA, you have two options:

      If your repository interface extends the QueryDslPredicateExecutor interface, you can use the any() method with collection path. More details about this approach is found from this Stack Overflow question.

      If you have created a custom repository implementation that extends the QueryDslRepositorySupport class (details here), you can use the from() method to get a JPQLQuery object and use the methods provided by it.

      I hope that this answered to your question.

  • SGB Nov 26, 2012 @ 22:42

    Petri,
    Very nice tutorials.
    As someone just starting to use SpringData and QueryDSL, this tutorial is very helpful.

    That said, in my Service impl class, this is not working for me:

    Iterable persons = personRepository.findAll(lastNameIsLike(searchTerm));

    Instead, I am having to do this:

    Iterable persons = personRepository.findAll(PersonPredicates.lastNameIsLike(searchTerm));

    to be able to use the static method lastNameIsLike in the PersonPredicates class.

    Not sure why - I must be making some mistake...

    Thanks again.

    • Petri Nov 26, 2012 @ 22:48

      Hi,

      Good to hear that this blog entry was useful to you. About your problem, are you having a compilation error or a runtime error?

    • Pedro Dusso Mar 30, 2015 @ 22:15

      I think what you are missing is the static import, like this:

      import static org.your.app.predicates.PersonPredicates.nameIsLike;

      • Petri Mar 30, 2015 @ 22:29

        That is true. I remove the imports which import classes (or static methods) that are found from the example application and described in the blog post. The reason for this is that if I wouldn't do it, the code listings would become way too long. I leave the other imports as is since I have noticed that they are useful to the reader.

        • Vicky Feb 11, 2017 @ 20:54

          Spring data rest with Query DSL support is very good but wanted to change the default behavior of Predicate boolean builder and "logic" to "or" when 2 or more arguments are passed with request. Please let me know, how to get this done.

          • Petri Feb 15, 2017 @ 18:26

            Hi,

            Unfortunately I don't know how you can modify the behavior of the predicate builder. One option is that you simply get its source code, make the required changes, and use your version of the predicate builder. However, if you want to update your Spring Data JPA version, you might have to rewrite your custom predicate builder component (if it has been changed by the Spring Data team).

  • Sean Creedon Dec 31, 2012 @ 19:44

    Hi,

    good tutorial,
    I am looking at passing multiple search terms (multiple predicates) which is fine within java as I can use BooleanExpression and or etc... methods to build a Predicate to pass to a findAll method.

    Where I'm failing to find info on is how to express this on a REST URL. I don't want to use findByXxxxAndYyyy as the number of properties is large enough to create a lot of these methods.

    Any advice or pointers much appreciated

    Regards
    Sean

    • Petri Jan 1, 2013 @ 20:20

      Are you using using Spring Data REST or a "normal" Spring MVC controller?

      If you are using a "normal" Spring MVC controller, you can create a request handler method that handles GET requests and specify the search conditions as optional request attributes (Use @RequestParam annotation and set the value of its required property to false.

      I have not personally tried Spring Data REST but I took a quick look at it and it seems that it can be used to provide automatic CRUD operations as a REST API. It has a support for search as well, but it seems that you have to create your queries by using the query generation by method name strategy. Like you said, this means that you have to create multiple repository methods if you have many optional search parameters. If this is not an option, you can always create a "normal" Spring MVC controller and use the technique described above.

      • Sean Creedon Jan 2, 2013 @ 23:27

        Thanks
        I'm using spring MVC controller at the moment, but I'm trying to ensure my URL is such that it aligns with spring data rest, so I'm trying to see what other people think. I'll do a bit more digging on Spring data REST and post back here.

    • Pedro Dusso Mar 30, 2015 @ 22:21

      If I may ask, how to you pass multiple predicates in the java?

  • Stephane Apr 25, 2013 @ 15:20

    Hi Petri,

    I'm now using your book, it's quite an easy ride into Spring Data JPA, thank you for that.

    My only road block for now is that I have a multi modules Maven project and that maven-apt-plugin configuration gives an error with a basedir not being found.

    [ERROR] execute error
    java.lang.IllegalStateException: basedir /home/stephane/dev/java/projects/learnintouch/src/main/java does not exist

    I guess there may be a property to set in there, specifying the input directory.

    But I can't yet find that information.

    Kind Regards,

    • Petri Apr 25, 2013 @ 20:50

      Hi Stephane,

      Have you configured the Maven APT plugin in the POM file of the module which contains your domain model classes?

  • Francois May 10, 2013 @ 13:00

    Hello Petri,

    Being new to Spring (and to java in general), I really enjoy your tutorials, this really help me get forward and learn new skills directly useable!
    I tired this one and I had at first tried the querydsl version 3.1.1, however I always got a nice stack:

    java.lang.NoSuchMethodError: com.mysema.query.jpa.impl.JPAQuery.from([Lcom/mysema/query/types/EntityPath;)Lcom/mysema/query/jpa/JPQLQueryBase;
    ....
    downgrading querydsl to 2.3.2 did the trick.

    I tried reading their documentation for v3.1.1, but I could not find anything that would point me into the correct direction?
    What should be changed in the above example to have it work with querydsl 3.1.1?

    Thanks a lot in advance ;)
    Francois

  • Nitin Jun 25, 2013 @ 16:32

    Hi,

    Thanks for the great tutorial. Is there any chance I can get this to work without maven? I have all the relevant queryDSL jars in my classpath. How do I go about generating the QueryDSL code (QPerson etc.)?

  • Pietro Bonanno Sep 28, 2013 @ 14:39

    Petri, thanks for these great tutorials!
    Actually I'm finding hard to use Querydsl extensively. Apart of NullPointerExceptions with some predicate, which could be my fault with packages' version, I had to switch to Jpql because it's not possible to specify eager load of properties inside predicate. I should do an extra call after querying and this would be inefficient.
    Are you having similar issues?

    • Petri Sep 29, 2013 @ 20:19

      Hi Pietro,

      I haven't personally experienced any NPEs but I have to admit that I haven't used Querydsl for creating complicated queries. The project in which I work now uses criteria queries and I use them too because I want to keep the code base consistent.

      I think that it might be a good idea to create a bug report to Github.

      Have you considered using joins or subqueries for fetching the required properties of an entity?

  • Pietro Bonanno Oct 7, 2013 @ 18:36

    It seems the problem was already raised, and waiting for a "higher level" solution: http://forum.spring.io/forum/spring-projects/data/108202-custom-fetch-groups-with-spring-data-jpa-possible
    I'll explore the subquery solution, but it would involve a sub select, don't know if suits my needs. Anyway, thanks for your suggestions.

    • Petri Oct 8, 2013 @ 17:23

      You are welcome.

      JPA 2.1 adds support for both named and unnamed entity graphs. I assume that it is just a matter of time before Spring Data JPA supports these features as well.

  • Pietro Bonanno Oct 15, 2013 @ 16:12

    Hi Petri, I found the reason for my NPE when building predicates...
    Querydsl by default initializes only direct properties, and leaves to the user the choice to init nested ones.
    So, by default, if QPerson person is your Query type, person.address.city throws an NPE.
    Solutions are to annotate entities, but I preferred the "create on demand" alternative, which is explained here:
    https://github.com/mysema/querydsl/issues/134

    Shortly, adding

    <querydsl.entityAccessors>true</querydsl.entityAccessors>

    to your Maven APT configuration, will replace direct fields with accessors (and will screw your existing code :P)

    Bye

    • Petri Oct 20, 2013 @ 22:03

      Great! Thanks for dropping a note about the solution!

  • S Atah Ahmed Khan Nov 18, 2013 @ 10:13

    Hi Petri;

    I want to write a join like 'Select * from A a left join B b on a.MEDIA_ID = b.ID',
    how to achieve this in Spring Data JPA.

    Thanks in advance.

    • Petri Nov 19, 2013 @ 18:52

      If the A class has a field called b and you have mapped your domain model correctly, you can create the query by adding the following query method to your repository interface:

      
      @Query("Select a From A a LEFT JOIN FETCH a.b")
      public List<A> getA();
      
      
      • S Atah Ahmed Khan Nov 29, 2013 @ 9:42

        thanks Petri,
        How to make association?

        I have two table TableA and TableB. Table A has column media_Id which is the primary key of Table B. and Table B has 'description' for a particular media_Id.

        Now i want to get the content of Table A with description added in the for every record. I don' t know what i need to write in My entities class for association.
        But when as kept

        @OneToOne(targetEntity=B.class)
        @JoinColumn(name='media_Id')
        private B b;

        I am getting all the content of Table B in findAll() query.

        But what i want is only description column from table b not entire row of table B. Hope you understand...

        Wating for your reply. Happy weekend....

        • Petri Nov 29, 2013 @ 20:01

          I think that your best option is to return a DTO instead of an entity. This way you can select the fields which you want to return. Take a look at this comment made by Dominik Werner. It should help you to get started.

          If you have further questions about this, don't hesitate to ask!

  • Marco Nov 29, 2013 @ 18:17

    Hi Petri!

    I have read your Spring Data book and I've used your implementation example in order to create some QueryDsl Predicates, using them by extending in my repositories QueryDslPredicateExecutor interface.

    Now I've a little bit complicated situation. My EntityA has a ManyToMany relationship with EntityB

    EntityA{
    ...
    @ManyToMany
    Set entitiesB;
    }

    EntityB{
    String name;
    }

    I have to filter EntityA based on the fact that EntityA have almost one EntityB which contained name string I'm looking for. For example I want to see all EntityA which have almost one EntityB with name like 'ISO'.

    Here is my JPA query:
    select eA from EntityA eA join eA.entitiesB eB where eB.name in (:names) group by eA

    This query works, I dont know if it is very optimized.. but it works.

    I cannot realize how can I translate this in a predicate, that is required by findAll method in my repository.. I have create a JpaQuery object, but Jpa repositories want Predicates..

    Thank you for any suggestion!

    • Petri Nov 29, 2013 @ 21:03

      Hi,

      It would be useful to see the SQL query which is created from your JPQL query. Could you add it here? The reason why I am asking this is that I am wondering what column is used in the group by clause.

      I haven't tested this predicate builder class in any way but here is what I have so far:

      
      public class EntityAPredicates {
          public static Predicate entityBNameIn(final Collection<String> names) {
              return QEntityA.entityA.entitiesB.any().name.in(names); 
          }
      }
      
      
      • Marco Dec 2, 2013 @ 8:22

        This is the query. I've not changed real name of entities this time..

        So the real query is:

        select n from Norma n join n.entiNormazione e where e.sigla in (:sigle) group by n

        that gives:

        select
        entinormaz0_.norma as norma6_1_,
        entinormaz0_.enti_normazione as enti2_7_1_,
        entenormaz1_.id as id2_0_,
        entenormaz1_.created_by as created2_2_0_,
        entenormaz1_.created_date as created3_2_0_,
        entenormaz1_.last_modified_by as last4_2_0_,
        entenormaz1_.last_modified_date as last5_2_0_,
        entenormaz1_.version as version2_0_,
        entenormaz1_.sigla as sigla2_0_
        from
        norma_enti_normazione entinormaz0_
        inner join
        ente_normazione entenormaz1_
        on entinormaz0_.enti_normazione=entenormaz1_.id
        where
        entinormaz0_.norma=?
        order by
        entenormaz1_.sigla

        Thanks for now, I'll let you know if it is correct.

        Thank you very much for your reply.
        Marco

      • Marco Dec 2, 2013 @ 8:31

        Thank you very much, it works!

        I had not really thought about this solution.

        now the generated query is:

        select
        norma0_.id as id6_,
        norma0_.created_by as created2_6_,
        norma0_.created_date as created3_6_,
        norma0_.last_modified_by as last4_6_,
        norma0_.last_modified_date as last5_6_,
        norma0_.version as version6_,
        norma0_.aggiornamento as aggiorna7_6_,
        norma0_.anno as anno6_,
        norma0_.descrizione as descrizi9_6_,
        norma0_.numero as numero6_,
        norma0_.titolo as titolo6_
        from
        norma norma0_
        where
        (
        exists (
        select
        1
        from
        ente_normazione entenormaz1_
        where
        (
        entenormaz1_.id in (
        select
        entinormaz2_.enti_normazione
        from
        norma_enti_normazione entinormaz2_
        where
        norma0_.id=entinormaz2_.norma
        )
        )
        and entenormaz1_.sigla=?
        )
        )
        and (
        exists (
        select
        1
        from
        ente_normazione entenormaz3_
        where
        (
        entenormaz3_.id in (
        select
        entinormaz4_.enti_normazione
        from
        norma_enti_normazione entinormaz4_
        where
        norma0_.id=entinormaz4_.norma
        )
        )
        and entenormaz3_.sigla=?
        )
        )
        order by
        norma0_.numero asc limit ?

  • David Apr 11, 2014 @ 23:25

    Thank you for the great tutorials you have provided, they really help clarify issues. I like the way you use QueryDSL, but I wonder if you have tried to use it with Gradle? As far as I can tell, based upon searching, it should work but haven't actually seen a working implementation.

    • Petri Apr 13, 2014 @ 11:24

      Thank you for your kind words. I really appreciate them!

      I haven't tried Gradle yet. However, I managed to find this Stack Overflow question which might help you out.

  • Toub Apr 15, 2014 @ 11:46
    • Petri Apr 15, 2014 @ 12:02

      It seems to be working fine now. Perhaps it was a temporary problem.

  • gaurav May 13, 2014 @ 12:32

    Hello, I was going through your amazing tutorials to learn Spring Data JPA. However, I am stuck with one issue i.e. I see reference for a class "net.petrikainulainen.spring.datajpa.model.QPerson". However, This class is no where to be found in the source code shared by you. Now this is giving the compile error in my eclipse.
    Kindly let me know if I am missing something.

    • Petri May 13, 2014 @ 18:07

      That class is generated by the Maven APT plugin. Its process goal is invoked at the generate-sources Maven lifecycle phase. If you cannot get this work in Eclipse, you should try the configuration explained in this comment.

  • Gustavo May 30, 2014 @ 22:13

    Thanks!!! great post i have the problem with "NoSuchMethodError: com.mysema.query.jpa.JPQLQuery.from" but after change the QUERYDSL dependecy for the versión 2.3.2 my proyect works!

    com.mysema.querydsl
    querydsl-apt
    2.3.2

    com.mysema.querydsl
    querydsl-jpa
    2.3.2

    greetings from chile!

    • Petri Jun 2, 2014 @ 21:00

      The example application of this blog post uses rather old versions of Spring Data JPA and Querydsl. You might want to update both dependencies to the latest versions (Spring Data JPA 1.6.0.RELEASE should support Querydsl 3.3.3).

      Anyway, It is good to hear that you were able to solve your problem!

  • Anders M Feb 25, 2015 @ 16:42

    Hey Petri, awesome guide, though I'm trying to combine different requirements, like isNumberLike and isAmountEnough.
    I can't seem to access get any of the ".any", ".and" etc. methods

    versions:
    compile('com.mysema.querydsl:querydsl-core:3.6.1')
    compile('com.mysema.querydsl:querydsl-apt:3.6.1')
    compile('com.mysema.querydsl:querydsl-jpa:3.6.1')
    springBootVersion = '1.2.1.RELEASE'

    Any idea, or would you like code example ?

  • Hongli Apr 18, 2015 @ 2:29

    Hi Petri,

    Just want to say thank you for your awesome write up.
    It's amazing to see your efforts to help every one out here.

    I will keep reading your posts.

    • Petri Apr 18, 2015 @ 10:20

      Hi Hongli,

      Thank you for your kind words. I really appreciate them.

  • noviceQueryDslUser May 25, 2015 @ 7:17

    Hi

    I am using queryDsl for complex search queries in my application. I am new to querydsl. I have started with a simple code to fetch few rows from one single table(TableA - Person table) by passing the id.
    But I have to find list of person(Count) with the same id in some other table(TableB).
    Can you please help me in finding the count of the pId to be fetched from tableB.
    I would like to know how to use join and also how to fetch count of rows using queryDSL.

    Please let me know if more info needed.
    Thanks in advance.

    • Petri May 25, 2015 @ 22:21

      Hi,

      I would like to know how to use join

      The Querydsl tutorial: 2.1.7 Using Joins describes how you can create joins with Querydsl.

      How to fetch count of rows using queryDSL

      You can get the number of persons which match with the created Querydsl Predicate by invoking the count() method of the QuerydslPredicateExecutor interface and passing your Predicate as a method parameter.

      I hope that this answered to your questions. If you have any additional questions, don't hesitate to ask them.

      • noviceQueryDslUser May 27, 2015 @ 18:34

        Thank you very much. Count method worked successfully in my application. However when I refer the suggested URL and tried to join tables , I get error. Can you please help on that.

        I tried to debug and get this value fetched from DEBUG editor in Eclipse.

        
        select tabB 
        from TABLEB tabB 
          inner join TABLE tabA on tabB.quotation_no = ?1 
        
        

        But I pass the id as Long value(1234). I am not aware of why did it formed "?1".

        Code Snippet:

        
        BooleanBuilder booleanBuilder = new BooleanBuilder();
        if (pId != null) {
        	booleanBuilder.or(tabB.pID.eq(qId));
        	jpaQuery =  jpaQuery.from(tabB).join(tabA).on(booleanBuilder.getValue());
        }
        
        
        • Petri May 28, 2015 @ 21:03

          Hi,

          I am sorry that it took me some time to answer to your question. I have one question for you:

          What error do you get? Also, if your application throws an exception, I would like to see the stacktrace.

          Anyway, it seems that you are trying to join the tabA table by specifying a join condition which states that the value of the quotation_no column must be X. The problem is that

          1. The quotation_no column is found from the tabB table.
          2. The join condition must specify a value that is found from a column of the tabA table.

          Check out this StackOverflow question. This answer describes how you can specify join conditions by using the on() method.

          By the way, if you want to write the actual SQL statements to the log file of your application, you should take look at log4jdbc.

          • noviceQueryDslUser Jun 2, 2015 @ 9:58

            Thankyou so much answering and also providing added info. It helped a lot.

          • Petri Jun 2, 2015 @ 11:04

            You are welcome! I am happy to hear that I could help you.

      • noviceQueryDslUser May 27, 2015 @ 18:41

        My application has a search page, where the user have nearly 20 form fields to fill. It can be entered or even empty. All fields are search criteria and are present in multiple tables. I need to form a complex query by joining multiple tables using queryDSL. It would be really great if you can help to solve this. Thanks...

        • Petri May 28, 2015 @ 21:08

          I answered to your question here.

  • Java User Jun 11, 2015 @ 6:42

    Hi. Your tutorial was very useful. But i get a error when accessing entitymanager. I have posted in stack over flow [link](http://stackoverflow.com/questions/30700156/error-while-accessing-entitymanager-in-spring-boot)
    Can I ask for your help on the issue.
    Thankyou.

    • Petri Jun 12, 2015 @ 20:47

      Hi,

      I have to confess that I have no idea how you can solve your problem. I have one question though:

      Why you want to access the EntityManager instead of creating the Predicate object by using the generated Querydsl query classes?

      • Anonymous Jun 13, 2015 @ 16:18

        Thankyou so much. I got exception without using entity manager. Below is the error
        java.lang.NullPointerException: null
        at com.mysema.query.jpa.impl.AbstractJPAQuery.createQuery(AbstractJPAQuery.java:129)

        So I tried adding entity manager.

        • Petri Jun 16, 2015 @ 21:48

          But the thing is that you don't have to create a new JPAQuery object. You can just:

          1. Create a Predicate object by using the generated Querydsl query classes.
          2. Pass the created Predicate object forward to your Spring Data JPA repository.

          Why you decided to use your approach? The reason why I ask this is that I am wondering if I am missing something.

  • Eva Tomovska Aug 6, 2015 @ 23:16

    Thank you for helpful tutorial:)

    • Petri Aug 6, 2015 @ 23:21

      You are welcome. I am happy to hear that this tutorial was useful to you!

  • Pradeep Oct 27, 2015 @ 12:31

    Hi Petri,

    I am forming a complex Query which has two subqueries with the help of SQLQuery and SQLSubQuery. I am putting the subquery in the projection(.list()) which will return a column with single row and the where clouse has user.id where user is the table i am also using in the main query. Separately all the query and subquery is working fine. But when I am putting it all together it is throwing null pointer exception.

    Please note that I have tested both the sub query they are not returning multiple rows. and when I am removing the subqueries from the main query then the main query is executing perfectly.

    Please share your views.

    A

    • Pradeep Oct 27, 2015 @ 15:07

      Adding more to .....bellow is the replica what I am trying to do...

      
      Configuration configuration = new Configuration(dialect);
      SQLQuery query1 = new SQLQuery(conn, configuration); 
      SQLSubQuery sql = new SQLSubQuery().from(user);
      List<List> tupleList = query1.from(user)
      			.list(sql.where(user.firstName.eq("Pradeep"))
      			.list(user.firstName));
      
      

      and generated query is

      
      select (select USER.first_name from USER USER where USER.first_name = ?) from USER USER
      
      

      which is correct. But I am getting null pointer exception.

      Can we use subquery in list. Is this the problem.

      • Petri Oct 27, 2015 @ 20:53

        Hi Pradeep,

        I have to admit that I don't know what is wrong. I suggest that you post this question to the Querydsl Google Group. I hope that the readers of that group can answer to your question.

  • jeremy Jan 28, 2016 @ 16:48

    Hi Petri
    It also have to add

    com.mysema.querydsl
    querydsl-jpa
    ${querydsl.version}

    to pom.xml

    • Petri Jan 28, 2016 @ 20:09

      Hi Jeremy,

      You are right! I cannot believe that I missed that one. Anyway, I will update this blog post tomorrow. Thank you for reporting this.

  • Harris Feb 29, 2016 @ 14:41

    is there a way I can use query dsl to query numbers especially bigintergers. its an e-commerce application.

  • Lucien May 14, 2016 @ 19:57

    Hi Petri
    How to config apt-maven-plugin in the gradle?
    Thanks

    • Petri May 17, 2016 @ 19:31

      Hi Lucien,

      You cannot use Maven plugins with Gradle. However, I found a StackOverflow answer that describes how you can create Querydsl query types with Gradle.

      • Lucien May 18, 2016 @ 13:40

        Thanks a lot
        The problem has been solved

        • Petri May 18, 2016 @ 17:13

          You are welcome.

  • Daoudi Mohamed Jun 1, 2016 @ 18:05

    Hi Petry, nice tutorial
    Thank you ;)

  • SGB Nov 6, 2016 @ 20:32

    I am working a sample app and followed this pattern of using a separate Predicate class where all the predicates live. I like the idea of extracting the predicate builders to a separate class so that it can be re-used in multiple business services. But upon testing, I find that when it is building a query from multiple Q* classes, it is doing a cross join instead of inner join.

    For eg, Say, we have two entities :
    Person {
    Long id;

    ... ... ... ... ... ...
    (mappedBy = "person")
    Address address;
    }

    Address {
    Long id;
    ... ... ... ... ... ...
    @OneToOne
    @JoinColumn(name = "person_id", foreignKey = @ForeignKey(name = "address_to_person_fk"))
    Person person;
    }

    In my service layer [ResidentService()], I have a method for a business function where construct a complicated predicate, for eg:
    public Person findResidentsWithLastnameInStreet(String lastNamePrefix, String street)) {
    Predicate predicate = new BooleanBuilder()
    .and(PersonPredicate.lastNameEndsWith(lastNamePrefix))
    .and(AddressPredicate.addressLine1Eq(line1))
    .getValue();
    return personRepo.findAll(predicate);
    }

    The JPA query generated by QueryDSL seems to be a cross product. Something like this:

    from
    person person0_ cross
    join
    address address1_

    where
    address1_.person_id = person0_.id
    and address1_.line1=?
    and person0_.lastName like ? escape '!'

    Am using QueryDSL 4.1.4 & spring-data-jpa 1.10.3

  • David Mar 3, 2017 @ 17:01

    Hi.

    I have used QueryDSL and I have expirience problems to make it work DISTINCT.
    Typical select distinct field1, not worked for me, maybe I'm doing something wrong. Could you please provide an example.
    thanks

    • Petri Mar 8, 2017 @ 11:10

      Hi,

      Can you post your query generation code here?

  • Anonymous Feb 21, 2018 @ 13:29

    You create a constructor that injects TodoRepository, but you don't show how you inject that field neither do you implement that interface. How do you do that?

    • Petri Feb 21, 2018 @ 14:16

      Hi,

      The TodoRepository bean is created by the Spring container that also injects it into the RepositoryTodoSearchService bean. Also, I don't have to implement the TodoRepository interface because Spring Data JPA provides an implementation for it. If you are not familiar with Spring Data JPA, I recommend that you take a look at these blog posts:

  • Kverchi Jul 18, 2018 @ 15:49

    Hi, Petri!
    Thank you so much for this tutorial! I hadn't understood where to create Predicate objects, but after reading your tutorial I got it. Also I found answers for my other questions. You have explained all things clearly and with good examples.

    • Petri Jul 19, 2018 @ 22:26

      Hi,

      You are welcome. I am happy to hear that this tutorial was useful to you!

  • oshatrk Sep 17, 2019 @ 15:04

    Hei, Petri!
    At first, thank You for this article.

    I found minor problem with Querydsl and SQL — the method findOne() doesn't emit 'LIMIT 1'.

    For my project I just overrided the line in Spring Data Jpa
    https://github.com/spring-projects/spring-data-jpa/blob/04d0aa140f3343dca19ed62519cdcda5a372e4aa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java#L93

    from
    `return Optional.ofNullable(createQuery(predicate).select(path).fetchOne());`
    to
    `return Optional.ofNullable(createQuery(predicate).limit(1).select(path).fetchOne());`

    To override I copied few files mentioned in my question on https://stackoverflow.com/q/57843412/8996454

    This change solved my problem, but I still unsure is it the correct place for this fix.

  • Pratick Aug 4, 2020 @ 16:37

    Hello Petri,
    Quick question on query dsl, Does queryDSL supports derived field for mongoDB.
    I have scenario, where status field is dependent on current dateTime. Can you please guide me.

    Thanks,

    • Petri Aug 4, 2020 @ 19:28

      Hi,

      Unfortunately I don't know the answer to your question. To be honest, I didn't even know that Querydsl has support for MongoDB (I haven't used Querydsl for years).

Leave a Reply