Skip to content
Vlad Mihalcea edited this page Sep 15, 2020 · 64 revisions

Java 8 or later

If you are using Java 8 or later edition of Java, then this is the latest FlexyPool release version:

<flexy-pool.version>2.2.3</flexy-pool.version>

Make sure you add this to your pom.xml.

Note

If you are using Java 1.9 or above you might want to add the following dependency:

<dependency>
    <groupId>com.vladmihalcea.flexy-pool</groupId>
    <artifactId>flexy-pool-core-java9</artifactId>
    <version>${flexy-pool.version}</version>
</dependency>

If you’re using Java 8 (or later), the FlexyPool defaults to using Dropwizard Metrics 4.

Note

If you’re using Java 8 (or later), and you want to use the older Dropwizard Metrics 3 framework, you should add the following dependency to your project:

<dependency>
    <groupId>com.vladmihalcea.flexy-pool</groupId>
    <artifactId>flexy-dropwizard3-metrics</artifactId>
    <version>${flexy-pool.version}</version>
</dependency>

Java 6 and Java 7

If you use Java 6 or Java 7, you need to use the 1.x FlexyPool versions.

Note

Also, make sure that you exclude the flexy-dropwizard-metrics dependency.

That’s because on Java 6 and 7 FlexyPool 1.x works only with Dropwizard Metrics 3, and not with Dropwizard Metrics 4.

For example, if you want to use FlexyPool with Hikari on Java 6 or 7, then you need to use the following dependency:

<dependency>
    <groupId>com.vladmihalcea.flexy-pool</groupId>
    <artifactId>flexy-hikaricp</artifactId>
    <version>1.3.0</version>
    <exclusions>
        <exclusion>
            <groupId>com.vladmihalcea.flexy-pool</groupId>
            <artifactId>flexy-dropwizard-metrics</artifactId>
        </exclusion>
    </exclusions>
</dependency>

Micrometer Metrics

If you want to use the Micrometer framework for metrics instead of Dropwizard Metrics, then you need to add the flexy-micrometer-metrics dependency and exclude the flexy-dropwizard-metrics dependency from the connection pool-specific FlexyPool dependency (e.g. flexy-hikaricp):

<dependency>
	<groupId>com.vladmihalcea.flexy-pool</groupId>
	<artifactId>flexy-micrometer-metrics</artifactId>
	<version>${flexy-pool.version}</version>
</dependency>

<dependency>
	<groupId>com.vladmihalcea.flexy-pool</groupId>
	<artifactId>flexy-hikaricp</artifactId>
	<version>${flexy-pool.version}</version>
	<exclusions>
		<exclusion>
			<groupId>com.vladmihalcea.flexy-pool</groupId>
			<artifactId>flexy-dropwizard-metrics</artifactId>
		</exclusion>
	</exclusions>
</dependency>

Managed environments

In a managed environment (e.g. Java EE), we need to use a declarative configuration model:

Stand-alone environments

For Spring Boot, check out this flexy-pool-spring-boot-starter written by Arthur Gavlyukovskiy.

Connection Pool Settings

Depending on your current project connection pool choice you need to follow one of these guides first:

FlexyPool Settings

Configuration

Each FlexyPool instance requires a data source specific Configuration, which is supplied prior to constructing a FlexyPoolDataSource instance. You’ll have to rename your target Data Source (e.g from dataSource to poolingDataSource), as the FlexyPool Data Source will impersonate the actual Data Source.

A full Configuration would look like this:

@org.springframework.context.annotation.Configuration
public class FlexyPoolConfiguration {

    public static class ConnectionAcquireTimeThresholdExceededEventListener
            extends EventListener<ConnectionAcquireTimeThresholdExceededEvent> {

        public static final Logger LOGGER = LoggerFactory.getLogger(
                ConnectionAcquireTimeThresholdExceededEventListener.class);

        public ConnectionAcquireTimeThresholdExceededEventListener() {
            super(ConnectionAcquireTimeThresholdExceededEvent.class);
        }

        @Override
        public void on(ConnectionAcquireTimeThresholdExceededEvent event) {
            LOGGER.info("Caught event {}", event);
        }
    }

    public static class ConnectionLeaseTimeThresholdExceededEventListener
            extends EventListener<ConnectionLeaseTimeThresholdExceededEvent> {

        public static final Logger LOGGER = LoggerFactory.getLogger(
                ConnectionLeaseTimeThresholdExceededEventListener.class);

        public ConnectionLeaseTimeThresholdExceededEventListener() {
            super(ConnectionLeaseTimeThresholdExceededEvent.class);
        }

        @Override
        public void on(ConnectionLeaseTimeThresholdExceededEvent event) {
            LOGGER.info("Caught event {}", event);
        }
    }

    public static class ConnectionAcquireTimeoutEventListener
            extends EventListener<ConnectionAcquireTimeoutEvent> {

        public static final Logger LOGGER = LoggerFactory.getLogger(
                ConnectionAcquireTimeoutEventListener.class);

        public ConnectionAcquireTimeoutEventListener() {
            super(ConnectionAcquireTimeoutEvent.class);
        }

        @Override
        public void on(ConnectionAcquireTimeoutEvent event) {
            LOGGER.info("Caught event {}", event);
        }
    }

    @Autowired
    private PoolingDataSource poolingDataSource;

    @Value("${flexy.pool.uniqueId}")
    private String uniqueId;



    @Bean
    public Configuration<PoolingDataSource> configuration() {
        return new Configuration.Builder<PoolingDataSource>(
                uniqueId,
                poolingDataSource,
                BitronixPoolAdapter.FACTORY
        )
        .setMetricsFactory(MetricsFactoryResolver.INSTANCE.resolve())
        .setConnectionProxyFactory(ConnectionDecoratorFactoryResolver.INSTANCE.resolve())
        .setMetricLogReporterMillis(TimeUnit.SECONDS.toMillis(5))
        .setMetricNamingUniqueName(UniqueNamingStrategy.INSTANCE)
        .setJmxEnabled(true)
        .setJmxAutoStart(true)
        .setConnectionAcquireTimeThresholdMillis(50L)
        .setConnectionLeaseTimeThresholdMillis(250L)
        .setEventListenerResolver(new EventListenerResolver() {
            @Override
            public List<EventListener<? extends Event>> resolveListeners() {
                return Arrays.<EventListener<? extends Event>>asList(
                    new ConnectionAcquireTimeoutEventListener(),
                    new ConnectionAcquireTimeThresholdExceededEventListener(),
                    new ConnectionLeaseTimeThresholdExceededEventListener()
                );
            }
        })
        .build();
    }

    @Bean(initMethod = "start", destroyMethod = "stop")
    public FlexyPoolDataSource dataSource() {
        Configuration<PoolingDataSource> configuration = configuration();
        return new FlexyPoolDataSource<PoolingDataSource>(configuration,
                new IncrementPoolOnTimeoutConnectionAcquiringStrategy.Factory(5),
                new RetryConnectionAcquiringStrategy.Factory(2)
        );
    }
}
Parameter name Parameter type Optional Default value Description

uniqueName

Supplied through Configuration.Builder constructor

false

N/A

Each FlexyPool instance requires a unique name so that JMX domains won’t clash

targetDataSource

Supplied through Configuration.Builder constructor

false

N/A

The target DataSource we are monitoring

poolAdapterFactory

Supplied through Configuration.Builder constructor

false

N/A

The specific pool adaptor factory associated with the target DataSource

metricsFactory

Supplied through Configuration.Builder setMetricsFactory

true

DropwizardMetrics.FACTORY

The metrics factory allows customizing the metrics implementation. A Dropwizard Metrics implementation is being supplied

connectionProxyFactory

Supplied through Configuration.Builder setConnectionProxyFactory

true

ConnectionDecoratorFactory.INSTANCE

The connection proxy provider. You can choose between decorating Connections and a JDK Dynamic Proxies implementation

jmxEnabled

Supplied through Configuration.Builder setJmxEnabled

true

true

Specifies if the JMX service is enabled

metricLogReporterMillis

Supplied through Configuration.Builder setMetricLogReporterMillis

true

TimeUnit.MINUTES.toMillis(5)

Specifies the metrics log reported interval

metricNamingUniqueName

Supplied through Configuration.Builder setMetricNamingUniqueName

true

com.vladmihalcea.flexypool.strategy.DefaultNamingStrategy

Specifies a class name that implements the MetricNamingStrategy interface

eventListenerResolver

Supplied through Configuration.Builder setEventListenerResolver

true

N/A

Specifies a class name responsible for supplying the EventListener implementations

connectionAcquireTimeThresholdMillis

Supplied through Configuration.Builder setConnectionAcquireTimeThresholdMillis

true

Long.MAX_VALUE

Specifies a time threshold for the connection acquire request. When the time limit is exceeded a log entry will be generated and a ConnectionAcquireTimeThresholdExceededEvent will be published.

connectionLeaseTimeThresholdMillis

Supplied through Configuration.Builder setConnectionLeaseTimeThresholdMillis

true

Long.MAX_VALUE

Specifies a time threshold for the connection lease. When the time limit is exceeded a log entry will be generated and a ConnectionLeaseTimeThresholdExceededEvent will be published.

Data Source

@Bean(initMethod = "start", destroyMethod = "stop")
public FlexyPoolDataSource dataSource() {
	Configuration<HikariDataSource> configuration = configuration();
	return new FlexyPoolDataSource<HikariDataSource>(configuration,
			new IncrementPoolOnTimeoutConnectionAcquiringStrategy.Factory(5),
			new RetryConnectionAcquiringStrategy.Factory(2)
	);
}
Parameter name Optional Description

configuration

false

Each FlexyPool instance requires it’s own configuration

strategies

true

The strategies will be applied when fetching a connection from the target connection pool. You can set up any number of strategies and they will be applied in the same order they were supplied

Strategies

A Strategy is a connection acquiring safety mechanisms, a resort that’s called when a connection is not successfully fetched from the target Connection Pool. You can define your own Strategies since this is a trivial job to do.

FlexyPool comes with the following default strategies

IncrementPoolOnTimeoutConnectionAcquiringStrategy

This strategy will increment the target connection pool maximum size on connection acquire timeout.

Parameter name Optional Description

maxOverflowPoolSize

false

This is the maximum limit a target connection pool can stretch to

timeoutMillis

true

If the connection acquiring time takes more than this value a pool size increment is attempted. If this value is not supplied, the target pool timeout will be used instead, so that when the target pool throws a timeout exception the pool size increment is attempted.

The connection pool has a minimum size and on demand it can grow up to its maximum size. The overflow is a buffer of extra connections allowing the connection pool to grow beyond its initial maximum size. Whenever a connection acquire timeout is detected, the current request won’t fail if the pool hasn’t grown to it’s maxOverflowPoolSize.

OverFlowPoolSize

It’s safe to set the target connection pool acquiring timeout interval to a value that’s appropriate for your application wait expectations. You might also set the connection expiring interval to free up unused connections, releasing them when they are no longer required.

A DBCP example setting the initial maximum size and the acquiring timeout interval.

<bean id="poolingDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
	<property name="driverClassName" value="${jdbc.driverClassName}" />
	<property name="url" value="${jdbc.url}" />
	<property name="username" value="${jdbc.username}" />
	<property name="password" value="${jdbc.password}" />
	<property name="maxActive" value="3"/>
	<property name="maxWait" value="100"/>
</bean>

RetryConnectionAcquiringStrategy

This strategy is useful for those connection pools lacking a connection acquiring retry mechanism

Parameter name Optional Description

retryAttempts

false

This is the maximum number of retries attempts before giving up

Event Handling

You can register your own event handlers for the following events:

  • ConnectionAcquireTimeoutEvent (generated when a connection acquire timeout exception is thrown)

  • ConnectionAcquireTimeThresholdExceededEvent (generated when the connection acquire threshold given by the connectionAcquireTimeThresholdMillis configuration property is exceeded)

  • ConnectionLeaseTimeThresholdExceededEvent (generated when the connection lease threshold given by the connectionLeaseTimeThresholdMillis configuration property is exceeded)

To supply the custom event listeners you should use the eventListenerResolver configuration property.

You should be aware that these events are published synchronously so you should avoid executing some time-consuming event handling logic because that would add an extra overhead to the current transaction and connection holding. If your event handling logic is time-consuming, you should consider submitting the event to an ExecutorService and execute it in one of its own worker threads.

Customized Metrics

An enterprise system must have use an integrated monitoring report tool, such as Ganglia or Graphite and it’s easy to instruct FlexyPool to use a different reporting mechanism than the default ones.

You may wish to customize different Reservoirs for specific Metrics or to use a CSV reporter and this is how you can do it:

@org.springframework.context.annotation.Configuration
public class DataSourceConfiguration {

    @Value("${data.source.type}")
    private String dataSourceType;

    @Value("${data.source.className}")
    private Class<?> dataSourceClassName;

    @Value("${data.source.uniqueName}")
    private String dataSourceUniqueName;

    @Value("${data.source.minPoolSize}")
    private int dataSourceMinPoolSize;

    @Value("${data.source.maxPoolSize}")
    private int dataSourceMaxPoolSize;

    @Value("${data.source.maxIdleTime}")
    private int dataSourceMaxIdleTime;

    @Value("${data.source.acquisitionTimeout}")
    private int dataSourceAcquisitionTimeout;

    @Value("${data.source.connectionAcquireTimeoutMillis}")
    private int dataSourceConnectionAcquireTimeoutMillis;

    @Value("${data.source.shareTransactionConnections}")
    private boolean shareTransactionConnections;

    @Value("${data.source.maxOverflow}")
    private int dataSourceMaxOverflow;

    @Value("${data.source.retryAttempts}")
    private int dataSourceRetryAttempts;

    @Value("${jdbc.url}")
    private String dataSourceUrl;

    @Value("${jdbc.username}")
    private String dataSourceUsername;

    @Value("${jdbc.password}")
    private String dataSourcePassword;

    @Value("${app.home}")
    private String applicationHome;

    private Map<String, Properties> dataSourceDriverPropertiesMap;

    public DataSourceConfiguration(Map<String, Properties> dataSourceDriverPropertiesMap) {
        this.dataSourceDriverPropertiesMap = dataSourceDriverPropertiesMap;
    }

    @Bean(initMethod = "init", destroyMethod = "close")
    public PoolingDataSource poolingDataSource() {
        PoolingDataSource poolingDataSource = new PoolingDataSource();
        poolingDataSource.setClassName(dataSourceClassName.getName());
        poolingDataSource.setUniqueName(dataSourceUniqueName);
        poolingDataSource.setMinPoolSize(dataSourceMinPoolSize);
        poolingDataSource.setMaxPoolSize(dataSourceMaxPoolSize);
        poolingDataSource.setMaxIdleTime(dataSourceMaxIdleTime);
        poolingDataSource.setAcquisitionTimeout(dataSourceAcquisitionTimeout);
        poolingDataSource.setShareTransactionConnections(shareTransactionConnections);
        poolingDataSource.setDriverProperties(dataSourceDriverPropertiesMap.get(dataSourceType));
        return poolingDataSource;
    }

    @Bean
    public Configuration<PoolingDataSource> configuration() {
        return new Configuration.Builder<PoolingDataSource>(
                DataSourceConfiguration.class.getSimpleName(),
                poolingDataSource(),
                BitronixPoolAdapter.FACTORY
        )
        .setMetricsFactory(new MetricsFactory() {
            @Override
            public Metrics newInstance(ConfigurationProperties configurationProperties) {
                return new DropwizardMetrics(configurationProperties, new ReservoirFactory() {
                    @Override
                    public Reservoir newInstance(Class<? extends Metric> metricClass, String metricName) {
                        if(Timer.class.isAssignableFrom(metricClass)) {
                            return new ExponentiallyDecayingReservoir(1028 * 8, 0.015);
                        } else if(FlexyPoolDataSource.CONCURRENT_CONNECTIONS_HISTOGRAM.equals(metricName) ||
                                FlexyPoolDataSource.CONCURRENT_CONNECTION_REQUESTS_HISTOGRAM.equals(metricName)) {
                            return new ExponentiallyDecayingReservoir(1028 * 16, 0.015);
                        }
                        return new ExponentiallyDecayingReservoir();
                    }
                }, new MetricsLifeCycleCallback() {

                    private CsvReporter csvReporter;

                    @Override
                    public MetricsLifeCycleCallback init(ConfigurationProperties configurationProperties, MetricRegistry metricRegistry) {
                        File metricsFolder = new File(applicationHome, "metrics");
                        metricsFolder.mkdirs();
                        csvReporter = CsvReporter.forRegistry(metricRegistry)
                                .build(metricsFolder);
                        return this;
                    }

                    @Override
                    public void start() {
                        csvReporter.start(15, TimeUnit.SECONDS);
                    }

                    @Override
                    public void stop() {
                        csvReporter.stop();
                    }
                });
            }
        })
        .build();
    }

    @Bean(initMethod = "start", destroyMethod = "stop")
    public FlexyPoolDataSource dtfDataSource() {
        Configuration<PoolingDataSource> configuration = configuration();
        return new FlexyPoolDataSource<PoolingDataSource>(configuration,
                new IncrementPoolOnTimeoutConnectionAcquiringStrategy.Factory(dataSourceMaxOverflow,
                        dataSourceConnectionAcquireTimeoutMillis),
                new RetryConnectionAcquiringStrategy.Factory(dataSourceRetryAttempts)
        );
    }
}