подскажите, что за проблема с этим кодом. он запускается внутри большой транзакции от spring batch
public class TablesSynchronizer {
...
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void forceTableUpdate(){
....
try {
if (!service.scheduleExecutionForTable(pid, table)) {
throw new RuntimeException("Could not set next time for table=" + table);
}
} catch(OptimisticLockException e) {
log.warn("OptimisticLockException catched for pid " + pid);
}
log.info("Next time for table set, table=" + table);
}
}
вижу WARN лог от OptimisticLockException и INFO "Next time set", но после них spring batch откатывает верхнюю транзакцию с сообщением
Commit failed while step execution data was already updated. Reverting to old version.
как так выходит, ведь exception в отдельной от spring batch транзакции ?
Здравствуйте, Ballista, Вы писали:
B>как так выходит, ведь exception в отдельной от spring batch транзакции ?
Какой transaction manager используется? И что там еще в логах написано, какой cause у исключения? А то ведь есть, например, и похожая проблема в конфигурации. Ну и если не поможет, копайте в сторону entity manager и его взаимодействия с транзакциями. OptimisticLockingException и вся ситуация намекает, что транзакция откатывается как раз entity manager'ом. А вот почему — не знаю. Или конфигурация неправильная. Или может вы какие-то entity из внешней транзакции протаскиваете. Или еще что-то.
B>>как так выходит, ведь exception в отдельной от spring batch транзакции ? M>Какой transaction manager используется? И что там еще в логах написано, какой cause у исключения? А то ведь есть, например, и похожая проблема в конфигурации. Ну и если не поможет, копайте в сторону entity manager и его взаимодействия с транзакциями. OptimisticLockingException и вся ситуация намекает, что транзакция откатывается как раз entity manager'ом. А вот почему — не знаю. Или конфигурация неправильная. Или может вы какие-то entity из внешней транзакции протаскиваете. Или еще что-то.
JpaTransactionManager, в логе
2022-12-09 00:55:41,527 INFO [P-3010-20221208220447-2302] org.springframework.batch.core.step.tasklet.TaskletStep - Commit failed while step execution data was already updated. Reverting to old version.
2022-12-09 00:55:41,527 ERROR [P-3010-20221208220447-2302] org.springframework.batch.core.step.tasklet.TaskletStep - Rolling back with transaction in unknown state
2022-12-09 00:55:41,528 ERROR [P-3010-20221208220447-2302] org.springframework.batch.core.step.AbstractStep - Encountered an error executing step PrepareStep in job MyJob1
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:526)
я полагаю была бы совсем неверная конфигурация, вообще бы не работало, а тут не работает только лишь, если в моем степе выскакивает OptimisticLockingException. я не пойму как события в моей логике могут влиять на спринг батч транзакцию, если они оформлены как совершенно отдельные транзакции. у моей логики отдельная транзакция.
ну и второй вопрос — верно ли я понимаю, что при OptimisticLockingException я обязан повторять попытку ? словить OptimisticLockingException и пойти дальше получается нельзя.
Здравствуйте, Ballista, Вы писали:
B>JpaTransactionManager
Это хорошо и многое объясняет. Есть отличный последний параграф в документации (выделение взято из оригинала):
This transaction manager supports nested transactions via JDBC 3.0 Savepoints. The "nestedTransactionAllowed" flag defaults to false though, since nested transactions will just apply to the JDBC Connection, not to the JPA EntityManager and its cached entity objects and related context. You can manually set the flag to true if you want to use nested transactions for JDBC access code which participates in JPA transactions (provided that your JDBC driver supports Savepoints). Note that JPA itself does not support nested transactions! Hence, do not expect JPA access code to semantically participate in a nested transaction.
Здесь почти все факты важны:
Вложенные транзакции вообще выключены по-умолчанию
Если они включены, то применяются только к JDBC
JPA вообще не поддерживает вложенные транзакции ни в каком виде.
Последнее в принципе понятно — определять семантику вложенных транзакций будет очень сложно. Что делать, если одно поле поменялось во внешней транзакции, а другое — во внутренней? Какое должно быть состяние объектов, если транзакция откатилась? И т.д.
B>я не пойму как события в моей логике могут влиять на спринг батч транзакцию, если они оформлены как совершенно отдельные транзакции. у моей логики отдельная транзакция.
А это потому, что связь ORM (это то, что бросается OptimisticLockingException) и JDBC (всякие *Template) сделана совершенно неочевидным образом. У вас первичная транзакция управляется Entity Manager (т.е. ORM). Для доступа к JDBC используется то же соединение, что и для управления всеми сущностями (entity). Соединение в enity manager прокидывается "сразу", когда впервые создается для потока. Смотрите ближе к концу здесь. Поэтому-то единственное, что вообще видит и может откатить entity manager — это верхняя транзакция. Даже если предположить, что все настроено так, что атрибуты транзакций не игнорируются, вы получите новую независимую транзакцию только для JDBC. А в enity manager так и останется та самая изначальная транзакция (разделяемая со внутренностями spring batch).
Почему сделали именно так — не знаю. В теории можно было бы устанавливать новый entity manager при возникновении новой транзакции. Но при этом остаются вопросы семантики (что делать, если мы смешиваем entity из двух разных транзакций?). И, вероятно, какие-то реализации entity manager используют "независимые от себя" thread local. Т.е. если заменить один экземпляр на другой, часть thread local и контекста все равно останется. Но в целом причины не важны. Результат мы уже знаем.
B>ну и второй вопрос — верно ли я понимаю, что при OptimisticLockingException я обязан повторять попытку ? словить OptimisticLockingException и пойти дальше получается нельзя.
Да, получается так. У вас "рано" инициализируется EntityManager и ORM-транзакция. Для ORM OptimisticLockingException является фатальным для всей транзакции. Поэтому цепляется и spring batch.
Продолжать можно было бы, если бы у вас entity manager инициализировался позже и на основе текущей транзакции. Т.е. в качестве transaction manager у вас был бы DataSourceTransactionManager. И уже потом, каким-то отдельным способом вы инициализировали EntityManager только в рамках отдельного запроса/контекста. Ну и как-то подсовывали его для всей автоматической машинерии в Spring. Создать entity manager вручную можно, смотрите второй ответ здесь. Что дальше с ним делать — не знаю . Можете явно методы вызывать. Можете пытаться куда-то подсунуть. Явный экземпляр хотя бы даст возможность вручную его транзакцией управлять. Что там будет нижележащее — текущая транзакция из JDBC или своя новая — тоже не знаю.
Здравствуйте, maxkar, Вы писали:
M>Здравствуйте, Ballista, Вы писали:
B>>JpaTransactionManager M>Это хорошо и многое объясняет. Есть отличный последний параграф в документации (выделение взято из оригинала): M>
M>This transaction manager supports nested transactions via JDBC 3.0 Savepoints. The "nestedTransactionAllowed" flag defaults to false though, since nested transactions will just apply to the JDBC Connection, not to the JPA EntityManager and its cached entity objects and related context. You can manually set the flag to true if you want to use nested transactions for JDBC access code which participates in JPA transactions (provided that your JDBC driver supports Savepoints). Note that JPA itself does not support nested transactions! Hence, do not expect JPA access code to semantically participate in a nested transaction.
M>Здесь почти все факты важны: M>
M> Вложенные транзакции вообще выключены по-умолчанию M> Если они включены, то применяются только к JDBC M> JPA вообще не поддерживает вложенные транзакции ни в каком виде. M>M>Последнее в принципе понятно — определять семантику вложенных транзакций будет очень сложно.
PROPAGATION_REQUIRES_NEW, in contrast to PROPAGATION_REQUIRED, uses a completely independent transaction for each affected transaction scope. In that case, the underlying physical transactions are different and hence can commit or roll back independently, with an outer transaction not affected by an inner transaction’s rollback status.
nested транзакция — согласен, мутная штука. но почему у нас разговор всплыл о nested, если REQUIRES_NEW должна была создать "completely independent transaction". получается, что не смотря на заявление в документации, реально эта "completely independent transaction" все равно подвид nested транзакции на уровне JPA и лишь на уровне JDBC отдельная коннекция/транзакция.
Здравствуйте, Ballista, Вы писали:
B>nested транзакция — согласен, мутная штука. но почему у нас разговор всплыл о nested, если REQUIRES_NEW должна была создать "completely independent transaction". получается, что не смотря на заявление в документации, реально эта "completely independent transaction" все равно подвид nested транзакции на уровне JPA и лишь на уровне JDBC отдельная коннекция/транзакция.
Возможно. Вообще пока есть и другой вариант. Кто у вас вызывает forceTableUpdate? И какая стратегия для добавления транзакций используется: proxy или манипуляция байткода? Ни в одном не будут работать вызовы this.forceTableUpdate(). При прокси не будут работать и вызовы через класс а не через интерфейс. При использовании модификации байткода вызовы через класс должны работать (при условии, что класс инжектится а не явно создается). Примеры на ORM содержат и REQUIRES_NEW, так что, возможно, оно все-таки поддерживается.
Ну а по вопросу. У entity manager же свои транзакции, в рамках которой он отслеживает изменения объектов. И вот с ним все равно можно смешивать объекты из внешней и внутренней (пусть и независимых) транзакций. Это в jdbc template такое вряд ли получится (ну хотя я могу предложить в процессе разбора result set вызывать какой-нибудь метод с requires_new транзакцией). Например, объект, созданный во "внутренней" транзакции может получить ссылку на объект, полученный во "внешней" транзакции.
Но я пока не уверен на 100%, что entity manager виноват. Для начала убедитесь, что простые JDBC-транзакции работают. Сделайте два jdbc template с каким нибудь простым запросом. Один — во внешней транзакции, другой — во внутренней. И поиграйтесь с запросами/исключениями. Если они действительно независимы (как транзакции), значит проблема в жизненном цикле entity manager. Иначе у вас не срабатывают аннотации транзакционности и нужно бороться сначала с этим.