Unify Hibernate and EclipseLink exception translations (#2388)
* Unify Hibernate and EclipseLink exception translations Signed-off-by: strailov <Stanislav.Trailov@bosch.io> * properly set custom hibernate jpa dialect Signed-off-by: strailov <Stanislav.Trailov@bosch.io> --------- Signed-off-by: strailov <Stanislav.Trailov@bosch.io>
This commit is contained in:
committed by
GitHub
parent
af19861de7
commit
7456e52095
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 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.utils;
|
||||||
|
|
||||||
|
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
|
||||||
|
import org.springframework.jdbc.support.SQLErrorCodes;
|
||||||
|
import org.springframework.jdbc.support.SQLExceptionTranslator;
|
||||||
|
import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single point of exception translators in hawkBit
|
||||||
|
* in order to be used in Hibernate and EclipseLink implementation
|
||||||
|
* and unify jpa exception translations behaviour in the project
|
||||||
|
*/
|
||||||
|
public class JpaExceptionTranslator {
|
||||||
|
|
||||||
|
private static final SQLErrorCodeSQLExceptionTranslator SQL_EXCEPTION_TRANSLATOR;
|
||||||
|
|
||||||
|
// providing list/set of codes which are not handled from the sql translator properly
|
||||||
|
private static final String[] DATA_INTEGRITY_VIOLATION_CODES = new String[] {
|
||||||
|
"1366"
|
||||||
|
};
|
||||||
|
private static final String[] DUPLICATE_KEY_VIOLATION_CODES = new String[] {
|
||||||
|
"1062"
|
||||||
|
};
|
||||||
|
|
||||||
|
static {
|
||||||
|
SQL_EXCEPTION_TRANSLATOR = new SQLErrorCodeSQLExceptionTranslator();
|
||||||
|
SQLErrorCodes codes = new SQLErrorCodes();
|
||||||
|
|
||||||
|
codes.setDataIntegrityViolationCodes(DATA_INTEGRITY_VIOLATION_CODES);
|
||||||
|
codes.setDuplicateKeyCodes(DUPLICATE_KEY_VIOLATION_CODES);
|
||||||
|
SQL_EXCEPTION_TRANSLATOR.setSqlErrorCodes(codes);
|
||||||
|
// explicitly set old translator as a fallback (uses Subclass translator by default)
|
||||||
|
SQL_EXCEPTION_TRANSLATOR.setFallbackTranslator(new SQLStateSQLExceptionTranslator());
|
||||||
|
}
|
||||||
|
|
||||||
|
private JpaExceptionTranslator() {}
|
||||||
|
|
||||||
|
public static SQLExceptionTranslator getTranslator() {
|
||||||
|
return SQL_EXCEPTION_TRANSLATOR;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,9 +14,8 @@ import java.sql.SQLException;
|
|||||||
|
|
||||||
import jakarta.persistence.PersistenceException;
|
import jakarta.persistence.PersistenceException;
|
||||||
|
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.utils.JpaExceptionTranslator;
|
||||||
import org.springframework.dao.DataAccessException;
|
import org.springframework.dao.DataAccessException;
|
||||||
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
|
|
||||||
import org.springframework.jdbc.support.SQLErrorCodes;
|
|
||||||
import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator;
|
import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.orm.jpa.JpaSystemException;
|
import org.springframework.orm.jpa.JpaSystemException;
|
||||||
@@ -56,23 +55,6 @@ class HawkbitEclipseLinkJpaDialect extends EclipseLinkJpaDialect {
|
|||||||
@Serial
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private static final SQLErrorCodeSQLExceptionTranslator SQL_EXCEPTION_TRANSLATOR;
|
|
||||||
|
|
||||||
// providing list/set of codes which are not handled from the sql translator properly
|
|
||||||
private static final String[] DATA_INTEGRITY_VIOLATION_CODES = new String[] {
|
|
||||||
"1366"
|
|
||||||
};
|
|
||||||
|
|
||||||
static {
|
|
||||||
SQL_EXCEPTION_TRANSLATOR = new SQLErrorCodeSQLExceptionTranslator();
|
|
||||||
SQLErrorCodes codes = new SQLErrorCodes();
|
|
||||||
|
|
||||||
codes.setDataIntegrityViolationCodes(DATA_INTEGRITY_VIOLATION_CODES);
|
|
||||||
SQL_EXCEPTION_TRANSLATOR.setSqlErrorCodes(codes);
|
|
||||||
// explicitly set old translator as a fallback (uses Subclass translator by default)
|
|
||||||
SQL_EXCEPTION_TRANSLATOR.setFallbackTranslator(new SQLStateSQLExceptionTranslator());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DataAccessException translateExceptionIfPossible(@NonNull final RuntimeException ex) {
|
public DataAccessException translateExceptionIfPossible(@NonNull final RuntimeException ex) {
|
||||||
final DataAccessException dataAccessException = super.translateExceptionIfPossible(ex);
|
final DataAccessException dataAccessException = super.translateExceptionIfPossible(ex);
|
||||||
@@ -102,7 +84,7 @@ class HawkbitEclipseLinkJpaDialect extends EclipseLinkJpaDialect {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return SQL_EXCEPTION_TRANSLATOR.translate("", null, sqlException);
|
return JpaExceptionTranslator.getTranslator().translate("", null, sqlException);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SQLException findSqlException(final RuntimeException jpaSystemException) {
|
private static SQLException findSqlException(final RuntimeException jpaSystemException) {
|
||||||
|
|||||||
@@ -10,9 +10,12 @@
|
|||||||
package org.eclipse.hawkbit.repository.jpa;
|
package org.eclipse.hawkbit.repository.jpa;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.EntityPropertyChangeListener;
|
import org.eclipse.hawkbit.repository.jpa.model.EntityPropertyChangeListener;
|
||||||
|
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.utils.JpaExceptionTranslator;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
import org.eclipse.hawkbit.tenancy.TenantAware;
|
||||||
import org.hibernate.boot.Metadata;
|
import org.hibernate.boot.Metadata;
|
||||||
import org.hibernate.boot.spi.BootstrapContext;
|
import org.hibernate.boot.spi.BootstrapContext;
|
||||||
@@ -24,23 +27,33 @@ import org.hibernate.event.spi.EventType;
|
|||||||
import org.hibernate.integrator.spi.Integrator;
|
import org.hibernate.integrator.spi.Integrator;
|
||||||
import org.hibernate.jpa.boot.spi.IntegratorProvider;
|
import org.hibernate.jpa.boot.spi.IntegratorProvider;
|
||||||
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
|
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
|
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
|
||||||
|
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
|
||||||
|
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
|
||||||
|
import org.springframework.transaction.jta.JtaTransactionManager;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General Hibernate configuration for hawkBit's Repository.
|
* General Hibernate configuration for hawkBit's Repository.
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
public class JpaConfiguration {
|
public class JpaConfiguration extends JpaBaseConfiguration {
|
||||||
|
|
||||||
private final TenantIdentifier tenantIdentifier;
|
private final TenantIdentifier tenantIdentifier;
|
||||||
private final boolean enableLazyLoadNoTrans;
|
private final boolean enableLazyLoadNoTrans;
|
||||||
|
|
||||||
protected JpaConfiguration(
|
protected JpaConfiguration(
|
||||||
|
final DataSource dataSource, final JpaProperties properties,
|
||||||
|
final ObjectProvider<JtaTransactionManager> jtaTransactionManagerProvider,
|
||||||
final TenantAware.TenantResolver tenantResolver,
|
final TenantAware.TenantResolver tenantResolver,
|
||||||
@Value("${hibernate.enable-lazy-load-no-trans:true}") final boolean enableLazyLoadNoTrans) {
|
@Value("${hibernate.enable-lazy-load-no-trans:true}") final boolean enableLazyLoadNoTrans) {
|
||||||
|
super(dataSource, properties, jtaTransactionManagerProvider);
|
||||||
tenantIdentifier = new TenantIdentifier(tenantResolver);
|
tenantIdentifier = new TenantIdentifier(tenantResolver);
|
||||||
this.enableLazyLoadNoTrans = enableLazyLoadNoTrans;
|
this.enableLazyLoadNoTrans = enableLazyLoadNoTrans;
|
||||||
}
|
}
|
||||||
@@ -50,33 +63,53 @@ public class JpaConfiguration {
|
|||||||
return tenantIdentifier;
|
return tenantIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Override
|
||||||
HibernatePropertiesCustomizer hibernatePropertiesCustomizers() {
|
protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
|
||||||
return hibernateProperties -> {
|
|
||||||
// override the default naming strategy
|
|
||||||
hibernateProperties.put("hibernate.physical_naming_strategy", org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl.class.getName());
|
|
||||||
hibernateProperties.put(MultiTenancySettings.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantIdentifier);
|
|
||||||
hibernateProperties.put("hibernate.multiTenancy", "DISCRIMINATOR");
|
|
||||||
// LAZY_LOAD - Enable lazy loading of lazy fields when session is closed - N + 1 problem occur.
|
|
||||||
// So it would be good if in future hawkBit run without that
|
|
||||||
// Otherwise, if false, call for the lazy field will throw LazyInitializationException
|
|
||||||
hibernateProperties.put("hibernate.enable_lazy_load_no_trans", enableLazyLoadNoTrans);
|
|
||||||
hibernateProperties.put("hibernate.integrator_provider", (IntegratorProvider) () -> Collections.singletonList(new Integrator() {
|
|
||||||
|
|
||||||
@Override
|
return new HibernateJpaVendorAdapter() {
|
||||||
public void integrate(
|
private final HibernateJpaDialect hibernateJpaDialect = new CustomHibernateJpaDialect();
|
||||||
final Metadata metadata, final BootstrapContext bootstrapContext,
|
|
||||||
final SessionFactoryImplementor sessionFactory) {
|
|
||||||
sessionFactory.getServiceRegistry()
|
|
||||||
.getService(EventListenerRegistry.class)
|
|
||||||
.appendListeners(EventType.POST_UPDATE, new EntityPropertyChangeListener());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disintegrate(final SessionFactoryImplementor sessionFactory, final SessionFactoryServiceRegistry serviceRegistry) {
|
public HibernateJpaDialect getJpaDialect() {
|
||||||
// do nothing
|
return hibernateJpaDialect;
|
||||||
}
|
}
|
||||||
}));
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Map<String, Object> getVendorProperties(DataSource dataSource) {
|
||||||
|
Map<String, Object> hibernateProperties = new HashMap<>();
|
||||||
|
hibernateProperties.put("hibernate.physical_naming_strategy", org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl.class.getName());
|
||||||
|
hibernateProperties.put(MultiTenancySettings.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantIdentifier);
|
||||||
|
hibernateProperties.put("hibernate.multiTenancy", "DISCRIMINATOR");
|
||||||
|
// LAZY_LOAD - Enable lazy loading of lazy fields when session is closed - N + 1 problem occur.
|
||||||
|
// So it would be good if in future hawkBit run without that
|
||||||
|
// Otherwise, if false, call for the lazy field will throw LazyInitializationException
|
||||||
|
hibernateProperties.put("hibernate.enable_lazy_load_no_trans", enableLazyLoadNoTrans);
|
||||||
|
hibernateProperties.put("hibernate.integrator_provider", (IntegratorProvider) () -> Collections.singletonList(new Integrator() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void integrate(
|
||||||
|
final Metadata metadata, final BootstrapContext bootstrapContext,
|
||||||
|
final SessionFactoryImplementor sessionFactory) {
|
||||||
|
sessionFactory.getServiceRegistry()
|
||||||
|
.getService(EventListenerRegistry.class)
|
||||||
|
.appendListeners(EventType.POST_UPDATE, new EntityPropertyChangeListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disintegrate(final SessionFactoryImplementor sessionFactory, final SessionFactoryServiceRegistry serviceRegistry) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
return hibernateProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class CustomHibernateJpaDialect extends HibernateJpaDialect {
|
||||||
|
|
||||||
|
protected CustomHibernateJpaDialect() {
|
||||||
|
super();
|
||||||
|
this.setJdbcExceptionTranslator(JpaExceptionTranslator.getTranslator());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user