Попробовал Spring Data JPA. Столкнулся с следующей проблемой.
После выполнения метода testTransaction, в БД появляется новая запись. Я ожидал, что транзакция будет откачена.
public class TestService {
@Autowired
private JpaRepository<User, String> userRepository;
@Transactional
void testTransaction(){
User user = new User("Tom");
userRepository.save(user);
throw new RuntimeException("Test exception");
}
}
Правильно ли я понимаю, что методы JpaRepository всегда выполняются во вложенной транзакции и результаты выполнения этих методов фиксируются независимо от внешней транзакции?
Здравствуйте, andyag, Вы писали:
A>Здравствуйте, sgp, Вы писали:
sgp>>После выполнения метода testTransaction, в БД появляется новая запись. Я ожидал, что транзакция будет откачена.
A>Рискну предположить, что вы просто что-то недонастроили и транзакции на самом деле нет.
Ну если данные сохранились в базе, то транзакция, наверное, все таки есть.
Однако я вполне допускаю, что что-то недонастроил. Есть идеи куда глянуть?
Здравствуйте, Blazkowicz, Вы писали:
B>Здравствуйте, sgp, Вы писали:
sgp>>Есть идеи куда глянуть? B>В логи и в спринговый Transaction Manager.
Продолжая тему вложенных транзакций появился еще вопрос. Есть 2 фрагмента кода:
// Случай 1
public class TestService {
@Autowired
private JpaRepository<User, String> userRepository;
@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.SERIALIZABLE)
void testTransaction(){
User user = userRepository.findOne("Tom");
user.setAge(20);
userRepository.save(user);
}
}
// Случай 2
public class TestServiceEm {
@PersistenceContext(unitName="default")
EntityManager em;
@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.SERIALIZABLE)
void testTransaction(){
User user = em.find("Tom");
user.setAge(25);
em.merge(user);
}
}
В первом случае открывается транзакция при входе в метод TestService.testTransaction, после чего вызовы методов userRepository выполняется в отдельных вложенных транзакциях.
Во втором случае все вызовы к EntityManager в теле метода TestServiceEm.testTransaction() выполняется в рамках одной транзакции.
Подскажите, пожалуйста, можно ли добиться что бы несколько вызовов JpaRepository в пределах одного метода сервисного класса выполнялись в рамках одной транзакции?
Здравствуйте, sgp, Вы писали:
sgp>Продолжая тему вложенных транзакций появился еще вопрос. Есть 2 фрагмента кода:
Мне сейчас некогда вникать. Но была вот такая вот офигезная статья по теме: http://www.k-press.ru/cs/2009/1/ts/ts.asp
Здравствуйте, Blazkowicz, Вы писали:
B>Здравствуйте, sgp, Вы писали:
sgp>>Продолжая тему вложенных транзакций появился еще вопрос. Есть 2 фрагмента кода: B>Мне сейчас некогда вникать. Но была вот такая вот офигезная статья по теме: B>http://www.k-press.ru/cs/2009/1/ts/ts.asp
Спасибо, статья познавательная, но, к сожалению, не содержащая ответ на вопрос почему в случае с JpaRepository его методы вызываются во вложенных транзакциях.
sgp> @Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.SERIALIZABLE)
Зачем тут SERIALIZABLE?
sgp>после чего вызовы методов userRepository выполняется в отдельных вложенных транзакциях.
Как это было определено? Может проблема в том что внешний транзакции небыло по какой-то причине?
Здравствуйте, Blazkowicz, Вы писали:
B>Здравствуйте, sgp, Вы писали:
sgp>> @Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.SERIALIZABLE) B>Зачем тут SERIALIZABLE?
В данном случае SERIALIZABLE установлена, что бы хорошо было видно отличие в работе первого и второго фрагмента кода.
Для первого фрагмента кода, при исполнении 2 конкурентных транзакций, обе выполнятся успешно, независимо от уровня изоляции транзакций.
Для второго фрагмента кода, при исполнении 2 конкурентных транзакций, одна из транзакций завершится успешно, а вторая завершится исключением, только в случае уровня изоляции SERIALIZABLE. Как раз такое поведение я и ожидаю, как от первого фрагмента так и от второго.
sgp>>после чего вызовы методов userRepository выполняется в отдельных вложенных транзакциях. B>Как это было определено? Может проблема в том что внешний транзакции небыло по какой-то причине?
Путем пошаговой трассировке в отладчике.
Поставил точки останова в TransactionAspectSupport.invokeWithinTransaction
одну перед началом транзакции:
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
вторую на фиксации транзакции
commitTransactionAfterReturning(txInfo);
и пронаблюдал, что в первом случае я попадаю в TransactionAspectSupport.invokeWithinTransaction 1 раз при входе/выходе из testTransaction и по одному разу на каждый из вызовов
— User user = userRepository.findOne("Tom");
— userRepository.save(user);
тогда как во втором случае открытие и закрытие транзакции в методе TransactionAspectSupport.invokeWithinTransaction происходит однократно. Транзакция открывается при вызове метода TestServiceEm.testTransaction и закрывается при возврате из него.
Здравствуйте, sgp, Вы писали:
sgp>одну перед началом транзакции: sgp> TransactionInfo txInfo = createTransaction IfNecessary(tm, txAttr, joinpointIdentification);
Это не обязательно начало новой транзакции.
А как данный метод вызывается? Насколько я помню, если создавать объект напрямую, не через Spring, и работать не через интерфейс, то прокси-объект не создается, и соответственно аннотация не работает.
Здравствуйте, DrDred, Вы писали:
DD>А как данный метод вызывается? Насколько я помню, если создавать объект напрямую, не через Spring, и работать не через интерфейс, то прокси-объект не создается, и соответственно аннотация не работает.
Autowired же как-то работает.
Здравствуйте, Blazkowicz, Вы писали:
B>Здравствуйте, sgp, Вы писали:
sgp>>одну перед началом транзакции: sgp>> TransactionInfo txInfo = createTransaction IfNecessary(tm, txAttr, joinpointIdentification); B>Это не обязательно начало новой транзакции.
В общем случае это не обязательно начало новой транзакции, но в моем случае, начинается именно новая транзакция, атрибуты которой отличаются от указанных мною. Даже если бы я не видел атрибутов, то по поведению, я вижу, что это новая транзакция.
Ведь я ожидаю отлуп для одной из конкурентных транзакций меняющих одну и ту же строку в БД, а получаю в случае с JpaRepository успешное выполнение обоих транзакций, и потерю обновления одной из них.
Здравствуйте, Blazkowicz, Вы писали:
B>Здравствуйте, DrDred, Вы писали:
DD>>А как данный метод вызывается? Насколько я помню, если создавать объект напрямую, не через Spring, и работать не через интерфейс, то прокси-объект не создается, и соответственно аннотация не работает. B>Autowired же как-то работает.
Естественно, экземпляры EntityManager и JpaRepository я получаю от спринга. Ну и попадание в TransactionAspectSupport подтверждает, что прокси создан и работает.
Здравствуйте, sgp, Вы писали:
sgp>В общем случае это не обязательно начало новой транзакции, но в моем случае, начинается именно новая транзакция, атрибуты которой отличаются от указанных мною. Даже если бы я не видел атрибутов, то по поведению, я вижу, что это новая транзакция.
У меня есть сильные сомнения на этот счет.
sgp>Ведь я ожидаю отлуп для одной из конкурентных транзакций меняющих одну и ту же строку в БД, а получаю в случае с JpaRepository успешное выполнение обоих транзакций, и потерю обновления одной из них.
С какой радости должен быть отлуп?
Здравствуйте, Blazkowicz, Вы писали:
B>Здравствуйте, sgp, Вы писали:
sgp>>В общем случае это не обязательно начало новой транзакции, но в моем случае, начинается именно новая транзакция, атрибуты которой отличаются от указанных мною. Даже если бы я не видел атрибутов, то по поведению, я вижу, что это новая транзакция. B>У меня есть сильные сомнения на этот счет.
Как я могу их равеять или подтвердить? У меня идеи кончились, к сожалению.
sgp>>Ведь я ожидаю отлуп для одной из конкурентных транзакций меняющих одну и ту же строку в БД, а получаю в случае с JpaRepository успешное выполнение обоих транзакций, и потерю обновления одной из них. B>С какой радости должен быть отлуп?
Ну как с какой радости?
2 конкурентные транзакции с уровнем изоляции SERIALIZABLE, меняют одно и тоже поле одной и той же записи в БД. Одна должна исполнится успешно, другая должна потерпеть крушение.
Здравствуйте, sgp, Вы писали:
sgp>2 конкурентные транзакции с уровнем изоляции SERIALIZABLE, меняют одно и тоже поле одной и той же записи в БД. Одна должна исполнится успешно, другая должна потерпеть крушение.
Во-первых так ведет себя Optimistic Lock и без всяких Serializable изоляций. А Serializable гарантирует максимальную изоляцию конкуренции вплоть до того что транзакции будут выполнены последовательно. Возможно речь о какой-то конкретной фиче какой-то конкретной БД?
Во-вторых чем именно этот код гарантирует что транзакции конкурентные?
Здравствуйте, Blazkowicz, Вы писали:
B>Здравствуйте, sgp, Вы писали:
sgp>>2 конкурентные транзакции с уровнем изоляции SERIALIZABLE, меняют одно и тоже поле одной и той же записи в БД. Одна должна исполнится успешно, другая должна потерпеть крушение. B>Во-первых так ведет себя Optimistic Lock и без всяких Serializable изоляций. А Serializable гарантирует максимальную изоляцию конкуренции вплоть до того что транзакции будут выполнены последовательно. Возможно речь о какой-то конкретной фиче какой-то конкретной БД? B>Во-вторых чем именно этот код гарантирует что транзакции конкурентные?
Уровень изоляции SERIALIZABLE не допускает одновременное изменение одного поля одной записи двумя конкурирующими транзакциями. Если такое случается, то одна из транзакций должна быть откачена. В моем случае используется Oracle. Абсолютно точно так ведет себя Postgres и Sybase. Похожим образом должны вести себя и другие СУБД.
Что касается уверенности в конкурентности транзакции, то я не вижу причины в этом сомневаться.
Если я ручками открываю 2 сессии к БД и в пошаговом режиме выполняю чтение и обновление записей, то почему я должен сомневаться в том, что транзакции конкурентны?
sgp> Правильно ли я понимаю, что методы JpaRepository всегда выполняются во вложенной транзакции и результаты выполнения этих методов фиксируются независимо от внешней транзакции?
А autocommit у datasource случайно не стоит?
Здравствуйте, ., Вы писали:
.>Здравствуйте, sgp, Вы писали:
sgp>> Правильно ли я понимаю, что методы JpaRepository всегда выполняются во вложенной транзакции и результаты выполнения этих методов фиксируются независимо от внешней транзакции? .>А autocommit у datasource случайно не стоит?
Думаю, нет. Ведь в моих тестах EntityManager и JpaRepository работают с одним источником данных.
Однако обязательно проверю, настройки автокомита.