The Builder pattern and the Spring framework

Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Wouldn’t that be just awesome?

Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.

So, enjoy spending your time on the things you love rather than fixing performance issues in your production system on a Saturday night!

Introduction

In this article, we are going to see how we can use the Builder pattern when creating beans with the Spring framework.

I like to make use of the builder pattern whenever an object has both mandatory and optional properties. But building objects is usually the Spring framework responsibility, so let’s see how you can employ it using both Java and XML-based Spring configurations.

A Builder example

Let’s start with the following Builder class.

public final class Configuration<T extends DataSource> 
         extends ConfigurationProperties<T, Metrics, PoolAdapter<T>> {

    public static final long DEFAULT_METRIC_LOG_REPORTER_MILLIS = TimeUnit.MINUTES.toMillis(5);

    public static class Builder<T extends DataSource> {
    
        private final String uniqueName;
        private final T targetDataSource;
        private final PoolAdapterFactory<T> poolAdapterFactory;
        private MetricsFactory metricsFactory;
        private ConnectionProxyFactory connectionProxyFactory = ConnectionDecoratorFactoryResolver.INSTANCE.resolve();
        private boolean jmxEnabled = true;
        private boolean jmxAutoStart = false;
        private long metricLogReporterMillis = DEFAULT_METRIC_LOG_REPORTER_MILLIS;
        private EventListenerResolver eventListenerResolver;
        private long connectionAcquireTimeThresholdMillis = Long.MAX_VALUE;
        private long connectionLeaseTimeThresholdMillis = Long.MAX_VALUE;

        public Builder(
                String uniqueName, 
                T targetDataSource, 
                PoolAdapterFactory<T> poolAdapterFactory) {
            this.uniqueName = uniqueName;
            this.targetDataSource = targetDataSource;
            this.poolAdapterFactory = poolAdapterFactory;
        }

        public Builder<T> setMetricsFactory(
                MetricsFactory metricsFactory) {
            this.metricsFactory = metricsFactory;
            return this;
        }

        public Builder<T> setConnectionProxyFactory(
                ConnectionProxyFactory connectionProxyFactory) {
            this.connectionProxyFactory = connectionProxyFactory;
            return this;
        }

        public Builder<T> setJmxEnabled(
                boolean enableJmx) {
            this.jmxEnabled = enableJmx;
            return this;
        }

        public Builder<T> setJmxAutoStart(
                boolean jmxAutoStart) {
            this.jmxAutoStart = jmxAutoStart;
            return this;
        }

        public Builder<T> setMetricLogReporterMillis(
                long metricLogReporterMillis) {
            this.metricLogReporterMillis = metricLogReporterMillis;
            return this;
        }

        public Builder<T> setEventListenerResolver(
                EventListenerResolver eventListenerResolver) {
            this.eventListenerResolver = eventListenerResolver;
            return this;
        }

        public Builder<T> setConnectionAcquireTimeThresholdMillis(
                Long connectionAcquireTimeThresholdMillis) {
            if (connectionAcquireTimeThresholdMillis != null) {
                this.connectionAcquireTimeThresholdMillis = connectionAcquireTimeThresholdMillis;
            }
            return this;
        }

        public Builder<T> setConnectionLeaseTimeThresholdMillis(
                Long connectionLeaseTimeThresholdMillis) {
            if (connectionLeaseTimeThresholdMillis != null) {
                this.connectionLeaseTimeThresholdMillis = connectionLeaseTimeThresholdMillis;
            }
            return this;
        }
        
        public Configuration<T> build() {
            EventPublisher eventPublisher = EventPublisher.newInstance(eventListenerResolver);
            Configuration<T> configuration = new Configuration<T>(
                uniqueName, 
                targetDataSource, 
                eventPublisher
            );
            configuration.setJmxEnabled(jmxEnabled);
            configuration.setJmxAutoStart(jmxAutoStart);
            configuration.setMetricLogReporterMillis(metricLogReporterMillis);
            configuration.setConnectionAcquireTimeThresholdMillis(connectionAcquireTimeThresholdMillis);
            configuration.setConnectionLeaseTimeThresholdMillis(connectionLeaseTimeThresholdMillis);
            if(metricsFactory == null) {
                metricsFactory = MetricsFactoryResolver.INSTANCE.resolve();
            }
            configuration.metrics = metricsFactory.newInstance(configuration);
            configuration.poolAdapter = poolAdapterFactory.newInstance(configuration);
            configuration.connectionProxyFactory = connectionProxyFactory;
            return configuration;
        }
    }

    private final T targetDataSource;
    private Metrics metrics;
    private PoolAdapter<T> poolAdapter;
    private ConnectionProxyFactory connectionProxyFactory;

    private Configuration(
            String uniqueName, 
            T targetDataSource, 
            EventPublisher eventPublisher) {
        super(uniqueName, eventPublisher);
        this.targetDataSource = targetDataSource;
    }

    public T getTargetDataSource() {
        return targetDataSource;
    }

    public Metrics getMetrics() {
        return metrics;
    }

    public PoolAdapter<T> getPoolAdapter() {
        return poolAdapter;
    }

    public ConnectionProxyFactory getConnectionProxyFactory() {
        return connectionProxyFactory;
    }
}

Java-based configuration

If you’re using Spring Java-based configuration then this is how you’d do it:

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

    @Autowired
    private AbstractDataSourceBean poolingDataSource;

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

    @Bean
    public Configuration<AbstractDataSourceBean> configuration() {
        return new Configuration.Builder<>(
            uniqueId,
            poolingDataSource,
            AtomikosPoolAdapter.FACTORY
        )
        .setJmxEnabled(true)
        .setMetricLogReporterMillis(TimeUnit.SECONDS.toMillis(5))
        .build();
    }

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

Awesome, right?

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

And there is more!

You can earn a significant passive income stream from promoting all these amazing products that I have been creating.

If you're interested in supplementing your income, then join my affiliate program.

Conclusion

You can make use of the Builder pattern no matter the Spring configuration mode you’ve already chosen. If you have doubts about its usefulness, here are three compelling reasons you should be aware of.

Transactions and Concurrency Control eBook

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.