web-dev-qa-db-fra.com

Requête dynamique Spring JPA Repository

Actuellement, j'utilise la requête personnalisée suivante de la base Spring JPA Repository et cela fonctionne très bien,

 @Query("SELECT usr FROM User usr  WHERE usr.configurable = TRUE "
              + "AND (" +
                        "lower(usr.name) like lower(:filterText) OR lower(usr.userType.classType.displayName) like lower(:filterText) OR lower(usr.userType.model) like lower(:filterText)"
              +      ")"
              + "")
  public List<User> findByFilterText(@Param("filterText") String filterText, Sort sort);

Je dois modifier cette requête lorsque le texte du filtre sera une valeur séparée par des virgules. Mais comme suit, ce sera une requête dynamique et comment puis-je l'exécuter. 

Requête dynamique que je dois construire, 

String sql = "SELECT usr FROM User usr WHERE usr.configurable = TRUE";

for(String Word : filterText.split(",")) {
                sql += " AND (lower(usr.name) like lower(:" + Word + ") OR lower(usr.userType.classType.displayName) like lower(:" + Word + ") OR lower(usr.userType.model) like lower(:" + Word + "))";
}
15
Channa

Selon JB Nizet et la documentation Spring-data , vous devez utiliser une implémentation d'interface personnalisée + référentiel.

Créez une interface avec la méthode:

public interface MyEntityRepositoryCustom {
    List<User> findByFilterText(Set<String> words);
}

Créer une implémentation:

@Repository
public class MyEntityRepositoryImpl implements MyEntityRepositoryCustom {
    @PersistenceContext
    private EntityManager entityManager;

    public List<User> findByFilterText(Set<String> words) {
        // implementation below
    }
}

Étendez la nouvelle interface dans votre interface de référentiel existante:

public interface MyEntityRepository extends JpaRepository<MyEntity, Long>, MyEntityRepositoryCustom {
    // other query methods
}

Enfin, appelez la méthode ailleurs:

dao.findByFilterText(new HashSet<String>(Arrays.asList(filterText.split(","))));

Implémentation de la requête

Votre méthode de production de la variable sql, notamment en concaténant certaines chaînes dans la requête, est incorrecte. Ne faites pas cela. 

La Word que vous concaténez doit être un identificateur JPQL valide , à savoir un : suivi d'un identificateur Java , éventuellement suivi de partie de l'identificateur Java . Cela signifie que si votre fichier CSV contient foo bar,baz, vous tenterez d'utiliser foo bar comme identifiant et vous obtiendrez une exception.

Vous pouvez utiliser à la place CriteriaBuilder pour construire la requête de manière sécurisée:

public List<User> findByFilterText(Set<String> words) {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<User> q = cb.createQuery(User.class);
    Root<User> user = q.from(User.class);

    Path<String> namePath = user.get("name");
    Path<String> userTypeClassTypeDisplayName = 
                     user.get("userType").get("classType").get("displayName");
    Path<String> userTypeModel = user.get("userType").get("model");
    List<Predicate> predicates = new ArrayList<>();
    for(String Word : words) {
        Expression<String> wordLiteral = cb.literal(Word);
        predicates.add(
                cb.or(
                    cb.like(cb.lower(namePath), cb.lower(wordLiteral)),
                    cb.like(cb.lower(userTypeClassTypeDisplayName),
                            cb.lower(wordLiteral)),
                    cb.like(cb.lower(userTypeModel), cb.lower(wordLiteral))
                )
        );
    }
    q.select(doc).where(
            cb.and(predicates.toArray(new Predicate[predicates.size()]))
    );

    return entityManager.createQuery(q).getResultList();
}
10
beerbajay

J'ai moi-même recherché la solution: La dénomination de l'interface de référentiel "Custom" et de son implémentation sont très strictes (comme indiqué ici Comment ajouter une méthode personnalisée à Spring Data JPA )

Donc, pour être clair, tout le code: (Mais @beerbajay avait raison)

L'interface de méthode personnalisée

public interface MyEntityRepositoryCustom {
    List<MyEntity> findSpecial();
}

L'implémentation de la méthode personnalisée

public class MyEntityRepositoryImpl implements MyEntityRepositoryCustom {
    @PersistenceContext
    private EntityManager em;

    //custom method implementation
    public List<Object> findSpecial() {
        List<Object> list = em.createNativeQuery("select name, value from T_MY_ENTITY").getResultList();
        return list;
    }
}

Le référentiel "original"

@Repository
public interface MyEntityRepository extends JpaRepository<MyEntity,Long>, MyEntityRepositoryCustom {
    //original methods here... do not redefine findSpecial()...
}

Vous pouvez maintenant utiliser le référentiel "original" avec les nouvelles méthodes personnalisées

@Service
public class MyService {
    @Autowired
    private DataRepository r;

    public void doStuff() {
        List<Object> list = r.findSpecial();
    }
}
3
Obscur Moirage

Spring Data JPA permet de créer des requêtes personnalisées et dynamiques avec des "Spécifications": Spring Data - Spécifications

Tout d’abord, votre interface qui étend JPARepository ou CRUDRepository doit également implémenter JpaSpecificationExecutor<...> et c’est tout ce dont vous avez besoin . Votre référentiel a maintenant une nouvelle méthode findAll qui accepte un objet Specification<...> et vous pouvez utiliser la méthode utilisée par Beerbajay pour créer des requêtes de critères en les annulant la méthode toPredicate(...) et là vous êtes libre de construire (presque) n'importe quelle requête que vous voulez comme ceci:

Specification<...> spec = new Specification<...>() {
   	@Override
   	public Predicate toPredicate(Root<...> entity, CriteriaQuery<?> query, CriteriaBuilder cb) {			    
   	    List<Predicate> conditions = buildManyPredicates(cb, entity);
   	    return cb.and(conditions.toArray(new Predicate[conditions.size()]));
   	}
 };

repository.findAll(spec, PageRequest.of(0, 10));

Cela résout le problème de Spring Data qui tente d'analyser les méthodes que vous avez ajoutées dans l'interface personnalisée (car il n'y a pas d'interface personnalisée).

2
Tom Elias