Today’s war story talks about the like expression handling of Spring Data JPA.

Although I have written earlier about a better solution for implementing text based search functions, using an external search server like Solr is not a viable option if the implemented search function is rather simple.

Let’s consider the following example.

Using the Like Expressions in the Old Way

We have a simple entity called Person which has two fields: id and lastName. The source code of this entity looks as follows:

 1import javax.persistence.*;
 2
 3@Entity
 4@Table(name = "persons")
 5public class Person {
 6
 7    @Id
 8    @GeneratedValue(strategy = GenerationType.AUTO)
 9    private Long id;
10
11    @Column(name = "last_name", nullable = false)
12    private String lastName;
13
14	//Methods are omitted.
15}

Let’s assume that we have to implement a search function which returns persons whose last name matches with the given like expression. Since this task is obviously so demanding, we have to implement the search function by using both SQL and JPQL queries. The query method used in the production version of our application is decided after we have figured out which implementation is faster.

After we have implemented the both query methods, the source code of our repository interface looks as follows:

 1import org.springframework.data.jpa.repository.JpaRepository;
 2import org.springframework.data.jpa.repository.Query;
 3import org.springframework.data.repository.query.Param;
 4
 5public interface PersonRepository extends JpaRepository<Person, Long> {
 6
 7    @Query(
 8            value = "SELECT * FROM persons WHERE last_name LIKE :searchTerm",
 9            nativeQuery = true
10    )
11    public List<Person> searchWithNativeQuery(@Param("searchTerm") String searchTerm);
12
13    @Query("SELECT p FROM Person p WHERE p.lastName LIKE :searchTerm")
14    public List<Person> searchWithJPQLQuery(@Param("searchTerm") String searchTerm);
15
16}

The problem of this approach is that we have to create the like expression on the service layer. The source code of our service implementation looks as follows:

 1import org.springframework.beans.factory.annotation.Autowired;
 2import org.springframework.stereotype.Service;
 3import org.springframework.transaction.annotation.Transactional;
 4
 5@Service
 6public class RepositoryPersonService implements PersonService {
 7
 8	private PersonRepository repository;
 9
10	@Autowired
11	public RepositoryPersonService(PersonRepository repository) {
12		this.repository = repository;
13	}
14
15	@Transactional(readOnly=true)
16	@Override
17	public List<Person> search(String searchTerm) {
18		String likeExpression = "%" + searchTerm + "%";
19		//Call the correct query method, pass the like expression as method parameter
20		//and return the found persons.
21	}
22}

Although I would not call this a huge problem, the creation of the like expression does not belong to the responsibilities of the service layer. Of course we could solve this problem by adding a custom method to our repository but that would be a bit extreme.

Luckily, Spring Data JPA offers a better to do this. Let’s find out what it is.

A New and Better World

Spring Data JPA version 1.3.1 added a possibility to specify like expressions “inside” the executed query. We can modify our example to use this strategy by following these steps:

  1. Modify the query method and specify the like expressions “inside” the queries.
  2. Clean up the RepositoryPersonService class by removing the like expression creation logic from it.

Let’s move on and find out how we move the like expression creation logic from the service layer to our repository interface.

Modifying the Query Methods

Because we want to find persons whose last name contains the given search term, we must modify the query methods of our repository by adding the character ‘%’ to the start and end of the placeholder (:searchTerm) which is replaced with the given search term when the executed query is constructed.

The source code of the PersonRepository interface looks as follows:

 1import org.springframework.data.jpa.repository.JpaRepository;
 2import org.springframework.data.jpa.repository.Query;
 3import org.springframework.data.repository.query.Param;
 4
 5public interface PersonRepository extends JpaRepository<Person, Long> {
 6
 7    @Query(
 8            value = "SELECT * FROM persons WHERE last_name LIKE %:searchTerm%",
 9            nativeQuery = true
10    )
11    public List<Person> searchWithNativeQuery(@Param("searchTerm") String searchTerm);
12
13    @Query("SELECT p FROM Person p WHERE p.lastName LIKE %:searchTerm%")
14    public List<Person> searchWithJPQLQuery(@Param("searchTerm") String searchTerm);
15
16}

Note: We can naturally use other like expressions as well:

  • If we want to find persons whose last name starts with the given search term, we must add the character ‘%’ to the end of the placeholder.
  • We can find the persons whose last name ends with the given search term by adding the character ‘%’ to the start of the placeholder.

Enough with the theory. Let’s take a quick look at our new service class.

Cleaning up the Service Class

We can now remove the like expression creation logic from our service class. The source code of the RepositoryPersonService class looks as follows:

 1import org.springframework.beans.factory.annotation.Autowired;
 2import org.springframework.stereotype.Service;
 3import org.springframework.transaction.annotation.Transactional;
 4
 5@Service
 6public class RepositoryPersonService implements PersonService {
 7
 8	private PersonRepository repository;
 9
10	@Autowired
11	public RepositoryPersonService(PersonRepository repository) {
12		this.repository = repository;
13	}
14
15	@Transactional(readOnly=true)
16	@Override
17	public List<Person> search(String searchTerm) {
18		//Call the correct query method and and return the results.
19	}
20}

We are done. Let’s spend a moment summarizing what we have learned.

Summary

This blog post has taught us two things:

  • We can specify a like expression “inside” the executed query if we are using Spring Data JPA 1.3.1 or newer.
  • We can use this approach for both native and JPQL queries.

Like I said earlier, this might seem like a minor improvement but we must remember that a clean code base is build by making a lot of small improvements.

A word of warning though. Spring Data JPA 1.3.2 introduced a bug which breaks the backwards compatibility of like expression handling. This means that if we want to use Spring Data JPA 1.3.2, we have to update our queries by using the approach described in this blog post.