Распространение транзакций EJB3
У меня есть безстоящий bean что-то вроде:
@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 (...), который фактически не взаимодействует с менеджером сущности. Он выполняет то, что ему нужно, и вызывает процесс (...) индивидуально для каждого обрабатываемого объекта. Продолжительность процесса (...) относительно короткая, но processObjects (...) может занять очень много времени, чтобы пробежать все. Поэтому я не хочу, чтобы он поддерживал открытую транзакцию. Мне нужны отдельные процессы (...) для работы в рамках их собственной транзакции. Это должна быть новая транзакция для каждого вызова. Наконец, я хотел бы оставить опцию открытой для того, чтобы клиент напрямую вызывал процесс (...).
Я пробовал несколько разных типов транзакций: никогда, не поддерживался, поддерживался (на processObjects) и требовался, требует новых (по процессу), но я получаю TransactionRequiredException каждый раз, когда вызывается merge().
Мне удалось заставить его работать, разделив методы на два разных beans:
@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();
}
}
но мне все еще интересно, если это возможно сделать в одном классе. Мне кажется, что диспетчер транзакций работает только на уровне bean, даже если отдельным методам даются более конкретные аннотации. Поэтому, если я помечу одному методу, чтобы предотвратить запуск транзакции другими методами внутри этого же экземпляра, также не будет создана транзакция, независимо от того, как они отмечены?
Я использую JBoss Application Server 4.2.1.GA, но неспецифические ответы приветствуются/предпочтительны.
Ответы
Ответ 1
Другой способ сделать это - фактически использовать оба метода в одном и том же bean - и иметь ссылку @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
и сохраняя только один класс. Уф!
Ответ 2
Мэтт, вопрос, который вы задаете, довольно классический, я считаю, что саморегулярное решение Herval/Pascal является опрятным. Существует более общее решение, не упомянутое здесь.
Это случай для транзакций пользователя "EJB". Поскольку вы находитесь в сеансе bean, вы можете получить транзакцию пользователя из контекста сеанса. Здесь, как ваш код будет выглядеть с пользовательскими транзакциями:
// 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();
}
}
Ответ 3
Я думаю, что каждая вещь bean обернута в прокси-сервер, который контролирует транзакционное поведение. Когда вы вызываете от одного bean к другому, вы проходите через прокси-сервер bean, и поведение транзакции может быть изменено прокси-сервером.
Но когда bean вызывает метод сам по себе с другим атрибутом транзакции, вызов не проходит через прокси-сервер, поэтому поведение не изменяется.
Ответ 4
Мэтт, для чего он стоит, я пришел к тому же выводу, что и вы.
TransactionAttributeTypes учитываются только при пересечении границ Bean. При вызове методов в пределах того же Bean TransactionAttributeTypes эффект не влияет, независимо от того, какие типы помещаются в методы.
Насколько я вижу, в спецификации EJB Persistence нет ничего, что указывало бы, какое поведение должно быть в этих обстоятельствах.
Я тоже испытал это в Jboss. Я также попробую в Glassfish и дам вам знать результаты.
Ответ 5
Если кто-то наткнется на этот день:
чтобы избежать циклических зависимостей (например, для самостоятельной привязки) в JBoss, используйте аннотацию "IgnoreDependency", например:
@IgnoreDependency
@EJB MySelf selfRef;
Ответ 6
Я еще не пробовал (я собираюсь), но альтернативой инъекции самореференции с помощью аннотации @EJB
является метод SessionContext.getBusinessObject()
. Это было бы еще одним способом избежать возможности циркулярной ссылки на вас, - хотя, по крайней мере, для безгосударственной beans инъекции, похоже, работают.
Я работаю над большой системой, в которой используются оба метода (предположительно, разными разработчиками), но я не уверен, что это правильный способ сделать это.
Ответ 7
Я думаю, что это связано с атрибутом @TransationAttribute (TransactionAttributeType.Never) по методу processObjects.
TransactionAttributeType.Never
http://docs.sun.com/app/docs/doc/819-3669/6n5sg7cm3?a=view
Если клиент работает в пределах транзакция и вызывает предприятие bean s, контейнер выбрасывает RemoteException. Если клиент не связанных с транзакцией, контейнер не запускает новый транзакции перед запуском метода.
Я предполагаю, что вы клиентский метод processObjects из кода клиента. Поскольку, вероятно, ваш клиент не связан с транзакцией, вызов метода с TransactionAttributeType.Never радует в первую очередь. Затем вы вызываете метод процесс из processObjects, который, хотя аннотация TransactionAttributeType.Required не была вызовом метода bean, а политика транзакции не соблюдается. Когда вы вызываете merge, вы получаете исключение, потому что вы все еще не связаны с транзакцией.
Попробуйте использовать TransactionAttributeType.Required для обоих методов bean, чтобы проверить, не делает ли это трюк.
Ответ 8
У меня были эти проблемы с круговой зависимостью, о которых говорил Кевин. Тем не менее, предлагаемая аннотация @IgnoreDependency является аннотацией, специфичной для jboss, и, например, Glassfish нет аналогов.
Так как он не работает с ссылкой EJB по умолчанию, мне было немного неудобно с этим решением.
Следовательно, я дал bluecarbon solution шанс, таким образом, начав внутреннюю транзакцию "вручную".
Кроме того, я не вижу решения, кроме как реализовать внутренний процесс() в другом bean, который также является уродливым, потому что мы просто хотим нарушить нашу модель класса для таких технических деталей.