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

Posted

技术标签:

【中文标题】JPA 数据访问对象 - 异常处理和回滚【英文标题】:JPA data access object - exception handling and rollback 【发布时间】:2015-11-09 12:33:01 【问题描述】:

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

举个例子

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 ,但这并不能明确告诉我们发生了什么。

它在生产质量代码中的外观如何?

【问题讨论】:

【参考方案1】:

假设我在某个地方有一个 RegisterService,有时我想将用户保存到数据库中。并且每个用户都需要有一个唯一的电子邮件。你如何检查,这段代码在哪里?

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

签入同一事务将保证数据库在插入期间不会导致违反约束的异常。建议您的“DAO”实际上是@Stateless EJB(基于您使用 [java-ee] 而不是例如 [spring] 标记问题的事实),然后客户端的每个单个方法调用默认计为单笔交易。

抛出自定义服务层特定的异常可以将您的前端(即调用该业务服务方法的人,例如 JSF、JAX-RS、Spring MVC、JSP/Servlet 等)与底层持久层分离。换句话说,您的前端代码库完全没有 javax.persistence.* 导入/依赖项。建议您使用 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 

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

另见:

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

【讨论】:

不错的答案!就像术语“预期的意外情况”一样。 感谢您的回答:) 这就是我的想法。如果有更多独特的属性并且前端想确切地知道出了什么问题,例如用户名被占用或电子邮件已经被占用,该怎么办。我只是做了那么多自定义例外吗?并使用查询检查这些属性的可用性?我真的不喜欢那个代码。 将其设置为异常的属性。至于喜欢技术要求来实现功能要求,不要教条,要务实。归根结底,您需要健壮而清晰的逻辑。 所以我不应该尝试捕获一些 JPA 特定的异常和类似的东西? 确实归结为以前端(客户端)不需要这样做的方式设计您的服务层。另请参阅第一个“另请参阅”链接,了解详细的问答。【参考方案2】:

这确实与您的知识和“风格”相符;人们有不同的做法,有些是对的,有些是错的。

我来自从不使用异常进行流控制的世界,这意味着:我不会恢复引发异常的情况。当流程中发生某些事情时,我总是让它进入***别,有时会重新抛出以添加更多的价值/意义/语义信息。因此,例如,在您的save(User user) 中,原因/问题的原因并不重要,因为它只是失败了。我不认为有一组异常比其他异常更重要......这就是为什么我可以走得更远并且并不真正需要/使用所有异常类型,因为只需要一个就足够了(Exception ) — 在这里人们不同意(有时没有想到......“为什么不止一个”?)。

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

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

其他人可能会说最好有不同的类型来“知道发生了什么”,例如:

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

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

希望对您有所帮助。这可能是一个“持续的事情”,但如果这种方法适合你的世界,我希望你能明白。

推荐

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

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

【讨论】:

很抱歉,但这确实是一个没有建设性的答案。最后的评论和抱怨的评论也没有意义。 @BalusC 就像我说的,人们有自己的“方式”;你介意在“非建设性”中详细说明吗?我想知道? 您的回答不是技术性的/客观的。您的回答是个人/主观的,没有任何可行的技术解释。换句话说,给出答案的方式读起来/感觉就像您只是在黑暗中射击/猜测。 我想说:这显然是技术性和客观性的;但就像生活中的一切一样,为了实现某个目标,有很多方法可以做同样的事情。还是谢谢! 就像生活中的一切一样,经验随着时间而来;)不客气!

以上是关于JPA 数据访问对象 - 异常处理和回滚的主要内容,如果未能解决你的问题,请参考以下文章

事务未在春季批处理项目编写器中回滚

Oracle 事务和异常处理

Spring事务控制和回滚

Spring Data JPA和Hibernate:数据库约束的异常处理

Spring Boot + 调度程序 + Spring Data JPA + Oracle 中的异常处理

如何在休眠中处理多个会话事务提交和回滚?