0

I have a hibernate filter defined on my entities which I am injecting using aspect for all repositories which implements my TenantableRepository.

The issue is that the filter is not getting injected in the repository calls executed within CompletetableFuture method. But filter is getting injected properly outside it. I understand that the threads are different but aspect is being called both the times though.

I want the filter to get enabled from all flows. API or async processes.

Here is the Aspect I have defined to work on all TenantableRepositories' methods starting with find.

@Aspect
@Component
@Slf4j
public class TenantFilterAspect {

    @PersistenceContext
    private EntityManager entityManager;

    @Before("execution(* com.demo.repository.TenantableRepository+.find*(..))")
    public void beforeFindOfTenantableRepository() {
        log.info("Called aspect");
        entityManager
                .unwrap(Session.class)
                .enableFilter(Tenantable.TENANT_FILTER_NAME)
                .setParameter(Tenantable.TENANT_COLUMN, TenantContext.getTenantId());
    }
}

I have a controller to test the flow. Here I am making 2 repository calls. One in main thread and one under async CompletetableFuture method.

import org.springframework.beans.factory.annotation.Autowired;

@RestController
@Slf4j
@RequestMapping(value = "/v1/api/test", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public class TestController {

    @Autowired
    MyEntityRepository myEntityRepository;

    @RequestMapping(value = "/aysnc", method = RequestMethod.GET, consumes = MediaType.ALL_VALUE)
    public @ResponseBody
    ResponseEntity<APIResponse> testAsync(HttpServletRequest httpServletRequest) throws InterruptedException {
        Optional<MyEntity> entity = myEntityRepository.findByFirstName("Firstname");
        if(!entity.isEmpty()){
            log.info("1. Main: found entity:{}",entity.get());
        }
        CompletableFuture.runAsync(this::callAsyncMethod);
        return ResponseEntity.status(HttpStatus.OK)
                .body("Ok");
    }

    public void callAsyncMethod() {
        Optional<MyEntity> entity = myEntityRepository.findByFirstName("Firstname");
        if(!entity.isEmpty()){
            log.info("2. Async: found entity:{}",entity.get());
        }
    }

}

The issue is that the filter is not getting injected in the repo call under async CompletetableFuture method callAsyncMethod(). But filter is getting injected properly in first repo call before async method. I understand that the threads are different but aspect is being called both the times. I get the log printed but the filter is still not enabled.

What am I doing wrong here?

5
  • Let me guess your TenantContext.getTenantId is based on a ThreadLocal. What do you think happens to the value bound to thread 1 when you execute in thread 2. Commented Sep 21, 2023 at 11:41
  • @M.Deinum TenantContext is InheritableThreadLocal. And I see tenant context available with thread 2 also. Commented Sep 21, 2023 at 13:54
  • I doubt it still is there as, if you are unlucky, the thread already cleaned up the resources before the other one executes. Commented Sep 21, 2023 at 14:17
  • @M.Deinum I am able to log tenant ID in the thread 2 when aspect gets called. Commented Sep 21, 2023 at 14:51
  • The problem is, I suspect, the fact that you have multiple entitymanagers. As there is no transactional bound entitymanger, you get 2 different ones. 1 for the aspect and one for the repository. This isn't the case in the normal execution as by default open-entitymanager in view is applied and you have 1 entitymanager. If you would call this in an @Transactional service method it would work as it would use a shared entitymanager. I suspect the same would happen if you set spring.jpa.open-in-view=false in your application.properties. Commented Sep 26, 2023 at 5:59

1 Answer 1

0

Spring Boot by default enables the Open Entitymanager In View pattern. Which means for the whole duration of the HTTP Request there is 1 single EntityManager available. This EntityManager is bound to the request handling thread using a ThreadLocal.

Now when executing the first findByFirstName it will operate on this shared EntityManager.

Your second call, which is done on a different thread doesn't see this shared EntityManager. As there is also no transactional context, which would create another thread bound EntityManager, the Aspect will get a fresh EntityManager. However the EntityManager used by the Aspect is discarded right after that because, as mentioned, no transactional context. Next it reaches the actual repository method, which doesn't see an active EntityManager either and gets a fresh one as well (without any filters applied because those were applied to another EntityManager instance).

When you set the spring.jpa.open-in-view property to false I the same will happen with the first call to findByFirstName as that disables the shared EntityManager.

The problem with this code is that this should be called from inside an @Transactional service method. The @Transactional will lead to the creation of a shared (thread-bound) EntityManager for the duration of the transaction.

@Service
public MyEntityService {

  private final MyEntityRepository repo;

  public MyEntityService(MyEntityRepository repo) {
    this.repo = repo;
  }
  

  @Transactional
  public Optional<MyEntity> findByFirstname(String firstname) {
    return repo.findByFirstName(firstname);
  }
}

Now if you would inject this service into your controller (as you should have done anyway), it will work due to the shared EntityManager for each call due to the @Transactional.

Sign up to request clarification or add additional context in comments.

9 Comments

Thanks for that clarification. Now I understand that EntityManagers are different between both the threads. But I am still unclear on why Aspect is being called twice? Is it called twice with the same first EntityManager? Does that mean Aspect cannot be called outside the REST API scope ?
Why shouldn't it be called twice? YOu are executing the method twice. But in total there are 3 EntityManager instancs in play. The first invocation of findByFirstName is part of the request procssing thread which uses 1 EntityManager due to open-entitymanager-in-view. The second call to findByFirstName uses 2 EntityManager s one for the aspect (which is discarded afterwards) and one for the actual findByFirstName method (which doesn't have the filter applied obviously because it is a fresh one).
Why 2nd EntityManager gets discarded? And even if new EntityManagers are being created, why filters don't get applied to those?
Why should the get applied for each entitymanager?. The filter is applied before the execution of the findByFirstName method on the repository. That is exactly what it is doing, as there is no point to keep the entitymanager open (no transactional context) it gets discarded right after executing the aspect. As soon as the actual method on the repository is invoked that retrieves a new one (because there wasn't one anyway). Which is why you should always use this in a transactional context (as explained in the answer!).
Ok so are you saying that for the second thread, there is no attached default Transaction and so even if we do multiple findByFirstName calls within that same child thread then all will use its own new EntityManagers? And aspect will be called each time but that EntityManagers gets closed and is not reused by repo call. If I use @Transactional annotation issue will get resolved. But is there something which I can configure at framework level or commonly so not every team member has to remember to put @Transactional?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.