diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml index ef0ecf425de..f8c2c72029a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml @@ -233,6 +233,11 @@ jersey-container-servlet-core true + + org.hibernate + hibernate-core + true + org.hibernate.validator hibernate-validator @@ -440,6 +445,11 @@ jsonassert test + + org.springframework + spring-orm + test + org.springframework.data spring-data-elasticsearch diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/orm/jpa/HibernateMetricsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/orm/jpa/HibernateMetricsAutoConfiguration.java new file mode 100644 index 00000000000..024e8278a06 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/orm/jpa/HibernateMetricsAutoConfiguration.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012-2018 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 + * + * 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. + */ + +package org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa; + +import java.util.Collections; +import java.util.Map; + +import javax.persistence.EntityManagerFactory; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.binder.jpa.HibernateMetrics; + +import org.springframework.beans.factory.annotation.Autowired; +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 + */ +@Configuration +@AutoConfigureAfter({ MetricsAutoConfiguration.class, HibernateJpaAutoConfiguration.class, + SimpleMetricsExportAutoConfiguration.class }) +@ConditionalOnClass({ EntityManagerFactory.class, MeterRegistry.class }) +@ConditionalOnBean({ EntityManagerFactory.class, MeterRegistry.class }) +public class HibernateMetricsAutoConfiguration { + + private static final String ENTITY_MANAGER_FACTORY_SUFFIX = "entityManagerFactory"; + + private final MeterRegistry registry; + + public HibernateMetricsAutoConfiguration(MeterRegistry registry) { + this.registry = registry; + } + + @Autowired + public void bindEntityManagerFactoriesToRegistry( + Map entityManagerFactories) { + entityManagerFactories.forEach(this::bindEntityManagerFactoryToRegistry); + } + + private void bindEntityManagerFactoryToRegistry(String beanName, + EntityManagerFactory entityManagerFactory) { + String entityManagerFactoryName = getEntityManagerFactoryName(beanName); + new HibernateMetrics(entityManagerFactory, entityManagerFactoryName, + Collections.emptyList()).bindTo(this.registry); + } + + /** + * Get the name of a {@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; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/orm/jpa/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/orm/jpa/package-info.java new file mode 100644 index 00000000000..ecb62032914 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/orm/jpa/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2018 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 + * + * 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. + */ + +/** + * Auto-configuration for JDBC metrics. + */ +package org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories index 60dabb05a23..01ba105602e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories @@ -49,6 +49,7 @@ org.springframework.boot.actuate.autoconfigure.metrics.export.signalfx.SignalFxM org.springframework.boot.actuate.autoconfigure.metrics.export.statsd.StatsdMetricsExportAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront.WavefrontMetricsExportAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa.HibernateMetricsAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.web.client.RestTemplateMetricsAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.web.reactive.WebFluxMetricsAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsAutoConfiguration,\ diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/orm/jpa/HibernateMetricsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/orm/jpa/HibernateMetricsAutoConfigurationTests.java new file mode 100644 index 00000000000..beff4b29646 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/orm/jpa/HibernateMetricsAutoConfigurationTests.java @@ -0,0 +1,201 @@ +/* + * Copyright 2012-2018 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 + * + * 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. + */ + +package org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa; + +import java.util.HashMap; +import java.util.Map; + +import javax.persistence.Entity; +import javax.persistence.EntityManagerFactory; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.PersistenceException; +import javax.sql.DataSource; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.hibernate.SessionFactory; +import org.junit.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.HibernateJpaAutoConfiguration; +import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; +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.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 + */ +public class HibernateMetricsAutoConfigurationTests { + + private ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .with(MetricsRun.simple()) + .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class, + HibernateJpaAutoConfiguration.class, + HibernateMetricsAutoConfiguration.class)) + .withUserConfiguration(BaseConfiguration.class); + + @Test + public 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 + public 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 + public 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 + public 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 + public void entityManagerFactoryInstrumentationIsDisabledIfNotHibernateSessionFactory() { + this.contextRunner + .withPropertyValues( + "spring.jpa.properties.hibernate.generate_statistics:true") + .withUserConfiguration( + NonHibernateEntityManagerFactoryConfiguration.class) + .run((context) -> { + // ensure EntityManagerFactory is not an 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(); + }); + } + + @Configuration + static class BaseConfiguration { + + @Bean + public SimpleMeterRegistry simpleMeterRegistry() { + return new SimpleMeterRegistry(); + } + + } + + @Entity + static class MyEntity { + + @Id + @GeneratedValue + private Long id; + + } + + @Configuration + static class TwoEntityManagerFactoriesConfiguration { + + private static final Class[] PACKAGE_CLASSES = new Class[] { + MyEntity.class }; + + @Primary + @Bean + public LocalContainerEntityManagerFactoryBean firstEntityManagerFactory( + DataSource ds) { + return createSessionFactory(ds); + } + + @Bean + public LocalContainerEntityManagerFactoryBean secondOne(DataSource ds) { + return createSessionFactory(ds); + } + + private LocalContainerEntityManagerFactoryBean createSessionFactory( + DataSource ds) { + Map jpaProperties = new HashMap<>(); + jpaProperties.put("hibernate.generate_statistics", "true"); + EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder( + new HibernateJpaVendorAdapter(), jpaProperties, null); + return builder.dataSource(ds).packages(PACKAGE_CLASSES).build(); + } + + } + + @Configuration + static class NonHibernateEntityManagerFactoryConfiguration { + + @Bean + public EntityManagerFactory entityManagerFactory() { + EntityManagerFactory mockedFactory = mock(EntityManagerFactory.class); + // enforces JPA contract + given(mockedFactory.unwrap(ArgumentMatchers.>any())) + .willThrow(PersistenceException.class); + return mockedFactory; + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/test/MetricsIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/test/MetricsIntegrationTests.java index 6a5beebe004..0abe5f1f4bb 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/test/MetricsIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/test/MetricsIntegrationTests.java @@ -38,6 +38,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfigu 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.RestTemplateMetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.web.reactive.WebFluxMetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsAutoConfiguration; @@ -143,6 +144,7 @@ public class MetricsIntegrationTests { @ImportAutoConfiguration({ MetricsAutoConfiguration.class, RabbitMetricsAutoConfiguration.class, CacheMetricsAutoConfiguration.class, DataSourcePoolMetricsAutoConfiguration.class, + HibernateMetricsAutoConfiguration.class, RestTemplateMetricsAutoConfiguration.class, WebFluxMetricsAutoConfiguration.class, WebMvcMetricsAutoConfiguration.class, JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/test/MetricsRun.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/test/MetricsRun.java index 67dffadea39..fa35d78a8ae 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/test/MetricsRun.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/test/MetricsRun.java @@ -37,6 +37,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.export.signalfx.Si import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.export.statsd.StatsdMetricsExportAutoConfiguration; 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.RestTemplateMetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.web.reactive.WebFluxMetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsAutoConfiguration; @@ -75,6 +76,7 @@ public final class MetricsRun { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class, RabbitMetricsAutoConfiguration.class, CacheMetricsAutoConfiguration.class, DataSourcePoolMetricsAutoConfiguration.class, + HibernateMetricsAutoConfiguration.class, RestTemplateMetricsAutoConfiguration.class, WebFluxMetricsAutoConfiguration.class, WebMvcMetricsAutoConfiguration.class); diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index 1a4dd9cda79..e006f6f37dd 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -1768,6 +1768,27 @@ Also, Hikari-specific metrics are exposed with a `hikaricp` prefix. Each metric by the name of the Pool (can be controlled with `spring.datasource.name`). + +[[production-ready-metrics-hibernate]] +==== Hibernate Metrics +Auto-configuration enables the instrumentation of all available Hibernate +`EntityManagerFactory` instances that have statistics enabled with a metric named +`hibernate`. + +Metrics are also tagged by the name of the `EntityManagerFactory` that is derived from +the bean name. + +To enable statistics, the standardJPA property `hibernate.generate_statistics` must be +set to `true`. You can enable that on the auto-configured `EntityManagerFactory` as shown +in the following example: + +[source,properties,indent=0] +---- + spring.jpa.properties.hibernate.generate_statistics=true +---- + + + [[production-ready-metrics-rabbitmq]] ==== RabbitMQ Metrics Auto-configuration will enable the instrumentation of all available RabbitMQ connection