18

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.

naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259
smeidak
  • 285
  • 1
  • 3
  • 13
  • 2
    I have the same question as you. I did see in the documentation though it says [link](http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#query-by-example.execution) Only SingularAttribute properties can currently be used for property matching. I tried researching what SingularAttribute properties are but I thought that might be referring to excluding Lists... – ejgreenwald Aug 20 '17 at 19:40
  • Did you find a solution to this problem?! Your question is spot on, and I am really interested in what you actually did. – Thodoris Feb 22 '18 at 14:44

1 Answers1

5

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);
Igor Bljahhin
  • 939
  • 13
  • 23