Add support for native query for multiple JPA vendors (#2129)
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import jakarta.persistence.Query;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
|
||||
public class Jpa {
|
||||
|
||||
public enum JpaVendor {
|
||||
ECLIPSELINK,
|
||||
HIBERNATE // NOT SUPPORTED!
|
||||
}
|
||||
|
||||
public static final JpaVendor JPA_VENDOR = JpaVendor.ECLIPSELINK;
|
||||
|
||||
public static char NATIVE_QUERY_PARAMETER_PREFIX = switch (JPA_VENDOR) {
|
||||
case ECLIPSELINK -> '?';
|
||||
case HIBERNATE -> ':';
|
||||
};
|
||||
|
||||
public static <T> String formatNativeQueryInClause(final String name, final List<T> list) {
|
||||
return switch (Jpa.JPA_VENDOR) {
|
||||
case ECLIPSELINK -> formatEclipseLinkNativeQueryInClause(IntStream.range(0, list.size()).mapToObj(i -> name + "_" + i).toList());
|
||||
case HIBERNATE -> ":" + name;
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> void setNativeQueryInParameter(final Query deleteQuery, final String name, final List<T> list) {
|
||||
if (Jpa.JPA_VENDOR == Jpa.JpaVendor.ECLIPSELINK) {
|
||||
for (int i = 0, len = list.size(); i < len; i++) {
|
||||
deleteQuery.setParameter(name + "_" + i, list.get(i));
|
||||
}
|
||||
} else if (Jpa.JPA_VENDOR == Jpa.JpaVendor.HIBERNATE) {
|
||||
deleteQuery.setParameter(name, list);
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatEclipseLinkNativeQueryInClause(final Collection<String> elements) {
|
||||
return "?" + String.join(",?", elements);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Contributors to the Eclipse Foundation
|
||||
*
|
||||
* This program and the accompanying materials are made
|
||||
* available under the terms of the Eclipse Public License 2.0
|
||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
public class JpaConstants {
|
||||
|
||||
public enum JpaVendor {
|
||||
ECLIPSELINK,
|
||||
HIBERNATE // NOT SUPPORTED!
|
||||
}
|
||||
|
||||
public static final JpaVendor JPA_VENDOR = JpaVendor.ECLIPSELINK;
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.Query;
|
||||
@@ -53,6 +52,7 @@ import org.eclipse.hawkbit.repository.exception.IncompatibleTargetTypeException;
|
||||
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
|
||||
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
|
||||
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException;
|
||||
import org.eclipse.hawkbit.repository.jpa.Jpa;
|
||||
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
|
||||
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
|
||||
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
||||
@@ -123,9 +123,29 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
*/
|
||||
private static final int ACTION_PAGE_LIMIT = 1000;
|
||||
private static final String QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED_DEFAULT =
|
||||
"DELETE FROM sp_action WHERE tenant=#tenant AND status IN (%s) AND last_modified_at<#last_modified_at LIMIT " + ACTION_PAGE_LIMIT;
|
||||
"DELETE FROM sp_action " +
|
||||
"WHERE tenant=" + Jpa.NATIVE_QUERY_PARAMETER_PREFIX + "tenant" +
|
||||
" AND status IN (%s)" +
|
||||
" AND last_modified_at<" + Jpa.NATIVE_QUERY_PARAMETER_PREFIX + "last_modified_at LIMIT " + ACTION_PAGE_LIMIT;
|
||||
private static final EnumMap<Database, String> QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED;
|
||||
|
||||
static {
|
||||
QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED = new EnumMap<>(Database.class);
|
||||
QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED.put(
|
||||
Database.SQL_SERVER,
|
||||
"DELETE TOP (" + ACTION_PAGE_LIMIT + ") FROM sp_action " +
|
||||
"WHERE tenant=" + Jpa.NATIVE_QUERY_PARAMETER_PREFIX + "tenant" +
|
||||
" AND status IN (%s)" +
|
||||
" AND last_modified_at<" + Jpa.NATIVE_QUERY_PARAMETER_PREFIX + "last_modified_at ");
|
||||
QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED.put(
|
||||
Database.POSTGRESQL,
|
||||
"DELETE FROM sp_action " +
|
||||
"WHERE id IN (SELECT id FROM sp_action " +
|
||||
"WHERE tenant=" + Jpa.NATIVE_QUERY_PARAMETER_PREFIX + "tenant" +
|
||||
" AND status IN (%s)" +
|
||||
" AND last_modified_at<" + Jpa.NATIVE_QUERY_PARAMETER_PREFIX + "last_modified_at LIMIT " + ACTION_PAGE_LIMIT + ")");
|
||||
}
|
||||
|
||||
private final EntityManager entityManager;
|
||||
private final DistributionSetManagement distributionSetManagement;
|
||||
private final TargetRepository targetRepository;
|
||||
@@ -141,18 +161,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
private final Database database;
|
||||
private final RetryTemplate retryTemplate;
|
||||
|
||||
static {
|
||||
QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED = new EnumMap<>(Database.class);
|
||||
QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED.put(
|
||||
Database.SQL_SERVER,
|
||||
"DELETE TOP (" + ACTION_PAGE_LIMIT + ") FROM sp_action " +
|
||||
"WHERE tenant=#tenant AND status IN (%s) AND last_modified_at<#last_modified_at ");
|
||||
QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED.put(
|
||||
Database.POSTGRESQL,
|
||||
"DELETE FROM sp_action " +
|
||||
"WHERE id IN (SELECT id FROM sp_action WHERE tenant=#tenant AND status IN (%s) AND last_modified_at<#last_modified_at LIMIT " + ACTION_PAGE_LIMIT + ")");
|
||||
}
|
||||
|
||||
public JpaDeploymentManagement(
|
||||
final EntityManager entityManager, final ActionRepository actionRepository,
|
||||
final DistributionSetManagement distributionSetManagement, final TargetRepository targetRepository,
|
||||
@@ -500,23 +508,18 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
if (status.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
* We use a native query here because Spring JPA does not support to specify a
|
||||
* LIMIT clause on a DELETE statement. However, for this specific use case
|
||||
* (action cleanup), we must specify a row limit to reduce the overall load on
|
||||
* the database.
|
||||
*/
|
||||
|
||||
final int statusCount = status.size();
|
||||
final Status[] statusArr = status.toArray(new Status[statusCount]);
|
||||
// We use a native query here because Spring JPA does not support to specify a LIMIT clause on a DELETE statement.
|
||||
// However, for this specific use case (action cleanup), we must specify a row limit to reduce the overall load of
|
||||
// the database.
|
||||
final List<Integer> statusList = status.stream().map(Status::ordinal).toList();
|
||||
|
||||
final String queryStr = String.format(getQueryForDeleteActionsByStatusAndLastModifiedBeforeString(database),
|
||||
formatInClauseWithNumberKeys(statusCount));
|
||||
final Query deleteQuery = entityManager.createNativeQuery(queryStr);
|
||||
final Query deleteQuery = entityManager.createNativeQuery(String.format(
|
||||
getQueryForDeleteActionsByStatusAndLastModifiedBeforeString(database),
|
||||
Jpa.formatNativeQueryInClause("status", statusList)));
|
||||
|
||||
IntStream.range(0, statusCount)
|
||||
.forEach(i -> deleteQuery.setParameter(String.valueOf(i), statusArr[i].ordinal()));
|
||||
deleteQuery.setParameter("tenant", tenantAware.getCurrentTenant().toUpperCase());
|
||||
Jpa.setNativeQueryInParameter(deleteQuery, "status", statusList);
|
||||
deleteQuery.setParameter("last_modified_at", lastModified);
|
||||
|
||||
log.debug("Action cleanup: Executing the following (native) query: {}", deleteQuery);
|
||||
@@ -600,14 +603,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
QUERY_DELETE_ACTIONS_BY_STATE_AND_LAST_MODIFIED_DEFAULT);
|
||||
}
|
||||
|
||||
private static String formatInClauseWithNumberKeys(final int count) {
|
||||
return formatInClause(IntStream.range(0, count).mapToObj(String::valueOf).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private static String formatInClause(final Collection<String> elements) {
|
||||
return "#" + String.join(",#", elements);
|
||||
}
|
||||
|
||||
private static RetryTemplate createRetryTemplate() {
|
||||
final RetryTemplate template = new RetryTemplate();
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ public class HawkbitBaseRepository<T, ID extends Serializable> extends SimpleJpa
|
||||
|
||||
private TypedQuery<T> withEntityGraph(final TypedQuery<T> query, final String entityGraph) {
|
||||
final EntityGraph<?> graph = ObjectUtils.isEmpty(entityGraph) ? null : entityManager.createEntityGraph(entityGraph);
|
||||
return graph == null ? query : query.setHint("javax.persistence.loadgraph", graph);
|
||||
return graph == null ? query : query.setHint("jakarta.persistence.loadgraph", graph);
|
||||
}
|
||||
|
||||
private <S extends T> Page<S> readPageWithoutCount(final TypedQuery<S> query, final Pageable pageable) {
|
||||
|
||||
Reference in New Issue
Block a user