Search code examples
javaspringspring-data-jpaspring-data

Spring Data method name for custom query accepting and-connected multiple search strings


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?


Solution

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