简体   繁体   English

拦截 Spring 数据中的存储库方法调用,动态优化查询

[英]Intercept repository method calls in Spring Data, refining the query on the fly

Say I've got a few interfaces extending CRUDRepositor .假设我有一些扩展CRUDRepositor的接口。 There are methods in there like findByField .那里有诸如findByField类的方法。 Some of these methods should only return entities that belong to a group of entities to which the user has access (the group is a column in the database, so it's a field that's defined for most entities).其中一些方法应该只返回属于用户有权访问的一组实体的实体(该group是数据库中的一个列,因此它是为大多数实体定义的字段)。 I want to achieve this by allowing the use of annotations (like @Protected) on the repository methods, and then when these methods are called instead of calling findByField a method findByFieldAndGroup is called behind the scenes.我想通过允许在存储库方法上使用注释(如@Protected)来实现这一点,然后当调用这些方法而不是调用findByField时,会在后台调用方法findByFieldAndGroup With the use of AOP (which intercepts methods annotated with my @Protected tag) the group can be assigned before the method is effectively executed.通过使用 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();

}

Is there a way to achieve this?有没有办法做到这一点? In the worst case I either add all the methods manually, or completely switch to a query by example approach (where you can more easily add the group dynamically) or generate methods with a Java agent using ASM (manipulating the bytecode)... but these are much less practical approaches which demand a good deal of refactoring.在最坏的情况下,我要么手动添加所有方法,要么完全切换到通过示例方法查询(您可以更轻松地动态添加组)或使用 ASM(操作字节码)使用 Java 代理生成方法......但是这些是不太实用的方法,需要大量的重构。

Edit: found these relevant questions Spring data jpa - modifying query before execution Spring Data JPA and spring-security: filter on database level (especially for paging) Other relevant references include 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 . Edit: found these relevant questions Spring data jpa - modifying query before execution Spring Data JPA and spring-security: filter on database level (especially for paging) Other relevant references include this ticket on GitHub (no progress, only a sort-of-solution with QueryDSL这排除了使用基于方法名称的查询)和这个线程

You can use filters , a specific Hibernate feature, for this problem.您可以使用过滤器,一个特定的 Hibernate 功能来解决这个问题。

The idea is the following.思路如下。

First, you need to annotate your entity with the different filters you want to apply, in your case, something like:首先,您需要使用要应用的不同过滤器来注释您的实体,在您的情况下,例如:

@Entity
//...
@Filters({
  @Filter(name="filterByGroup", condition="group_id = :group_id")
})
public class MyEntity implements Serializable { 
  // ...
}

Then, you need access to the underlying EntityManager because you need to interact with the associated Hibernate Session .然后,您需要访问底层的EntityManager ,因为您需要与关联的 Hibernate Session进行交互。 You have several ways to do this.你有几种方法可以做到这一点。 For example, you can define a custom transaction manager for the task, something like:例如,您可以为任务定义自定义事务管理器,例如:

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;
  }
}

Then, register this TransationManager in your database configuration instead of the default JpaTransactionManager :然后,在您的数据库配置中注册这个TransationManager而不是默认的JpaTransactionManager

@Bean
public PlatformTransactionManager transactionManager() {
  JpaTransactionManager transactionManager = new FilterAwareJpaTransactionManager();
  transactionManager.setEntityManagerFactory(entityManagerFactory());
  return transactionManager;
}

You can also have access to the EntityManager and associated Session by creating a custom JpaRepository or by injecting @PersistenceContext in your beans, but I think the above-mentioned approach is the simpler one although it has the drawback of being always applied.您还可以通过创建自定义JpaRepository或在您的 bean 中注入@PersistenceContext来访问EntityManager和关联的Session ,但我认为上述方法更简单,尽管它具有始终应用的缺点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM