4

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

让我们假设以下方法:

 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在这里说我应该返回 NullObject 而不是 null 值。我想知道那是什么。

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

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

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

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

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

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

4

6 回答 6

8

我会避免以下情况

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

而是使用PreparedStatement及其相关的参数设置setString()方法colValuecolValue

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

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

如果在构建Dog对象时遇到问题该怎么办?我认为这取决于你希望你的应用程序有多健壮和有弹性。返回Dogs 的子集是否有问题,还是完全是灾难性的,您需要报告这个?这是一个应用程序要求(过去我不得不满足任何一种情况——尽力而为全有或全无)。

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

于 2009-10-09T16:59:55.173 回答
6

你问了五个问题

1. 你有什么方法可以改进这种带一些属性的狗归还技巧?

实际上有几个。

  • 你的方法是静态的——这并不可怕,但会导致你使用另一个静态的“executeQuery”,这对我来说是单例的味道......
  • “Dogs”类违反了面向对象的命名惯例——复数名词不能成为好的类名,除非该类的一个实例包含一组事物——而且看起来 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. 我注意到人们和一群人说应该将应用程序分成层或某种级别。

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

于 2009-10-09T19:44:32.283 回答
4

空对象模式是一种设计模式,您始终返回一个对象以避免 NPE:s 和代码中的任何空检查。在您的情况下,这意味着不是返回null,而是返回一个空Hashtable<Long, Dogs>

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

准确地说,空对象是一个类/接口的特殊实现,它完全不做任何事情,因此没有任何副作用。由于其不null使用的性质,它将使您的代码更清晰,因为当您知道无论方法内部发生什么情况时,您总是会从方法调用中获取一个对象,您甚至不必检查空值或编写代码对他们做出反应!因为 Null Object 不做任何事情,您甚至可以将它们作为单例放置在周围,从而节省内存。

于 2009-10-09T17:10:02.173 回答
3

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

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

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

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

rs = ps.executeQuery();

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

于 2009-10-09T19:53:15.553 回答
2

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

于 2009-10-09T17:16:53.390 回答
0

通过使用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);
}

春天照顾:

  1. 遍历 ResultSet
  2. 关闭结果集
  3. 一致地处理异常

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

    String sql = 
        "SELECT * FROM dogs_table " +
        "WHERE " + "colName" + " = '" + colValue + "'";
于 2009-10-09T17:55:08.677 回答