简单的CRUD操作异常设计

Bar*_*lla 9 java exception crud jdbc

我正在开发一个非常小的测试来模拟一个3层系统,这样我就可以理解Exceptions是如何工作的.同时我想提出一个合理的方法,以便我可以将此设计作为其他应用程序中类似操作的进一步参考.

我一直在阅读有关该主题的不同文章,似乎关于使用已检查或未经检查的异常存在巨大争议,这使我对我的最终设计产生怀疑.

我不会通过批评或支持已检查/未检查异常的论据,因为可能它们都是众所周知的,而是我会提出我的设计,寻找一些建议,如何改进它并尽可能地使它(尽可能长)一个真正的应用程序.

系统负责使用JDBC在关系数据库(简称MySQL)中执行基本的CRUD操作.我有以下内容:表示层,服务层和持久层.

基于这个答案处理服务层中的Dao异常,我有理由不暴露特定的层实现并解耦层.所以我决定创建自定义异常并将它们包装到每层的基本异常中,这样我就可以将特定的层异常(即SQLException)转换为一般的层异常(即PersistentException,BusinessException).如果稍后实现更改,我可以简单地将新的一个包装到更高层所期望的基本异常中.所以我有以下例外:

com.user.persistent.exceptions
    |_ PersistentException
    |_ ExistingUserException
    |_ Some more..

com.user.business.exceptions
    |_ BusinessException
    |_ somemore....
Run Code Online (Sandbox Code Playgroud)

来自Josh Bloch的书"Effective Java":"使用已检查的异常来调用可以合理地预期调用者恢复的条件."我也不确定,但我相信用户可以从SQLExeption恢复(即用户错误地提供了现有的ID,他可以重新键入正确的一个)所以我决定使之前的异常检查异常.以下是类的外观概述:

持久层.

public interface UserDAO 
{
    public void create(User team) throws PersistentException;
}

//Implementation in DefaultUserDAO.java
@Override
    public void create(User team) throws PersistentException 
    {       
        try
        {
            System.out.println("Attempting to create an user - yikes the user already exists!");
            throw new SQLIntegrityConstraintViolationException();           
        }
        catch(SQLIntegrityConstraintViolationException e)
        {
            throw new ExistingUserException(e);
        }
        catch (SQLException e) 
        {
            throw new PersistentException(e);
        } 
        finally 
        {
            //close connection
        }
    }
Run Code Online (Sandbox Code Playgroud)

服务层:

public interface UserService 
{
    void create(User user) throws BusinessException;
}

//Implementation of UserService
@Override
public void create(User user) throws BusinessException 
{
    System.out.println("Doing some business logic before persisting the user..");

    try 
    {
        userDao.create(user);
    } 
    catch (PersistentException e) 
    {
        throw new BusinessException(e);
    }

}
Run Code Online (Sandbox Code Playgroud)

介绍

    try 
    {
        userService.create(user);
    } catch (BusinessException e) 
    {   
        e.printStackTrace();
    }
Run Code Online (Sandbox Code Playgroud)

现在,以下几点让我对这个设计感到不确定.

  1. 我喜欢让编译器验证方法的客户端在使用已检查的异常时是否捕获/抛出声明的异常.但是,与此同时,我认为这种方法会导致杂乱的代码处理所有异常.不确定是不是因为我没有正确使用异常,或者因为检查异常确实导致杂乱的代码.
  2. 我也喜欢通过将特定图层异常包装到常规图层中来解耦图层的想法.但是,我可以看到为每个可能的异常创建了许多新类,而不仅仅是抛出现有的java异常.
  3. 我还可以看到,该应用程序上的许多现有代码专门用于处理异常,其中一小部分专门用于系统的实际目标.

这些实际上是我的主要关注点,让我想知道这是不是一个好的设计.我想听听你对它的看法(也许是一些示例代码的小片段).你们能给我一些关于如何改进它的提示吗?在某种程度上,我可以实现层之间的分离,并避免泄漏层特定的问题..

ag1*_*112 1

@Bartzilla:我也不太喜欢在每一层中包装和展开异常对象,它确实使应用程序代码变得混乱。我宁愿认为错误代码和错误消息方法是更好的方法。我认为这个问题的解决方案有以下三个:

1) 将数据库层异常包装在应用程序定义的 RunTimeException 类中。这个 RuntimeException 应该包含一个错误代码字段、一条错误消息和原始异常对象。由于所有 DAO API 只会抛出运行时异常,因此这意味着业务层不一定需要捕获它。它将被允许冒泡,直到有必要处理它为止。例如

class DAOException extends RuntimeException{
 private int errorCode;
 private String errorMessage;
 private Exception originalException;

 DAOException(int errorCode, String errorMessage, Exception originalException){
    this.errorCode=errorCode;
    this.errorMessage=errorMessage;
    this.originalException=originalException;
    }

}
Run Code Online (Sandbox Code Playgroud)

现在,以这种方式,您的 DAO 方法将根据异常确定错误代码,例如:-

int Integrity_Voildation_ERROR=3;
 public void create(User team) throws DAOException
    {       
        try
        {
            System.out.println("Attempting to create an user - yikes the user already exists!");
            throw new SQLIntegrityConstraintViolationException();           
        }
        catch(SQLIntegrityConstraintViolationException e)
        {   int errorCode=Integrity_Voildation_ERROR;
            throw new DAOException(Integrity_Voildation_ERROR,"user is already found",e);
        }
}
Run Code Online (Sandbox Code Playgroud)

并且这个异常可以在它应该在的层中捕获。在这种情况下,每个错误代码都意味着一个可恢复(可操作)的异常。当然,应用程序(servlet 或过滤器或任何东西)的入口点必须捕获一般异常以捕获不可恢复的异常并向用户显示有意义的错误。

2)让您的 DAO API 返回一个 Result 类型的对象,其中包含与上述情况中 DAOException 中提供的信息相同的信息。

所以你有 Result 类:-

    Class Result implements IResult{

    private boolean isSuccess;
    private int errorCode;
     private String errorMessage;
     private Exception originalException;//this might be optional to put here.
    }


So a DAO API:
    public IResult create(User team) throws DAOException
    {      
   IResult result=new Result();
        try
        {
            System.out.println("Attempting to create an user - yikes the user already exists!");
            throw new SQLIntegrityConstraintViolationException();           
        }
        catch(SQLIntegrityConstraintViolationException e)
        {   int errorCode=Integrity_Voildation_ERROR;
            result.setSuccess(false);
        result.setErrorCode(errorCode);
        result.setErrorMessage("user is already found");    
        }
    return result;
}
Run Code Online (Sandbox Code Playgroud)

在上述情况下,约束是每个 DAO API 都需要返回相同的结果对象。当然,您的业务相关数据可以填充在 ResultClass 的各种子类中。请注意,在情况 1 和 2 中,您可以使用枚举来定义所有可能的错误代码。错误消息可以从数据库表等中提取。

3)如果你想避免使用errorCode,你可以这样做:你可以为每种可能的可恢复SQL异常类型定义一个异常层次结构,而不是像DAOException(上面的情况1)那样定义单个RunTimeException类,每个异常层次结构都是父类DAOException的子类。

基于 java 的 spring 框架 DAO 异常层次结构就是一个很好的例子。请看一下这个