Small JPA improvements & test code style (#2122)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-12-06 13:03:48 +02:00
committed by GitHub
parent efc6d051f7
commit 2a476d1268
17 changed files with 243 additions and 324 deletions

View File

@@ -553,7 +553,9 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
@Override
public void deleteExistingTarget(@NotEmpty final String controllerId) {
targetRepository.deleteById(targetRepository.getByControllerId(controllerId).getId());
final JpaTarget target = targetRepository.getByControllerId(controllerId);
targetRepository.deleteById(target.getId());
entityManager.flush();
}
@Override

View File

@@ -421,7 +421,9 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
@Override
public Optional<DistributionSet> getWithDetails(final long id) {
return distributionSetRepository.findById(id).map(DistributionSet.class::cast);
return distributionSetRepository
.findOne(distributionSetRepository.byIdSpec(id), JpaDistributionSet_.GRAPH_DISTRIBUTION_SET_DETAIL)
.map(DistributionSet.class::cast);
}
@Override

View File

@@ -128,12 +128,9 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
* @param properties properties to get the underlying database
*/
public JpaSystemManagement(final JpaProperties properties) {
final String isDeleted = isPostgreSql(properties) ? "false" : "0";
countArtifactQuery = "SELECT COUNT(a.id) FROM sp_artifact a INNER JOIN sp_base_software_module sm ON a.software_module = sm.id WHERE sm.deleted = "
+ isDeleted;
countSoftwareModulesQuery = "select SUM(file_size) from sp_artifact a INNER JOIN sp_base_software_module sm ON a.software_module = sm.id WHERE sm.deleted = "
+ isDeleted;
countArtifactQuery = "SELECT COUNT(a.id) FROM sp_artifact a INNER JOIN sp_base_software_module sm ON a.software_module = sm.id WHERE sm.deleted = " + isDeleted;
countSoftwareModulesQuery = "select SUM(file_size) from sp_artifact a INNER JOIN sp_base_software_module sm ON a.software_module = sm.id WHERE sm.deleted = " + isDeleted;
}
@Override

View File

@@ -198,8 +198,8 @@ public class JpaTargetManagement implements TargetManagement {
@Override
public long countByRsqlAndCompatible(final String targetFilterQuery, final Long distributionSetIdTypeId) {
final List<Specification<JpaTarget>> specList = List.of(RSQLUtility.buildRsqlSpecification(targetFilterQuery,
TargetFields.class, virtualPropertyReplacer, database),
final List<Specification<JpaTarget>> specList = List.of(RSQLUtility.buildRsqlSpecification(
targetFilterQuery, TargetFields.class, virtualPropertyReplacer, database),
TargetSpecifications.isCompatibleWithDistributionSetType(distributionSetIdTypeId));
return JpaManagementHelper.countBySpec(targetRepository, specList);
@@ -207,8 +207,8 @@ public class JpaTargetManagement implements TargetManagement {
@Override
public long countByRsqlAndCompatibleAndUpdatable(String targetFilterQuery, Long distributionSetIdTypeId) {
final List<Specification<JpaTarget>> specList = List.of(RSQLUtility.buildRsqlSpecification(targetFilterQuery,
TargetFields.class, virtualPropertyReplacer, database),
final List<Specification<JpaTarget>> specList = List.of(RSQLUtility.buildRsqlSpecification(
targetFilterQuery, TargetFields.class, virtualPropertyReplacer, database),
TargetSpecifications.isCompatibleWithDistributionSetType(distributionSetIdTypeId));
return targetRepository.count(AccessController.Operation.UPDATE, combineWithAnd(specList));
}

View File

@@ -58,6 +58,12 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
@Column(name = "id")
private Long id;
@Version
@Column(name = "optlock_revision")
private int optLockRevision;
// Audit fields. use property access to ensure that setters will be called and checked for modification
// (touch implementation depends on setLastModifiedAt(0).
@Column(name = "created_by", updatable = false, nullable = false, length = USERNAME_FIELD_LENGTH)
private String createdBy;
@Column(name = "created_at", updatable = false, nullable = false)
@@ -67,10 +73,6 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
@Column(name = "last_modified_at", nullable = false)
private long lastModifiedAt;
@Version
@Column(name = "optlock_revision")
private int optLockRevision;
@CreatedBy
public void setCreatedBy(final String createdBy) {
if (isController()) {
@@ -170,14 +172,16 @@ public abstract class AbstractJpaBaseEntity implements BaseEntity {
return false;
}
final AbstractJpaBaseEntity other = (AbstractJpaBaseEntity) obj;
final Long id = getId();
final Long otherId = other.getId();
if (id == null) {
if (other.id != null) {
if (otherId != null) {
return false;
}
} else if (!id.equals(other.id)) {
} else if (!id.equals(otherId)) {
return false;
}
return optLockRevision == other.optLockRevision;
return getOptLockRevision() == other.getOptLockRevision();
}
@Override

View File

@@ -10,6 +10,7 @@
package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import java.util.Objects;
import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass;
@@ -76,11 +77,7 @@ public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEn
return false;
}
final AbstractJpaTenantAwareBaseEntity other = (AbstractJpaTenantAwareBaseEntity) obj;
if (tenant == null) {
return other.tenant == null;
} else {
return tenant.equals(other.tenant);
}
return Objects.equals(getTenant(), other.getTenant());
}
@Override

View File

@@ -12,6 +12,7 @@ package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -36,11 +37,13 @@ import jakarta.persistence.NamedEntityGraph;
import jakarta.persistence.NamedEntityGraphs;
import jakarta.persistence.NamedSubgraph;
import jakarta.persistence.OneToMany;
import jakarta.persistence.PostUpdate;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Setter;
import org.eclipse.hawkbit.repository.MaintenanceScheduleHelper;
import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent;
@@ -96,42 +99,65 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
@Column(name = "active")
private boolean active;
@Column(name = "action_type", nullable = false)
@Convert(converter = ActionTypeConverter.class)
@NotNull
private ActionType actionType;
@Column(name = "forced_time")
private long forcedTime;
@Setter
@Column(name = "weight")
@Min(Action.WEIGHT_MIN)
@Max(Action.WEIGHT_MAX)
private Integer weight;
@Column(name = "status", nullable = false)
@Convert(converter = StatusConverter.class)
@NotNull
private Status status;
@OneToMany(mappedBy = "action", targetEntity = JpaActionStatus.class, fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE })
private List<JpaActionStatus> actionStatus;
private List<JpaActionStatus> actionStatus = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(
name = "rolloutgroup", updatable = false,
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rolloutgroup"))
private JpaRolloutGroup rolloutGroup;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(
name = "rollout", updatable = false,
foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rollout"))
private JpaRollout rollout;
// a cron expression to be used for scheduling.
@Setter
@Column(name = "maintenance_cron_schedule", updatable = false, length = Action.MAINTENANCE_WINDOW_SCHEDULE_LENGTH)
private String maintenanceWindowSchedule;
// the duration of an available maintenance schedule indexes HH:mm:ss format
@Setter
@Column(name = "maintenance_duration", updatable = false, length = Action.MAINTENANCE_WINDOW_DURATION_LENGTH)
private String maintenanceWindowDuration;
// the time zone specified as +/-hh:mm offset from UTC for example +02:00 for CET summer time and +00:00 for UTC. The
// start time of a maintenance window calculated based on the cron expression is relative to this time zone.
@Setter
@Column(name = "maintenance_time_zone", updatable = false, length = Action.MAINTENANCE_WINDOW_TIMEZONE_LENGTH)
private String maintenanceWindowTimeZone;
@Column(name = "external_ref", length = Action.EXTERNAL_REF_MAX_LENGTH)
private String externalRef;
@Setter
@Column(name = "initiated_by", updatable = false, nullable = false, length = USERNAME_FIELD_LENGTH)
private String initiatedBy;
@Setter
@Column(name = "last_action_status_code", nullable = true, updatable = true)
private Integer lastActionStatusCode;
@@ -194,10 +220,6 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
return Optional.ofNullable(weight);
}
public void setWeight(final Integer weight) {
this.weight = weight;
}
@Override
public RolloutGroup getRolloutGroup() {
return rolloutGroup;
@@ -221,47 +243,16 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
return maintenanceWindowSchedule;
}
/**
* Sets the maintenance schedule.
*
* @param maintenanceWindowSchedule is a cron expression to be used for scheduling.
*/
public void setMaintenanceWindowSchedule(final String maintenanceWindowSchedule) {
this.maintenanceWindowSchedule = maintenanceWindowSchedule;
}
@Override
public String getMaintenanceWindowDuration() {
return maintenanceWindowDuration;
}
/**
* Sets the maintenance window duration.
*
* @param maintenanceWindowDuration is the duration of an available maintenance schedule in
* HH:mm:ss format.
*/
public void setMaintenanceWindowDuration(final String maintenanceWindowDuration) {
this.maintenanceWindowDuration = maintenanceWindowDuration;
}
@Override
public String getMaintenanceWindowTimeZone() {
return maintenanceWindowTimeZone;
}
/**
* Sets the time zone to be used for maintenance window.
*
* @param maintenanceWindowTimeZone is the time zone specified as +/-hh:mm offset from UTC for
* example +02:00 for CET summer time and +00:00 for UTC. The
* start time of a maintenance window calculated based on the
* cron expression is relative to this time zone.
*/
public void setMaintenanceWindowTimeZone(final String maintenanceWindowTimeZone) {
this.maintenanceWindowTimeZone = maintenanceWindowTimeZone;
}
@Override
public String getExternalRef() {
return externalRef;
@@ -277,20 +268,15 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
return initiatedBy;
}
public void setInitiatedBy(final String initiatedBy) {
this.initiatedBy = initiatedBy;
}
@Override
public Optional<Integer> getLastActionStatusCode() {
return Optional.ofNullable(lastActionStatusCode);
}
@Override
public Optional<ZonedDateTime> getMaintenanceWindowStartTime() {
return MaintenanceScheduleHelper.getNextMaintenanceWindow(maintenanceWindowSchedule, maintenanceWindowDuration,
maintenanceWindowTimeZone);
return MaintenanceScheduleHelper.getNextMaintenanceWindow(
maintenanceWindowSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone);
}
@Override
@@ -330,24 +316,15 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
return status == Status.WAIT_FOR_CONFIRMATION;
}
public void setLastActionStatusCode(final Integer lastActionStatusCode) {
this.lastActionStatusCode = lastActionStatusCode;
}
public List<ActionStatus> getActionStatus() {
if (actionStatus == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(actionStatus);
}
@Override
public String toString() {
return "JpaAction [distributionSet=" + distributionSet.getId() + ", version=" + getOptLockRevision() + ", id="
+ getId() + ", actionType=" + getActionType() + ", weight=" + getWeight() + ", isActive=" + isActive()
+ ", createdAt=" + getCreatedAt() + ", lastModifiedAt=" + getLastModifiedAt() + ", status="
+ getStatus().name() + "]";
return "JpaAction [distributionSet=" + distributionSet.getId() + ", version=" + getOptLockRevision() + ", id=" + getId() +
", actionType=" + getActionType() + ", weight=" + getWeight() + ", isActive=" + isActive() +
", createdAt=" + getCreatedAt() + ", lastModifiedAt=" + getLastModifiedAt() + ", status=" + getStatus().name() + "]";
}
@Override

View File

@@ -132,7 +132,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
@Setter
@Getter
@ManyToOne(optional = true, fetch = FetchType.LAZY)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "installed_distribution_set", foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_target_inst_ds"))
private JpaDistributionSet installedDistributionSet;

View File

@@ -10,20 +10,18 @@
package org.eclipse.hawkbit.repository.jpa.repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import jakarta.persistence.EntityGraph;
import jakarta.persistence.EntityManager;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaTenantAwareBaseEntity;
import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.CrudRepository;
@@ -43,6 +41,10 @@ public interface BaseEntityRepository<T extends AbstractJpaTenantAwareBaseEntity
extends PagingAndSortingRepository<T, Long>, CrudRepository<T, Long>, JpaSpecificationExecutor<T>,
JpaSpecificationEntityGraphExecutor<T>, NoCountSliceRepository<T>, ACMRepository<T> {
default T getById(final Long id) {
return findOne(byIdSpec(id)).orElseThrow(() -> new EntityNotFoundException(getManagementClass(), id));
}
/**
* Overrides {@link org.springframework.data.repository.CrudRepository#saveAll(Iterable)} to return a list of created entities instead
* of an instance of {@link Iterable} to be able to work with it directly in further code processing instead of converting the
@@ -133,4 +135,26 @@ public interface BaseEntityRepository<T extends AbstractJpaTenantAwareBaseEntity
default Optional<AccessController<T>> getAccessController() {
return Optional.empty();
}
@SuppressWarnings("uchecked")
default Class<? extends BaseEntity> getManagementClass() {
final Class<T> domainClass = getDomainClass();
final String domainClassSimpleName = domainClass.getSimpleName();
if (!domainClassSimpleName.toLowerCase().startsWith("jpa")) {
return domainClass;
}
final String managementClassSimpleName = domainClassSimpleName.substring(3);
final Class<?> superClass = domainClass.getSuperclass();
if (superClass != null) {
if (superClass.getSimpleName().equals(managementClassSimpleName) && BaseEntity.class.isAssignableFrom(superClass)) {
return (Class<? extends BaseEntity>)superClass;
}
}
for (final Class<?> interfaceClass : domainClass.getInterfaces()) {
if (interfaceClass.getSimpleName().equals(managementClassSimpleName) && BaseEntity.class.isAssignableFrom(interfaceClass)) {
return (Class<? extends BaseEntity>)interfaceClass;
}
}
return domainClass;
}
}

View File

@@ -290,9 +290,7 @@ public final class TargetSpecifications {
public static Specification<JpaTarget> hasNotDistributionSetInActions(final Long distributionSetId) {
return (targetRoot, query, cb) -> {
final ListJoin<JpaTarget, JpaAction> actionsJoin = targetRoot.join(JpaTarget_.actions, JoinType.LEFT);
actionsJoin.on(cb.equal(actionsJoin.get(JpaAction_.distributionSet).get(JpaDistributionSet_.id),
distributionSetId));
actionsJoin.on(cb.equal(actionsJoin.get(JpaAction_.distributionSet).get(JpaDistributionSet_.id), distributionSetId));
return cb.isNull(actionsJoin.get(JpaAction_.id));
};
}
@@ -308,10 +306,8 @@ public final class TargetSpecifications {
*/
public static Specification<JpaTarget> isCompatibleWithDistributionSetType(final Long distributionSetTypeId) {
return (targetRoot, query, cb) -> {
// Since the targetRoot is changed by joining we need to get the
// isNull predicate first
// Since the targetRoot is changed by joining we need to get the isNull predicate first
final Predicate targetTypeIsNull = getTargetTypeIsNullPredicate(targetRoot);
return cb.or(targetTypeIsNull, cb.equal(getDsTypeIdPath(targetRoot), distributionSetTypeId));
};
}
@@ -328,15 +324,15 @@ public final class TargetSpecifications {
*/
public static Specification<JpaTarget> notCompatibleWithDistributionSetType(final Long distributionSetTypeId) {
return (targetRoot, query, cb) -> {
// Since the targetRoot is changed by joining we need to get the
// isNotNull predicate first
// Since the targetRoot is changed by joining we need to get the isNotNull predicate first
final Predicate targetTypeNotNull = targetRoot.get(JpaTarget_.targetType).isNotNull();
final Subquery<Long> compatibilitySubQuery = query.subquery(Long.class);
final Root<JpaTarget> subQueryTargetRoot = compatibilitySubQuery.from(JpaTarget.class);
compatibilitySubQuery.select(subQueryTargetRoot.get(JpaTarget_.id))
.where(cb.and(cb.equal(targetRoot.get(JpaTarget_.id), subQueryTargetRoot.get(JpaTarget_.id)),
.where(cb.and(
cb.equal(targetRoot.get(JpaTarget_.id), subQueryTargetRoot.get(JpaTarget_.id)),
cb.equal(getDsTypeIdPath(subQueryTargetRoot), distributionSetTypeId)));
return cb.and(targetTypeNotNull, cb.not(cb.exists(compatibilitySubQuery)));
@@ -599,10 +595,6 @@ public final class TargetSpecifications {
private static Path<Long> getDsTypeIdPath(final Root<JpaTarget> root) {
final Join<JpaTarget, JpaTargetType> targetTypeJoin = root.join(JpaTarget_.targetType, JoinType.LEFT);
targetTypeJoin.fetch(JpaTargetType_.distributionSetTypes);
final SetJoin<JpaTargetType, JpaDistributionSetType> dsTypeTargetTypeJoin = targetTypeJoin
.join(JpaTargetType_.distributionSetTypes, JoinType.LEFT);
return dsTypeTargetTypeJoin.get(JpaDistributionSetType_.id);
return targetTypeJoin.join(JpaTargetType_.distributionSetTypes, JoinType.LEFT).get(JpaDistributionSetType_.id);
}
}