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