Вопросы о правильном закрытии / фиксации транзакций базы данных и выполнении запросов

#java #coding-style

#java #стиль кодирования

Вопрос:

Я работаю над обновлением проекта, и я просто хочу знать лучшие практики по этим двум вопросам:

  1. Должен ли я продолжать использовать Connections / Statements / ResultSet для выполнения запросов или я должен использовать что-то еще?
  2. Является ли это правильным способом фиксации / закрытия соединений / Операторов / наборов результатов? Я имею в виду, выполняю ли я фиксацию / закрытие в правильном порядке с блоками try / catch / finally в нужных местах?

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

Фрагмент кода из select метода типа:

 public  ArrayList getMethod() {

    ArrayList a = new ArrayList();

    Connection con = null;
    ResultSet rs = null;
    Statement stmt = null;

    try {

        con = BoneCPConnection.getConnectionPool().getConnection();     // get a connection
        con.setAutoCommit(true);            
        stmt = con.createStatement();

        String query = "select * from example";

        rs = stmt.executeQuery(query);

        while(rs.next()) {
            System.out.println("Result: "  rs.getInt(1));
        }

    } catch (Exception e) {
        System.out.println("Issue with getMethod");
        e.printStackTrace();
    } finally {

        try {
            rs.close();
            stmt.close();
            con.close();
        } catch (Exception e) {
            con = null;
        }

        rs = null;
        stmt = null;
        con = null;
    }

    return a;
}
 

Фрагмент кода из update метода типа

 public void updateMethod() {

    ArrayList a = new ArrayList();

    Connection con = null;
    Statement stmt = null;
    int updateCount = null;

    try {

        con = BoneCPConnection.getConnectionPool().getConnection();     // get a connection     
        stmt = con.createStatement();

        String query = "update example set id = 1";

        updateCount = stmt.executeUpdate(query);

        System.out.println("Result: "  updateCount);    

    } catch (Exception e) {
        System.out.println("Issue with updateMethod");
        e.printStackTrace();
    } finally {

        try {
            con.commit();
            stmt.close();
            con.close();
        } catch (Exception e) {
            con = null;
        }

        stmt = null;
        con = null;
    }
}
 

Ответ №1:

Как минимум, вам, вероятно, следует переключиться на PreparedStatement инструкции instead of plain . Причина этого в том, что драйвер JDBC в большинстве случаев отправляет инструкцию в базу данных при создании, чтобы ее можно было предварительно скомпилировать. Затем вы можете привязать свои параметры к оператору и выполнить. Помимо преимуществ производительности при предварительной компиляции, вы также получаете небольшую защиту от атак SQL-инъекций, поскольку способ установки параметров более строго типизирован. На сайте Oracle есть хорошее описание подготовленных инструкций.

Если вы используете Spring (или хотите сделать скачок, чтобы добавить его в свою систему), вы можете взглянуть на JdbcTemplate JdbcDaoSupport классы and (оба описаны здесь). Основное преимущество заключается в том, что он заботится о коде очистки соединения для вас (поэтому вам не нужно так сильно беспокоиться о пропущенном close вызове).

Аналогично, если вы добавите Spring в свой проект, вы можете использовать его для настройки своих транзакций (либо с помощью аннотаций, либо с помощью контекстного файла Spring). Это позволит вам извлечь управление транзакциями из фактической реализации и сделать код в вашем Dao немного чище.

Что касается вашей обработки фиксации / закрытия: вы должны переместить свои операторы фиксации из ваших блоков finally в основной путь выполнения. Вы должны сохранить свои операторы close в блоке finally, хотя, поскольку вы хотите, чтобы они происходили, несмотря ни на что.

Примером того, как будет выглядеть ваш код обновления с использованием PreparedStatements, является:

 public void updateMethod() {
    Connection con = null;
    PreparedStatement stmt = null;
    int updateCount = null;

    try {
        con = BoneCPConnection.getConnectionPool().getConnection();
        stmt = con.prepareStatement("update example set id = ?");        
        stmt.setInt(1,1);
        updateCount = stmt.executeUpdate(query);
        con.commit();
    } catch (Exception e) {
       if(con != null){
        con.rollback();
       }
    } finally {

        try {
          if(stmt != null){
            stmt.close();
          }
          if(con != null){                
            con.close();
          }
        } catch (Exception e) {
            con = null;
        }        
    }
}
 

Если бы вы использовали JdbcDaoSuport от Spring, это выглядело бы так:

 public class YourDao extends JdbcDaoSupport{

  public void updateMethod(){
    String sql = "update example set id = ?";
    getJdbcTemplate().update(sql, new Object[] { new Integer(1)});           
  }

}
 

Ответ №2:

  1. Вы почти всегда должны предпочитать PreparedStatement для параметризованных запросов, поскольку это:
    • защищает вас от SQL-инъекций
    • получает предварительную компиляцию по базе данных
  2. Вам не нужно повторно инициализировать rs, stmt и con в значение null, поскольку они выходят за пределы области действия в конце метода
  3. Вам почти всегда лучше использовать какую-нибудь платформу доступа к БД. Одним из примеров является Spring с его JdbcTemplate, другим является Apache Commons DBUtils
  4. Если вы обновляете систему, обновите ее до java 7 — вы получите try-with-resources, который закроет ваши соединения и операторы бесплатно, и ваш код будет выглядеть следующим образом:
 

    public void updateMethod() {
        int updateCount = 0;

        String query = "update example set id = ?";
        try (Connection con = BoneCPConnection.getConnectionPool().getConnection();                         
            PreparedStatement stmt = con.prepareStatement(query)) {
                stmt.setInt(1, 1);

                updateCount = stmt.executeUpdate();

                System.out.println("Result: "   updateCount);

        } catch (Exception e) {
            System.out.println("Issue with updateMethod");
            e.printStackTrace();
        }
    }

    public ArrayList getMethod() {

        ArrayList a = new ArrayList();
        String query = "select * from example";

        try (Connection con = BoneCPConnection.getConnectionPool().getConnection(); 
            Statement stmt = con.createStatement()) {

            con.setAutoCommit(false);

            try (
                ResultSet rs = stmt.executeQuery(query)) {

                while (rs.next()) {
                    System.out.println("Result: "   rs.getInt(1));
                }

            }

        } catch (Exception e) {
            System.out.println("Issue with getMethod");
            e.printStackTrace();
        }

        return a;

    }
 

Комментарии:

1. Полезно знать о java 7, но у меня нет возможности обновлять это, так что на данный момент это не вариант.

Ответ №3:

самое важное, что нужно закрыть, — это соединение. как правило, вы хотите попробовать /, наконец, каждый ресурс (rs, stmt, conn) при закрытии.

кроме того, вы не хотите фиксировать в блоке finally. вы хотите зафиксировать только в счастливом пути (внутри блока catch).

наконец, никогда, никогда, никогда не помещайте «e.printStackTrace()» в свой код.

Обновить:

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

  • если вы хотите продолжить после исключения, запишите его в соответствующее средство ведения журнала (используйте java.util.Регистратор, если ничего другого)
  • если вы не можете обработать исключение, повторно выбросьте его

(технически, существует также несколько случаев, когда это действительно игнорируемое исключение, и в этом случае добавьте комментарий в like // i never, ever care if i get this exception ).

Комментарии:

1. lastly, never, ever, ever put "e.printStackTrace()" in your code. Почему бы и нет?

2. @Dave — обновление ответа, слишком много, чтобы писать в комментарии.

3. Хорошо, запах кода, я куплюсь на это. Тем не менее, это не значит, что это неправильно, просто флаг для незаконченных дел; Я думаю, что «никогда, никогда, никогда» немного сильно без оговорки «в окончательном коде». Вы заставили меня думать, что я не знал о каком-то побочном эффекте.

4. @Dave — дело в том, что это неправильно. если вы не хотите иметь дело с обработкой этого исключения в данный момент времени, то просто дайте ему всплыть или повторно создайте его с соответствующей оболочкой. это так же просто, как ввести функцию printStackTrace(), и у нее нет никакого риска случайного проглатывания исключения. нет никаких веских причин когда-либо включать это в свой код.

5. @Dave — есть побочный эффект: исключение остановлено, и маловероятно, что кто-нибудь увидит эту трассировку стека, иначе «скрывающую ошибку».

Ответ №4:

Ответ на вопрос 1: — если вам не очень нравятся фреймворки и ваша бизнес-логика взаимодействия с БД достаточно проста, вы можете продолжать использовать этот подход. Другими альтернативами являются: Hibernate, JPA или Spring JDBC. Все они делают одно и то же, аналогично вашему коду, но скрыто в реализации фреймворка. Каждый из этих фреймворков имеет свои преимущества / недостатки. Но опять же, это вопрос предпочтений / требований / сложности.

Ответ на вопрос 2: — вы открываете / закрываете ресурсы в правильном порядке, однако есть некоторые проблемы: 1. Попробуйте перехватить более конкретное исключение, а не исключение. Исключение SQLException должно быть зафиксировано в вашем примере. 2. Лучше использовать PreparedStatement вместо Statement, чтобы избежать внедрения SQL или если вы запускаете один и тот же запрос несколько раз. 3. Производственный код не должен использовать System.out.println или e.printStackTrace, но для спайков или использования новых материалов это не проблема. (Что касается предыдущего ответа).