Вложенные транзакции JPA и блокировка - программирование
Подтвердить что ты не робот

Вложенные транзакции JPA и блокировка

Рассмотрим сценарий. Существуют два метода для разных безстоящих bean

public class Bean_A {
   Bean_B beanB; // Injected or whatever
   public void methodA() {
    Entity e1 = // get from db
    e1.setName("Blah");
    entityManager.persist(e1);
    int age = beanB.methodB();

   }
} 
public class Bean_B {
  //Note transaction
  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
   public void methodB() {

    // complex calc to calculate age  
  }

}

Сделка, начатая BeanA.methodA, будет приостановлена, и в BeanB.methodB будет запущена новая транзакция. Что делать, если метод B должен получить доступ к тому же объекту, который был изменен методом A. Это приведет к тупиковой ситуации. Можно ли предотвратить это, не полагаясь на уровни изоляции?

4b9b3361

Ответ 1

Hm, перечислим все случаи.

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

T1 T2          T1 T2
―              ―
|              |
               |
   ―           |  ―
   |           |  |
   |     =     |  |
   ―           |  ―
               |
|              |
―              ―

Затем нам нужно рассмотреть оптимистичную и пессимистическую блокировку.

Кроме того, нам нужно рассмотреть flushes, которыми управляют ORM. С ORM у нас нет четкого контроля при записи, поскольку flush управляется инфраструктурой. Как правило, один неявный флеш происходит до фиксации, но если многие записи изменяются, фреймворк также может делать промежуточные флеши.

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

Чтение T1 не получает блокировки.

1a) Если T1 действительно промотировал изменения преждевременно, он приобрел эксклюзивный замок. Когда T2 совершает ошибку, он пытается получить блокировку, но не может. Система заблокирована. Это может быть как раз какой-то тупик. Завершение зависит от того, как транзакции или блокировки тайм-аут.

1b) Если T1 не промотировал изменения преждевременно, блокировка не была получена. Когда T2 совершает ошибку, он приобретает и освобождает его и становится успешным. Когда T1 пытается совершить, он замечает конфликт и терпит неудачу.

2) Давайте рассмотрим пессимистическую блокировку, где чтение получает общие блокировки и записывает эксклюзивные блокировки.

Чтение с помощью T1 приобретает общую блокировку.

2a) Если T1 краснеет преждевременно, он превращает блокировку в эксклюзивную блокировку. Ситуация похожа на 1a)

2b) Если T1 не высвечивается преждевременно, T1 имеет общую блокировку. Когда T2 совершает ошибку, он пытается получить эксклюзивную блокировку и блокировки. Система снова заблокирована.

Заключение: это прекрасно с оптимистичной блокировкой, если не происходит преждевременных вспышек, которые вы не можете строго контролировать.

Ответ 2

Передайте сущность и слияние...

Вы можете передать свой новый объект в methodB() и объединить его с новым EntityManager. Когда метод возвращает обновление вашей сущности, чтобы увидеть изменения:

public class Bean_A {
  Bean_B beanB; // Injected or whatever
  public void methodA() {
    Entity e1 = // get from db
    e1.setName("Blah");
    entityManager.persist(e1);
    int age = beanB.methodB(e1);
    entityManager.refresh(e1);
  }
} 

public class Bean_B {
  //Note transaction
  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public void methodB(Entity e1) {
    e1 = entityManager.merge(e1);
    // complex calc to calculate age  
  }

}

Обратите внимание, что это зафиксирует ваш объект, когда новая транзакция завершится после methodB.

... или сохранить его перед вызовом методаB

Если вы используете метод выше, объект сохраняется отдельно от основной транзакции, поэтому вы не потеряете ничего, если сохраните его с Bean_A перед вызовом methodB():

public class Bean_A {
  Bean_B beanB; // Injected or whatever

  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public void createEntity() {
    Entity e1 = // get from db
    e1.setName("Blah");
    entityManager.persist(e1);
  }

  public void methodA() {
    createEntity()   
    int age = beanB.methodB();
  }
} 

public class Bean_B {
  //Note transaction
  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public void methodB() {

    // complex calc to calculate age  
  }

}

Ответ 3

Вот недавняя статья об использовании демаркации транзакций REQUIRES_NEW.

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

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

Ответ 4

путем программной фиксации транзакции после entityManager.persist(e1); и до int age = beanB.methodB();?

public class Bean_A {
   Bean_B beanB; // Injected or whatever
   public void methodA() {
    EntityManager em = createEntityManager();
    Entity e1 = // get from db
    e1.setName("Blah");
    entityManager.persist(e1);
    em.getTransaction().commit();
    int age = beanB.methodB();

   }
} 
public class Bean_B {
  //Note transaction
  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
   public void methodB() {

    // complex calc to calculate age  
  }

}

EDIT: CMT

Если у вас есть CMT, вы все равно можете программно реализовать, вы просто получаете транзакцию из EJBContext. например: http://geertschuring.wordpress.com/2008/10/07/how-to-use-bean-managed-transactions-with-ejb3-jpa-and-jta/

или вы можете добавить @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void methodC(), который сделал бы e1.setName("Blah"); entityManager.persist(e1);, то есть он сохранил бы e1 в транзакции. то ваш methodA() будет вызывать

methodC();
beanB.methodB();