J'ai une méthode insertOrUpdate
qui insère une Entity
quand elle n'existe pas ou la met à jour si c'est le cas. Pour l'activer, je dois findByIdAndForeignKey
, s'il a renvoyé null
insert sinon sinon, mettez à jour. Le problème est comment puis-je vérifier s'il existe? Alors j'ai essayé getSingleResult
. Mais il jette une exception si le
public Profile findByUserNameAndPropertyName(String userName, String propertyName) {
String namedQuery = Profile.class.getSimpleName() + ".findByUserNameAndPropertyName";
Query query = entityManager.createNamedQuery(namedQuery);
query.setParameter("name", userName);
query.setParameter("propName", propertyName);
Object result = query.getSingleResult();
if (result == null) return null;
return (Profile) result;
}
mais getSingleResult
jette une Exception
.
Merci
Lancer une exception est comment getSingleResult()
indique qu'il est introuvable. Personnellement, je ne supporte pas ce type d'API. Cela force la gestion des exceptions parasites sans aucun bénéfice réel. Vous devez juste envelopper le code dans un bloc try-catch.
Sinon, vous pouvez rechercher une liste et voir si elle est vide. Cela ne jette pas une exception. En fait, étant donné que vous ne faites pas techniquement une recherche de clé primaire, il peut y avoir plusieurs résultats (même si un, les deux ou la combinaison de vos clés étrangères ou de vos contraintes rend cela impossible en pratique), c'est probablement la solution la plus appropriée.
J'ai encapsulé la logique dans la méthode d'assistance suivante.
public class JpaResultHelper {
public static Object getSingleResultOrNull(Query query){
List results = query.getResultList();
if (results.isEmpty()) return null;
else if (results.size() == 1) return results.get(0);
throw new NonUniqueResultException();
}
}
Voici une bonne option pour cela:
public static <T> T getSingleResult(TypedQuery<T> query) {
query.setMaxResults(1);
List<T> list = query.getResultList();
if (list == null || list.isEmpty()) {
return null;
}
return list.get(0);
}
Spring a une méthode d’utilité pour cela:
TypedQuery<Profile> query = em.createNamedQuery(namedQuery, Profile.class);
...
return org.springframework.dao.support.DataAccessUtils.singleResult(query.getResultList());
Essayez ceci en Java 8:
Optional first = query.getResultList().stream().findFirst();
Si vous souhaitez utiliser le mécanisme try/catch pour traiter ce problème, vous pouvez l'utiliser comme si/else. J'ai utilisé try/catch pour ajouter un nouvel enregistrement alors que je n'en trouvais pas.
try { //if part
record = query.getSingleResult();
//use the record from the fetched result.
}
catch(NoResultException e){ //else part
//create a new record.
record = new Record();
//.........
entityManager.persist(record);
}
Voici une version typée/générique, basée sur l'implémentation de Rodrigo IronMan:
public static <T> T getSingleResultOrNull(TypedQuery<T> query) {
query.setMaxResults(1);
List<T> list = query.getResultList();
if (list.isEmpty()) {
return null;
}
return list.get(0);
}
J'ai fait (en Java 8):
query.getResultList().stream().findFirst().orElse(null);
Alors ne fais pas ça!
Vous avez deux options:
Exécutez une sélection pour obtenir le COUNT de votre jeu de résultats et n'extrayez les données que si ce nombre est différent de zéro; ou
Utilisez l'autre type de requête (qui obtient un ensemble de résultats) et vérifiez s'il contient 0 résultat ou plus. Il devrait en avoir 1, alors extrayez-le de votre collection de résultats et vous avez terminé.
J'accepterais la deuxième suggestion, en accord avec Cletus. Cela donne de meilleures performances que (potentiellement) 2 requêtes. Aussi moins de travail.
Il y a une alternative que je recommanderais:
Query query = em.createQuery("your query");
List<Element> elementList = query.getResultList();
return CollectionUtils.isEmpty(elementList ) ? null : elementList.get(0);
Cela protège contre l'exception Null Pointer Exception et garantit qu'un seul résultat est renvoyé.
La méthode non documentée uniqueResultOptional
dans org.hibernate.query.Query devrait faire l'affaire. Au lieu d'avoir à attraper une NoResultException
, vous pouvez simplement appeler query.uniqueResultOptional().orElse(null)
.
En combinant les bits utiles des réponses existantes (en limitant le nombre de résultats, en vérifiant que le résultat est unique) et en utilisant le nom de méthode estabilshed (Hibernate), on obtient:
/**
* Return a single instance that matches the query, or null if the query returns no results.
*
* @param query query (required)
* @param <T> result record type
* @return record or null
*/
public static <T> T uniqueResult(@NotNull TypedQuery<T> query) {
List<T> results = query.setMaxResults(2).getResultList();
if (results.size() > 1) throw new NonUniqueResultException();
return results.isEmpty() ? null : results.get(0);
}
J'ai résolu ce problème en utilisant List<?> myList = query.getResultList();
et en vérifiant si myList.size()
est égal à zéro.
Voici la même logique que celle suggérée par d’autres (obtenez la liste de résultats, renvoyez son seul élément ou null), à l’aide de Google Guava et d’une TypedQuery.
public static <T> getSingleResultOrNull(final TypedQuery<T> query) {
return Iterables.getOnlyElement(query.getResultList(), null);
}
Notez que Guava renverra une exception IllegalArgumentException non intuitive si le jeu de résultats contient plusieurs résultats. (L'exception a du sens pour les clients de getOnlyElement (), car elle prend comme argument la liste des résultats, mais est moins compréhensible pour les clients de getSingleResultOrNull ().)
Voici une autre extension, cette fois en Scala.
customerQuery.getSingleOrNone match {
case Some(c) => // ...
case None => // ...
}
Avec ce souteneur:
import javax.persistence.{NonUniqueResultException, TypedQuery}
import scala.collection.JavaConversions._
object Implicits {
class RichTypedQuery[T](q: TypedQuery[T]) {
def getSingleOrNone : Option[T] = {
val results = q.setMaxResults(2).getResultList
if (results.isEmpty)
None
else if (results.size == 1)
Some(results.head)
else
throw new NonUniqueResultException()
}
}
implicit def query2RichQuery[T](q: TypedQuery[T]) = new RichTypedQuery[T](q)
}
à partir de JPA 2.2 , au lieu de .getResultList()
et en vérifiant si la liste est vide ou en créant un flux, vous pouvez rétablir le flux et prendre le premier élément.
.getResultStream()
.findFirst()
.orElse(null);
J'ai atteint cet objectif en obtenant une liste de résultats puis en vérifiant si elle est vide
public boolean exist(String value) {
List<Object> options = getEntityManager().createNamedQuery("AppUsers.findByEmail").setParameter('email', value).getResultList();
return !options.isEmpty();
}
Il est tellement agaçant que getSingleResult()
lève des exceptions
Jette:
Donc, toute la solution "essayer de réécrire sans exception" de cette page a un problème mineur. Soit son exception NonUnique ne soit pas lancée, soit jeté dans des cas erronés aussi (voir ci-dessous).
Je pense que la solution appropriée est (peut-être) ceci:
public static <L> L getSingleResultOrNull(TypedQuery<L> query) {
List<L> results = query.getResultList();
L foundEntity = null;
if(!results.isEmpty()) {
foundEntity = results.get(0);
}
if(results.size() > 1) {
for(L result : results) {
if(result != foundEntity) {
throw new NonUniqueResultException();
}
}
}
return foundEntity;
}
Son retour avec null s'il y a 0 élément dans la liste, renvoyant non unique s'il y a différents éléments dans la liste, mais ne retournant pas non unique lorsque l'un de vos sélections n'est pas correctement conçu et renvoie le même objet plusieurs fois.
N'hésitez pas à commenter.