Spring jdbc «выбрать для обновления»

У меня есть следующий метод, который я использую с Spring JDBC

public String getState() {
    String stateLink = template.queryForObject(
            "select state_url from state_scrape_queue where in_use = false ORDER BY scrape_timestamp NULLS FIRST LIMIT 1",
            (result, rowNum) -> {
                return result.getString("state_url");
            });
    return stateLink;
}

Я не могу найти пример того, как сделать для обновления с помощью Spring JDBC. Я хочу, чтобы для in_use было установлено значение true, используя для обновления.

Мне нужно использовать выбор для обновления, так как это приложение будет использоваться в многопоточном режиме. Я не хочу, чтобы несколько потоков получали одну и ту же строку, и способ предотвратить это — использовать select for update

Я смог сделать это с помощью простого JDBC, вот вопрос, который я задал, как это сделать с помощью простого JDBC.

выбрать «для обновления» с JDBC?

Кто-нибудь знает, как это будет сделано?

FOR UPDATE ничего не обновляет, а только блокирует выбранные строки, как если бы они были обновлены. Таким образом, вы не можете установить что-то в true, используя FOR UPDATE, вам нужно будет выполнить отдельный оператор UPDATE. В любом случае, с использованием spring-jdbc все работает так же, как и с прямым выполнением операторов. В нынешнем виде неясно, что вы на самом деле спрашиваете.
Mark Rotteveel 29.05.2019 09:21

Обновление имеет свою цель, поэтому оно существует. Это очень полезно в многопоточных приложениях. Это предотвращает получение той же строки другим потоком, потому что использование select for update блокирует строку и в этом примере обновит in_use до true, поэтому другой поток не получит ту же строку.

Arya 29.05.2019 16:54

Предложение for update в select не будет обновлять строку, оно только заблокирует строку, поэтому параллельные транзакции не могут обновляться и, в зависимости от модели блокировки, не могут читать эту строку. Ваше предположение, что оно изменится in_use на true, неверно.

Mark Rotteveel 29.05.2019 16:56

@MarkRotteveel Этот ответ предназначен для выполнения select for update с использованием простого JDBC. stackoverflow.com/a/46995306/492015 Пользуюсь очень давно и работает отлично. Два потока никогда не получали одну и ту же строку. Мне просто нужно сделать то же самое с Spring JDBC

Arya 29.05.2019 17:01

И это будет работать точно так же, как с JDBCTemplate. Основное отличие, вероятно, заключается в границе вашей транзакции. Вызывает ли какой-либо из методов в цепочке этот метод @Transactional, если нет, то транзакция зафиксирована и снятые на момент блокировки queryForObject возвращаются.

Mark Rotteveel 29.05.2019 17:45

Я думаю, что автоматическая фиксация должна быть отключена, чтобы это работало? А поскольку Spring JDBC использует объединение соединений, реализовать это будет проблематично. Я могу просто использовать простой JDBC для выбора методов обновления.

Arya 29.05.2019 18:19

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

Mark Rotteveel 29.05.2019 18:49

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

Arya 29.05.2019 19:27

Давайте продолжить обсуждение в чате.

Arya 29.05.2019 19:53

@MarkRotteveel, что вы думаете об ответе?

Arya 29.05.2019 23:49

Это в основном просто ответ на вопрос, который вы связали. В любом случае, почему вы не можете использовать UPDATE для этого напрямую?

Mark Rotteveel 30.05.2019 11:11

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

Arya 30.05.2019 18:14
4
12
1 689
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вот что я придумал, не стесняйтесь рекомендовать улучшения

public String getState() throws SQLException {
    String state = null;

    Connection conn = DataSourceUtils.getConnection(template.getDataSource());
    try {
        conn.setAutoCommit(false);

        String[] colNames = { "id", "state_url", "in_use" };
        String query = "select " + Stream.of(colNames).collect(Collectors.joining(", "))
                + " from state_scrape_queue where in_use = false ORDER BY scrape_timestamp NULLS FIRST LIMIT 1 FOR UPDATE";
        System.out.println(query);
        try (Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
                ResultSet rs = stmt.executeQuery(query)) {
            while (rs.next()) {
                // Get the current values, if you need them.
                state = rs.getString(colNames[1]);

                rs.updateBoolean(colNames[2], true);
                rs.updateRow();
                conn.commit();
            }
        }
    } catch (SQLException e) {
        conn.setAutoCommit(true);
        e.printStackTrace();
    } finally {
        conn.setAutoCommit(true);
    }

    return state;
}

Я попробовал это, не отключая AutoCommit, и похоже, что он работает нормально.

Arya 29.05.2019 20:38

Другие вопросы по теме