Hayi
Hayi

Reputation: 6266

Dynamic search by criteria

I am using QueryDSL with Spring Data Jpa and i want execute some dynamic search.

I follow this Answer and it's okey with BooleanBuilder But in my case I have to make joins.

So how can i make it if i have 3 joins on player, player_team, team and i have optional parameters on the name of player and name of his team ?

 ________       ___________________       _______
| player |    | player_team         |    | team  |
|------  |    |----------------     |    |-------|
| id     |    | player_team_id (pk) |    | id    |
| name   |    | player_id (fk)      |    | name  |
  ------      | team_id (fk)        |     -------
                -----------      

player.java

@Entity
@Table(...)
public class Player implements java.io.Serializable {

    private Integer idPlayer ;
    private String namePlayer;
    private Set<PlayerTeam> player_teams = new HashSet<PlayerTeam>(0);  
    ...
}

team.java

@Entity
@Table(...)
public class Team implements java.io.Serializable {

    private Integer idTeam ;
    private String nameTeam;
    private Set<PlayerTeam> player_teams = new HashSet<PlayerTeam>(0);  
    ...
}  

player_team.java

@Entity
@Table(...)
public class PlayerTeam implements java.io.Serializable {

    private Integer idPlayerTeam ;
    private Team team;
    private Player paleyr;
    ...
} 

and for each domaine i have respository like that :

public interface PlayerRespository extends JpaRepository<Player, Integer>, QueryDslPredicateExecutor<Player> {

}

Upvotes: 2

Views: 3359

Answers (2)

Timo Westk&#228;mper
Timo Westk&#228;mper

Reputation: 22200

If you don't put extra properties into PlayerTeam it shouldn't be modeled as an entity. Concerning the conditions it would be

player.namePlayer.eq(...)

and

new JPASubQuery().from(playerTeam)
    .where(playerTeam.player.eq(player), palyerTeam.team.name.eq(...))
    .exists()

Upvotes: 1

SergeyB
SergeyB

Reputation: 9868

Have you tried using Specification? Spring's JPA Repositories has this method for finding results using Specifications:

List<T> findAll(Specification<T> spec);

There are different approaches for building a specification, my approach is tailored for accepting request from my REST service, so I essentially make a blank entity of a given type (Foo in this case) and set whatever search criteria was provided in the request (name for example), then I build predicates from each field (if name field is specified then add 'name equals "bob"' predicate).

Here is an example of a Specification builder:

import static org.springframework.data.jpa.domain.Specifications.where;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.lang3.StringUtils;
import org.springframework.data.jpa.domain.Specification;

import com.acme.model.security.Principal;

public class FooSpecification { 

    private final Foo criteria;
    private String query;

    public FooSpecification(String query, Foo criteria) {
        this.query = query;
        this.criteria = criteria;
    }

    public Specification<Foo> trainerEquals() {
        if (criteria.getTrainer() == null) {
            return null;
        }

        return new Specification<Foo>() {

            @Override
            public Predicate toPredicate(Root<Foo> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                return cb.equal(root.<Principal>get("trainer").<Long>get("id"), criteria.getTrainer().getId());
            }
        };
    }

    public <T> Specification<Foo> valueEquals(final T value, final String field) {
        if (value == null) {
            return null;
        }

        return new Specification<Foo>() {

            @Override
            public Predicate toPredicate(Root<Foo> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                return cb.equal(root.<T> get(field), value);
            }
        };
    }

    /**
     * Convert input string to lower case, appends '%' around it and does SQL LIKE comparison with the field value, also lower cased.
     * If value is null, no comparison is done.  Example:
     * 
     * value = "John";
     * field = "firstName";
     * 
     * resulting specification = "name like '%john%'"
     * 
     * @param value string or null
     * @param field field name
     * @return SQL LIKE specification for the given value or null if value is null
     */
    public Specification<Foo> stringLike(final String value, final String field) {
        if (StringUtils.isBlank(value)) {
            return null;
        }

        return new Specification<Foo>() {

            @Override
            public Predicate toPredicate(Root<Foo> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                return cb.like(cb.lower(root.<String> get(field)), getLikePattern(value));
            }
        };
    }

    private String getLikePattern(String searchTerm) {
        return new StringBuilder("%")
                .append(searchTerm.toLowerCase().replaceAll("\\*", "%"))
                .append("%")
                .toString();
    }

    public Specification<Foo> fullSearch() {
        return where(trainerEquals())
                .and(valueEquals(criteria.getName(), "name"))
                .and(valueEquals(criteria.getInstructions(), "description"))
                .and(valueEquals(criteria.isAwesome(), "isAwesome"))
                .and(
                    where(
                            stringLike(query, "name"))
                        .or(stringLike(query, "instructions")
                    )
                );
    }
}

Upvotes: 0

Related Questions