Как контейнер Java EE контролирует транзакции?
У меня есть вопрос о том, как транзакции контролируются контейнером EE. Это псевдокод, чтобы дать некоторый контекст моему вопросу. Это не то, как я кодирую, поэтому, пожалуйста, оставайтесь на вопросе и не превращайте тему в другие вещи.
Рассмотрим следующие две службы и связанный контроллер. Обе службы внедрили EntityManager, и у обоих есть методы, которые должны выполняться внутри транзакции. Один сервис имеет метод, который не нуждается в поддержке транзакций.
@Stateless
class UserService {
@PersistenceContext private EntityManager em;
public void saveUser(User user) {
em.merge(user);
}
public String getFullName(User user) {
return user.getFirstName() + " " + user.getLastName();
}
}
@Stateless
class LogService {
@PersistenceContext private EntityManager em;
public void logEvent(String eventText) {
Event event=new Event();
event.setText(eventText);
event.setTime(new Date());
em.persist(event);
}
}
@Named
class UserController {
User user;
@Inject UserService userService;
@Inject LogService logService;
public void updateUser(user) { // button posts to this method
String fullName=userService.getFullName(user); // 1
if(fullName.startsWith("X")) return; // 2
userService.saveUser(user); // 3
logService.logEvent("Saved user " + fullName); // 4
}
}
Теперь представьте, что есть кнопка, которая отправляет форму в userController.updateUser.
Я предполагаю, что UserController.updateUser()
выполнит userService.saveUser(user);
а также logService.logEvent("Saved user " + fullName);
в рамках той же транзакции. Так что если вызов logService.logEvent()
Сбой с исключением SQL, пользовательский объект не будет обновлен. Кроме того, я предполагаю, что призыв к userService.getFullName(user)
не выполняется внутри какой-либо транзакции, и если мы преждевременно выйдем из метода, когда имя пользователя начинается с X, транзакция не будет создана. Но ясно, что это всего лишь догадки.
Может кто-нибудь объяснить, что будет делать контейнер Java EE для поддержки UserController.updateUser()
метод с транзакцией и что на самом деле запускает транзакцию? Кроме того, любое дальнейшее чтение, на которое вы можете указать мне, будет высоко ценится. Я видел некоторые материалы в Интернете, но все же я что-то упустил здесь и не получил никакой помощи, спрашивая вокруг на работе, либо. Так что я, конечно, не единственный, у кого есть пробел в этом.
3 ответа
В вашем случае будут запущены 3 независимых транзакции. Каждый по одному из ваших @Stateless
бобы методы. Это потому, что сессионные EJB-компоненты имеют транзакционные методы с типом транзакции TransactionAttribute.REQUIRED
по умолчанию. Это означает, что если транзакция еще не запущена, новая будет создана до вызова метода.
Чтобы запустить все ваши сессионные EJB-методы в одной транзакции, вы должны заключить их в одну транзакцию. В вашем случае вы можете сделать это, комментируя updateUser(...)
метод с @Transactional
Вы должны изменить свой @Inject
аннотации к @EJB
тогда по умолчанию с CMT (транзакции, управляемые контейнером) каждый вызов bean-компонента CDI будет находиться в своей собственной области TX. Если вы не хотите, чтобы один из вызовов метода вызывал TX, добавьте в метод @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED).
По внешнему виду вашей бизнес-логики вы могли бы действительно упростить вещи, изменив @Named
в @Stateless
, менять @Inject
в @EJB
и добавить @TransactionalAttribute(TransactionAttributeType.NOT_SUPPORTED)
в getFullName(..), если вы не хотите, чтобы этот метод выполнялся в TX. С этими изменениями вы получите поведение TX, которое вы ищете.
Если вы хотите, чтобы UserController был управляемым компонентом JSF, то наряду с другими модами, которые я предложил, я бы изменил @Named
в @ManagedBean
и просто добавить @Stateless
под @ManagedBean
вместо смены @Named
в @Stateless
,
Для тех, у кого эта проблема еще не возникла, принятый ответ ( Flying Dumpling) неверен.
На самом деле происходит следующее. Контейнер имеет TransactionAttributeType = REQUIRED по умолчанию. Это означает, что если вы не аннотируете какие-либо компоненты, они всегда будут ОБЯЗАТЕЛЬНЫМИ.
Теперь вот что происходит:
Вы вызываете метод UserController.updateUser(), и когда вы это делаете, будет создана транзакция (по умолчанию, если нет аннотаций, указывающих на обратное, контейнер создает транзакцию каждый раз, когда выполняется метод, и завершает ее, как только выполнение завершается).
Когда вы вызываете userService.getFullName(user), поскольку этот метод ТРЕБУЕТСЯ, произойдет то, что та же транзакция, начатая изначально, когда вы впервые вызвали UserController.updateUser(), будет использоваться здесь снова. Затем контейнер возвращается к первому bean-компоненту и вызывает другой метод, userService.saveUser(user), снова, поскольку тип транзакции ТРЕБУЕТСЯ, тогда будет использоваться та же транзакция. И когда он возвращается и вызывает третий метод, logService.logEvent("Сохраненный пользователь" + fullName), использует тот же самый.
В этом случае, если вы хотите, чтобы каждая операция выполнялась в отдельной транзакции, чтобы избежать отката всего в случае сбоя одной из них, вы можете использовать REQUIRES_NEW в каждом методе, который взаимодействует с БД. Таким образом вы гарантируете, что каждый раз, когда вы запускаете метод, создается новая транзакция, и нет никакого вреда, если одна из них выйдет из строя, а вы захотите продолжить работу с другими.
Более подробную информацию можно найти здесь: https://docs.oracle.com/javaee/5/tutorial/doc/bncij.html
Транзакции в Java EE должны явно контролироваться либо с использованием UserTransaction
из JNDI или с использованием дескриптора / аннотаций развертывания в EJB. Для компонента CDI здесь UserController
по умолчанию транзакция не запускается. (ТранзакцииEDIT для методов EJB включены по умолчанию, если ничего не указано.)
Итак, для начала ваше предположение:
UserController.updateUser()
выполнитuserService.saveUser(user);
а такжеlogService.logEvent("Saved user " + fullName)
; в рамках той же транзакции
неправильно! Я верю, что новая транзакция будет создана и передана каждому em.persist()
/em.merge()
вызов.
Для того, чтобы обернуть звонки saveUser()
а также logEvent()
в той же транзакции вы можете использовать вручную UserTransaction
:
public void updateUser(user) {
InitialContext ic = new InitialContext();
UserTransaction utx = (UserTransaction) ic.lookup("java:comp/UserTransaction");
utx.begin();
...
utx.commit(); // or rollback(), wrap them in try...finally
}
Или дружелюбнее:
@Resource UserTransaction utx;
...
public void updateUser(user) {
utx.begin();
...
utx.commit(); // or rollback(), wrap them in try...finally
}
Или даже лучше @Transactional
аннотации либо в Java EE 7, либо в Java EE 6 с расширением DeltaSpike JPA (или любым другим подобным перехватчиком).
@Transactional
public void updateUser(user) {
...
}
Вы можете указать, что методы EJB являются транзакционными, используя javax.ejb.TransactionAttribute
аннотаций. В этом случае все равно будет 2 транзакции. В качестве альтернативы вы можете переместить "бизнес" логику с веб-уровня на уровень EJB с помощью метода, помеченного как @TransactionAttribute
и добиться выполнения методов БД в одной транзакции.
Что касается дальнейшего чтения, ознакомьтесь с главой "Поддержка транзакций" в спецификации EJB 3.