拦截 Spring 数据中的存储库方法调用,动态优化查询
Intercept repository method calls in Spring Data, refining the query on the fly
假设我有几个扩展 CRUDRepositor
的接口。那里有像 findByField
这样的方法。其中一些方法应该只 return 属于用户有权访问的一组实体的实体(group
是数据库中的一列,因此它是为大多数实体定义的字段)。我想通过允许在存储库方法上使用注释(如@Protected)来实现这一点,然后在调用这些方法而不是调用 findByField
时在幕后调用方法 findByFieldAndGroup
。使用 AOP(拦截用我的@Protected 标记注释的方法)可以在方法有效执行之前分配组。
public interface MyRepository extends CRUDRepository<MyEntity,long> {
@Protected
Optional<MyEntity> findById(Long id); // Should become findByIdAndGroup(Long id, String group) behind the scenes
@Protected
Collection<MyEntity> findAll();
}
有办法实现吗?在最坏的情况下,我要么手动添加所有方法,要么完全切换到 示例查询 方法(您可以更轻松地动态添加组)或使用 [=28 生成方法=] 使用 ASM 的代理(操纵字节码)...但是这些方法不太实用,需要大量重构。
编辑:找到了这些相关问题Spring data jpa - modifying query before execution
Spring Data JPA and spring-security: filter on database level (especially for paging)
其他相关参考资料包括 this ticket on GitHub (no progress, only a sort-of-solution with QueryDSL which precludes the use of queries based on method names) and this thread.
您可以使用 filters 特定的 Hibernate 功能来解决此问题。
思路如下
首先,您需要使用要应用的不同过滤器来注释您的实体,在您的情况下,类似于:
@Entity
//...
@Filters({
@Filter(name="filterByGroup", condition="group_id = :group_id")
})
public class MyEntity implements Serializable {
// ...
}
然后,您需要访问底层 EntityManager
,因为您需要与关联的 Hibernate Session
进行交互。您有几种方法可以做到这一点。例如,您可以为任务定义自定义事务管理器,类似于:
public class FilterAwareJpaTransactionManager extends JpaTransactionManager {
@Override
protected EntityManager createEntityManagerForTransaction() {
final EntityManager entityManager = super.createEntityManagerForTransaction();
// Get access to the underlying Session object
final Session session = entityManager.unwrap(Session.class);
// Enable filter
try{
this.enableFilterByGroup(session);
}catch (Throwable t){
// Handle exception as you consider appropriate
t.printStackTrace();
}
return entityManager;
}
private void enableFilterByGroup(final Session session){
final String group = this.getGroup();
if (group == null) {
// Consider logging the problem
return;
}
session
.enableFilter("filterByGroup")
.setParameter("group_id", group)
;
}
private String getGroup() {
// You need access to the user information. For instance, in the case of Spring Security you can try:
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return null;
}
// Your user type
MyUser user = (MyUser)authentication.getPrincipal();
String group = user.getGroup();
return group;
}
}
然后,在您的数据库配置中注册这个 TransationManager
而不是默认的 JpaTransactionManager
:
@Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new FilterAwareJpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory());
return transactionManager;
}
您还可以通过创建自定义 JpaRepository
或通过在 bean 中注入 @PersistenceContext
来访问 EntityManager
和关联的 Session
,但我认为以上-提到的方法是更简单的方法,尽管它有总是被应用的缺点。
假设我有几个扩展 CRUDRepositor
的接口。那里有像 findByField
这样的方法。其中一些方法应该只 return 属于用户有权访问的一组实体的实体(group
是数据库中的一列,因此它是为大多数实体定义的字段)。我想通过允许在存储库方法上使用注释(如@Protected)来实现这一点,然后在调用这些方法而不是调用 findByField
时在幕后调用方法 findByFieldAndGroup
。使用 AOP(拦截用我的@Protected 标记注释的方法)可以在方法有效执行之前分配组。
public interface MyRepository extends CRUDRepository<MyEntity,long> {
@Protected
Optional<MyEntity> findById(Long id); // Should become findByIdAndGroup(Long id, String group) behind the scenes
@Protected
Collection<MyEntity> findAll();
}
有办法实现吗?在最坏的情况下,我要么手动添加所有方法,要么完全切换到 示例查询 方法(您可以更轻松地动态添加组)或使用 [=28 生成方法=] 使用 ASM 的代理(操纵字节码)...但是这些方法不太实用,需要大量重构。
编辑:找到了这些相关问题Spring data jpa - modifying query before execution Spring Data JPA and spring-security: filter on database level (especially for paging) 其他相关参考资料包括 this ticket on GitHub (no progress, only a sort-of-solution with QueryDSL which precludes the use of queries based on method names) and this thread.
您可以使用 filters 特定的 Hibernate 功能来解决此问题。
思路如下
首先,您需要使用要应用的不同过滤器来注释您的实体,在您的情况下,类似于:
@Entity
//...
@Filters({
@Filter(name="filterByGroup", condition="group_id = :group_id")
})
public class MyEntity implements Serializable {
// ...
}
然后,您需要访问底层 EntityManager
,因为您需要与关联的 Hibernate Session
进行交互。您有几种方法可以做到这一点。例如,您可以为任务定义自定义事务管理器,类似于:
public class FilterAwareJpaTransactionManager extends JpaTransactionManager {
@Override
protected EntityManager createEntityManagerForTransaction() {
final EntityManager entityManager = super.createEntityManagerForTransaction();
// Get access to the underlying Session object
final Session session = entityManager.unwrap(Session.class);
// Enable filter
try{
this.enableFilterByGroup(session);
}catch (Throwable t){
// Handle exception as you consider appropriate
t.printStackTrace();
}
return entityManager;
}
private void enableFilterByGroup(final Session session){
final String group = this.getGroup();
if (group == null) {
// Consider logging the problem
return;
}
session
.enableFilter("filterByGroup")
.setParameter("group_id", group)
;
}
private String getGroup() {
// You need access to the user information. For instance, in the case of Spring Security you can try:
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return null;
}
// Your user type
MyUser user = (MyUser)authentication.getPrincipal();
String group = user.getGroup();
return group;
}
}
然后,在您的数据库配置中注册这个 TransationManager
而不是默认的 JpaTransactionManager
:
@Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new FilterAwareJpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory());
return transactionManager;
}
您还可以通过创建自定义 JpaRepository
或通过在 bean 中注入 @PersistenceContext
来访问 EntityManager
和关联的 Session
,但我认为以上-提到的方法是更简单的方法,尽管它有总是被应用的缺点。