0

I am trying to implement Spring Boot AOP for data-source pointcut - where before running any query I need to set client context in DB connection.

I was trying this approach of using DelegatingDataSource. But I am getting below error during server startup

 org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'dataSource': Requested bean is currently in creation: Is there an unresolvable circular reference?

Please let me know DeletegatingDatasource for JNDI based DB lookup.

Edit 1: AOP - I tried to add pointcut execution(public * javax.sql.DataSource+.getConnection(..)). This works only when Spring datasource is used with username/password. Once i deploy in Jboss with JNDI I am getting WildFlyDataSource Proxy error. So, instead of AOP approach I thought of using DelegatingDatasource

   // AOP Example

    @Pointcut("execution(public * javax.sql.DataSource+.getConnection(..))")
    void prepareConnectionPointcut() {
        logger.debug("prepareConnectionPointcut");
    }
    
    @AfterReturning(pointcut = "prepareConnectionPointcut()", returning = "connection")
    void afterPrepareConnection(Connection connection) {
        // Set context in Connection - return same connection for query execution
    }

But when i deploy this code in JBoss - I am getting WildFlyDataSource datasource bean creation error.

Error creating bean with name 'org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/JndiDataSourceAutoConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class org.jboss.as.connector.subsystems.datasources.WildFlyDataSource: Common causes of this problem include using a final class or a non-visible class; nested exception is org.springframework.cglib.core.CodeGenerationException: java.lang.NoClassDefFoundError-->org/jboss/as/connector/subsystems/datasources/WildFlyDataSource

I have also added proxyTargetClass flag during initialization

@EnableAspectJAutoProxy(proxyTargetClass = true)

Karthikeyan
  • 506
  • 2
  • 8
  • 20
  • That will obviously not work, as you are defining the data source yourself and have a dependency on it it is a circular dependency. Also not sure why you would call this AOP as it is just a proxy. Use a `BeanPostProcessor` to wrap an existing `DataSource` with your delegating implementation. – M. Deinum Jul 30 '20 at 09:50
  • @Deinum: Thanks for your response. I have added reason for terming AOP. Will BeanPostProcessor be called whenever datasource bean is called ? Meaning whenever some query is executed in DAO (through entitymanager.createNativeQuery or Repository.find ? – Karthikeyan Jul 30 '20 at 10:12
  • I suggest you readup on what a `BeanPostProcessor` is and does. – M. Deinum Jul 30 '20 at 11:03
  • I tried to implement `BeanPostProcessor` & understand its execution. It is called first time during initialization or bean. But what i am looking is to aspect pointcut every time before running any queries/storedprocedure to set DB session context. Equivalent of [this](https://docs.spring.io/spring-data/jdbc/old-docs/1.2.1.RELEASE/reference/html/orcl.connection.html) in spring boot – Karthikeyan Jul 30 '20 at 12:30
  • 1
    You clearly don't understand the usage of `BeanPostProcessor` and how to combine it with `DelegatingDataSourc`. Use the `BeanPostProcessor` to wrap the existing datasource with a delegating one, which executes your logic in the `getCOnnection` method. You don't need to use app for this. – M. Deinum Jul 30 '20 at 12:41
  • Thanks for your help @M.Deinum. I am able to implement your suggestion & is working fine in both JBoss JNDI based deployment & Spring Boot Datasource URL based connection. I will update my snippet as answer for reference. – Karthikeyan Jul 31 '20 at 05:19

1 Answers1

0

Thanks @M.Deinum for recommendation of using BeanPostProcessor & Implement DelegatingDatasource for setting client info. Please find snippet below which i have implemented to accomplish this in Spring Boot which works well with JBoos JNDI based connection or Spring Boot URL Datasource connection.

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    private static Logger logger = LoggerFactory.getLogger(MyBeanPostProcessor.class);

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        if (bean instanceof DataSource) {
            // Check DataSource bean initialization & enclose it with DelegatingDataSource 
            logger.debug("MyBeanPostProcessor:: postProcessAfterInitialization:: DataSource");

            DataSource beanDs = (DataSource) bean;
            return new MyDelegateDS(beanDs);
        }

        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof DataSource) {
            logger.debug("MyBeanPostProcessor:: postProcessBeforeInitialization:: DataSource");
        }

        logger.debug("MyBeanPostProcessor:: postProcessBeforeInitialization:: " + beanName);

        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

}

My implementation of DelegatingDataSource to handle each user request to set client context in DB connection session

 public class MyDelegateDS extends DelegatingDataSource {

    private static Logger logger = LoggerFactory.getLogger(MyDelegateDS.class);

    public MyDelegateDS(DataSource delegate) {
        super(delegate);
        logger.debug("MyDelegateDS:: constructor");
    }

    @Override
    public Connection getConnection() throws SQLException {
        logger.debug("MyDelegateDS:: getConnection");

        // To do this context only for user Request - to avoid this during Server initialization
        if (RequestContextHolder.getRequestAttributes() != null
                && ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest() != null) {

            logger.debug("MyDelegateDS:: getConnection: valid user request");

            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                    .getRequest();
                    
            // Checking each user request & calling SP to set client context before invoking actual native query/SP
        }

        logger.debug("MyDelegateDS:: getConnection: Not User Request");

        return super.getConnection();
    }

}

Hope this is helpful for someone facing same problem

Karthikeyan
  • 506
  • 2
  • 8
  • 20