diff --git a/docs/src/main/asciidoc/hibernate-orm.adoc b/docs/src/main/asciidoc/hibernate-orm.adoc index a5d8b2398b44f..5017dd72d4e5b 100644 --- a/docs/src/main/asciidoc/hibernate-orm.adoc +++ b/docs/src/main/asciidoc/hibernate-orm.adoc @@ -1111,3 +1111,26 @@ In that case, the interceptor implementation doesn't need to be thread-safe. Due to a limitation in Hibernate ORM itself, `@PreDestroy` methods on `@Dependent`-scoped interceptors will never get called. ==== + + +[[statement_inspectors]] +== Statement Inspectors + +You can assign a `org.hibernate.engine.jdbc.spi.StatementInspector` to your `SessionFactory` by simply defining a CDI bean with the appropriate qualifier: + +[source,java] +---- +@PersistenceUnitExtension // <1> +public class MyStatementInspector implements StatementInspector { // <2> + @Override + public String inspect(String sql) { + // ... + return sql; + } +} +---- +<1> Annotate the statement inspector implementation with the `@PersistenceUnitExtension` qualifier +to tell Quarkus it should be used in the default persistence unit. ++ +For <>, use `@PersistenceUnitExtension("nameOfYourPU")` +<2> Implement `org.hibernate.engine.jdbc.spi.StatementInspector`. diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/ClassNames.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/ClassNames.java index 644689095d286..effe1a6823ba6 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/ClassNames.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/ClassNames.java @@ -55,5 +55,6 @@ private static DotName createConstant(String fqcn) { public static final DotName SESSION = createConstant("org.hibernate.Session"); public static final DotName INTERCEPTOR = createConstant("org.hibernate.Interceptor"); + public static final DotName STATEMENT_INSPECTOR = createConstant("org.hibernate.resource.jdbc.spi.StatementInspector"); } diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java index 18d19e56a3147..38bcc201eed4d 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmCdiProcessor.java @@ -46,7 +46,8 @@ public class HibernateOrmCdiProcessor { private static final Set PERSISTENCE_UNIT_EXTENSION_VALID_TYPES = Set.of( ClassNames.TENANT_RESOLVER, ClassNames.TENANT_CONNECTION_RESOLVER, - ClassNames.INTERCEPTOR); + ClassNames.INTERCEPTOR, + ClassNames.STATEMENT_INSPECTOR); @BuildStep AnnotationsTransformerBuildItem convertJpaResourceAnnotationsToQualifier( diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/jdbc/ApplicationScopedStatementInspectorTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/jdbc/ApplicationScopedStatementInspectorTest.java new file mode 100644 index 0000000000000..81881654bc0db --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/jdbc/ApplicationScopedStatementInspectorTest.java @@ -0,0 +1,92 @@ +package io.quarkus.hibernate.orm.jdbc; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; + +import javax.enterprise.event.Observes; +import javax.inject.Inject; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.transaction.UserTransaction; + +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.resource.jdbc.spi.StatementInspector; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.orm.PersistenceUnitExtension; +import io.quarkus.runtime.StartupEvent; +import io.quarkus.test.QuarkusUnitTest; + +public class ApplicationScopedStatementInspectorTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(MyEntity.class) + .addClass(ApplicationStatementInspector.class)) + .withConfigurationResource("application.properties"); + + @Inject + SessionFactory sessionFactory; + + @Inject + Session session; + + @Inject + UserTransaction transaction; + + public void initData(@Observes StartupEvent event) throws Exception { + transaction.begin(); + for (int i = 0; i < 3; i++) { + MyEntity entity = new MyEntity(i); + session.persist(entity); + } + transaction.commit(); + } + + @BeforeEach + public void clearStatementInspector() { + ApplicationStatementInspector.statements.clear(); + } + + @Test + public void testStatementInspectorIsLoaded() throws Exception { + transaction.begin(); + session.find(MyEntity.class, 0); + transaction.commit(); + assertThat(ApplicationStatementInspector.statements).hasSize(1); + } + + @Entity(name = "myentity") + @Table + public static class MyEntity { + + @Id + public Integer id; + + public MyEntity() { + } + + public MyEntity(int id) { + this.id = id; + } + } + + @PersistenceUnitExtension // @ApplicationScoped is the default + public static class ApplicationStatementInspector implements StatementInspector { + + static List statements = new ArrayList<>(); + + @Override + public String inspect(String sql) { + statements.add(sql); + return sql; + } + } +} diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootEntityManagerFactoryBuilder.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootEntityManagerFactoryBuilder.java index cad28dcac61a9..f634a1f889564 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootEntityManagerFactoryBuilder.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootEntityManagerFactoryBuilder.java @@ -23,6 +23,7 @@ import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; import org.hibernate.proxy.EntityNotFoundDelegate; +import org.hibernate.resource.jdbc.spi.StatementInspector; import org.hibernate.service.ServiceRegistry; import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.tool.schema.spi.CommandAcceptanceException; @@ -184,6 +185,12 @@ protected void populate(String persistenceUnitName, SessionFactoryOptionsBuilder if (!interceptorInstance.isUnsatisfied()) { options.applyStatelessInterceptorSupplier(interceptorInstance::get); } + + InjectableInstance statementInspectorInstance = PersistenceUnitUtil + .singleExtensionInstanceForPersistenceUnit(StatementInspector.class, persistenceUnitName); + if (!statementInspectorInstance.isUnsatisfied()) { + options.applyStatementInspector(statementInspectorInstance.get()); + } } private static class ServiceRegistryCloser implements SessionFactoryObserver {