From a6f8351dd64ec288424ceb5bc6e4505e00c7d5a2 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 15 May 2017 17:04:24 +0100 Subject: [PATCH] Close Liquibase-specific DataSource once database has been migrated Previously, when the liquibase.url, .username, and .password properties were used to configure a DataSource specifically for Liquibase that DataSource would never be explicitly closed. As it is created by DataSourceBuilder with no explicitly configured type it will use whichever connection pool is available and, therefore, will create and keep open the pool's minimum number of connections. This is an unnecessary use of resources both in the application and in the database. This commit updates LiquibaseAutoConfiguration so that if it uses DataSourceBuilder to create a DataSource then it will also close that DataSource once the database has been migrated. Closes gh-9218 --- .../liquibase/LiquibaseAutoConfiguration.java | 49 +++++++++++++++++-- .../LiquibaseAutoConfigurationTests.java | 6 ++- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java index 46abc3f268f..7570d9a47b0 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java @@ -16,10 +16,13 @@ package org.springframework.boot.autoconfigure.liquibase; +import java.lang.reflect.Method; + import javax.annotation.PostConstruct; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; +import liquibase.exception.LiquibaseException; import liquibase.integration.spring.SpringLiquibase; import org.springframework.beans.factory.ObjectProvider; @@ -42,6 +45,7 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; /** * {@link EnableAutoConfiguration Auto-configuration} for Liquibase. @@ -99,10 +103,9 @@ public class LiquibaseAutoConfiguration { @Bean public SpringLiquibase liquibase() { - SpringLiquibase liquibase = new SpringLiquibase(); + SpringLiquibase liquibase = createSpringLiquibase(); liquibase.setChangeLog(this.properties.getChangeLog()); liquibase.setContexts(this.properties.getContexts()); - liquibase.setDataSource(getDataSource()); liquibase.setDefaultSchema(this.properties.getDefaultSchema()); liquibase.setDropFirst(this.properties.isDropFirst()); liquibase.setShouldRun(this.properties.isEnabled()); @@ -112,6 +115,22 @@ public class LiquibaseAutoConfiguration { return liquibase; } + private SpringLiquibase createSpringLiquibase() { + SpringLiquibase liquibase; + DataSource dataSource = getDataSource(); + if (dataSource == null) { + dataSource = DataSourceBuilder.create().url(this.properties.getUrl()) + .username(this.properties.getUser()) + .password(this.properties.getPassword()).build(); + liquibase = new DataSourceClosingSpringLiquibase(); + } + else { + liquibase = new SpringLiquibase(); + } + liquibase.setDataSource(dataSource); + return liquibase; + } + private DataSource getDataSource() { if (this.liquibaseDataSource != null) { return this.liquibaseDataSource; @@ -119,9 +138,7 @@ public class LiquibaseAutoConfiguration { else if (this.properties.getUrl() == null) { return this.dataSource; } - return DataSourceBuilder.create().url(this.properties.getUrl()) - .username(this.properties.getUser()) - .password(this.properties.getPassword()).build(); + return null; } } @@ -142,4 +159,26 @@ public class LiquibaseAutoConfiguration { } + /** + * A custom {@link SpringLiquibase} extension that close the underlying + * {@link DataSource} once the database has been migrated. + */ + private static final class DataSourceClosingSpringLiquibase extends SpringLiquibase { + + @Override + public void afterPropertiesSet() throws LiquibaseException { + super.afterPropertiesSet(); + closeDataSource(); + } + + private void closeDataSource() { + Method closeMethod = ReflectionUtils.findMethod(getDataSource().getClass(), + "close"); + if (closeMethod != null) { + ReflectionUtils.invokeMethod(closeMethod, getDataSource()); + } + } + + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java index c9a3f58c19c..c71d0ea5c10 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 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. @@ -186,7 +186,9 @@ public class LiquibaseAutoConfigurationTests { PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); SpringLiquibase liquibase = this.context.getBean(SpringLiquibase.class); - assertThat(liquibase.getDataSource().getConnection().getMetaData().getURL()) + DataSource dataSource = liquibase.getDataSource(); + assertThat(ReflectionTestUtils.getField(dataSource, "pool")).isNull(); + assertThat(dataSource.getConnection().getMetaData().getURL()) .isEqualTo("jdbc:hsqldb:mem:liquibase"); }