0

I have configured multiple database following this link https://fable.sh/blog/splitting-read-and-write-operations-in-spring-boot/. DB call is routed based on the Transactional annotation value. Call to DB is happening correctly till we try to make a nested call.

In my service class, I have 2 methods. One is performing read operation using Replica Server and another is performing write operation using Primary Server. But when I try to call read method inside write method, both DB calls routed through Primary server only. Below is code snippet for Service class

@Service
public class MyService {
    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
    
    @Transactional(readOnly = true) //Make a call to Replica Server
    public List<String> replica() {
        String sqlQuery = "SELECT QUERY";
        return namedParameterJdbcTemplate.query(sqlQuery, (rs, rowNum) -> rs.getString(1));
    }
    
    @Transactional(readOnly = false) //Make a call to Primary Server
    public List<String> primary() {
        String sqlQuery = "INSERT QUERY";
        namedParameterJdbcTemplate.update(sqlQuery, params);
        
        return replica(); //DB call should be made to Replica Server but it's calling Primary Server
    }

}

Below is AOP configured to route DB calls to different server based on Transactional annotation

@Aspect
@Component
@Order(0)
@Slf4j
public class DataSourceRouteInterceptor {

    @Around("@annotation(org.springframework.transaction.annotation.Transactional)")
    public Object proceed(ProceedingJoinPoint call) throws Throwable {
        MethodSignature signature = (MethodSignature) call.getSignature();
        Method method = signature.getMethod();

        Transactional tx = method.getAnnotation(Transactional.class);
        try {
            if (tx.readOnly()) {
                RoutingDataSource.setReplicaRoute();
                log.info("Routing DB call to Replica Server");
            }
            return call.proceed();
        } finally {
            RoutingDataSource.clearReplicaRoute();
        }
    }
}

My expectation is when I call replica method inside primary method, DB calls should be routed based on the Transactional annotation value configured on that method.

Harsh Kanakhara
  • 909
  • 4
  • 13
  • 38

1 Answers1

1

You are using Spring AOP, therefore the self-invocation return replica() inside of primary() will never even trigger the aspect, as described in my answer here. You either need to use native AspectJ and set it up correctly, or you need to use the workaround described at the end of my answer under "Solutions", self-injecting the target object and calling the method on the self-injected proxy in order to trigger Spring AOP.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • Never worked on Spring AOP or AspectJ before. Self-Injecting the Object worked for me. Is there any performance drawback of doing this? – Harsh Kanakhara Apr 29 '23 at 04:58
  • 1
    No, I do not think so. The performance drawback compared to native AspectJ is always Spring AOP itself with its dynamic proxies. The self-injection does not matter performance-wise. It is just ugly, because the whole point of AOP is that the core code should be unaware of it. By self-injecting a bean in order to accommodate Spring AOP, you make the corresponding core class AOP-aware, which kind of defeats the whole idea. It is a workaround, and it works - no more, no less. – kriegaex Apr 29 '23 at 07:04