简体   繁体   English

简单的CRUD操作异常设计

[英]Simple CRUD operations Exception design

I am developing a very small test to simulate a 3 layered system so I can understand how Exceptions work. 我正在开发一个非常小的测试来模拟一个3层系统,这样我就可以理解Exceptions是如何工作的。 At the same time I would like to come up with a reasonable approach so I can use this design as further reference for similar operations in other applications. 同时我想提出一个合理的方法,以便我可以将此设计作为其他应用程序中类似操作的进一步参考。

I have been reading through different articles on the topic and it seems that there is a huge controversy over using checked or unchecked exceptions which is making me doubt about my final design. 我一直在阅读有关该主题的不同文章,似乎关于使用已检查或未经检查的异常存在巨大争议,这使我对我的最终设计产生怀疑。

I won't go through the arguments used to criticize or support checked/unchecked exceptions because probably they are all well known but rather I will present my design looking for some advices in how to improve it and make it (as long as possible) similar to a real application. 我不会通过批评或支持已检查/未检查的异常的论据,因为可能它们都是众所周知的,而是我会提出我的设计,寻找一些如何改进它并尽可能地(尽可能长)的建议一个真正的应用程序。

The system is in charge to perform basic CRUD operations in a relational DB (lets say MySQL) using JDBC. 系统负责使用JDBC在关系数据库(简称MySQL)中执行基本的CRUD操作。 I have the following: a presentation layer, a service layer and a persistence layer. 我有以下内容:表示层,服务层和持久层。

Based on this answer Handling Dao exceptions in service layer it makes sense for me not to expose specific layer implemantation and decouple the layers. 基于这个答案处理服务层中的Dao异常,我有理由不暴露特定的层实现并解耦层。 So I decided to create my custom exceptions and wrap them into a Base Exception per layer so I can translate specific layer exceptions (ie SQLException) into general layer exceptions (ie PersistentException, BusinessException). 所以我决定创建自定义异常并将它们包装到每层的基本异常中,这样我就可以将特定的层异常(即SQLException)转换为一般的层异常(即PersistentException,BusinessException)。 And if later on the implementation changes I can simply wrap the new one into the base exception expected by the higher layer. 如果稍后实现更改,我可以简单地将新的一个包装到更高层所期望的基本异常中。 So I have the following exceptions: 所以我有以下例外:

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

com.user.business.exceptions
    |_ BusinessException
    |_ somemore....

From Josh Bloch's book Effective Java: “Use checked exceptions for conditions from which the caller can reasonably be expected to recover.” I am also not that sure but I believe a user can recover from a SQLExeption (ie A user by mistake provides an existing ID, he can re-type the right one ) so I decided to make the previous exceptions checked exceptions. 来自Josh Bloch的书“Effective Java”: “使用已检查的异常来调用可以合理地预期调用者恢复的条件。”我也不确定,但我相信用户可以从SQLExeption恢复(即用户错误地提供了现有的ID,他可以重新键入正确的一个)所以我决定使之前的异常检查异常。 Here is an overview of how the classes look like: 以下是类的外观概述:

Persistence Layer. 持久层。

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

Service Layer: 服务层:

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

}

Presentation 介绍

    try 
    {
        userService.create(user);
    } catch (BusinessException e) 
    {   
        e.printStackTrace();
    }

Now the following points make me feel unsure about this design. 现在,以下几点让我对这个设计感到不确定。

  1. I like the idea of having the compiler verifying if clients of the method catch/throw the declared exceptions when using checked exceptions. 我喜欢让编译器验证方法的客户端在使用已检查的异常时是否捕获/抛出声明的异常。 However, at the same time I think this approach leads to clutter code to handle all the exceptions. 但是,与此同时,我认为这种方法会导致杂乱的代码处理所有异常。 Not sure if it is because I am not making proper usage of exceptions or because checked exceptions really lead to clutter code. 不确定是不是因为我没有正确使用异常,或者因为检查异常确实导致杂乱的代码。
  2. I also like the idea of decoupling layers by wrapping specific layer exceptions into General ones. 我也喜欢通过将特定图层异常包装到常规图层中来解耦图层的想法。 But, I can see a lot of new classes being created for every possible exception instead of just throwing an existing java exception. 但是,我可以看到为每个可能的异常创建了许多新类,而不仅仅是抛出现有的java异常。
  3. I can also see that a lot the existing code on this application is devoted to handle exceptions and a small portion of it is devoted to the actual objective of the system. 我还可以看到,该应用程序上的许多现有代码专门用于处理异常,其中一小部分专门用于系统的实际目标。

These are actually my main concerns and make me wonder if this is really a good design. 这些实际上是我的主要关注点,让我想知道这是不是一个好的设计。 I would like to hear your opinions about it (and perhaps some small snippets of sample code). 我想听听你对它的看法(也许是一些示例代码的小片段)。 Can you guys give me some hints on how can I possibly improve it? 你们能给我一些关于如何改进它的提示吗? In a way I can achieve decoupling between layers and avoid leaking layer specific concerns.. 在某种程度上,我可以实现层之间的分离,并避免泄漏层特定的问题..

I believe your application should not handle SQL exceptions when the development is complete. 我相信您的应用程序不应该在开发完成时处理SQL异常。 So, you should not catch exceptions like SQLException. 所以,你不应该捕获像SQLException这样的异常。 Or if you are forced to catch them, then just rethrow RuntimeException. 或者,如果您被迫捕获它们,那么只需重新抛出RuntimeException。 From my honour experience creating your own exceptions only makes sence if you develop some sort of library for someone else. 从我的荣誉经验来看,如果你为别人开发某种类型的库,那么创建你自己的例外只会产生。 And even in this case in many cases you may use existing exceptions. 即使在这种情况下,在许多情况下您也可以使用现有的例外。 Try to develop without creating your exceptions. 尝试开发而不创建例外。 Create them only when you realize that you can't do without them. 只有当你意识到你离不开它们时才创造它们。

My thoughts here: 我的想法在这里:
Regarding 1 - Yes, checked exceptions add "clutter code" - it's a trade-off, 关于1 - 是,检查异常添加“杂乱代码” - 这是一个权衡,
and you need to think what is more important to you. 你需要思考什么对你更重要。
In many designs there is no a perfect solution, 在许多设计中,没有完美的解决方案,
and you must decide what suits you more. 你必须决定什么更适合你。

Regarding BusinessException - I personally don't like it. 关于BusinessException - 我个人不喜欢它。
I would like to know at client side, when I add user, that it already exists . 我想在客户端知道,当我添加用户时,它已经存在。
I do not want to write a code that "Peals" BusinessException to get the Root cause. 我不想写一个代码“Peals”BusinessException来获取Root的原因。

And a general suggestion - Please use Generics for crud exception. 一般建议 - 请使用泛型用于crud例外。
For example, don't use UserAlreadyExistsException , but use EntityAlreadyExistsException<User> 例如,不要使用UserAlreadyExistsException ,而是使用EntityAlreadyExistsException<User>

@Bartzilla: I am also not great fan of wrapping and unwrapping exception objects in each layer, it really clutters the application code. @Bartzilla:我也不是很喜欢在每一层中包装和解包异常对象,它真的使应用程序代码混乱。 I rather considers an error code and an error message approach a better way. 我宁愿考虑错误代码和错误消息方法更好的方法。 I think there are three solutions to this issue: 我认为这个问题有三种解决方案:

1) Wrap DB layer exception in your application defined RunTimeException class. 1)在应用程序定义的RunTimeException类中包装DB层异常。 This RuntimeException should contain an errorcode field, an error message and original exception object.Since all your DAO APIs would throw only runtime exception, So it means business layer need not necessarily catch it. 此RuntimeException应包含错误代码字段,错误消息和原始异常对象。因为所有DAO API都只会抛出运行时异常,所以它意味着业务层不一定需要捕获它。 It will be allowed to bubble up till the point where it make sense to handle it. 它将被允许冒泡直到处理它的意义。 for eg 例如

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

}

Now in this manner, your DAO method would be deciding the error code based on exception, eg:- 现在以这种方式,你的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);
        }
}

and this exception can be caught in layer where it is supposed to be. 并且这个异常可以在它应该存在的层中捕获。 In this scenario, each error code means a recoverable(actionable) exception. 在这种情况下,每个错误代码表示可恢复(可操作)的异常。 Ofcourse the entry point to application(servlet or filter or anything) must catch general Exception to catch irrecoverable exception and show the meaningful error to user. 当然,应用程序的入口点(servlet或过滤器或任何东西)必须捕获一般的异常以捕获不可恢复的异常并向用户显示有意义的错误。

2)Let your DAO API returns a Result kind of object which contains same information as provided in DAOException in case above. 2)让你的DAO API返回一个Result类型的对象,该对象包含与上面的DAOException中提供的信息相同的信息。

So you have Result class:- 所以你有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;
}

In above case, constraint is each DAO API would need to return same Result Object. 在上面的例子中,约束是每个DAO API需要返回相同的Result Object。 Ofcourse your business related Data can be populated in various subclasses of ResultClass. 当然,您的业务相关数据可以填充在ResultClass的各个子类中。 Note in both case 1 and 2, you can use an enum for defining all possible error codes. 请注意,在情况1和2中,您可以使用枚举来定义所有可能的错误代码。 Error messages can be pulled from Database table etc. 可以从数据库表等中提取错误消息。

3) If you want to avoid using errorCode, you can do: Instead of defining single RunTimeException class like DAOException(case1 above), you can define an exception hierarchy for each possible type of recoverable SQL exception, each is subclass to parent class DAOException. 3)如果要避免使用errorCode,可以这样做:不是像DAOException(上面的case1)那样定义单个RunTimeException类,而是可以为每种可能的可恢复SQL异常类型定义异常层次结构,每个异常层次结构都是父类DAOException的子类。

An excellent example of same thing is done by java based spring framework DAO exception hierarchy. 基于java的spring框架DAO异常层次结构就是一个很好的例子。 Please go through this . 请通过

Based on convention over configuration concept you can design your exception handler, i think the fastest and reliable way for this purpose is using AOP, for this purpose you can handle exceptions in your aspect based on type of exceptions, you can make a decision for recovering from exception or not. 根据约定优于配置概念,您可以设计异常处理程序,我认为最快和最可靠的方法是使用AOP,为此您可以根据异常类型处理方面中的异常,您可以做出恢复的决定从是否例外。 for example if validation exception occurred you can return your input page path to sending client to existing page for filling data's correctly, if unrecoverable exception occurred you can return error page in your exception handler. 例如,如果发生验证异常,您可以将输入页面路径返回到将客户端发送到现有页面以正确填充数据,如果发生不可恢复的异常,您可以在异常处理程序中返回错误页面。

you can create a convention for your input and error pages for example, giving the global name for input and error pages. 例如,您可以为输入和错误页面创建约定,为输入和错误页面提供全局名称。 in this way you can make a decision to sending request to appropriate page based on exception type. 通过这种方式,您可以根据异常类型决定将请求发送到适当的页面。

you can follow this post for Aspect Oriented Programming 您可以按照这篇文章进行面向方面编程

sample added below 样品在下面添加

    @Aspect
    public class ExceptionHandlerAspect {

        @Around("@annotation(ExceptionHandler)")
        public Object isException(ProceedingJoinPoint proceedingJoinPoint) {
            try {
                return proceedingJoinPoint.proceed();
            } catch (Exception exception) {
                if (exception instanceof UnRecoverableException) {
                    addErrorMessage("global system error occurred");
                    return "errorPage";
                } else if (exception instanceof ValidationException) {
                    addErrorMessage("validation exception occurred");
                    return "inputPage";
                } else {
                    addErrorMessage("recoverable exception occurred");
                    return "inputPage";
                }
            }
        }
    }

@Target({METHOD})
@Retention(RUNTIME)
public @interface ExceptionHandler {
}

Presentation Layer 表达层

@ExceptionHandler
public String createUser() {
    userService.create(user);
    return "success";
}

Have a look at how spring-jdbc does it. 看看spring-jdbc是如何做到的。 I think that is a very good design. 我认为这是一个非常好的设计。 Spring handles checked exceptions from the driver layer and throws unchecked exceptions. Spring处理来自驱动程序层的已检查异常并抛出未经检查的异常。 It also translates different exceptions from MySQL, Postgres etc into standard spring ones. 它还将MySQL,Postgres等不同的异常翻译成标准的弹簧。

I've switched to unchecked exceptions in all my code for the last 6 years and haven't looked back. 在过去的6年中,我已经在所有代码中切换到未经检查的异常,并且没有回头。 90% of the time you can't handle the codition. 90%的时间你无法处理这个法典。 And the cases where you do, you know about it, either by design or by testing, and put the relevent catch block in. 你所知道的情况,无论是通过设计还是通过测试,并将相关的阻塞块放入其中。

You say 3 layered system - does this imply that these layers might run on different computers or in different processes? 你说3分层系统 - 这是否意味着这些层可能在不同的计算机上运行或在不同的进程中运行? Or is it just organizing a code into a set of layers defined by specific function and everything running on a single node? 或者只是将代码组织到由特定函数定义的一组层中,以及在单个节点上运行的所有内容?

The reason for asking this - unless your layers are running in different processes - it adds no value to have CheckedExceptions. 问这个的原因 - 除非您的图层在不同的进程中运行 - 它没有为CheckedExceptions添加任何值。 Having lots of CheckedExceptions just clutters the code and add unnecessary complexity. 拥有大量CheckedExceptions只会使代码混乱并增加不必要的复杂性。

Exceptions design is important and doing something like the way ag112 use has suggested above ie using RunTimeException is clean, less code and manageable. 异常设计很重要,并且执行类似于上面提到的ag112使用的方式,即使用RunTimeException是干净的,更少的代码和可管理的。

..just my thoughts ..只是我的想法

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM