в одном из наших проектов мы столкнулись с проблемой, когда 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)
Добавил интерфейсы.
1. deleteByValidity
следует пометить как @Transactional
, так как он запускает операцию изменения. 2. Попробуйте @EnableTransactionManagement(proxyTargetClass = true)
, поскольку методы класса, аннотированные @Transactional
, реализуют методы интерфейса, которые сами по себе не аннотированы @Transactional
. Или переместите @Transactional
в объявления методов в интерфейсах CacheA
и CacheB
.
Попробуйте использовать только одну аннотацию 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 работает, как ожидалось.
Можете ли вы добавить код CacheARepository, CacheBRepository и объекты кода, которые используются в этом репозитории. У вас есть «fetchtype.lazy» или каскадное удаление, например «@OneToMany (cascade = CascadeType.ALL, ...)» в объектах в CacheARepository?
Я добавил код. Нет, есть просто ценности. Никаких других сущностей.
Мы обнаружили, что это проблема вызвано автоконфигурацией Spring Boot. Поскольку автоматическая конфигурация уже настраивает управление транзакциями, наша настраиваемая конфигурация @EnableTransactionManagement
нарушила создание экземпляров советников по транзакциям. Удаление @EnableTransactionManagement
из нашей конфигурации решает проблему.
{5h later ...} подтвердите решение в моем случае (более сложная настройка с загрузкой kotlin / spring - но уже отлажена, что некоторые методы сканировались с помощью spring aop utils)
Код
CacheA
,CacheB
?