Распространение транзакции EJB3
-
01-07-2019 - |
Вопрос
У меня есть компонент без состояния, что-то вроде:
@Stateless
public class MyStatelessBean implements MyStatelessLocal, MyStatelessRemote {
@PersistenceContext(unitName="myPC")
private EntityManager mgr;
@TransationAttribute(TransactionAttributeType.SUPPORTED)
public void processObjects(List<Object> objs) {
// this method just processes the data; no need for a transaction
for(Object obj : objs) {
this.process(obj);
}
}
@TransationAttribute(TransactionAttributeType.REQUIRES_NEW)
public void process(Object obj) {
// do some work with obj that must be in the scope of a transaction
this.mgr.merge(obj);
// ...
this.mgr.merge(obj);
// ...
this.mgr.flush();
}
}
Обычно в этом случае клиент вызывает processObjects(...), который фактически не взаимодействует с entity manager.Он делает то, что ему нужно, и вызывает process(...) индивидуально для каждого обрабатываемого объекта.Продолжительность process(...) относительно невелика, но processObjects(...) может занять очень много времени для выполнения всего.Поэтому я не хочу, чтобы он поддерживал открытую транзакцию.Я делай нужны операции отдельного процесса (...) для работы в рамках их собственной транзакции.Это должна быть новая транзакция для каждого вызова.Наконец, я хотел бы оставить открытой возможность для клиента напрямую вызывать process (...).
Я перепробовал несколько различных типов транзакций:никогда, не поддерживается, поддерживается (в processObjects) и требуется, требуется new (в process), но я получаю TransactionRequiredException каждый раз, когда вызывается merge().
Я смог заставить это работать, разделив методы на два разных компонента:
@Stateless
@TransationAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class MyStatelessBean1 implements MyStatelessLocal1, MyStatelessRemote1 {
@EJB
private MyStatelessBean2 myBean2;
public void processObjects(List<Object> objs) {
// this method just processes the data; no need for a transaction
for(Object obj : objs) {
this.myBean2.process(obj);
}
}
}
@Stateless
public class MyStatelessBean2 implements MyStatelessLocal2, MyStatelessRemote2 {
@PersistenceContext(unitName="myPC")
private EntityManager mgr;
@TransationAttribute(TransactionAttributeType.REQUIRES_NEW)
public void process(Object obj) {
// do some work with obj that must be in the scope of a transaction
this.mgr.merge(obj);
// ...
this.mgr.merge(obj);
// ...
this.mgr.flush();
}
}
но мне все еще любопытно, возможно ли выполнить это в одном классе.Мне кажется, что диспетчер транзакций работает только на уровне компонента, даже когда отдельным методам даны более конкретные аннотации.Итак, если я отмечу один метод таким образом, чтобы предотвратить запуск транзакции, вызов других методов в том же экземпляре также не создаст транзакцию, независимо от того, как они отмечены?
Я использую сервер приложений JBoss 4.2.1.GA, но неспецифические ответы приветствуются / предпочтительны.
Решение
Другой способ сделать это - фактически использовать оба метода в одном и том же компоненте - и иметь @EJB
ссылка на саму себя!Что - то в этом роде:
// supposing processObjects defined on MyStatelessRemote1 and process defined on MyStatelessLocal1
@Stateless
@TransationAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class MyStatelessBean1 implements MyStatelessLocal1, MyStatelessRemote1 {
@EJB
private MyStatelessLocal1 myBean2;
public void processObjects(List<Object> objs) {
// this method just processes the data; no need for a transaction
for(Object obj : objs) {
this.myBean2.process(obj);
}
}
@TransationAttribute(TransactionAttributeType.REQUIRES_NEW)
public void process(Object obj) {
// do some work with obj that must be in the scope of a transaction
this.mgr.merge(obj);
// ...
this.mgr.merge(obj);
// ...
this.mgr.flush();
}
}
Таким образом, вы фактически "заставляете" process()
метод, доступ к которому осуществляется через стек прокси-серверов ejb, следовательно, принимая @TransactionAttribute
фактически - и по-прежнему сохраняя только один класс.Фух!
Другие советы
Мэтт, вопрос, который вы задаете, довольно классический, я думаю, что решение для самостоятельной ссылки от Herval / Pascal является аккуратным.Существует более общее решение, здесь не упомянутое.
Это относится к "пользовательским" транзакциям EJB.Поскольку вы находитесь в сеансовом компоненте, вы можете получить транзакцию пользователя из контекста сеанса.Вот как будет выглядеть ваш код с пользовательскими транзакциями:
// supposing processObjects defined on MyStatelessRemote1 and process defined on MyStatelessLocal1
@Stateless
@TransationAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class MyStatelessBean1 implements MyStatelessLocal1, MyStatelessRemote1 {
@Resource
private SessionContext ctx;
@EJB
private MyStatelessLocal1 myBean2;
public void processObjects(List<Object> objs) {
// this method just processes the data; no need for a transaction
for(Object obj : objs) {
this.myBean2.process(obj);
}
}
public void process(Object obj) {
UserTransaction tx = ctx.getUserTransaction();
tx.begin();
// do some work with obj that must be in the scope of a transaction
this.mgr.merge(obj);
// ...
this.mgr.merge(obj);
// ...
this.mgr.flush();
tx.commit();
}
}
Я думаю, дело в том, что каждый компонент завернут в прокси, который управляет поведением транзакций.Когда вы вызываете из одного компонента в другой, вы переходите через прокси-сервер этого компонента, и поведение транзакции может быть изменено прокси-сервером.
Но когда компонент вызывает метод для самого себя с другим атрибутом транзакции, вызов не проходит через прокси, поэтому поведение не меняется.
Мэтт, как бы то ни было, я пришел к точно такому же выводу, что и ты.
Типы TransactionAttributeTypes принимаются во внимание только при пересечении границ компонента.При вызове методов внутри одного и того же компонента TransactionAttributeTypes не оказывают никакого эффекта, независимо от того, какие типы заданы для методов.
Насколько я могу видеть, в спецификации EJB Persistence нет ничего, что указывало бы, каким должно быть поведение при этих обстоятельствах.
Я также испытал это в Jboss.Я также попробую это в Glassfish и сообщу вам о результатах.
На случай, если кто-нибудь однажды наткнется на это:
чтобы избежать циклических зависимостей (например, позволяющих ссылаться на себя) в JBoss, используйте аннотацию 'IgnoreDependency', например:
@Игнорируемая зависимость @EJB Я сам, сам по себе.;
Я еще не пробовал это (я собираюсь), но альтернатива введению ссылки на себя через @EJB
аннотация - это SessionContext.getBusinessObject()
способ.Это был бы еще один способ избежать возможности того, что циклическая ссылка обрушится на вас - хотя, по крайней мере, для инъекции beans без состояния, похоже, работает.
Я работаю над большой системой, в которой используются оба метода (предположительно, разными разработчиками), но я не уверен, какой из них является "правильным" способом сделать это.
Я думаю, это связано с @TransationAttribute(тип TransactionAttributeType.Never) о методе Обрабатываемые объекты.
TransactionAttributeType.Никогда
http://docs.sun.com/app/docs/doc/819-3669/6n5sg7cm3?a=view
Если клиент выполняется в рамках транзакции и вызывает метод enterprise bean, контейнер выдает RemoteException.Если клиент не связан с транзакцией, контейнер не запускает новую транзакцию перед запуском метода.
Я предполагаю, что вы являетесь клиентом этого метода Обрабатываемые объекты из клиентского кода.Поскольку, вероятно, ваш клиент не связан с транзакцией, вызов метода с TransactionAttributeType.Никогда счастлив в первую очередь.Затем вы вызываете процесс способ из Обрабатываемые объекты это несмотря на то, что у TransactionAttributeType.Требуется аннотация не была вызовом метода bean, и политика транзакций не применялась.Когда ты позвонишь слияние вы получаете исключение, потому что вы все еще не связаны с транзакцией.
Попробуйте использовать TransactionAttributeType.Требуется для обоих методов bean, чтобы посмотреть, сработает ли это.
У меня были проблемы с циклической зависимостью, о которых упоминал Кевин.Однако предлагаемая аннотация @IgnoreDependency является специфичной для jboss аннотацией, и в ней нет аналога, например, в Glassfish.
Поскольку это не работает со ссылкой EJB по умолчанию, я чувствовал себя немного неловко из-за этого решения.
Поэтому я дал шанс решению bluecarbon's, запустив таким образом внутреннюю транзакцию "вручную".
Помимо этого, я не вижу другого решения, кроме как реализовать внутренний процесс () в другом компоненте, что также некрасиво, потому что мы просто хотим нарушить нашу модель класса из-за таких технических деталей.