2

У меня есть транзакция Spring, которая читает из базы данных и, если то, что ищет, не существует, она ее создает. Пример:Как я могу заставить две идентичные транзакции одновременного чтения + записи ждать друг друга?

@Transactional 
public int getUserIdCreateIfNotExistent(String email) throws Exception { 
    try { 
    return jdbcTemplate.queryForObject("SELECT id FROM users WHERE email = ?", Integer.class, email); 
    } catch (IncorrectResultSizeDataAccessException e) { 
    Thread.sleep(10000); 
    return jdbcTemplate.queryForObject("INSERT INTO users (email) values(?) RETURNING id", Integer.class, email); 
    } 
} 

Когда этот метод вызывается дважды одновременно он приводит, имеющий два различных пользователей с одной и той же электронной почты в БД (нет ограничений по уникальной электронной почты, это только для целей иллюстрации).

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

EDIT: Прежде чем кто предлагает блокировки решений в Java с помощью synchronized или что-то подобное, это важно понимать, что этот метод является частью веб-приложения и вызывается, когда конкретная конечная точка удара. Веб-приложение работает на нескольких разных машинах и сбалансировано по нагрузке, и проблема возникает, когда конечная точка вызывается дважды одновременно. Это означает, что код может выполняться параллельно на двух разных машинах, разговаривающих с одной и той же БД на другой машине.

+0

Уровни изоляции не поможет, так как вы вставляя новые строки. если вы не столкнетесь с уникальными ограничениями. –

ответ

0

решаемые с консультативными замками:

@Transactional 
public int getUserIdCreateIfNotExistent(String email) throws Exception { 
    try { 
    jdbcTemplate.queryForRowSet("SELECT pg_advisory_xact_lock(hashtext('users'), hashtext(?))", email); 
    return jdbcTemplate.queryForObject("SELECT id FROM users WHERE email = ?", Integer.class, email); 
    } catch (IncorrectResultSizeDataAccessException e) { 
    Thread.sleep(10000); 
    return jdbcTemplate.queryForObject("INSERT INTO users (email) values(?) RETURNING id", Integer.class, email); 
    } 
} 
0

Вы можете использовать таблицу «замков». Он будет содержать все необходимое для блокировки, например, имя таблицы (например, «пользователи») и строку (например, «электронная почта»).

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