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 + "))";
}
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();
}
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();
}
}
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).