JPA数据访问对象——异常处理和回滚

JPA data access object - exception handling and rollback

我想知道数据访问对象中异常处理的最佳方式是什么,我对生产质量代码很感兴趣。

举个例子

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

假设我在某处有一个 RegisterService,在某个时候我想将用户保存到数据库中。并且每个用户都需要有一个唯一的电子邮件。 你如何检查这个代码去哪里了? 在保存之前,我们是否使用查询检查保存方法中是否已经有该电子邮件的用户?或者该代码是否用于服务?或者我们可能会尝试捕获一些异常?

但据我所知,除了例外,我们永远无法确定发生了什么,我们可以尝试捕获 ConstraintViolationException,但这并没有明确告诉我们发生了什么。

它在生产质量代码中的表现如何?

这确实与您的知识相吻合 "style";人们有不同的做法,有些是对的,有些是错的。

我来自从不使用异常进行流量控制的世界,这意味着:我不会恢复抛出异常的情况。当流程中发生某些事情时,我总是让它进入最高级别,有时会重新抛出以添加更多 value/meaning/semantic 信息。因此,例如,在您的 save(User user) 中, cause/issue 是什么并不重要,因为它只是失败了。我不认为有一组异常比其他异常更重要....这就是为什么我可以走得更远并且不真正 need/use 所有异常类型因为只代表一种行为就足够了(Exception) — 这里人们不同意(有时不假思索..."why more than one"?)。

在你的情况下,人们通常会这样做:

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

其他人可能会说 与 "know what's going on" 不同的类型更好,例如:

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

...这与使用它们来控制流量没有什么不同。

希望对您有所帮助。这可以是 "ongoing thing",但如果这种方法适合您的世界,我希望您能理解。

建议

final 设为您的 UserDaoImpl(如果您知道没有其他人从它扩展)。您的方法也应该是 privatefinal,以及参数。明智地使用 public,即使在 class 级别,有时我们不会在同一个包之外使用它们。

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

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 ?

在您插入的同一事务中检查它并抛出触发完全回滚的自定义异常。

签入同一个事务将保证数据库在插入过程中不会出现约束违反异常。建议您的“DAO”实际上是一个 @Stateless EJB(基于您用 [java-ee] 而不是 [spring] 标记问题的事实),然后每个默认情况下,客户端调用的方法计为单个事务。

抛出自定义服务层特定的异常会将您的前端(即调用该业务服务方法的任何人,例如 JSF、JAX-RS、Spring MVC、JSP/Servlet 等)与底层分离持久层。换句话说,您的前端代码库完全没有 javax.persistence.* imports/dependencies。建议您使用 EJB 作为服务层 API,然后使用 @ApplicationException(rollback=true).

注释自定义异常

例如

@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 {}

然后,在您的前端,只需捕获特定于自定义服务层的异常(我们现在确定它正是导致异常的预期意外情况)并相应地处理,例如向最终用户显示 “嘿,你已经注册了!也许你想登录或重设密码?” 消息。

另请参阅: