2017-01-20 5 views
0

Скажем, у меня есть класс Java с методом, как это (просто пример)Spring @Transactional с синхронизированной ключевое слово не работает

@Transactional 
public synchronized void onRequest(Request request) { 

    if (request.shouldAddBook()) { 
     if (database.getByName(request.getBook().getName()) == null) { 
      database.add(request.getBook()); 
     } else { 
      throw new Exception("Cannot add book - book already exist"); 
     } 
    } else if (request.shouldRemoveBook()) { 
     if (database.getByName(request.getBook().getName()) != null) { 
      removeBook(); 
     } else { 
      throw new Exception("Cannot remove book - book doesn't exist"); 
     } 
    } 
} 

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

Чтобы справиться с этим, мы могли бы попробовать (как и я) добавить вышеуказанный код @Transactional, а затем также «синхронизироваться», когда @Transactional не работает. Но странно это терпит неудачу при втором вызове с

«Невозможно добавить книгу уже существует».

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

+0

Если вы хотите изменить детали, обновите его. Вы не удаляете и не добавляете его. Это ужасный дизайн. Если вы все сделаете правильно, вам не понадобится синхронизация. – Kayaman

+0

хорошая попытка захватить upvotes ... почему вы спросили, знаете ли вы уже ответ? – Andrew

+0

Эта проблема не была найдена, потому что я хотел обновить детали книги. Это будет плохой дизайн, как вы говорите. Это просто пример. Есть и другие причины, по которым может возникнуть эта картина, и кто-то другой может поразить эту странную ситуацию так же, как я, поэтому я хотел показать другим, почему это не работает, поэтому им не нужно так долго работать, пытаясь понять как я. – JavaDevSweden

ответ

2

При удалении и добавление imedeately назад книги, если мы не имеем «транзакционный» или «синхронизировано» мы начинаем с этим исполнением резьбы:

T1: | ----- удалить книгу ---- ->

T2: | ------- добавить книгу ------->

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

T1: | ----- удалить книгу -----> T2: | -------- добавить книгу ------>

Аннотация @Transactional - это Aspect, и что она делает, это создает класс прокси-класса java вокруг вашего класса и добавляет к нему код («начать транзакцию») перед вызовом метода, затем вызывает метод, а затем вызывает еще один код («совершить транзакцию»). Таким образом, первый поток теперь выглядит следующим образом:

T1: | --Spring начинается transaction-- | ----- удалить книгу ----- | --Spring совершает транзакции --->

или короче: Т1: | -B- | -R- | -C ->

и второго потока, как это:

Т2: | начинается --Spring transaction-- | ----- --add книга ------- | --Spring совершает сделки --->

T2: | -B- | -А- | -C ->

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

НУ ВОТ FUN ЧАСТЬ:

Сделка код, который Spring добавляет не является частью синхронизированного метода, поэтому Т2 нить может на самом деле начать свой метод до того, как код «фиксации» завершен выполняется сразу после первого вызова метода.Как это:

T1: | -B- | -R- | -C-- | ->

T2: | -B ------ | -А- | -C-- >

So. когда метод «добавить» считывает базу данных, код удаления был запущен, но НЕ код фиксации, поэтому он все равно находит объект в базе данных и выдает ошибку. Миллисекунды позже будут удалены из базы данных.

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

+0

Конечно, настоящая проблема заключается в использовании 'synchronized'. Это не то, что вы хотите поместить в код доступа к данным. Это, по сути, плохое решение для сломанного дизайна. – Kayaman

+0

Если он удаляет аннотацию @Transactional, как он будет управлять транзакцией? Это не способ решить проблему, даже если я согласен с тем, что синхронизация является проблемой. – Rouliboy

+0

согласитесь, что это не может быть «решение проблемы» больше похоже на «удаление конкретной ситуации, в которой вы находитесь». – JavaDevSweden

2

Вам необходимо установить уровень изоляции транзакций для защиты от грязных чтений из базы данных, а не беспокоиться о безопасности потоков.

@Transactional(isolation = Isolation.SERIALIZABLE) 
public void onRequest(Request request) { 

    if (request.shouldAddBook()) { 
     if (database.getByName(request.getBook().getName()) == null) { 
      database.add(request.getBook()); 
     } else { 
      throw new Exception("Cannot add book - book already exist"); 
     } 
    } else if (request.shouldRemoveBook()) { 
     if (database.getByName(request.getBook().getName()) != null) { 
      removeBook(); 
     } else { 
      throw new Exception("Cannot remove book - book doesn't exist"); 
     } 
    } 
} 

Это отличное объяснение раскрытия и изоляции транзакций.

Spring @Transactional - isolation, propagation

+0

Разве база данных не считает, что два книжных объекта отличаются друг от друга и позволяют читать и писать их одновременно? – JavaDevSweden

+0

База данных не делает это по умолчанию, но я думаю, что ее можно настроить на уровне базы данных. Это материал DBA, и я не эксперт в этом, поэтому я считаю, что это делается на уровне кода :) – Avinash

+0

После пробования всех уровней изоляции я могу сказать, что (по крайней мере, в моем примере на моей текущей установке) оба потока всегда разрешены для чтения значения из базы данных, хотя другой поток недавно прочитал одно и то же значение, не совершая транзакции. Единственное, что работает до сих пор, это синхронизированное ключевое слово ... – JavaDevSweden

0

«synchronized» должен использоваться перед методом @Transaction. В противном случае при многопоточности объект разблокируется, но транзакция не отправляется.

Смежные вопросы