Reinstate support for Hibernate Metrics

Closes gh-31675
pull/31689/head
Andy Wilkinson 2 years ago
parent b10c57551c
commit 9b113272d1

@ -108,6 +108,7 @@ dependencies {
}
optional("org.flywaydb:flyway-core")
optional("org.hibernate.orm:hibernate-core")
optional("org.hibernate.orm:hibernate-micrometer")
optional("org.hibernate.validator:hibernate-validator")
optional("org.influxdb:influxdb-java")
optional("org.liquibase:liquibase-core") {

@ -0,0 +1,101 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* 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
*
* https://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.
*/
package org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa;
import java.util.Collections;
import java.util.Map;
import io.micrometer.core.instrument.MeterRegistry;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.PersistenceException;
import org.hibernate.SessionFactory;
import org.hibernate.stat.HibernateMetrics;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} for metrics on all available
* Hibernate {@link EntityManagerFactory} instances that have statistics enabled.
*
* @author Rui Figueira
* @author Stephane Nicoll
* @since 2.1.0
*/
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter({ MetricsAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
SimpleMetricsExportAutoConfiguration.class })
@ConditionalOnClass({ EntityManagerFactory.class, SessionFactory.class, HibernateMetrics.class, MeterRegistry.class })
@ConditionalOnBean({ EntityManagerFactory.class, MeterRegistry.class })
public class HibernateMetricsAutoConfiguration implements SmartInitializingSingleton {
private static final String ENTITY_MANAGER_FACTORY_SUFFIX = "entityManagerFactory";
private final Map<String, EntityManagerFactory> entityManagerFactories;
private final MeterRegistry meterRegistry;
public HibernateMetricsAutoConfiguration(Map<String, EntityManagerFactory> entityManagerFactories,
MeterRegistry meterRegistry) {
this.entityManagerFactories = entityManagerFactories;
this.meterRegistry = meterRegistry;
}
@Override
public void afterSingletonsInstantiated() {
bindEntityManagerFactoriesToRegistry(this.entityManagerFactories, this.meterRegistry);
}
public void bindEntityManagerFactoriesToRegistry(Map<String, EntityManagerFactory> entityManagerFactories,
MeterRegistry registry) {
entityManagerFactories.forEach((name, factory) -> bindEntityManagerFactoryToRegistry(name, factory, registry));
}
private void bindEntityManagerFactoryToRegistry(String beanName, EntityManagerFactory entityManagerFactory,
MeterRegistry registry) {
String entityManagerFactoryName = getEntityManagerFactoryName(beanName);
try {
new HibernateMetrics(entityManagerFactory.unwrap(SessionFactory.class), entityManagerFactoryName,
Collections.emptyList()).bindTo(registry);
}
catch (PersistenceException ex) {
// Continue
}
}
/**
* Get the name of an {@link EntityManagerFactory} based on its {@code beanName}.
* @param beanName the name of the {@link EntityManagerFactory} bean
* @return a name for the given entity manager factory
*/
private String getEntityManagerFactoryName(String beanName) {
if (beanName.length() > ENTITY_MANAGER_FACTORY_SUFFIX.length()
&& StringUtils.endsWithIgnoreCase(beanName, ENTITY_MANAGER_FACTORY_SUFFIX)) {
return beanName.substring(0, beanName.length() - ENTITY_MANAGER_FACTORY_SUFFIX.length());
}
return beanName;
}
}

@ -0,0 +1,20 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* 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
*
* https://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.
*/
/**
* Auto-configuration for JPA metrics.
*/
package org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa;

@ -70,6 +70,7 @@ org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront.Wavefron
org.springframework.boot.actuate.autoconfigure.metrics.integration.IntegrationMetricsAutoConfiguration
org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration
org.springframework.boot.actuate.autoconfigure.metrics.mongo.MongoMetricsAutoConfiguration
org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa.HibernateMetricsAutoConfiguration
org.springframework.boot.actuate.autoconfigure.metrics.r2dbc.ConnectionPoolMetricsAutoConfiguration
org.springframework.boot.actuate.autoconfigure.metrics.redis.LettuceMetricsAutoConfiguration
org.springframework.boot.actuate.autoconfigure.metrics.startup.StartupTimeMetricsListenerAutoConfiguration

@ -0,0 +1,207 @@
/*
* Copyright 2012-2022 the original author or authors.
*
* 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
*
* https://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.
*/
package org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.PersistenceException;
import org.hibernate.SessionFactory;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryBuilderCustomizer;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link HibernateMetricsAutoConfiguration}.
*
* @author Rui Figueira
* @author Stephane Nicoll
*/
class HibernateMetricsAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().with(MetricsRun.simple())
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class, HibernateMetricsAutoConfiguration.class))
.withUserConfiguration(BaseConfiguration.class);
@Test
void autoConfiguredEntityManagerFactoryWithStatsIsInstrumented() {
this.contextRunner.withPropertyValues("spring.jpa.properties.hibernate.generate_statistics:true")
.run((context) -> {
MeterRegistry registry = context.getBean(MeterRegistry.class);
registry.get("hibernate.statements").tags("entityManagerFactory", "entityManagerFactory").meter();
});
}
@Test
void autoConfiguredEntityManagerFactoryWithoutStatsIsNotInstrumented() {
this.contextRunner.withPropertyValues("spring.jpa.properties.hibernate.generate_statistics:false")
.run((context) -> {
context.getBean(EntityManagerFactory.class).unwrap(SessionFactory.class);
MeterRegistry registry = context.getBean(MeterRegistry.class);
assertThat(registry.find("hibernate.statements").meter()).isNull();
});
}
@Test
void entityManagerFactoryInstrumentationCanBeDisabled() {
this.contextRunner.withPropertyValues("management.metrics.enable.hibernate=false",
"spring.jpa.properties.hibernate.generate_statistics:true").run((context) -> {
context.getBean(EntityManagerFactory.class).unwrap(SessionFactory.class);
MeterRegistry registry = context.getBean(MeterRegistry.class);
assertThat(registry.find("hibernate.statements").meter()).isNull();
});
}
@Test
void allEntityManagerFactoriesCanBeInstrumented() {
this.contextRunner.withPropertyValues("spring.jpa.properties.hibernate.generate_statistics:true")
.withUserConfiguration(TwoEntityManagerFactoriesConfiguration.class).run((context) -> {
context.getBean("firstEntityManagerFactory", EntityManagerFactory.class)
.unwrap(SessionFactory.class);
context.getBean("secondOne", EntityManagerFactory.class).unwrap(SessionFactory.class);
MeterRegistry registry = context.getBean(MeterRegistry.class);
registry.get("hibernate.statements").tags("entityManagerFactory", "first").meter();
registry.get("hibernate.statements").tags("entityManagerFactory", "secondOne").meter();
});
}
@Test
void entityManagerFactoryInstrumentationIsDisabledIfNotHibernateSessionFactory() {
this.contextRunner.withPropertyValues("spring.jpa.properties.hibernate.generate_statistics:true")
.withUserConfiguration(NonHibernateEntityManagerFactoryConfiguration.class).run((context) -> {
// ensure EntityManagerFactory is not a Hibernate SessionFactory
assertThatThrownBy(() -> context.getBean(EntityManagerFactory.class).unwrap(SessionFactory.class))
.isInstanceOf(PersistenceException.class);
MeterRegistry registry = context.getBean(MeterRegistry.class);
assertThat(registry.find("hibernate.statements").meter()).isNull();
});
}
@Test
void entityManagerFactoryInstrumentationIsDisabledIfHibernateIsNotAvailable() {
this.contextRunner.withClassLoader(new FilteredClassLoader(SessionFactory.class))
.withUserConfiguration(NonHibernateEntityManagerFactoryConfiguration.class).run((context) -> {
assertThat(context).doesNotHaveBean(HibernateMetricsAutoConfiguration.class);
MeterRegistry registry = context.getBean(MeterRegistry.class);
assertThat(registry.find("hibernate.statements").meter()).isNull();
});
}
@Test
void entityManagerFactoryInstrumentationDoesNotDeadlockWithDeferredInitialization() {
this.contextRunner.withPropertyValues("spring.jpa.properties.hibernate.generate_statistics:true",
"spring.sql.init.schema-locations:city-schema.sql", "spring.sql.init.data-locations=city-data.sql")
.withConfiguration(AutoConfigurations.of(SqlInitializationAutoConfiguration.class))
.withBean(EntityManagerFactoryBuilderCustomizer.class,
() -> (builder) -> builder.setBootstrapExecutor(new SimpleAsyncTaskExecutor()))
.run((context) -> {
JdbcTemplate jdbcTemplate = new JdbcTemplate(context.getBean(DataSource.class));
assertThat(jdbcTemplate.queryForObject("SELECT COUNT(*) from CITY", Integer.class)).isEqualTo(1);
MeterRegistry registry = context.getBean(MeterRegistry.class);
registry.get("hibernate.statements").tags("entityManagerFactory", "entityManagerFactory").meter();
});
}
@Configuration(proxyBeanMethods = false)
static class BaseConfiguration {
@Bean
SimpleMeterRegistry simpleMeterRegistry() {
return new SimpleMeterRegistry();
}
}
@Entity
static class MyEntity {
@Id
@GeneratedValue
private Long id;
}
@Configuration(proxyBeanMethods = false)
static class TwoEntityManagerFactoriesConfiguration {
private static final Class<?>[] PACKAGE_CLASSES = new Class<?>[] { MyEntity.class };
@Primary
@Bean
LocalContainerEntityManagerFactoryBean firstEntityManagerFactory(DataSource ds) {
return createSessionFactory(ds);
}
@Bean
LocalContainerEntityManagerFactoryBean secondOne(DataSource ds) {
return createSessionFactory(ds);
}
private LocalContainerEntityManagerFactoryBean createSessionFactory(DataSource ds) {
Map<String, String> jpaProperties = new HashMap<>();
jpaProperties.put("hibernate.generate_statistics", "true");
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), jpaProperties, null).dataSource(ds)
.packages(PACKAGE_CLASSES).build();
}
}
@Configuration(proxyBeanMethods = false)
static class NonHibernateEntityManagerFactoryConfiguration {
@Bean
EntityManagerFactory entityManagerFactory() {
EntityManagerFactory mockedFactory = mock(EntityManagerFactory.class);
// enforces JPA contract
given(mockedFactory.unwrap(ArgumentMatchers.<Class<SessionFactory>>any()))
.willThrow(PersistenceException.class);
return mockedFactory;
}
}
}

@ -40,6 +40,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.SystemMetricsAutoC
import org.springframework.boot.actuate.autoconfigure.metrics.amqp.RabbitMetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.cache.CacheMetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa.HibernateMetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.web.client.HttpClientMetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.web.reactive.WebFluxMetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsAutoConfiguration;
@ -137,8 +138,9 @@ class MetricsIntegrationTests {
@ImportAutoConfiguration({ MetricsAutoConfiguration.class, JvmMetricsAutoConfiguration.class,
LogbackMetricsAutoConfiguration.class, SystemMetricsAutoConfiguration.class,
RabbitMetricsAutoConfiguration.class, CacheMetricsAutoConfiguration.class,
DataSourcePoolMetricsAutoConfiguration.class, HttpClientMetricsAutoConfiguration.class,
WebFluxMetricsAutoConfiguration.class, WebMvcMetricsAutoConfiguration.class, JacksonAutoConfiguration.class,
DataSourcePoolMetricsAutoConfiguration.class, HibernateMetricsAutoConfiguration.class,
HttpClientMetricsAutoConfiguration.class, WebFluxMetricsAutoConfiguration.class,
WebMvcMetricsAutoConfiguration.class, JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, RestTemplateAutoConfiguration.class,
WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class })

@ -906,6 +906,25 @@ Each metric is tagged by the name of the pool (you can control it with `spring.d
[[actuator.metrics.supported.hibernate]]
==== Hibernate Metrics
If `org.hibernate.orm:hibernate-micrometer` is on the classpath, all available Hibernate `EntityManagerFactory` instances that have statistics enabled are instrumented with a metric named `hibernate`.
Metrics are also tagged by the name of the `EntityManagerFactory`, which is derived from the bean name.
To enable statistics, the standard JPA property `hibernate.generate_statistics` must be set to `true`.
You can enable that on the auto-configured `EntityManagerFactory`:
[source,yaml,indent=0,subs="verbatim",configprops,configblocks]
----
spring:
jpa:
properties:
"[hibernate.generate_statistics]": true
----
[[actuator.metrics.supported.spring-data-repository]]
==== Spring Data Repository Metrics
Auto-configuration enables the instrumentation of all Spring Data `Repository` method invocations.

Loading…
Cancel
Save