Java 和 SQL:返回 null 还是抛出异常?

Posted

技术标签:

【中文标题】Java 和 SQL:返回 null 还是抛出异常?【英文标题】:Java and SQL : return null or throw exception? 【发布时间】:2010-12-05 10:07:43 【问题描述】:

这是另一个有争议的主题,但这次我只寻找简单且有记录的答案。场景:

让我们假设以下方法:
 public static Hashtable<Long, Dog> getSomeDogs(String colName, String colValue) 
  Hashtable<Long, Dog> result = new Hashtable<Long, Dog>();
  StringBuffer sql = null;
  Dog dog = null;
  ResultSet rs = null;
      try 
          sql = new StringBuffer();
          sql.append("SELECT * FROM ").append("dogs_table");
          sql.append(" WHERE ").append(colName).append("='");
          sql.append(colValue).append("'");
          rs = executeQuery(sql.toString());
              while (rs.next()) 
                  dog= new Dog();
                  //...initialize the dog from the current resultSet row
              result.put(new Long(dog.getId()), dog);
              
          
     catch (Exception e) 
         createErrorMsg(e);
         result = null; //i wonder....
         
     finally 
         closeResultSet(rs); //this method tests for null rs and other stuff when closing the rs.
     
   return result;
 

问题:

1。你有什么建议来改进这种归还一些狗的技术,有一些属性?

2。 rs.next() 将为 null ResultSet 返回 false,或者将生成异常,如下所示:

字符串 str = null; System.out.println(str.toString());

3。如果在从 ResultSet 的当前行初始化 dog 对象时,发生了一些不好的事情,例如:连接失败、不兼容的值已传递给 dog 属性设置器等,该怎么办?我现在可能在哈希表中有 10 个元素,或者没有(第一行)。下一步将是什么: a) 返回 null 哈希表; b) 返回结果哈希表,就是这个阶段的样子; c) 抛出异常:这里的异常类型是什么?

4。我想你们都会同意这一点:没有什么不好的事情发生,审讯中没有行,将返回一个空值。但是,@Thorbjørn Ravn Andersen 说 here 我应该返回 NullObject 而不是 null 值。我想知道那是什么。

5。我注意到人们和一群人说应该将应用程序分成层或某种级别。考虑到上面的例子,这里有哪些层,除了我能想到的这些:

Layer1 :: 执行操作的数据库层:此方法。

第二层 :: ??? : 构造新 Dog 对象的某个层:我的 Dog 对象。

第三层 :: ? :我打算对狗的集合做某事的某个层:主要是 GUI 层,或用户界面的子层。

按照应用流程,如果第一层发生异常,最好的办法是什么?我的想法:捕获异常,记录异常,返回一些值。这是最佳做法吗?

曼尼感谢您的回答,我期待看到其他人对这些问题的看法。

【问题讨论】:

代码缩进4个空格。 我没有大惊小怪。缩进,但实际上只是通过代码格式化程序对其进行格式化,这似乎越来越少:-( 我为问题方面道歉,但我应该怎么做呢?我在“String str = null; System.out.println(str.toString());”这些行上按了 n 次“代码”按钮;“..什么也没发生 :-(. 这不是问题,但我想知道格式是否与其他 html 元素(例如

s)混淆了
Hyper:选择要格式化为代码的行,然后按代码按钮。 【参考方案1】:

我会避免以下情况

   sql.append("SELECT * FROM ").append("dogs_table");
   sql.append(" WHERE ").append(colName).append("='");
                        sql.append(colValue).append("'");

而是使用 PreparedStatement 及其相关的参数设置方法 (setString()) 等。这将防止 colValue 的值出现问题,以及 SQL 注入攻击(或更一般地说,colValue 形成一些SQL 语法)。

如果集合只是空的,我将永远返回 null。从客户的角度来看,这似乎非常违反直觉,并且完全出乎意料。

我不建议在错误条件下返回 null,因为您的客户必须明确检查这一点(并且可能会忘记)。如果需要,我会返回一个空集合(这可能类似于您对空对象的评论),或者更有可能抛出异常(取决于情况和严重性)。该异常很有用,因为它将携带一些与遇到的错误相关的信息。 Null 什么也不告诉你。

如果在构建Dog 对象时遇到问题该怎么办?我认为这取决于你希望你的应用程序有多健壮和有弹性。返回Dogs 的子集是否有问题,还是完全是灾难性的,您需要报告这个?这是一个应用程序要求(过去我必须满足两种情况 - best-effortall-or-nothing)。

几个观察。我会使用 HashMap 而不是旧的 Hashtable (同步所有访问,更重要的是,不是正确的 Collection - 如果你有一个 Collection 你可以将它传递给任何其他期望 的方法任何 Collection) 和StringBuilder 超过StringBuffer 出于类似原因。不是大问题,但值得了解。

【讨论】:

+1 表示PreparedStatement 和使用HashMap 而不是Hashtable。在现代代码中没有充分的理由使用Hashtable。并且任何时候您手动将 SQL 构建为字符串,您都会为各种 SQL 注入漏洞设置自己。 在我看来,在这里抛出异常不会给我的代码增加任何价值,因为它需要在更高的层次上被捕获(我们可以忘记这样做)并且不会告诉我更多信息,因为我已经记录了错误详细信息。是否应在此处提示用户发生错误? 您能否将创建 StringBuffer 的代码翻译成 PreparedStatement 版本,以清楚地标记差异?顺便说一句,如果创建者足够关注它,那么形成查询的 StringBuffer 会出现什么问题? 我不知道 HashMap..我用谷歌搜索它并创建了这个:coderanch.com/t/202040/Performance/java/Hashtable-vs-HashMap。与失去线程安全属性相比,性能略有提高并没有回报。 我会使用它只是因为它实现了 java.util.Collection 并且可以传递给任何采用 Collections 的方法【参考方案2】:

你问了五个问题

1.你有什么方法可以改进这种返回一些具有某些属性的狗的技术?

实际上有几个。

您的方法是静态的 - 这并不可怕,但会导致您使用另一个静态“executeQuery”,这对我来说有点单例的味道... “Dogs”类违反了 OO 命名惯例 - 复数名词不会成为好的类名,除非该类的一个实例包含一组事物 - 而且 Dogs 实际上是“Dog”。 HashTable 几乎已被弃用。 HashMap 或 ConcurrentHashMap 提供更好的性能。 我看不出有理由使用多个追加来创建查询的第一部分 - 这还不错,但它的可读性不如预期,所以 sql.append ("SELECT * FROM dogs_table WHERE ");如果您只是要硬编码选定的列 (*) 和表名 (dogs_table),那么这是一个更明智的开始。

2. rs.next() 将为 null ResultSet 返回 false,或者会生成异常

这似乎不是一个问题,但是是的,一旦不再有任何行要处理,rs.next() 就会返回 false。

3.如果在从 ResultSet 的当前行初始化狗对象时,发生了一些不好的事情怎么办​​

如果“发生了不好的事情”,接下来要做什么取决于您和您的设计。有宽容的方法(尽可能返回所有行)和不宽容的方法(抛出异常)。我倾向于倾向于“不宽容”的方法,因为使用“宽容”的方法,用户不会知道您没有返回所有存在的行 - 只是您在错误之前得到的所有行。但也可能存在宽恕方法的情况。

4.我想你们都会同意这一点:没有什么不好的事情发生,审讯中没有行,将返回一个空值。

这不是一个明显的正确答案。首先,这不是书面方法中发生的事情。它将返回一个空的 HashTable(这就是“空对象”的含义)。其次,在“未找到结果”的情况下,null 并不总是答案。

我看到了 null,但我也看到了一个空的结果变量。我声称它们都是正确的方法,但我更喜欢空的结果变量。但是,始终最好保持一致,因此请选择一种返回“无结果”的方法并坚持下去。

5.我注意到一些人和一群人说应该将应用程序分成层或某种级别。

这比其他问题更难回答,因为没有看到您的应用程序的其余部分。

【讨论】:

感谢您的上下文和简洁的回答。你说得对。 5 个问题(我在想 4 个,但实际上我又添加了一个 :-))。我正在使用单调管理器进行数据库访问(这很糟糕吗?)。如果建议的类型表现更好,我将采用它们。听起来可能很愚蠢,但我不知道这里的一些事情..如果我知道,我不会询问信息。再次感谢。我将使用:HashMap、StringBuilder(在这种情况下)、PreparedStatement 代替执行字符串、NullObjects 代替空值和 Dog 代替 Dogs。这是有价值的信息。 更新。我检查了我的项目中与db相关的对象,它们都被命名为“Dog而不是Dogs”。事实是我有点不知道这个约定,但不知何故它似乎更合适。黑暗中的另一盏灯:-)。 不客气。是的,Singleton 模式是……好吧,我不会说 bad,但这是一个值得商榷的做法。每当我在设计中看到一个时,它都会让我怀疑它是否真的需要。谷歌一下,你就会明白我的意思了。 我想知道为什么我应该在所有情况下都使用 HashMap。我已经对 HashMap 与 Hashtable 的问题进行了搜索,并且我倾向于坚持使用 Hashtable。在某些情况下,HashMap 往往比 Hashtable 快 5%(牺牲你的集合的线程安全属性),但同步 HashMap 比“旧”哈希表慢。那么..我为什么要再次使用 HashMap? 有些人在这里做了一些测试:forums.sun.com/…【参考方案3】:

Null Object Pattern 是一种设计模式,您总是返回一个对象以避免 NPE:s 和代码中的任何 null 检查。在您的情况下,这意味着不是返回null,而是返回一个空的Hashtable&lt;Long, Dogs&gt;

原因是,由于它是一个集合,而您的其他代码将访问它,因此如果您返回一个空集合,它不会崩溃;它不会被迭代,它不会包含任何令人惊讶的东西,它不会导致 NPE:s 被抛出等等。

确切地说,空对象是类/接口的特殊实现,它完全不做任何事情,因此没有任何副作用。因为它不是null 的性质,所以使用它会让你的代码更干净,因为当你知道你总是会从你的方法调用中得到一个对象时无论方法内部发生什么你都不会'甚至不必检查空值,也不让代码对它们做出反应!因为 Null Object 不做任何事情,您甚至可以将它们作为 singletons 随意放置,从而节省内存。

【讨论】:

感谢您解决这个问题。看起来可以使用 Null 对象的静态初始化器构建一个特殊的类,如下所示:final static String[2] EMPTY_STRING_ARRAY = new String[]"", "",对吧? 是的,那将是长度为 2 的字符串数组的 Null 对象版本。现在不再执行 if(arr[i] != null && arr[i].equals ("val")) 你可以简单地做 if(arr[i].equals("val")) 这更简单更干净。 罗杰。如果我的对象 X 是手工制作的,有 Y 字段和 Z 方法怎么办?我必须编写一个名为 NullX 的类,其中包含 Y“默认”或“空”值和 Z 空方法,以替换原始对象?这也意味着要创建一个接口。是不是太麻烦了?使用带有默认值的 new X() 不能做同样的事情(除了每个实例消耗的额外内存?)。或者也许我可以在某处使这个对象(新的 X - 我的空对象)成为一个静态变量,以便在所有情况下调用? 有点混乱,但据我所知,那是单例。考虑到 POJO 和 bean 中的 Null 对象模式,是的,这意味着您必须对字段等实现 Null 对象,但最常见的是用“”替换 null 字符串,用 -1 或 0 替换数字就足够了。 我已经阅读了 NullObjects 的可能实现:有 2 个主要的..1。 2. 使用接口,Null Object 将扩展 RealObject,因此必须实现他的方法。对这种模式存在严重的担忧 blog.bielu.com/2008/12/…>。我希望我的评论链接能按预期工作......【参考方案4】:

不要像你正在做的那样通过连接字符串来构建 SQL 查询:

sql = new StringBuffer();
sql.append("SELECT * FROM ").append("dogs_table");
sql.append(" WHERE ").append(colName).append("='");
sql.append(colValue).append("'");

这会使您的代码容易受到众所周知的安全攻击SQL injection。而不是这样做,使用PreparedStatement 并通过调用适当的set...() 方法来设置参数。请注意,您只能使用它来设置列 values,不能像您正在做的那样使用它来动态构造列 name。示例:

PreparedStatement ps = connection.prepareStatement("SELECT * FROM dogs_table WHERE MYCOL=?");
ps.setString(1, colValue);

rs = ps.executeQuery();

如果您使用PreparedStatement,JDBC 驱动程序将自动处理转义可能出现在colValue 中的某些字符,因此 SQL 注入攻击不再起作用。

【讨论】:

说得好。感谢你。看到改变的原因后,改变就会随之而来。【参考方案5】:

如果发生错误,则抛出异常。如果没有数据,则返回一个空集合,而不是 null。 (此外,通常您应该返回更通用的“地图”,而不是具体的实现),

【讨论】:

好一个。我错过了显式类型的返回。 +1。【参考方案6】:

您可以通过使用Spring-JDBC 而不是普通的旧 JDBC 来显着减少样板 JDBC 代码的数量。这是使用 Spring-JDBC 重写的相同方法

public static Hashtable<Long, Dogs> getSomeDogs(String colName, String colValue) 

    StringBuffer sql = new StringBuffer();
    sql.append("SELECT * FROM ").append("dogs_table");
    sql.append(" WHERE ").append(colName).append("='");
    sql.append(colValue).append("'");

    Hashtable<Long, Dogs> result = new Hashtable<Long, Dogs>();

    RowMapper mapper = new RowMapper() 

        public Object mapRow(ResultSet rs, int rowNum) throws SQLException 
            Dogs dog = new Dogs();
            //...initialize the dog from the current resultSet row
            result.put(new Long(dog.getId()), dog);
        
    ;
    (Hashtable<Long, Dogs>) jdbcTemplate.queryForObject(sql, mapper);

春天照顾:

    遍历 ResultSet 关闭结果集 一致地处理异常

正如其他人所提到的,您确实应该使用 PreparedStatement 而不是 String(或 StringBuffer)来构造 SQL。如果由于某种原因你不能这样做,你可以通过构造这样的 SQL 来提高查询的可读性:

    String sql = 
        "SELECT * FROM dogs_table " +
        "WHERE " + "colName" + " = '" + colValue + "'";

【讨论】:

因为这似乎更具可读性,如果使用“手动”查询构建器,如我的示例所示,我发现它不是。使用“已弃用”的 StringBuilder 的 .append() 方法,将要构建的查询分隔为更独特的标记。现在所有这些都毫无意义,与人们建议总是根据他们自己的方式改变当前风格相比。或者推荐的。但是,对于 X 个不同的回答者,一个开发者不能采用 X 种风格。例如,checked or unchecked use of exceptions 问题到目前为止还没有答案……等等…… 有些人甚至更关注问题的格式,而不是问题的本质。我尽力让它看起来不错,这就是它进入野外的方式。我问了 4 个不同的问题,我得到了一些模糊的答案..我很抱歉,但除了使用新的 Java 类型(如 StringBuilder 或 HashMap)之外,到目前为止,我无法从答案中提取任何有用的东西:-(。是的,也许SpringJDBC 是一个很棒的框架,但我没有要求它。它只是我项目的另一个依赖项..我不确定我是否要承诺。 关于 SpringJDBC 的优点: 1. 我不讨厌自己迭代结果集; 2.我有一个安全关闭resultSet的静态方法; 3.我已经使用 apache.commons.lang 来处理异常,比如获取 rootStackTrace() 而不是一些讨厌的嵌套堆栈跟踪,那么..还有什么优势?

以上是关于Java 和 SQL:返回 null 还是抛出异常?的主要内容,如果未能解决你的问题,请参考以下文章

Find() 和 First() 抛出异常,如何改为返回 null?

java 中提示抛出异常

在clickhouse中,投射失败时如何返回null而不是抛出异常?

java抛出异常--后续代码是否还会执行

Java异常详解

返回null时,Camel REST服务抛出异常