how to use spring data example matcher for list attribute - query issue

smeidak picture smeidak · Feb 3, 2017 · Viewed 13.4k times · Source

I would like to ask how to use exampleMatcher for class with List attribute. Lets assume, we have a user which can have multiple roles at the same time. I want to get all users with user role from DB

entities

@Entity(name = "UserEntity")
public class User {
    Private Long id;
    private String name;
    private String surname;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn
    private Address address;

    @ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
    @JoinColumn
    private List<UserRole> roles;
}

@Entity
public class UserRole {
    private Long id;    
    private String name;
}

I send an User class with some attributes to getExampleEntity. I am trying to send the list of selected roles from UI now.

function in controller

@Override
    protected User getExampleEntity() {
        User clonedUser = new User();
        List<UserRole> selectedRole = new ArrayList<>();
        // this cycle just find and add all roles in db based on selection from UI
        for (Long roleID : selectedUserRoleIDs)
           selectedRole.add(userRoleService.find(roleID));
        clonedUser.setRoles(selectedRole);
        return clonedUser;
    }

Function from JpaRepository, which call findByExample function with example Matcher.

    @Override
    public List<TObjectType> findByExample(int first, int pageSize, String sortField, Sort.Direction sortOrder, TObjectType type)
    {
        PageRequest pageRequest = getPageRequest(first, pageSize, sortField, sortOrder);
        ExampleMatcher exampleMatcher = getExampleMatcher();
        Example<TObjectType> example = Example.of(type, exampleMatcher);
        return repository.findAll(example, pageRequest).getContent();
    }

    /**
     * Generates an example matcher for the instance of {@link TObjectType}.
     * This can be overriden if a custom matcher is needed! The default property matcher ignores the "id" path and ignores null values.
     * @return
     */
    protected ExampleMatcher getExampleMatcher() {
        return ExampleMatcher.matching()
                .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING)
                .withIgnoreNullValues();

    }

It works as a dream if will send a User with attribute name/surname or even any attribute in Address class, but it does not work with List roles. I will appreciate any tips how to solve this problem and how I can use findByExample with array of TObjectType as an attribute. Thank you very much

EDIT: I found the problem. There is a code of repository.findAll function (org.springframework.data.repository.query.QueryByExampleExecutor#findAll)

@Override
    public <S extends T> Page<S> findAll(Example<S> example, Pageable pageable) {

        ExampleSpecification<S> spec = new ExampleSpecification<S>(example);
        Class<S> probeType = example.getProbeType();
        TypedQuery<S> query = getQuery(new ExampleSpecification<S>(example), probeType, pageable);

        return pageable == null ? new PageImpl<S>(query.getResultList()) : readPage(query, probeType, pageable, spec);
    }

Generated query does not include list attribute, but I have no idea why and it is included of Example object. Has someone experience with this problem? I guess that there is some setting/annotation problem only.

Answer

Igor Bljahhin picture Igor Bljahhin · Mar 5, 2018

You should try to use transformer at first. Example below is for Mongodb, but you can see approach. In my case User class contains the list of roles as strings:

final ExampleMatcher matcher = ExampleMatcher.matching()
                .withIgnoreNullValues()
                .withMatcher("roles", match -> match.transform(source -> ((BasicDBList) source).iterator().next()).caseSensitive());

        users = userRepository.findAll(Example.of(criteria, matcher), pageRequest);