Workarounds to make Listeners and non-autocommit work on Quarkus
Closes #13200
This commit is contained in:
parent
2f216ad505
commit
4d19099c66
5 changed files with 67 additions and 23 deletions
|
@ -23,28 +23,38 @@ import org.hibernate.event.service.spi.EventListenerRegistry;
|
|||
import org.hibernate.event.spi.EventType;
|
||||
import org.hibernate.integrator.spi.Integrator;
|
||||
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.listeners.JpaAutoFlushListener;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.listeners.JpaEntityVersionListener;
|
||||
import org.keycloak.models.map.storage.jpa.hibernate.listeners.JpaOptimisticLockingListener;
|
||||
|
||||
/**
|
||||
* Adding listeners to Hibernate's entity manager for the JPA Map store.
|
||||
* This used to be a JPA map storage specific configuration via the Hibernate option <code>hibernate.integrator_provider</code>
|
||||
* which worked in an Undertow setup but not in a Quarkus setup.
|
||||
* As this will be called for both the legacy store and the new JPA Map store, it first checks if the JPA Map store has been enabled.
|
||||
* A follow-up issue to track this is here: <a href="https://github.com/keycloak/keycloak/issues/13219">#13219</a>
|
||||
*/
|
||||
public class EventListenerIntegrator implements Integrator {
|
||||
|
||||
@Override
|
||||
public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactoryImplementor,
|
||||
SessionFactoryServiceRegistry sessionFactoryServiceRegistry) {
|
||||
final EventListenerRegistry eventListenerRegistry =
|
||||
sessionFactoryServiceRegistry.getService( EventListenerRegistry.class );
|
||||
if (Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE)) {
|
||||
final EventListenerRegistry eventListenerRegistry =
|
||||
sessionFactoryServiceRegistry.getService(EventListenerRegistry.class);
|
||||
|
||||
eventListenerRegistry.appendListeners(EventType.PRE_INSERT, JpaOptimisticLockingListener.INSTANCE);
|
||||
eventListenerRegistry.appendListeners(EventType.PRE_UPDATE, JpaOptimisticLockingListener.INSTANCE);
|
||||
eventListenerRegistry.appendListeners(EventType.PRE_DELETE, JpaOptimisticLockingListener.INSTANCE);
|
||||
eventListenerRegistry.appendListeners(EventType.PRE_INSERT, JpaOptimisticLockingListener.INSTANCE);
|
||||
eventListenerRegistry.appendListeners(EventType.PRE_UPDATE, JpaOptimisticLockingListener.INSTANCE);
|
||||
eventListenerRegistry.appendListeners(EventType.PRE_DELETE, JpaOptimisticLockingListener.INSTANCE);
|
||||
|
||||
eventListenerRegistry.appendListeners(EventType.PRE_INSERT, JpaEntityVersionListener.INSTANCE);
|
||||
eventListenerRegistry.appendListeners(EventType.PRE_UPDATE, JpaEntityVersionListener.INSTANCE);
|
||||
eventListenerRegistry.appendListeners(EventType.PRE_DELETE, JpaEntityVersionListener.INSTANCE);
|
||||
eventListenerRegistry.appendListeners(EventType.PRE_INSERT, JpaEntityVersionListener.INSTANCE);
|
||||
eventListenerRegistry.appendListeners(EventType.PRE_UPDATE, JpaEntityVersionListener.INSTANCE);
|
||||
eventListenerRegistry.appendListeners(EventType.PRE_DELETE, JpaEntityVersionListener.INSTANCE);
|
||||
|
||||
// replace auto-flush listener
|
||||
eventListenerRegistry.setListeners(EventType.AUTO_FLUSH, JpaAutoFlushListener.INSTANCE);
|
||||
// replace auto-flush listener
|
||||
eventListenerRegistry.setListeners(EventType.AUTO_FLUSH, JpaAutoFlushListener.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -152,14 +152,6 @@ public class JpaMapStorageProviderFactory implements
|
|||
|
||||
public static final String HIBERNATE_DEFAULT_SCHEMA = "hibernate.default_schema";
|
||||
|
||||
public static Map<String, Object> configureHibernateProperties() {
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
properties.put("hibernate.integrator_provider", new KeycloakIntegratorProvider());
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
private volatile EntityManagerFactory emf;
|
||||
private final Set<Class<?>> validatedModels = ConcurrentHashMap.newKeySet();
|
||||
private Config.Scope config;
|
||||
|
@ -268,12 +260,16 @@ public class JpaMapStorageProviderFactory implements
|
|||
// check the session for a cached provider before creating a new one.
|
||||
JpaMapStorageProvider provider = session.getAttribute(this.sessionProviderKey, JpaMapStorageProvider.class);
|
||||
if (provider == null) {
|
||||
provider = new JpaMapStorageProvider(this, session, emf.createEntityManager(), this.sessionTxKey);
|
||||
provider = new JpaMapStorageProvider(this, session, getEntityManager(), this.sessionTxKey);
|
||||
session.setAttribute(this.sessionProviderKey, provider);
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
protected EntityManager getEntityManager() {
|
||||
return emf.createEntityManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
this.config = config;
|
||||
|
@ -347,8 +343,6 @@ public class JpaMapStorageProviderFactory implements
|
|||
properties.put("hibernate.format_sql", config.getBoolean("formatSql", true));
|
||||
properties.put("hibernate.dialect", config.get("driverDialect"));
|
||||
|
||||
properties.putAll(configureHibernateProperties());
|
||||
|
||||
logger.trace("Creating EntityManagerFactory");
|
||||
ParsedPersistenceXmlDescriptor descriptor = PersistenceXmlParser.locateIndividualPersistenceUnit(
|
||||
JpaMapStorageProviderFactory.class.getClassLoader()
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
#
|
||||
# Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||
# and other contributors as indicated by the @author tags.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
org.keycloak.models.map.storage.jpa.EventListenerIntegrator
|
|
@ -245,7 +245,6 @@ class KeycloakProcessor {
|
|||
} else {
|
||||
descriptor = PersistenceXmlParser.locateIndividualPersistenceUnit(
|
||||
Thread.currentThread().getContextClassLoader().getResource("default-map-jpa-persistence.xml"));
|
||||
descriptor.getProperties().putAll(QuarkusJpaMapStorageProviderFactory.configureHibernateProperties());
|
||||
}
|
||||
|
||||
producer.produce(new PersistenceXmlDescriptorBuildItem(descriptor));
|
||||
|
|
|
@ -26,9 +26,12 @@ import java.sql.Connection;
|
|||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
import javax.enterprise.inject.Instance;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import org.hibernate.internal.SessionFactoryImpl;
|
||||
import org.hibernate.internal.SessionImpl;
|
||||
import org.keycloak.config.StorageOptions;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory;
|
||||
|
||||
import io.quarkus.arc.Arc;
|
||||
|
@ -52,10 +55,30 @@ public class QuarkusJpaMapStorageProviderFactory extends JpaMapStorageProviderFa
|
|||
return getEntityManagerFactory("keycloak-default").orElseThrow(() -> new IllegalStateException("Failed to resolve the default entity manager factory"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EntityManager getEntityManager() {
|
||||
EntityManager em = super.getEntityManager();
|
||||
try {
|
||||
Connection connection = em.unwrap(SessionImpl.class).connection();
|
||||
// In the Undertow setup, Hibernate sets the connection to non-autocommit, and in the Quarkus setup the XA transaction manager does this.
|
||||
// For the Quarkus setup without a XA transaction manager, we didn't find a way to have this setup automatically.
|
||||
// There is also no known option to configure this in the Agroal DB connection pool in a Quarkus setup:
|
||||
// While the connection pool supports it, it hasn't been exposed as a Quarkus configuration option.
|
||||
// At the same time, disabling autocommit is essential to keep the transactional boundaries of the application.
|
||||
// The failure we've seen is the failed unique constraints that are usually deferred (for example, for client attributes).
|
||||
// A follow-up issue to track this is here: https://github.com/keycloak/keycloak/issues/13222
|
||||
if (connection.getAutoCommit()) {
|
||||
connection.setAutoCommit(false);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new ModelException("unable to set non-auto-commit to false");
|
||||
}
|
||||
return em;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Connection getConnection() {
|
||||
SessionFactoryImpl entityManagerFactory = getEntityManagerFactory().unwrap(SessionFactoryImpl.class);
|
||||
|
||||
try {
|
||||
return entityManagerFactory.getJdbcServices().getBootstrapJdbcConnectionAccess().obtainConnection();
|
||||
} catch (SQLException cause) {
|
||||
|
|
Loading…
Reference in a new issue