Транзакция клиента TransactionRolledbackLocalException прервана при обращении к @Singleton


Примечание: добавление этого вопроса в краткой форме с ответом, потратив гораздо больше времени, чем я хотел бы признать, на поиск причины. Надеюсь, я избавлю кого-нибудь еще от боли.

При делегировании вызова метода EJB с аннотацией @Singleton контейнер создает исключение в виде:

TransactionRolledbackLocalException Client's transaction aborted 

Одноэлементный Боб не имеет доступа к данным происходящий.

ServiceBeanImpl.java

@Stateless
@Local
public class ServiceBean extends BaseBean{ 
  @EJB private CacheService cacheService;

  public FooObj getFooFromCache(int id) {
    FooObj fooObj = (FooObj) cacheService.get(id);
    if (fooObj == null) {
      fooObj = getEntityById(FooObj.class, id);
      cacheService.put(id, fooObj);  //This throws exception
    }
    return cacheService.get(id);
  }
}

CacheServiceImpl.java

@Singleton
@Startup
public class CacheServiceImpl implements CacheService {
    private Cache cache;

    @PostConstruct
    public void init() {
        CacheManager instance = CacheManager.getInstance();
        cache = instance.getCache("cache");
    }

    @PreDestroy
    public void destroy() {
        CacheManager.getInstance().shutdown();
    }

    public Object get(Object id) {
      return cache.get(id);
    }

    public void put(Object id, Object obj) {
      return cache.put(id, obj);
    }
}

Вопрос Почему вызов Одноэлементного компонента, который не имеет доступа к данным, вызывает исключение транзакции?

3 4

3 ответа:

Краткий ответ: проверьте стек кода, который выполнялся ранее (предполагая, что в StackTrace нет подсказок) для исключения, которое захвачено и не распространяется (таким образом, никаких подсказок в StackTrace).

В данном конкретном случае было try/catch вокруг создания именованного запроса. Если этот именованный запрос не удался, в блоке catch выполнялся вторичный запрос. Запросы не требовали транзакции, поэтому резервный запрос выполнялся правильно и возвращал ожидаемые сущности.

Однако...

Это также (я думаю) означает, что транзакция нуждается в откате. Фасоль @Singleton является чем-то вроде красной селедки. Что-то в передаче управления этому Бобу заставило контейнер проверить транзакцию, что, в свою очередь, вызвало исключение в этой точке, но исключение было уместным, так как транзакция больше не действительна.

Мораль рассказа:

  • пройдите свой путь назад к последнему взаимодействию с базой данных, потому что это, вероятно, где твоя проблема.
  • только потому, что последнее взаимодействие с базой данных вернуло данные, не означает, что у него не было проблемы (это было убийцей для меня).

Я столкнулся с подобной проблемой и обнаружил интересную вещь:

Из @Startup @Singleton у меня есть следующие вызовы

@PostConstruct
public void initHardcodedUsers() {
for (User u : getHardcodedUsers()) {
  if (!userRepository.userExists(u.getName())) {
    userRepository.add(u);
  }
 }
}

UserRepository - это EJB @без состояния с EntityManager. В методе userExists() я в основном делаю запрос "findUserByName" с помощью getSingleResult(). Если результат недоступен, то JPA выдает NoResultException. Я ловлю его внутри userExists() и возвращаю false ("пользователь не существует").

Поскольку NoResultException является RuntimeException, откат здесь принудительный, так как я только что узнал!

Самый простой подход для этого состоит в том, чтобы избежать вызова getSingleResult(). Вместо этого я использую getResultList() и setMaxResults(1). Это просто вернет пустой список в случае отсутствия результатов, следовательно, никаких исключений, следовательно, никакого отката tx.

Удачи:)

Крис

Аналогичная проблема с TransactionRolledbackLocalException в GlassFish / Payara Server случилась и со мной. Но это было вызвано ошибкой в GlassFish / Payara, ничто не откатило транзакцию в моем приложении. Перезапуск домена исправил его, как описано здесь: javax.EJB-компонента.TransactionRolledbackLocalException (Glassfish 3 + JPA + EclipseLink)