Куда относится аннотация @Transactional?

528

Если вы поместите @Transactionalв DAOклассах и / или их методы или лучше аннотировать классы услуг , которые призывают с помощью объектов DAO? Или имеет смысл аннотировать оба «слоя»?

Томас Эйнваллер
источник

Ответы:

537

Я думаю, что транзакции относятся к уровню сервиса. Это тот, который знает о единицах работы и случаях использования. Это правильный ответ, если у вас есть несколько DAO, введенных в Сервис, которые должны работать вместе в одной транзакции.

duffymo
источник
Я согласен с этим. Иногда это не имеет значения, но иногда вы можете извлечь выгоду из этого, например, сеанс Hibernate охватывает транзакцию while, поэтому все загруженные объекты находятся в кеше 1-го уровня, и вам не нужно повторно присоединять объекты к сеансу снова, а также загружать их с отложенной загрузкой. свойства функционируют без пуха.
dma_k
5
Может ли глобальная транзакция состоять из более чем одного из этих @Transactional (распространение = Propagation.REQUIRED)? Или @Transactional всегда является границей для транзакции? Я не уверен, что я получил это из документации, но немного похоже, что вы можете создавать даже транзакции, которые состоят из методов @Transactional и всего, что выполняется внутри
lisak
1
Да, я думаю, что самый внешний @Transactional - это тот, который становится границей для транзакции, если Propagation.REQUIRED включен.
Даффимо
309

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

Тем не менее, в то же время я также начал добавлять @Transactional(propagation = Propagation.MANDATORY)к своему уровню DAO (и другим уровням, которым не разрешено запускать транзакции, но требуются существующие), потому что намного легче обнаружить ошибки, когда вы забыли начать транзакцию в вызывающей стороне ( например, услуга). Если ваш DAO аннотирован обязательным распространением, вы получите исключение, указывающее, что при вызове метода нет активной транзакции.

У меня также есть интеграционный тест, в котором я проверяю все bean-компоненты (постпроцессор bean-компонентов) на наличие этой аннотации и завершаю работу, если есть @Transactionalаннотация с распространением, отличным от Mandatory, в bean-компоненте, который не принадлежит уровню сервисов. Таким образом я убеждаюсь, что мы не запускаем транзакции не на том слое.

MNP
источник
3
Должно ли это быть сделано на уровне dao (interfaces), в dao impl. слой или оба?
Йохан
3
Я не знаю, вписывается ли это в это обсуждение, но другой совет можно добавить @Transactional (readOnly = true) в dao impl в операциях без записи.
Maxivis
10
@Johan Spring рекомендует размещать аннотации Transaction на классах реализации вместо интерфейсов.
Матиас Г.
Мне очень нравится эта идея, пробовать ее и для моих проектов
Томас Эйнваллер
1
Позвольте мне понять, понимаю ли я ... Вы говорите, что я должен включить @Transactionalкласс реализации Service, и я должен включить реализацию класса @Transactional(propagation = MANDATORY)DAO (хранилище)?
Джон Хенкель
93

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

Например, ваш звонок «сменить пароль». Это состоит из двух операций

  1. Сменить пароль.
  2. Аудит изменения.
  3. Напишите клиенту, что пароль изменился.

Таким образом, в вышеприведенном случае, если проверка завершится неудачно, то также может произойти сбой смены пароля? Если это так, то транзакция должна быть около 1 и 2 (так на уровне обслуживания). Если электронная почта не работает (вероятно, на ней должна быть какая-то защита от сбоев, чтобы она не сработала), то должна ли она отменить изменение пароля и аудит?

Это те вопросы, которые вам нужно задавать при принятии решения о том, где поставить @Transactional.

Майкл Уайлс
источник
41

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

Весной появляется тенденция к разработке на основе доменов (DDD). Spring Roo прекрасно иллюстрирует эту тенденцию. Идея состоит в том, чтобы сделать POJO объекта домена намного богаче, чем на типичных архитектурах Spring (обычно они анемичны ), и, в частности, поместить семантику транзакций и персистентности в сами объекты домена. В случаях, когда все, что требуется, это простые операции CRUD, веб-контроллеры работают непосредственно с объектами POJO домена (в этом контексте они функционируют как объекты), и уровень обслуживания отсутствует. В случаях, когда требуется некоторая координация между объектами домена, у вас может быть обработчик служебного компонента, который@Transactionпо традиции. Вы можете установить распространение транзакции на объектах домена примерно REQUIREDтак, чтобы объекты домена использовали любые существующие транзакции, например транзакции, которые были запущены в компоненте службы.

Технически эта техника использует AspectJ и <context:spring-configured />. Roo использует определения между типами AspectJ, чтобы отделить семантику сущности (транзакции и постоянство) от содержимого объекта домена (в основном, полей и бизнес-методов).

naXa
источник
38

Обычным случаем будет аннотирование на уровне сервисного уровня, но это действительно зависит от ваших требований.

Аннотирование на сервисном уровне приведет к более длительным транзакциям, чем аннотирование на уровне DAO. В зависимости от уровня изоляции транзакции, который может вызвать проблемы, так как параллельные транзакции не будут видеть изменения друг друга, например. ПОВТОРЯЕМЫЙ ЧИТАТЬ.

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

Не имеет смысла аннотировать оба слоя, если режим распространения установлен по умолчанию.

hammarback
источник
31

Я помещаю @Transactionalна @Serviceслой и устанавливаю rollbackForлюбое исключение и readOnlyоптимизирую транзакцию дальше.

По умолчанию @Transactionalбудет выполняться поиск только « RuntimeExceptionНепроверенных исключений», а при установке отката на « Exception.classПроверенные исключения» будет выполняться откат для любого исключения.

@Transactional(readOnly = false, rollbackFor = Exception.class)

См. « Проверенные и непроверенные исключения» .

user2601995
источник
17

Или имеет смысл аннотировать оба «слоя»? - не имеет ли смысла аннотировать и уровень обслуживания, и уровень дао - если кто-то хочет удостовериться, что метод DAO всегда вызывается (распространяется) из уровня обслуживания с распространением "обязательно" в DAO. Это обеспечило бы некоторое ограничение для вызова методов DAO из уровня пользовательского интерфейса (или контроллеров). Кроме того, при модульном тестировании уровня DAO, в частности, аннотирование DAO также гарантирует его тестирование на функциональность транзакций.

tweekran
источник
Не приведет ли это к вложенным транзакциям? И все тонкие проблемы, которые сопровождают это?
HDave
11
Нет, в JPA нет вложенных транзакций. Поместить их в оба было бы прекрасно - если вы уже находитесь в транзакции, когда нажмете DAO, эта транзакция будет продолжена.
fool4jesus
4
Вложенная транзакция может произойти, если вы используете propagation=Propagation.REQUIRES_NEW. В противном случае, в большинстве случаев, включая распространение = обязательное, DAO будет просто участвовать в существующей транзакции, запущенной уровнем обслуживания.
Дэн Картер
15

Для транзакции на уровне базы данных

в основном я использовал @Transactionalв DAO только на уровне метода, поэтому конфигурация может быть специально для метода / с использованием по умолчанию (обязательно)

  1. Метод DAO, который получает выборку данных (выберите ..) - это не нужно, @Transactional это может привести к некоторым издержкам из-за перехватчика транзакции / и прокси-сервера AOP, которые также должны быть выполнены.

  2. Методы DAO, которые делают вставку / обновление, получат @Transactional

очень хороший блог о трансакционном

Для уровня приложения -
я использую транзакционную для бизнес-логики, я хотел бы иметь возможность откат в случае непредвиденной ошибки

@Transactional(rollbackFor={MyApplicationException.class})
public void myMethod(){

    try {    
        //service logic here     
    } catch(Throwable e) {

        log.error(e)
        throw new MyApplicationException(..);
    }
}
lukass77
источник
6
+1 очень хорошая статья о TransactionalвJava
дуб
11

Обычно нужно поместить транзакцию на уровень сервиса.

Но, как было сказано ранее, атомарность операции - это то, что говорит нам, где необходима аннотация. Таким образом, если вы используете фреймворки, такие как Hibernate, где одна операция «сохранить / обновить / удалить / ... модификацию» над объектом может потенциально изменить несколько строк в нескольких таблицах (из-за каскада через граф объектов) Конечно, также должно быть управление транзакциями по этому конкретному методу DAO.

yannick555
источник
10

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

Итак, давайте возьмем пример:

У нас есть 2 модели то есть Countryи City. Реляционное сопоставление Countryи Cityмодель похожи на то, что Countryможно иметь несколько городов, поэтому сопоставление похоже на

@OneToMany(fetch = FetchType.LAZY, mappedBy="country")
private Set<City> cities;

Здесь Страна сопоставлена ​​с несколькими городами с получением их Lazily. Таким образом, здесь возникает роль, @Transactinalкогда мы получаем объект Country из базы данных, тогда мы получим все данные объекта Country, но не получим набор городов, потому что мы выбираем города LAZILY.

//Without @Transactional
public Country getCountry(){
   Country country = countryRepository.getCountry();
   //After getting Country Object connection between countryRepository and database is Closed 
}

Когда мы хотим получить доступ к множеству городов из объекта страны, тогда мы получим нулевые значения в этом наборе, потому что объект набора, созданный только в этом наборе, не инициализируется с этими данными, чтобы получить значения набора, который мы используем, @Transactionalт.е.

//with @Transactional
@Transactional
public Country getCountry(){
   Country country = countryRepository.getCountry();
   //below when we initialize cities using object country so that directly communicate with database and retrieve all cities from database this happens just because of @Transactinal
   Object object = country.getCities().size();   
}

Таким образом, в основном @TransactionalСервис может сделать несколько вызовов в одной транзакции, не закрывая соединение с конечной точкой.

Харшал Патил
источник
2
очень информативно, спасибо! Как раз то, что я искал, объяснение того, что на @Transactionalсамом деле
CybeX
6

Лучше иметь его на уровне сервиса! Это ясно объясняется в одной из статей, с которыми я столкнулся вчера! Вот ссылка, которую вы можете проверить!

sundary
источник
5

введите описание изображения здесь

@TransactionalСледует использовать на уровне услуг , поскольку он содержит бизнес - логику. Уровень DAO обычно имеет только операции CRUD базы данных.

// the service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);
}

Spring doc: https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html

Алинь
источник
5

Сервисный слой - лучшее место для добавления @Transactionalаннотаций, так как большая часть бизнес-логики, представленной здесь, содержит поведение на уровне детализации.

Предположим, что мы добавляем его в DAO, и из службы мы вызываем 2 класса DAO, один из которых был неудачным, а другой - успешным, в этом случае, если @Transactionalне в службе, одна БД выполнит фиксацию, а другая откатится.

Поэтому я рекомендую использовать эту аннотацию с умом и использовать только на уровне сервиса.

Проект Github - Ява-Алгос

VishalTambe
источник
Исключения, такие как ObjectOptimisticLockingFailureException, возникают только после завершения транзакции. Если у вас есть отдельные потоки для других операций, таких как почтовый сервис, такой дизайн полностью провалится. Мы страдаем прямо сейчас. Единственным оставленным решением будет АОП.
Набин Кумар Хативада
4

Прежде всего давайте определимся, где мы должны использовать транзакцию ?

Я думаю, что правильный ответ - когда нам нужно убедиться, что последовательность действий будет завершена вместе как одна атомарная операция, или никакие изменения не будут сделаны, даже если одно из действий не выполнится.

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

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

smaiakov
источник
3

Лучше держать @Transactional на отдельном промежуточном уровне между DAO и Service Layer. Поскольку откат очень важен, вы можете поместить все ваши манипуляции с БД на средний уровень и написать бизнес-логику на уровне обслуживания. Средний слой будет взаимодействовать с вашими слоями DAO.

Это поможет вам во многих ситуациях, таких как ObjectOptimisticLockingFailureException - это исключение возникает только после завершения вашей транзакции. Таким образом, вы не можете поймать его в среднем слое, но теперь вы можете поймать его в слое сервиса. Это было бы невозможно, если у вас есть @Transactional в слое Service. Хотя вы можете поймать контроллер, но контроллер должен быть как можно более чистым.

Если вы отправляете почту или смс в отдельном потоке после выполнения всех параметров сохранения, удаления и обновления, вы можете сделать это в сервисе после завершения транзакции на среднем уровне. Опять же, если вы упомянули @Transactional на уровне сервиса, ваша почта будет отправляться, даже если ваша транзакция не удалась.

Таким образом, наличие среднего слоя @Transaction поможет сделать ваш код лучше и проще в обращении. В противном случае, если вы используете слой DAO, вы не сможете откатить все операции. Если вы используете сервисный уровень, вам, возможно, придется использовать AOP (Aspect Oriented Programming) в некоторых случаях.

Набин Кумар Хативада
источник
2

В идеале сервисный уровень (менеджер) представляет вашу бизнес-логику, и поэтому он должен быть аннотирован с помощью @Transactional.Service layer может вызывать разные DAO для выполнения операций с БД. Предположим, что у вас есть N операций DAO в сервисном методе. Если ваша 1-я операция DAO завершилась неудачно, другие могут все еще быть пропущены, и вы получите несовместимое состояние БД. Слой Annoating Service может спасти вас от таких ситуаций.

salsinga
источник
1

Я предпочитаю использовать @Transactionalна уровне услуг на уровне метода.

Искровые для начинающих
источник
1

@Transactionalиспользует на уровне обслуживания, который вызывается с помощью уровня контроллера ( @Controller) и вызова уровня обслуживания к уровню DAO ( @Repository), то есть операции, связанной с базой данных.

Суббота
источник