Configuring a datasource-proxy in Spring Boot

Have you ever heard about datasource proxies? No worries, it’s not complicated at all.

Let’s say you want to access the database from an application. First you have to configure a DataSource object which creates the actual Connections to the underlying database. With the Connection objects you can execute queries, get the metadata of the database and you can do other things as well.

Of course, not many people are using the Connection class directly as there are many frameworks (for example JPA implementations) which are hiding it for us. Now let’s say you want to add some metrics and measure the execution time of the queries you are sending to the database, or let’s say you wanna log the queries. For this type of task, altering the behavior of the Connection object is the best candidate and it can be easily done by creating a custom DataSource object – which acts as a factory to create Connection objects but now it will create custom Connection objects with the additional logic. However, we are not discarding any of the existing functionality but we are going to extend it using the Proxy design pattern. I don’t want to go into the details of the pattern as there are plenty of good articles about it and it’s relatively simple to understand.

In this article, I’m going to use an existing library called datasource-proxy which has multiple features built-in like query logging, query metrics, slow query detection. Configuring this in a standard application is not complicated and can be done with a few lines of code:

DataSource dataSource = ProxyDataSourceBuilder.create(originalDataSource).logQueryBySlf4j(INFO).build();

where originalDataSource is the actual DataSource object which is creating the Connections.

Configuration issue with Spring Boot

Okay, now we know how to configure the proxy DataSource object. In a standard Spring application one could do the following way:

@Bean 
public DataSource dataSource() {
    DataSource originalDataSource = ...
    DataSource dataSource = ProxyDataSourceBuilder.create(originalDataSource).logQueryBySlf4j(SLF4JLogLevel.INFO).build();
    return dataSource;
}

Spring Boot is a really great tool as it’s easy to use, almost no configuration is necessary and everything is working out-of-the-box. There is no difference with the DataSource objects. Spring Boot is built on top of so called AutoConfigurations which are working when certain classes are on the classpath or certain type of beans are already defined, etc.

If you try to define a custom DataSource bean in your Spring Boot app, the AutoConfiguration will be aware that “oh there is a custom DataSource bean, I don’t want to override that” and it won’t do the automatic configuration. In this case, you have to manually configure a DataSource which is a pain in the ass when we have Spring Boot, right?

BeanPostProcessor for the help

BeanPostProcessors are a great things, you can read more about them here. Long story short, you can define classes implementing the BeanPostProcessor interface that have two methods. The method we care about is the postProcessAfterInitialization which will be called by the Spring container after the a bean is fully configured. Here we can simply check whether the bean instance is a DataSource and if yes, we can simply wire in our custom proxy DataSource object. After we have the class, we can simply register this class as a bean into the container.

@Component
public class DatasourceProxyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
        if (bean instanceof DataSource) {
            DataSource dataSourceBean = (DataSource) bean;
            return ProxyDataSourceBuilder.create(dataSourceBean).logQueryBySlf4j(SLF4JLogLevel.INFO).build();
        }
        return bean;
    }
}

Now, if you execute a simple test which does some database operation you can see the following log message:

Name:dataSource, Time:0, Success:True, Type:Prepared, Batch:False, QuerySize:1, BatchSize:0, 
Query:["insert into persons (name, id) values (?, ?)"], Params:[(Arnold,1)]

This simple trick with the BeanPostProcessor can be easily used by any Spring application whether it’s Boot or not.

The source can be found on GitHub. Feel free to reach me out in case of questions in the comments or on Twitter.

UPDATE: So it seems that this approach is not that good on the long-run as it’s not working with Spring Boot 2.0.0.M3+.

After a bit of discussion with Andy Wilkinson and Dave Syer, I ended up using a CGLib proxy in order to keep the original bean types.

Now the DatasourceProxyBeanPostProcessor looks like this:

@Component
public class DatasourceProxyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
        if (bean instanceof DataSource) {
            ProxyFactory factory = new ProxyFactory(bean);
            factory.setProxyTargetClass(true);
            factory.addAdvice(new ProxyDataSourceInterceptor((DataSource) bean));
            return factory.getProxy();
        }
        return bean;
    }

    private static class ProxyDataSourceInterceptor implements MethodInterceptor {
        private final DataSource dataSource;

        public ProxyDataSourceInterceptor(final DataSource dataSource) {
            this.dataSource = ProxyDataSourceBuilder.create(dataSource).countQuery().logQueryBySlf4j(SLF4JLogLevel.INFO).build();
        }

        @Override
        public Object invoke(final MethodInvocation invocation) throws Throwable {
            Method proxyMethod = ReflectionUtils.findMethod(dataSource.getClass(), invocation.getMethod().getName());
            if (proxyMethod != null) {
                return proxyMethod.invoke(dataSource, invocation.getArguments());
            }
            return invocation.proceed();
        }
    }
}

The updated code can be found on GitHub. In case of questions, feel free to contact me.

22 Replies to “Configuring a datasource-proxy in Spring Boot”

  1. Rentius says:
    1. sycyhy says:
    2. Arnold Galovics says:
  2. Greg says:
    1. Arnold Galovics says:
  3. Raja says:
    1. Arnold Galovics says:
  4. xuqiangYang says:
    1. xuqiangYang says:
      1. Arnold Galovics says:
        1. xuqiangYang says:
          1. Arnold Galovics says:
  5. xuqiangYang says:
  6. Ayub says:
  7. Dany says:

Leave a Reply

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