Java и SQL: вернуть ноль или выбросить исключение? - PullRequest
3 голосов
/ 09 октября 2009

Это еще одна дискуссионная тема, но на этот раз я ищу только простые и документированные ответы. Сценарий:

Давайте предположим следующий метод:
 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 () вернет false для нулевого ResultSet или сгенерирует исключение, например:

String str = null; System.out.println (str.toString ());

3. Что если при инициализации объекта dog из текущей строки ResultSet произойдет что-то плохое, например: сбой соединения, несовместимое значение было передано в установщик свойств dog и т. Д.? У меня сейчас может быть 10 элементов в хеш-таблице или ни одного (первая строка). Какими будут следующие действия: а) вернуть нулевую хеш-таблицу; б) вернуть результат хеш-таблицы, такой, какой она есть на данном этапе; в) бросить исключение: какой тип исключения будет здесь?

4. Я думаю, вы все согласитесь с этим: ничего плохого не происходит, на запросе нет строк, будет возвращено нулевое значение. Однако @ Thorbjørn Ravn Andersen сказал здесь , что я должен вернуть NullObject вместо нулевого значения. Интересно, что это?

5. Я заметил, что люди и группы людей говорят, что нужно разбить приложение на слои или уровни. Учитывая приведенный выше пример, какие здесь слои, кроме тех, о которых я могу подумать:

Layer1 :: Уровень базы данных, где выполняются действия: этот метод.

Layer2 :: ??? : некоторый слой, в котором построен новый объект Dog: мой объект Dog.

Layer3 ::? : какой-то слой, где я собираюсь что-то сделать с коллекцией собак: слой GUI, в основном, или подуровень пользовательского интерфейса.

Если после 1-го уровня возникает исключение, что лучше всего с ним делать? Мои мысли: поймать исключение, зарегистрировать исключение, вернуть какое-то значение. Это лучшая практика?

Мэнни, спасибо за ваши ответы, я с нетерпением жду, чтобы узнать, что другие люди думают об этом.

Ответы [ 6 ]

8 голосов
/ 09 октября 2009

Я бы избежал следующего

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

и вместо этого используйте PreparedStatement со связанными с ним методами установки параметров (setString()) и т. Д. Это предотвратит проблемы со значениями для colValue, имеющими кавычки, и атаки с использованием SQL-инъекций (или в более общем случае *) 1008 * формирует некоторый синтаксис SQL).

Я бы никогда не вернул бы ноль, если коллекция была просто пустой. Это кажется очень нелогичным и совершенно неожиданным с точки зрения клиента.

Я бы не рекомендовал возвращать нулевое значение в условиях ошибки, поскольку ваш клиент должен явно проверить это (и, вероятно, забудет). Я бы возвратил пустую коллекцию в случае необходимости (это может быть аналогом вашего комментария относительно нулевого объекта) или, более вероятно, сгенерирует исключение (в зависимости от обстоятельств и серьезности). Исключение полезно в том смысле, что оно будет содержать некоторую информацию, касающуюся обнаруженной ошибки. Нуль ничего не говорит.

Что делать, если возникла проблема при создании объекта Dog? Я думаю, это зависит от того, насколько надежным и устойчивым вы хотите, чтобы ваше приложение было. Является ли проблемой возвращение подмножества Dog с, или это будет полностью катастрофическим, и вам нужно сообщить об этом? Это требование к приложению (в прошлом мне приходилось обслуживать любой из сценариев - из лучших сил или все или ничего ).

Пара наблюдений. Я бы использовал HashMap , а не старый Hashtable (синхронизированный для любого доступа и, что более важно, не правильный Collection - если у вас есть Collection, вы можете передать его любому другому методу ожидая any Collection) и StringBuilder сверх StringBuffer по аналогичным причинам. Не большая проблема, но стоит знать.

5 голосов
/ 09 октября 2009

Вы задаете пять вопросов

1. Какие способы вы предлагаете улучшить эту технику возвращения некоторых собак с некоторым атрибутом?

Несколько, на самом деле.

  • Ваш метод статичен - это не страшно, но приводит к тому, что вы используете другой статический "executeQuery", который пахнет мне Синглтоном ...
  • Класс «Собаки» нарушает практику именования ОО - множественные существительные не дают хороших имен классов, если только один экземпляр класса не содержит коллекцию вещей - и кажется, что Собаки на самом деле «Собаки».
  • HashTable почти устарел. HashMap или ConcurrentHashMap обеспечивают лучшую производительность.
  • Я не вижу причины для создания первой части вашего запроса с несколькими добавлениями - это неплохо, но менее читабельно, чем могло бы быть, поэтому sql.append ("SELECT * FROM dogs_table WHERE"); делает более разумным начало, если вы все равно собираетесь жестко кодировать выбранные столбцы (*) и имя таблицы (dogs_table).

2. rs.next () вернет false для нулевого ResultSet или сгенерирует исключение

Кажется, это не вопрос, но да, rs.next () возвращает false, как только больше нет строк для обработки.

3. Что если во время инициализации объекта dog из текущей строки ResultSet произойдет что-то плохое

Если «что-то плохое случится», то что вы будете делать дальше, зависит от вас и вашего дизайна. Есть подход прощения (вернуть все строки, которые вы можете) и прощение (выбросить исключение). Я склонен склоняться к «не прощающему» подходу, так как с «прощающим» подходом пользователи не будут знать, что вы не вернули все существующие строки - только те, которые вы получили до ошибки. Но могут быть случаи для прощающего подхода.

4. Я думаю, вы все согласитесь с этим: ничего плохого не происходит, на запросе нет строк, будет возвращено нулевое значение.

Это не то, на что есть очевидный правильный ответ. Во-первых, это не то, что происходит в методе, как написано. Он собирается вернуть пустой HashTable (это то, что подразумевается под «нулевым объектом»). Во-вторых, null не всегда является ответом в случае «ничего не найдено».

Я видел ноль, но вместо этого я видел пустую переменную результата. Я утверждаю, что они оба правильные подходы, но я предпочитаю пустую переменную результата. Однако всегда лучше быть последовательным, поэтому выберите метод возврата «без результатов» и придерживайтесь его.

5. Я заметил, что люди и группы людей говорят, что нужно разбить приложение на слои или уровни какого-то рода.

На этот вопрос ответить труднее, чем на остальных, не видя остальную часть вашего заявления.

4 голосов
/ 09 октября 2009

Шаблон нулевого объекта - это шаблон проектирования, в котором вы всегда возвращаете объект, чтобы избежать проверок NPE и любых нулевых проверок в вашем коде. В вашем случае это означает, что вместо возврата null вместо него возвращается пустой Hashtable<Long, Dogs>.

Причина в том, что, поскольку это коллекция и ваш другой код будет обращаться к ней как таковой, она не сломается, если вы вернете пустую коллекцию; он не будет повторяться, он не будет содержать ничего удивительного, он не вызовет выброс NPE: и все такое.

Если быть точным, Null Object - это специальная реализация класса / интерфейса, которая абсолютно ничего не делает и, следовательно, не имеет никаких побочных эффектов. Из-за того, что он не используется null, он сделает ваш код чище, поскольку, когда вы знаете, что вы всегда получите объект из вызовов метода , независимо от того, что происходит внутри метода , вы этого не сделаете даже не нужно проверять наличие нулей и не реагировать на них! Поскольку Null Object ничего не делает, вы даже можете иметь их в виде синглетонов , просто лежащих вокруг и таким образом сохраняющих память, делая это.

3 голосов
/ 09 октября 2009

Не создавайте SQL-запросы путем объединения строк, как вы делаете:

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

Это делает ваш код уязвимым для хорошо известной атаки безопасности, SQL-инъекция . Вместо этого используйте PreparedStatement и установите параметры, вызвав соответствующие методы set...(). Обратите внимание, вы можете использовать это только для установки столбца значения , вы не можете использовать его для динамического построения столбца name , как вы делаете. Пример:

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

rs = ps.executeQuery();

Если вы используете PreparedStatement, драйвер JDBC автоматически позаботится о экранировании определенных символов, которые могут присутствовать в colValue, чтобы атаки с использованием SQL-инъекций больше не работали.

2 голосов
/ 09 октября 2009

Если возникает ошибка, то исключение. Если данных нет, верните пустую коллекцию, а не ноль. (Кроме того, как правило, вы должны возвращать более общую 'Map', а не конкретную реализацию),

0 голосов
/ 09 октября 2009

Вы можете значительно уменьшить количество стандартного кода JDBC, используя Spring-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. Закрытие ResultSet
  3. Последовательная обработка исключений

Как уже упоминали другие, вам действительно следует использовать PreparedStatement для создания SQL вместо String (или StringBuffer). Если по какой-то причине вы не можете сделать это, вы можете улучшить читабельность запроса, создав вместо этого SQL-код следующим образом:

    String sql = 
        "SELECT * FROM dogs_table " +
        "WHERE " + "colName" + " = '" + colValue + "'";
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...