3

I'd like to know what the best way of exception handling in data access objects is, I'm interested in production quality code.

As an example

public UserDaoImpl implements UserDao{
    @PersistenceContext
    private EntityManager em;

    void save(User user){
       em.persist(user);
    }
    User getById(long id){
       return em.find(User.class,id);
    }
}

Let's say that for example I have a RegisterService somewhere where at some point I'd like to save the user to the database. And that each User needs to have a unique email. How do you check that and where does this code go ? Do we check if there's a user with that email already in the save method using queries before persisting? Or does that code go to the service? Or maybe we try to catch some exceptions?

But with exceptions as far as I know we'd never know for sure what happened, we could try to catch a ConstraintViolationException but that doesn't tell us explicitely what happened.

How does it look in a production quality code?

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Greyshack
  • 1,901
  • 7
  • 29
  • 48

2 Answers2

3

Let's say that for example I have a RegisterService somewhere where at some point I'd like to save the user to the database. And that each User needs to have a unique email. How do you check that and where does this code go ?

Check it in the same transaction as you're inserting and throw a custom exception which triggers a full rollback.

Checking in the same transaction will guarantee that the DB won't cause a constraint violation exception during the insert. Proposed that your "DAO" is actually a @Stateless EJB (based on the fact that you tagged your question with [java-ee] and not e.g. [spring]), then each single method call by the client counts by default as a single transaction.

Throwing a custom service layer specific exception decouples your frontend (i.e. whoever is calling that business service method, e.g. JSF, JAX-RS, Spring MVC, JSP/Servlet, etc) from the underlying persistence layer. In other words, your frontend codebase is completely free of javax.persistence.* imports/dependencies. Proposed that you're using EJB as service layer API, then annotate the custom exception with @ApplicationException(rollback=true).

E.g.

@Stateless
public class UserService {

    @PersistenceContext
    private EntityManager em;

    public User findByEmail(String email) {
        List<User> users = em.createQuery("SELECT u FROM User u WHERE email = :email", User.class)
            .setParameter("email", email)
            .getResultList();
        return users.isEmpty() ? null : users.get(0);
    }

    public void register(User user) {
        if (findByEmail(user.getEmail()) != null) {
            throw new DuplicateEntityException();
        }

        em.persist(user);
    }

    // ...
}
@ApplicationException(rollback=true)
public class DuplicateEntityException extends RuntimeException {}

Then, in your frontend, just catch that custom service layer specific exception (which we now know for sure that it's exactly the expected unexpected condition causing the exception) and handle accordingly by e.g. showing a "Hey, you're already registered! Perhaps you want to login or reset your password?" message to enduser.

See also:

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • 1
    Nice answer! Like the term "the expected unexpected condition". – asgs Aug 17 '15 at 06:52
  • Thanks for the answer :) That's what I was thinking. What if there are more unique properties and the frontend wants to know exactly what went wrong, for example the username is taken or the email is taken already. Do I just make that many custom exceptions? And check the availability of these properties by using queries? I really dont like that code. – Greyshack Aug 17 '15 at 08:35
  • Set it as property of the exception. As to liking the technical requirement to achieve the functional requirement, don't be dogmatic, be pragmatic. Ultimately, you want robust and clean logic. – BalusC Aug 17 '15 at 08:36
  • So I should never try to catch some JPA specific exceptions and stuff like that ? – Greyshack Aug 17 '15 at 08:51
  • It indeed boils down to designing your service layer in such way that the frontend (clients) don't need to do that. See also the first "See also" link for an elaborate Q&A on specifically that. – BalusC Aug 17 '15 at 08:53
2

That really goes with your knowledge and "style"; people have different ways of doing this, some are right, some are wrong.

I'm from the world that never uses exceptions for flow control, meaning: I don't recover situations where exceptions are thrown. When something happens in the flow I always let it go to the highest level, re-throwing sometimes to add more value/meaning/semantic information. So, for instance, in your save(User user) it doesn't really matters what's the cause/issue because it just failed. I don't think there are a set of exceptions more important than others....that's why I can go further and don't really need/use all the exception types because to represent that behavior just one is enough (Exception) — here people disagree (sometimes without thinking..."why more than one"?).

In your case usually people do:

void save(User user) {
  try {
    em.persist(user);
  } catch (SomeExceptionType e) {
    log.error("something here");
    // What now here, huh?
  }
}

Others might say it is better to have different types to "know what's going on", like:

void save(User user) {
  try {
    em.persist(user);
  } catch (AnExceptionType e) {
  } catch (AnotherExceptionType e) {
  } catch (SomeOtherExceptionType e) {
  }
}

...that's no different from using them to control flow anyway.

Hope it helps. This can be an "ongoing thing", but I hope you get the idea, if this approach fits in your world.

RECOMMENDATION

Make final your UserDaoImpl (if you know nobody else is extending from it). Your methods should be private and final also, as well as parameter(s). Use public wisely, even at class level, sometimes we don't use them outside the same package.

public final UserDaoImpl implements UserDao {
    // Grrrr, can't make 'em' final...that's because DI is an
    // anti-pattern but that's another story
    @PersistenceContext
    private EntityManager em;

    private void save(final User user){
      em.persist(user);
    }

    private User getById(final long id){
      return em.find(User.class, id);
    }
}
x80486
  • 6,627
  • 5
  • 52
  • 111
  • 2
    Sorry to say, but this is really an unconstructive answer. That final remark and complaining comment makes also no sense. – BalusC Aug 17 '15 at 06:34
  • @BalusC Like I said, people have their "own way"; do you mind elaborating in "unconstructive"? I'm wondering? – x80486 Aug 17 '15 at 13:19
  • Your answer is not technical/objective. Your answer is personal/subjective without any feasible technical explanation for the choice. Differently put, the way the answer is put reads/feels like you're just shooting/guessing in the dark. – BalusC Aug 17 '15 at 13:20
  • I would say: it's clearly technical and objective; but like everything in life, there are many ways of doing the same thing to achieve some goal. Thanks anyway! – x80486 Aug 17 '15 at 13:24
  • Like everything in life, experience comes with time ;) You're welcome! – BalusC Aug 17 '15 at 13:26