我在我的实体上定义了一个休眠过滤器,我使用方面为所有实现我的 TenantableRepository 的存储库注入该过滤器。
问题是过滤器没有被注入到在 CompletetableFuture 方法中执行的存储库调用中。但过滤器在其外部正确注入。 我知道线程是不同的,但方面两次都被调用。
我希望从所有流中启用过滤器。 API 或异步进程。
这是我定义的用于处理以 find 开头的所有 TenantableRepositories 方法的方面。
@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());
}
}
我有一个控制器来测试流量。这里我进行了 2 次存储库调用。一个在主线程,一个在异步
CompletetableFuture
方法下。
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());
}
}
}
问题是过滤器没有被注入到异步
CompletetableFuture
方法 callAsyncMethod()
下的存储库调用中。但过滤器在异步方法之前的第一次存储库调用中被正确注入。
我知道线程是不同的,但方面两次都被调用。我打印了日志,但过滤器仍未启用。
我在这里做错了什么?
Spring Boot 默认启用 Open Entitymanager In View 模式。这意味着在 HTTP 请求的整个持续时间内,有 1 个
EntityManager
可用。该 EntityManager
使用 ThreadLocal
绑定到请求处理线程。
现在执行第一个
findByFirstName
时,它将在此共享 EntityManager
上运行。
您的第二个调用是在不同的线程上完成的,看不到这个共享的实体管理器。因此,方面和存储库都独立运行
EntityManager
,不共享。
当您将
spring.jpa.open-in-view
属性设置为 false
时,我怀疑第一次调用 findByFirstName
时也会发生同样的情况,因为这会禁用共享 EntityManager
。
此代码的问题在于,应该从
@Transactional
服务方法内部调用。 @Transactional
将导致在交易期间创建共享 EntityManager
。
@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);
}
}
现在,如果您将此服务注入到控制器中(无论如何您都应该这样做),由于
EntityManager
,每个调用都会共享 @Transactional
,它将起作用。