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?
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
EntityManageravailable. ThisEntityManageris bound to the request handling thread using aThreadLocal.Now when executing the first
findByFirstNameit will operate on this sharedEntityManager.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 boundEntityManager, the Aspect will get a freshEntityManager. However theEntityManagerused 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 activeEntityManagereither and gets a fresh one as well (without any filters applied because those were applied to anotherEntityManagerinstance).When you set the
spring.jpa.open-in-viewproperty tofalseI the same will happen with the first call tofindByFirstNameas that disables the sharedEntityManager.The problem with this code is that this should be called from inside an
@Transactionalservice method. The@Transactionalwill lead to the creation of a shared (thread-bound)EntityManagerfor the duration of the transaction.Now if you would inject this service into your controller (as you should have done anyway), it will work due to the shared
EntityManagerfor each call due to the@Transactional.