Reputation: 1
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?
Upvotes: 0
Views: 658
Reputation: 125202
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
.
Upvotes: 0