Pytanie Najlepsza praktyka dla WZP z opcją Java8 Opcjonalny zwrot?


Uwielbiam semantykę Java8. Używam wielu takich kodów w moich DAO:

  public Optional<User> findBy(String username) {
    try {
      return Optional.of(
        emp.get().createQuery("select u from User u where u.username = :username" , User.class)
        .setParameter("username" , username)
        .setMaxResults(1)
        .getSingleResult()
      );
    } catch (NoResultException e) {
      return Optional.empty();
    }
  }

Działa dobrze, ale taki kod (try catch NoResultException) rozprasza się nad moimi DAO. I muszę złapać wyjątek, który w jakiś sposób obniża wydajność.

Zastanawiam się, czy to najlepsze rozwiązanie? lub jakiekolwiek lepsze rozwiązanie, bez try-catch?

Jeśli nie jest to możliwe (ponieważ w JPA zdefiniowano wyjątek NoResultException), skrót do templatowania takiego przepływu pracy?

Dzięki.


15
2018-01-31 08:55


pochodzenie


Najlepszym rozwiązaniem jest naprawienie kodu, który rzuca wyjątek, który, jak sądzę, jest getSingleResult. "Brak wyniku" nie wyjątkowy warunek, wyjątek dla niego jest niewłaściwy. - T.J. Crowder
@ T.J.Crowder, który jest częścią specyfikacji JPA i zaimplementowany przez dostawcę JPA - nie jest pewny, czy jest to opcja. - Boris the Spider
@BoristheSpider: Yikes. Jak się stało w imię nieba że przejrzeć poziomy recenzji ?! - T.J. Crowder
@ T.J.Crowder Projekt według komisji ... zawsze kończy się źle. Ekwiwalent Hibernate po prostu wraca null bez rezultatu. Jednak na kartach nadal znajduje się wyjątek z więcej niż jednym faktycznym wynikiem. - Marko Topolnik
Powodem, dla którego kwerenda JPA nie zwraca wartości null, jest to, że zapytanie może wybrać pewną kolumnę, a zawartość kolumny ma wartość null. Różni się to od "braku takiego rzędu". Oto kilka dyskusji: stackoverflow.com/questions/1579560/... - smallufo


Odpowiedzi:


Jeśli oczywiście możesz to zrobić, używając magii lambdas!

Zacznij od @FunctionalInterface zdefiniować umowę lambda:

@FunctionalInterface
public interface DaoRetriever<T> {
    T retrieve() throws NoResultException;
}

Jest to interfejs pojedynczej metody (lub SMI), który będzie enkapsulować zachowanie twojej metody.

Teraz utwórz metodę narzędzia do korzystania z SMI:

public static <T> Optional<T> findOrEmpty(final DaoRetriever<T> retriever) {
    try {
        return Optional.of(retriever.retrieve());
    } catch (NoResultException ex) {
        //log
    }
    return Optional.empty();
}

Teraz za pomocą import static w kodzie wywołującym powyższa metoda staje się:

public Optional<User> findBy(String username) {
    return findOrEmpty(() ->
            emp.get().createQuery("select u from User u where u.username = :username", User.class)
                    .setParameter("username", username)
                    .setMaxResults(1)
                    .getSingleResult());
}

Więc tu, () -> emp.get()... jest lambda, która przechwytuje zachowanie pobierania. The interface DaoRetriever wolno rzucać NoResultException więc lambda też.

Alternatywnie, użyłbym innej metody TypedQuery - getResultList - i zmień kod w następujący sposób:

public Optional<User> findBy(String username) {
    return emp.get().createQuery("select u from User u where u.username = :username", User.class)
            .setParameter("username", username)
            .setMaxResults(1)
            .getResultList()
            .stream()
            .findFirst();
}

Ma to tę zaletę, że jest prostsze, ale ma tę wadę, że po prostu odrzuca inne wyniki, jeśli takie istnieją.


23
2018-01-31 09:08



Wow, fantastycznie, bez względu na szablon lub rozwiązanie stream.findFirst (). Dużo się nauczyłem . Dzięki. - smallufo
@smallufo moja przyjemność. Właśnie nieznacznie zmieniłem SMI - aby było to klasą ogólną. - Boris the Spider
setMaxResults(1) gwarancje, że żadne dodatkowe wyniki nie zostaną pobrane --- utworzę szablon z tego idiomu (przyjmij zapytanie, zadzwoń setMaxResults(1).getResultList().stream().findFirst()). BTW nie nazwałbym tej metody findOrEmpty---właśnie find byłoby wystarczające, biorąc pod uwagę opcjonalny typ zwrotu. - Marko Topolnik
} catch (NoResultException ex) {nie jest dobrym pomysłem, z wyjątkiem wyjątkowego przypadku i zostanie ponownie wyrzucony zamiast zalogowanego. - cyprian


Boris jest na dobrej drodze, ale można to zrobić lepiej. Potrzebujemy więcej abstrakcji. Ta konwersja nie ma nic wspólnego z daos.

Potrzebujemy rodzinnych lub funkcjonalnych interfejsów różnych arii, które konwertują lambdy, które wyrzucają wyjątki od tych, które tego nie robią. FunctionalJava (http://www.functionaljava.org/) robi to:

Mamy więc rodzinę klas Try: Try0, Try1 itp.

public interface Try0<A, Z extends Exception> {
    A f() throws Z;
}

i chcemy przekonwertować to na funkcję, która nie rzuca wyjątku:

static public <A, E extends Exception> Supplier<Validation<E, B>> toSupplierValidation(final Try0<A, E> t) {
    return () -> {
        try {
            return Validation.success(t.f());
        } catch (Exception e) {
            return Validation.fail((E) e);
        }
    };
}

Zwróć uwagę, że sprawdzanie poprawności jest wyjątkiem w przypadku niepowodzenia lub wartością regularną, jeśli się to uda (https://functionaljava.ci.cloudbees.com/job/master/javadoc/). Jeśli nie dbasz o wyjątek, możesz przekształcić przypadek niepowodzenia na pusty opcjonalny, a przypadek sukcesu, aby wartość była opcjonalna. Ta metoda wygląda jak Boris, ale bez referencji dao (które są nieistotne):

static public <A, E extends Exception> Supplier<Optional<A>> toSupplierOptional(final Try0<A, E> t) {
    return () -> {
        try {
            return Optional.of(t.f());
        } catch (Exception e) {
            return Optional.empty();
        }
    };
}

3
2018-01-31 10:02



Dzięki. Twoje rozwiązanie jest zbyt abstrakcyjne, by mnie zrozumieć. Może potrzebuję czasu na trawienie. I dziękuję za rozwiązanie FunctionalJava. (Nie dotykałem wcześniej FJ) - smallufo
Interfejs DaoRetriever byłby lepszy, gdyby reprezentował dowolną funkcję, która nie pobierała parametrów i zwracała wynik lub generowała ogólny wyjątek. To jest interfejs Try0. Być może pomoże to, jeśli zmienię Try1 na Try0 zamiast dopasować do poprzedniego przykładu. - Mark Perry