18

I love Java8's semantics. I use a lot of such code in my DAOs :

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

It works well , but such code (try catch NoResultException ) scatters over my DAOs. And I have to catch Exception , which somehow lowers performance .

I wonder if it the best solution ? or any better solution , without try-catch ?

If it is not possible (because NoResultException is defined in JPA) , any shortcut to 'templatize' such workflow ?

Thanks.

smallufo
  • 11,516
  • 20
  • 73
  • 111
  • 3
    The best solution is to fix the code that's throwing the exception, which I'm guessing is `getSingleResult`. "No result" is **not** an exceptional condition, throwing an exception for it is inappropriate. – T.J. Crowder Jan 31 '15 at 09:05
  • 4
    @T.J.Crowder that's part of the JPA spec, and implemented by a JPA provider - not sure that's an option. – Boris the Spider Jan 31 '15 at 09:11
  • @BoristheSpider: Yikes. How in heaven's name did *that* get through the levels of review?! – T.J. Crowder Jan 31 '15 at 09:25
  • @T.J.Crowder Design by committee... it always ends bad. Hibernate's equivalent just returns `null` for no result. However, an exception is still in the cards for the case with more than one actual result. – Marko Topolnik Jan 31 '15 at 14:26
  • 3
    The reason JPA's Query not returning null , is because the query may select some column , and the content of the column is null. This is different from "no such row". Here has some discussion : http://stackoverflow.com/questions/1579560/why-in-jpa-entitymanager-queries-throw-noresultexception-but-find-does-not – smallufo Jan 31 '15 at 15:46

2 Answers2

28

If course you can templatize it, using the magic of lambdas!

Start with an @FunctionalInterface to define the lambda's contract:

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

This is a Single Method Interface (or SMI) that will encapsulate the behaviour of your method.

Now create a utility method to use the SMI:

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

Now, using an import static in your calling code, your method above becomes:

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

So here, () -> emp.get()... is a lambda that captures the retrieval behaviour. The interface DaoRetriever is allowed to throw a NoResultException so the lambda is too.

Alternatively, I would use the other method of TypedQuery - getResultList - and change the code as follows:

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

This has the advantage of being simpler, but the disadvantage of simply discarding other results if there are any.

Boris the Spider
  • 59,842
  • 6
  • 106
  • 166
  • wow , fantastic , no matter the template or the stream.findFirst() solution . I learned a lot . Thanks. – smallufo Jan 31 '15 at 09:24
  • 1
    @smallufo my pleasure. Just changed the SMI slightly - to make it a generic class. – Boris the Spider Jan 31 '15 at 09:25
  • 1
    `setMaxResults(1)` guarantees no extra results will be fetched---I would make a template out of that idiom (accept query, call `setMaxResults(1).getResultList().stream().findFirst()`). BTW I wouldn't call the method `findOrEmpty`---just `find` would be enough given the Optional return type. – Marko Topolnik Jan 31 '15 at 14:29
  • } catch (NoResultException ex) { is not a good idea except it's an exceptional case and it will be rethrowed instead of logged. – cyprian Dec 15 '17 at 11:21
4

Boris is on the right track, but it can be done better. We need some more abstraction. This conversion has nothing to do with daos.

We need a family or functional interfaces of different arities that convert lambdas that throw exceptions to those that don't. FunctionalJava (http://www.functionaljava.org/) does this:

So we have a family of Try classes: Try0, Try1, etc.

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

and we want to convert that to a function that does not throw an exception:

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);
        }
    };
}

Note that Validation is either an exception in the failure case or the regular value if it succeeds (https://functionaljava.ci.cloudbees.com/job/master/javadoc/). If you don't care about the exception you can transform the failure case to the empty optional and the success case to have the value in the optional. This method looks like Boris's, but without the dao references (which are irrelevant):

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();
        }
    };
}
Mark Perry
  • 1,656
  • 1
  • 9
  • 6
  • 1
    Thanks. Your solution is too abstract for me to understand. Maybe I need some time to digest. And thank you for your FunctionalJava's solution. (I haven't touched FJ before) – smallufo Jan 31 '15 at 10:45
  • 1
    The DaoRetriever interface would be better if it represented any function that took no parameters and returned a result or threw a generic exception. This is the Try0 interface. Perhaps it will help if I edit Try1 to be Try0 instead to match the previous example. – Mark Perry Jan 31 '15 at 11:41