JPA data access object - exception handling and rollback

北战南征 提交于 2019-12-10 19:49:48

问题


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?


回答1:


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:

  • Letting the presentation layer (JSF) handle business exceptions from service layer (EJB)
  • JSF Controller, Service and DAO



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


来源:https://stackoverflow.com/questions/32040779/jpa-data-access-object-exception-handling-and-rollback

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!