128

What is the simplest way of declaring a Spring data JPA query that uses properties of an input parameter as query parameters?

For example, suppose I have an entity class:

public class Person {
    @Id
    private long id;

    @Column
    private String forename;

    @Column
    private String surname;
}

and another class:

public class Name {
    private String forename;
    private String surname;

    [constructor and getters]
}

... then I would like to write a Spring data repository as follows:

public interface PersonRepository extends CrudRepository<Person, Long> {
    @Query("select p from Person p where p.forename = ?1.forename and p.surname = ?1.surname")
    findByName(Name name);
}

... but Spring data / JPA doesn't like me specifying property names on the ?1 parameter.

What is the neatest alternative?

Jacob van Lingen
  • 8,989
  • 7
  • 48
  • 78
Kkkev
  • 4,716
  • 5
  • 27
  • 43

10 Answers10

256

This link will help you: Spring Data JPA M1 with SpEL expressions supported. The similar example would be:

@Query("select u from User u where u.firstname = :#{#customer.firstname}")
List<User> findUsersByCustomersFirstname(@Param("customer") Customer customer);

For more details see: SpEL support in Spring Data JPA @Query definitions

Manuel Jordan
  • 15,253
  • 21
  • 95
  • 158
sunday
  • 2,763
  • 1
  • 13
  • 12
  • 5
    this is spring-data-jpa 1.7.2 + and requires spring 4 – chrismarx May 14 '15 at 15:58
  • Customer seems to have to be an entity here, evaluating POJOs doesn't work for me. – Schaka Mar 15 '16 at 16:11
  • Haven't tried with a pojo, I can confirm this is working with entities at least. Someone else did mention that they ran into problems if they include both an object, and additional arguments (see the comments here) - https://spring.io/blog/2014/07/15/spel-support-in-spring-data-jpa-query-definitions – chrismarx Sep 02 '16 at 15:42
  • 3
    And I can confirm I have the same problem, even with spring boot 1.4/spring 4/spring data jpa 1.10 - – chrismarx Sep 02 '16 at 18:32
  • Hi Sunday, It is working for me, I would like asking something. if I used Param within the repository, could I use Dtos with Param annotations? – J. Abel Oct 04 '19 at 14:45
  • Awesome! Couldn't find this even in the documentation. Thanks bruv! – de.la.ru Dec 19 '19 at 09:07
  • 1
    customer can be a DTO this will work, tested with spring boot 2.2.0.RELEASE – dom Nov 13 '21 at 21:03
34

Define the query method with signatures as follows.

@Query(select p from Person p where p.forename = :forename and p.surname = :surname)
User findByForenameAndSurname(@Param("surname") String lastname,
                             @Param("forename") String firstname);
}

For further details, check the Spring Data JPA reference

CuriosMind...
  • 477
  • 5
  • 10
  • 6
    That doesn't answer the question. The pertinent point is that I would like to use properties of an input parameter as query parameters, not separate input parameters. – Kkkev Jun 02 '14 at 11:23
  • Oops, I mis-read your query. I believe that support is not provided by Spring Data JPA(probably even JDBC might not; I may be wrong here). As another alternative(apart from the custom repository approach), you can write a service wrapper method that takes the Person object as parameter and pass the relevant parameters to the repository method. Advantage with this approach is that the signature that you expected(pass Person object) is intact and also you use the implementation provided by Spring Data JPA. – CuriosMind... Jun 04 '14 at 13:52
12

You could also solve it with an interface default method:

 @Query(select p from Person p where p.forename = :forename and p.surname = :surname)
User findByForenameAndSurname(@Param("surname") String lastname,
                         @Param("forename") String firstname);

default User findByName(Name name) {
  return findByForenameAndSurname(name.getLastname(), name.getFirstname());
}

Of course you'd still have the actual repository function publicly visible...

mmey
  • 1,545
  • 16
  • 24
11

What you want is not possible. You have to create two parameters, and bind them separately:

select p from Person p where p.forename = :forename and p.surname = :surname
...
query.setParameter("forename", name.getForename());
query.setParameter("surname", name.getSurname());
JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • 1
    Presumably this would need to be implemented in a custom repository method as per http://static.springsource.org/spring-data/data-jpa/docs/current/reference/html/#repositories.single-repository-behaviour? – Kkkev May 30 '12 at 07:53
  • 1
    I know how JPA works, but I don't know what spring-data does for you and what you must do by yourself. Sorry. – JB Nizet May 30 '12 at 08:26
9

You can try something like this:

public interface PersonRepository extends CrudRepository<Person, Long> {
       @Query("select p from Person AS p"
       + " ,Name AS n"  
       + " where p.forename = n.forename "
       + " and p.surname = n.surname"
       + " and n = :name")
       Set<Person>findByName(@Param("name") Name name);
    }
Casi
  • 101
  • 1
  • 5
3

if we are using JpaRepository then it will internally created the queries.

Sample

findByLastnameAndFirstname(String lastname,String firstname)

findByLastnameOrFirstname(String lastname,String firstname)

findByStartDateBetween(Date date1,Date2)

findById(int id)

Note

if suppose we need complex queries then we need to write manual queries like

@Query("SELECT salesOrder FROM SalesOrder salesOrder WHERE salesOrder.clientId=:clientId AND salesOrder.driver_username=:driver_username AND salesOrder.date>=:fdate AND salesOrder.date<=:tdate ")
 @Transactional(readOnly=true)
 List<SalesOrder> findAllSalesByDriver(@Param("clientId")Integer clientId, @Param("driver_username")String driver_username, @Param("fdate") Date fDate, @Param("tdate") Date tdate);
Community
  • 1
  • 1
Mohammad Adil
  • 503
  • 6
  • 13
3

The simplicity of Spring Data JPA is that it tries to interpret from the name of the function in repository without specifying any additional @Query or @Param annotations. If you are supplying the complete name, try to break it down as firstname and lastname and then use something like this -

HotelEntity findByName(String name);

My HotelEntity does contain the field name so JPA tries to interpret on its own to infer the name of the field I am trying to query on and create a subsequent query internally. Some more evidence from JPA documentation - enter image description here

Further details - here

Apostolos
  • 10,033
  • 5
  • 24
  • 39
Anvita Shukla
  • 409
  • 7
  • 7
1

Are you working with a @Service too? Because if you are, then you can @Autowired your PersonRepository to the @Service and then in the service just invoke the Name class and use the form that @CuriosMind... proposed:

@Query(select p from Person p where p.forename = :forename and p.surname = :surname)
User findByForenameAndSurname(@Param("surname") String lastname,
                         @Param("forename") String firstname);
}

and when invoking the method from the repository in the service, you can then pass those parameters.

Leo
  • 472
  • 5
  • 9
-3
    for using this, you can create a Repository for example this one:
    Member findByEmail(String email);

    List<Member> findByDate(Date date);
    // custom query example and return a member
   @Query("select m from Member m where m.username = :username and m.password=:password")
        Member findByUsernameAndPassword(@Param("username") String username, @Param("password") String password);
KIBOU Hassan
  • 381
  • 4
  • 13
-5
@Autowired
private EntityManager entityManager;

@RequestMapping("/authors/{fname}/{lname}")
    public List actionAutherMulti(@PathVariable("fname") String fname, @PathVariable("lname") String lname) {
        return entityManager.createQuery("select A from Auther A WHERE A.firstName = ?1 AND A.lastName=?2")
                .setParameter(1, fname)
                .setParameter(2, lname)
                .getResultList();
    }
Ram Pukar
  • 1,583
  • 15
  • 17