Spring игнорирует аннотацию @Transactional

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

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled. 2018-09-13 15:05:18,406 ERROR [main] org.springframework.boot.SpringApplication Application run failed org.springframework.dao.InvalidDataAccessApiUsageException: No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call; nested exception is javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call at com.my.service.CacheAService.deleteShortTermCache(CacheAService.java:70) ~[classes/:na]

Я нашел похожие вопросы, но ни одно из решений не применимо к этому случаю.

  • @EnableTransactionManagement присутствует
  • Транзакционный класс реализует интерфейс
  • Транзакционный метод общедоступен
  • Транзакционный метод не вызывается внутри

Когда я аннотирую CacheService с помощью @Transactional, все снова работает. Но я пытаюсь понять, почему Spring игнорирует @Transactional на CacheAService.

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

2018-09-13 15:05:18,242 TRACE [main] org.springframework.transaction.interceptor.TransactionInterceptor Don't need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.deleteByValidity]: This method isn't transactional.

Вот упрощенный код. Код вызывается во время запуска приложения Spring ContextRefreshedEvent.

@Service
public class CacheService implements Cache {

    @Autowired
    private CacheA cacheAService;
    @Autowired
    private CacheB cacheBService;

    @Override
    public void clearCache() {
        cacheAService.deleteShortTermCache();
        cacheBService.deleteAll();
    }
}

public interface CacheA {
    void deleteShortTermCache();
}

@Service
@Transactional(readOnly = true)
public class CacheAService implements CacheA {

    @Autowired
    private CacheARepository cacheARepository;

    @Override
    @Transactional
    public void deleteShortTermCache() {
        cacheARepository.deleteByValidity(CacheValidity.SHORT_TERM);
    }
}

public interface CacheB {
    void deleteAll();
}

@Service
@Transactional(readOnly = true)
public class CacheBService implements CacheB {

    @Autowired
    private CacheBRepository cacheBRepository;

    @Override
    @Transactional
    public void deleteAll {
        cacheBRepository.deleteAll();
    }
}

public enum CacheValidity {
    SHORT_TERM,
    LONG_TERM
}

@Repository
public interface CacheARepository extends JpaRepository<CacheItem, Integer> {
    void deleteByValidity(CacheValidity validity);
}

public enum CacheItemKey {
    AVAILABLE,
    FUTURE,
    AVAILABLE_UTM,
    FUTURE_UTM,
    REGION
}

@Entity
@Table(name = "cache_item")
public class CacheItem {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "cache_item_id_seq")
    @SequenceGenerator(name = "cache_item_id_seq", sequenceName = "cache_item_id_seq", allocationSize = 1)
    private Integer id;

    @Column(nullable = false, unique = true)
    @Enumerated(EnumType.STRING)
    private CacheItemKey key;

    @Column(nullable = false)
    private String value;

    @Column(name = "date_modified", nullable = false)
    @Temporal(TemporalType.TIMESTAMP)
    private Date dateModified;

    @Column(nullable = false)
    @Enumerated(EnumType.STRING)
    private CacheValidity validity;

    public Integer getId() {
        return id;
    }

    public void setId(final Integer id) {
        this.id = id;
    }

    public CacheItemKey getKey() {
        return key;
    }

    public void setKey(final CacheItemKey key) {
        this.key = key;
    }

    public String getValue() {
        return value;
    }

    public void setValue(final String value) {
        this.value = value;
    }

    public Date getDateModified() {
        return dateModified;
    }

    public void setDateModified(final Date dateModified) {
        this.dateModified = dateModified;
    }

    public CacheValidity getValidity() {
        return validity;
    }

    public void setValidity(final CacheValidity validity) {
        this.validity = validity;
    }

}

Редактировать: Покопавшись, я нашел это в журналах.

2018-09-14 06:24:11,174 INFO [localhost-startStop-1] org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker Bean 'cacheAService' of type [com.my.service.CacheAService] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

Код CacheA, CacheB?

NiVeR 13.09.2018 15:53

Добавил интерфейсы.

Januson 13.09.2018 16:01

1. deleteByValidity следует пометить как @Transactional, так как он запускает операцию изменения. 2. Попробуйте @EnableTransactionManagement(proxyTargetClass = true), поскольку методы класса, аннотированные @Transactional, реализуют методы интерфейса, которые сами по себе не аннотированы @Transactional. Или переместите @Transactional в объявления методов в интерфейсах CacheA и CacheB.

manish 14.09.2018 05:20
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
3
2 718
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Попробуйте использовать только одну аннотацию Transactional (в классе или методе). Возможно, проблема связана с @Transactional(readOnly = true), поскольку ваша транзакция не readOnly, я не могу точно сказать, какую аннотацию Transactional предпочитает Spring. Попробуй использовать:

@Service
public class CacheAService implements CacheA {

    @Autowired
    private CacheARepository cacheARepository;

    @Override
    @Transactional
    public void deleteShortTermCache() {
        cacheARepository.deleteByValidity(CacheValidity.SHORT_TERM);
    }
}

или

@Service
@Transactional
public class CacheAService implements CacheA {

    @Autowired
    private CacheARepository cacheARepository;

    @Override
    public void deleteShortTermCache() {
        cacheARepository.deleteByValidity(CacheValidity.SHORT_TERM);
    }
} 

Спасибо за ваш ответ. К сожалению, это не работает. Я пробовал оба варианта. Странно то, что не работает только CacheA. Когда я комментирую это, кеш B работает, как ожидалось.

Januson 13.09.2018 16:30

Можете ли вы добавить код CacheARepository, CacheBRepository и объекты кода, которые используются в этом репозитории. У вас есть «fetchtype.lazy» или каскадное удаление, например «@OneToMany (cascade = CascadeType.ALL, ...)» в объектах в CacheARepository?

Slava Vedenin 13.09.2018 16:54

Я добавил код. Нет, есть просто ценности. Никаких других сущностей.

Januson 13.09.2018 17:19
Ответ принят как подходящий

Мы обнаружили, что это проблема вызвано автоконфигурацией Spring Boot. Поскольку автоматическая конфигурация уже настраивает управление транзакциями, наша настраиваемая конфигурация @EnableTransactionManagement нарушила создание экземпляров советников по транзакциям. Удаление @EnableTransactionManagement из нашей конфигурации решает проблему.

{5h later ...} подтвердите решение в моем случае (более сложная настройка с загрузкой kotlin / spring - но уже отлажена, что некоторые методы сканировались с помощью spring aop utils)

wendro 13.03.2019 20:16

Другие вопросы по теме