Add cache for SoftwareModule, DistributionSet and Target types (#2784)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-10-28 15:38:59 +02:00
committed by GitHub
parent d488ad6b5f
commit aff871f988
18 changed files with 59 additions and 44 deletions

View File

@@ -15,12 +15,13 @@ import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.event.entity.EntityDeletedEvent;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent;
/**
* Defines the remote event of deleting a {@link DistributionSetType}.
*/
@NoArgsConstructor // for serialization libs like jackson
public class DistributionSetTypeDeletedEvent extends RemoteIdEvent implements EntityDeletedEvent {
public class DistributionSetTypeDeletedEvent extends RemoteIdEvent implements EntityDeletedEvent, CacheEvictEvent {
@Serial
private static final long serialVersionUID = 1L;

View File

@@ -15,12 +15,13 @@ import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.event.entity.EntityDeletedEvent;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent;
/**
* Defines the remote event of deleting a {@link SoftwareModuleType}.
*/
@NoArgsConstructor // for serialization libs like jackson
public class SoftwareModuleTypeDeletedEvent extends RemoteIdEvent implements EntityDeletedEvent {
public class SoftwareModuleTypeDeletedEvent extends RemoteIdEvent implements EntityDeletedEvent, CacheEvictEvent {
@Serial
private static final long serialVersionUID = 1L;

View File

@@ -15,12 +15,13 @@ import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.event.entity.EntityDeletedEvent;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent;
/**
* Defines the remote event of deleting a {@link TargetType}.
*/
@NoArgsConstructor // for serialization libs like jackson
public class TargetTypeDeletedEvent extends RemoteIdEvent implements EntityDeletedEvent {
public class TargetTypeDeletedEvent extends RemoteIdEvent implements EntityDeletedEvent, CacheEvictEvent {
@Serial
private static final long serialVersionUID = 1L;

View File

@@ -14,12 +14,13 @@ import java.io.Serial;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.event.entity.EntityCreatedEvent;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent;
/**
* Defines the remote event of creating a new {@link DistributionSetType}.
*/
@NoArgsConstructor // for serialization libs like jackson
public class DistributionSetTypeCreatedEvent extends RemoteEntityEvent<DistributionSetType> implements EntityCreatedEvent {
public class DistributionSetTypeCreatedEvent extends RemoteEntityEvent<DistributionSetType> implements EntityCreatedEvent, CacheEvictEvent {
@Serial
private static final long serialVersionUID = 1L;

View File

@@ -15,12 +15,13 @@ import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.event.entity.EntityUpdatedEvent;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent;
/**
* Defines the remote event for updating a {@link SoftwareModuleType}.
*/
@NoArgsConstructor // for serialization libs like jackson
public class DistributionSetTypeUpdatedEvent extends RemoteEntityEvent<DistributionSetType> implements EntityUpdatedEvent {
public class DistributionSetTypeUpdatedEvent extends RemoteEntityEvent<DistributionSetType> implements EntityUpdatedEvent, CacheEvictEvent {
@Serial
private static final long serialVersionUID = 1L;

View File

@@ -14,12 +14,13 @@ import java.io.Serial;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.event.entity.EntityUpdatedEvent;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent;
/**
* Defines the remote event for updating a {@link SoftwareModuleType}.
*/
@NoArgsConstructor // for serialization libs like jackson
public class SoftwareModuleTypeUpdatedEvent extends RemoteEntityEvent<SoftwareModuleType> implements EntityUpdatedEvent {
public class SoftwareModuleTypeUpdatedEvent extends RemoteEntityEvent<SoftwareModuleType> implements EntityUpdatedEvent, CacheEvictEvent {
@Serial
private static final long serialVersionUID = 1L;

View File

@@ -14,12 +14,13 @@ import java.io.Serial;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.event.entity.EntityUpdatedEvent;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent;
/**
* Defines the remote event for updating a {@link SoftwareModule}.
*/
@NoArgsConstructor // for serialization libs like jackson
public class SoftwareModuleUpdatedEvent extends RemoteEntityEvent<SoftwareModule> implements EntityUpdatedEvent {
public class SoftwareModuleUpdatedEvent extends RemoteEntityEvent<SoftwareModule> implements EntityUpdatedEvent, CacheEvictEvent {
@Serial
private static final long serialVersionUID = 1L;

View File

@@ -14,12 +14,13 @@ import java.io.Serial;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.event.entity.EntityCreatedEvent;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent;
/**
* Defines the remote event of creating a new {@link TargetType}.
*/
@NoArgsConstructor // for serialization libs like jackson
public class TargetTypeCreatedEvent extends RemoteEntityEvent<TargetType> implements EntityCreatedEvent {
public class TargetTypeCreatedEvent extends RemoteEntityEvent<TargetType> implements EntityCreatedEvent, CacheEvictEvent {
@Serial
private static final long serialVersionUID = 1L;

View File

@@ -14,12 +14,13 @@ import java.io.Serial;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.event.entity.EntityUpdatedEvent;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent;
/**
* Defines the remote event for updating a {@link TargetType}.
*/
@NoArgsConstructor // for serialization libs like jackson
public class TargetTypeUpdatedEvent extends RemoteEntityEvent<TargetType> implements EntityUpdatedEvent {
public class TargetTypeUpdatedEvent extends RemoteEntityEvent<TargetType> implements EntityUpdatedEvent, CacheEvictEvent {
@Serial
private static final long serialVersionUID = 1L;

View File

@@ -134,7 +134,6 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public T create(final C create) {
getCache().ifPresent(cache -> afterCommit(cache::clear));
return jpaRepository.save(AccessController.Operation.CREATE, jpaEntity(create));
}
@@ -142,46 +141,35 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public List<T> create(final Collection<C> create) {
getCache().ifPresent(cache -> afterCommit(cache::clear));
return jpaRepository.saveAll(AccessController.Operation.CREATE, create.stream().map(this::jpaEntity).toList());
}
@Override
@SuppressWarnings("unchecked")
public T get(final long id) {
final Cache cache = getCache().orElse(null);
if (cache == null) {
return jpaRepository.getById(id);
} else {
final T cached = (T) cache.get(id);
if (cached == null) {
final T entity = jpaRepository.getById(id);
if (entity != null) { // should not be null - but throw exception - but for sure check
cache.put(id, entity);
try {
return cache.get(id, () -> jpaRepository.getById(id));
} catch (final Cache.ValueRetrievalException e) {
if (e.getCause() instanceof EntityNotFoundException enf) {
throw enf;
} else {
throw e;
}
return entity;
} else {
return cached;
}
}
}
@Override
@SuppressWarnings("unchecked")
public Optional<T> find(final long id) {
final Cache cache = getCache().orElse(null);
if (cache == null) {
return jpaRepository.findById(id);
} else {
final T cached = (T) cache.get(id);
if (cached == null) {
final Optional<T> entity = jpaRepository.findById(id);
// should not be null - but throw exception - but for sure check
entity.ifPresent(t -> cache.put(id, t));
return entity;
} else {
return Optional.of(cached);
}
// we cache only value - not optionals
return Optional.ofNullable(cache.get(id, () -> jpaRepository.findById(id).orElse(null)));
}
}
@@ -226,7 +214,6 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
@SuppressWarnings("java:S1066") // javaS1066 - better readable that way
public T update(final U update) {
getCache().ifPresent(cache -> afterCommit(() -> cache.evict(update.getId())));
final T entity = getValid(update.getId());
// update getId has no setter in target JPA entity but shall have getter and the value shall be the same
// otherwise the Utils will throw an exception that there is no counterpart setter for getId
@@ -264,7 +251,6 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
if (toSave.isEmpty()) {
return toUpdate;
} else {
getCache().ifPresent(cache -> afterCommit(() -> toSave.forEach(updated -> cache.evict(updated.getId()))));
final List<T> savedEntities = jpaRepository.saveAll(toSave);
final Map<Long, T> result = new HashMap<>(toUpdate);
for (final T savedEntity : savedEntities) {
@@ -278,7 +264,6 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public void delete(final long id) {
getCache().ifPresent(cache -> afterCommit(() -> cache.evict(id)));
delete0(List.of(id));
}
@@ -286,7 +271,6 @@ abstract class AbstractJpaRepositoryManagement<T extends AbstractJpaBaseEntity,
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = TX_RT_MAX, backoff = @Backoff(delay = TX_RT_DELAY))
public void delete(final Collection<Long> ids) {
getCache().ifPresent(cache -> afterCommit(() -> ids.forEach(cache::evict)));
delete0(ids);
}

View File

@@ -36,7 +36,9 @@ import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetTypeSpec
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.cache.Cache;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
@@ -89,6 +91,11 @@ public class JpaDistributionSetTypeManagement
super.delete0(ids);
}
@Override
protected Optional<Cache> getCache() {
return Optional.of(TenantAwareCacheManager.getInstance().getCache(JpaDistributionSetType.class.getSimpleName()));
}
@Override
public Optional<JpaDistributionSetType> findByKey(final String key) {
return jpaRepository.findOne(DistributionSetTypeSpecification.byKey(key));

View File

@@ -20,7 +20,9 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTypeRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.cache.Cache;
import org.springframework.stereotype.Service;
@Service
@@ -42,6 +44,11 @@ public class JpaSoftwareModuleTypeManagement
this.softwareModuleRepository = softwareModuleRepository;
}
@Override
protected Optional<Cache> getCache() {
return Optional.of(TenantAwareCacheManager.getInstance().getCache(JpaSoftwareModuleType.class.getSimpleName()));
}
@Override
public Optional<JpaSoftwareModuleType> findByKey(final String key) {
return jpaRepository.findByKey(key);

View File

@@ -31,7 +31,9 @@ import org.eclipse.hawkbit.repository.jpa.specifications.TargetTypeSpecification
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.retry.annotation.Backoff;
@@ -78,6 +80,12 @@ public class JpaTargetTypeManagement
super.delete0(ids);
}
@Override
protected Optional<Cache> getCache() {
return Optional.of(TenantAwareCacheManager.getInstance().getCache(JpaTargetType.class.getSimpleName()));
}
@Override
@Cacheable(value = CACHE_TARGET_TYPE_NAME, key = "#key")
public Optional<TargetType> findByKey(final String key) {

View File

@@ -36,7 +36,6 @@ import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.NamedVersionedEntity;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.test.matcher.Expect;
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
import org.junit.jupiter.api.Test;
@@ -72,7 +71,8 @@ class DistributionSetTypeManagementTest extends AbstractRepositoryManagementTest
// remove OS
distributionSetTypeManagement.unassignSoftwareModuleType(updatableType.getId(), osType.getId());
assertThat(distributionSetTypeManagement.findByKey(USED_BY_DS_TYPE_KEY).orElseThrow().getMandatoryModuleTypes()).containsOnly(runtimeType);
assertThat(distributionSetTypeManagement.findByKey(USED_BY_DS_TYPE_KEY).orElseThrow().getMandatoryModuleTypes()).containsOnly(
runtimeType);
}
/**

View File

@@ -12,16 +12,12 @@ package org.eclipse.hawkbit.repository.jpa.management;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.util.List;
import jakarta.validation.ConstraintViolationException;
import org.assertj.core.api.Assertions;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement.Create;
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement.Update;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.junit.jupiter.api.Test;

View File

@@ -74,7 +74,7 @@ class TargetTypeManagementTest extends AbstractRepositoryManagementTest<TargetTy
@ExpectEvents({ @Expect(type = TargetTypeUpdatedEvent.class) })
void failIfReferNotExistingEntity() {
verifyThrownExceptionBy(() -> targetTypeManagement.delete(NOT_EXIST_IDL), "TargetType");
verifyThrownExceptionBy(() -> targetTypeManagement.update(Update.builder().id(NOT_EXIST_IDL).build()),"TargetType");
verifyThrownExceptionBy(() -> targetTypeManagement.update(Update.builder().id(NOT_EXIST_IDL).build()), "TargetType");
}
/**

View File

@@ -27,13 +27,11 @@ import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.T
import org.junit.jupiter.api.Test;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.test.context.TestPropertySource;
/**
* Feature: Component Tests - Repository<br/>
* Story: Tenant Configuration Management
*/
@TestPropertySource(properties = {"hawkbit.cache.JpaTenantConfiguration.spec=maximumSize=0"}) // disable cache, its async processed
class TenantConfigurationManagementTest extends AbstractJpaIntegrationTest implements EnvironmentAware {
private Environment env;

View File

@@ -23,7 +23,13 @@ spring.jpa.properties.eclipselink.logging.parameters=true
# Enforce persistence of target polls for test predictability.
hawkbit.server.repository.eagerPollPersistence=true
# disables Rollout and RoulloutGroup status cache for tests
# disables tenant config cache for tests
hawkbit.cache.JpaTenantConfiguration.spec=maximumSize=0
# disables type cache for tests
hawkbit.cache.JpaSoftwareModuleType.spec=maximumSize=0
hawkbit.cache.JpaDistributionSetType.spec=maximumSize=0
hawkbit.cache.JpaTargetType.spec=maximumSize=0
# disables Rollout and RolloutGroup status cache for tests
hawkbit.cache.RolloutStatus.spec=maximumSize=0
hawkbit.cache.RolloutGroupStatus.spec=maximumSize=0