Comments 16
«Зачем внутри одной транзакции создавать другую» — видел такое при необходимости сохранения информации в БД о попытке совершения транзакции. Т.е. пользователь осуществляет транзакцию, но нужно независимо от результата сохранить в БД информацию о юзере и какую процедуру он запустил. Если не создавать еще одну транзакцию, то при неудачной основной транзакции в лог тоже ничего не запишется.
Хороший пример. Но я бы все-таки рекомендовал подходить к нему вот так. Есть два сервиса: 1) тот который должен выполнить нашу бизнес-логику и 2) логгер, который позволяет независимо от результатов первого сохранять информацию. И есть код который собственно и использует эти сервисы. Именно в этом коде мы и стартуем методы первого сервиса, и, в случае неудачи, по try catch вызывать методы второго.
зачем может понадобиться посередине одной транзакции создавать другуюНе так уж редко это нужно. У нас используется в таких ситуациях:
* Фоновый процесс, который рассчитывает много данных за несколько месяцев. На методе
посчитатьЗаОдинМесяц()
стоит аннотация @Transactional(propagation = Propagation.REQUIRES_NEW)
, и этот метод вызывается в цикле. Если считать несколько месяцев в одной транзакции, то соединение с базой разрывается по таймауту.* Обработчик ошибок 404, 500. Если веб-страница упала с ошибкой 500, и транзакция на странице откатилась, то исключение нужно записать в базу в новой транзакции, чтобы оно сохранилось.
* Сложная валидация объектов. Например, есть у нас в базе 2 объекта, один работает с 8:00 до 12:00, а второй с 12:00 до 16:00. Нужно, чтобы время не пересекалось. Пользователь редактирует второй объект и пытается поставить время с 10:00 до 16:00. При сохранении объекта Hibernate вызывает валидацию, которая пытается выбрать из базы первый объект (чтобы проверить, не пересекается ли время). При этом, перед select-ом второй объект автоматически сохраняется, даже если он неправильный! Валидация возвращает ошибку, но неправильный объект уже сохранён в базу. Чтобы это побороть, сделали валидацию в отдельной транзакции, и тогда второй объект не сохраняется автоматически.
перед select-ом второй объект автоматически сохраняется, даже если он неправильный
А нужно всего лишь сконфигурировать FlushModeType.
Что бы было проще отвечать пронумерую случаи в том порядке, в каком они были у вас приведены.
1) Здесь вот совсем не вижу причин для использования Propagation.REQUIRES_NEW. просто в цикле вызываем метод
2) Этот пример, если я его правильно понял, похож на тот, что выше привел nemavasi. Можно конечно спорить стоит ли вводить сервис агрегатор или просто воспользоваться возможностью Propagation.REQUIRES_NEW, но мое мнение такое. Как я писал выше, есть два сервиса: бизнес логики и логер. Оба над методами имеют
3) Тут я не уверен, что понял пример. Выглядит так, что эти действия в принципе должны были выполняться в одной транзакции.
1) Здесь вот совсем не вижу причин для использования Propagation.REQUIRES_NEW. просто в цикле вызываем метод
посчитатьЗаОдинМесяц()
над котороым стоит Propagation.REQUIRED (ну или никакой propagation вообще не указываем, поскольку это дефолтный). Метод закончился, траннзакция комитнулась. Следующий вызов — новая транзакция.2) Этот пример, если я его правильно понял, похож на тот, что выше привел nemavasi. Можно конечно спорить стоит ли вводить сервис агрегатор или просто воспользоваться возможностью Propagation.REQUIRES_NEW, но мое мнение такое. Как я писал выше, есть два сервиса: бизнес логики и логер. Оба над методами имеют
@Transactional
(с default propagation). И есть третий сервис (агрегатор), нетранзакционный. Он вызывает метод бизнес логики, тот в свою очередь (выполнив своючасть работы) вызывает логер. Если они нормально отрабатывают, то в одной транзакции завершают свою работу. Если они падают (неважно кто виноват, бизнес или логи) то агрегатор по try catch ловит этот случай и уже сам пытается воспользоваться сервисом логов.3) Тут я не уверен, что понял пример. Выглядит так, что эти действия в принципе должны были выполняться в одной транзакции.
упс. промахнулся с веткой. Это был ответ на пост elegorod
Вот еще интересный пример, только с бинами.
Тут все по-настоящему, CGLib обертка не прикидывается, что уважает ООП.
Если бы было иначе, автор потобного кода мог нарваться на двойную инициализацию какого-нибудь дорогостоящего синглтона, но нет, спринг и таких бережет :)
@Configuration
public class Cfg {
@Bean
public A a() {
System.out.println("here");
return new A();
}
@Bean
public B b(A a) {
// каноничная инъекция
return new B(a);
}
@Bean
public C c() {
// this - proxy, this.a() ~ ctx.getBean()
return new C(a());
}
}
Тут все по-настоящему, CGLib обертка не прикидывается, что уважает ООП.
Если бы было иначе, автор потобного кода мог нарваться на двойную инициализацию какого-нибудь дорогостоящего синглтона, но нет, спринг и таких бережет :)
Тут все по-настоящему, CGLib обертка не прикидывается, что уважает ООП.
Или я неправильно понял, что имелось ввиду, или как раз наоборот. С помощью CGLib создается класс наследник и дальше все как положено: наследование, полиморфизм…
Т.е. Спринг на самом деле не порождает бинов класса Cfg. Вместо этого создается класс наследник и уже от этого класса будет создан инстанс. В этом классе переопределяются все методы помеченные аннотацией Bean. После чего любое обращение к такому методу приведет к тому что будяет отрабатывать код а-ля context.getBean(). Обращение же к методам которые мы видим у класса конфигурации будут только тогда, когда действительно необходимо инстанциирование бина (определенного данным методом).
спринг и таких бережет :)
с этим полностью согласен
С помощью CGLib создается класс наследник и дальше все как положено: наследование, полиморфизм…
Все так. И ситуация эта отличается тем, что для аспектов наследник работает специальным образом, что заставляет код реального класса думать, что this — это он и есть. Другими словами, реализуется Proxy прямо как в учебнике. Если бы это было верно для конфигурации, то вызов a() из кода класса Cfg каждый раз печатал бы «here».
Да нет там никаких специальных образов. Есть два разных подхода (несмотря на то, что в обоих случаях используется CGLib).
Когда речь идет об аспектах, инстанс класса бизнес-логики создается в любом случае. Если методы данного класса подпадают под определения среза, то дополнительно создается прокси-объект. Именно этот прокси-объект регестрируется в контейнере в качестве бина. При этом он содержит ссылку на созданный инстанс бизнес-логики.
Если класс помечен аннотацией @Configuration то создается только инстанс класса наследника от него (т.е. никакого прокси нет и впомине).
Когда речь идет об аспектах, инстанс класса бизнес-логики создается в любом случае. Если методы данного класса подпадают под определения среза, то дополнительно создается прокси-объект. Именно этот прокси-объект регестрируется в контейнере в качестве бина. При этом он содержит ссылку на созданный инстанс бизнес-логики.
Если класс помечен аннотацией @Configuration то создается только инстанс класса наследника от него (т.е. никакого прокси нет и впомине).
Со временем еще больше убеждаюсь, что эта магия мне нравится все меньше и меньше! И дело не в том, что я сам дурак и не разобрался, а в том, что даже разобравшись во всем, рано или поздно забудешь о каком-нибудь нюансике и потом придется мучаться с отладкой. Ибо все равно, бежит не тот код, который написан!
Помимо инжектирования компонента в самого себя, еще можно заюзать AopContext и достать оттуда ссылку на проксю.
Что заинжектить бин в самого себя нужно использовать @Lazy
@Lazy
@Autowired
private MyServiceImpl myService;
все равно не понятно: а почему связывание по месту исполнения не дает выполниться аннотации около method2 ?
Sign up to leave a comment.
Spring AOP. Маленький вопросик с собеседования