-1

Is there a way to build a custom repository method for a special query in Spring Data, that accepts multiple and-connected search strings, meaning that all those strings need to be found in one database field?

Take this example, where only one string is accepted:

import java.util.List;

import org.jean.dossier.model.entities.Contact;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ContactRepository extends JpaRepository<Contact, Long> {
    List<Contact> findTop10ByNameContainingOrderByName(String name);
}

Here I essentially select all the contacts that have the content of the name variable within the contact.name parameter. I also sort the result by name, and I limit the output to 10 rows.

This search only takes one parameter name, i.e. "alfonso" to find all the contacts that are called "alfonso". What I would like to achieve though, is the same search with multiple strings.

Example: I provide the strings "al" and "du". Possible results of the query would be:

  1. Alfonso Ducatti
  2. Malina Perfudu
  3. Galina Padufi

I would assume that a correct way to change the above method would be something like:

List findTop10ByNameContainingOrderByName(String[] name);

Is there any way to do that?

1

1 Answer 1

1

Apparently what I am looking for is not possible with Spring Data. I therefore built it a bit different, by still achieving my result. Maybe there is another one out there facing the same issue as I did, so hopefully this way I can provide a possible solution.

I added a new findAll()-method to the ContactRepository, accepting a specification (for the string restrictions on the name field) and a pageable (for limiting the quantity of output lines):

import org.jean.dossier.model.entities.Contact;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ContactRepository extends JpaRepository<Contact, Long> {
    Page<Contact> findAll(Specification<Contact> spec, Pageable pageable);
}

To call this repository method I created a new service where I prepare the query:

import java.util.ArrayList;
import java.util.List;

import org.jean.dossier.data.ContactRepository;
import org.jean.dossier.model.entities.Contact;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import jakarta.persistence.criteria.Predicate;

@Service
public class ContactService {
    
    @Autowired
    private ContactRepository contactRepository;

    private final Logger logger = LoggerFactory.getLogger(ContactService.class);

    public List<Contact> findFirst10ByMultipleStrings(String... searchStrings) {
        this.logger.info("Find contacts using multiple strings.");

        // Sort by attribute "name" ascending.
        Sort sort = Sort.by(Sort.Direction.ASC, "name");

        // Limit to the first 10 lines.
        Pageable pageable = PageRequest.of(0, 10, sort);
        Page<Contact> resultPage = this.contactRepository.findAll(createSpecification(searchStrings), pageable);
        return resultPage.getContent();
    }

    private Specification<Contact> createSpecification(String... searchStrings) {
        this.logger.info("Create specification for query.");

        return (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();

            for (String searchString : searchStrings) {
                predicates.add(criteriaBuilder.like(root.get("name"), "%" + searchString + "%"));
            }

            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        };
    }
}

Eventually, calling this becomes easy from i.e. a controller:

@Autowired
private ContactService contactService;

List<Contact> contacts = this.contactService.findFirst10ByMultipleStrings(name.split(" "));
Sign up to request clarification or add additional context in comments.

1 Comment

This was going to be my suggestion, to use a Specification. Note that you can also perform your sorting in the spec too, simply by adding criteriaQuery.orderBy(criteriaBuilder.desc(root.get("name"))); and it looks like you've already figured out that you'll have to use a Page to get the top n records.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.