拦截 Spring Data 中的存储库方法调用,即时优化查询 [英] Intercept repository method calls in Spring Data, refining the query on the fly

查看:20
本文介绍了拦截 Spring Data 中的存储库方法调用,即时优化查询的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有一些扩展 CRUDRepositor 的接口.那里有像 findByField 这样的方法.其中一些方法应该只返回属于用户有权访问的一组实体的实体(group 是数据库中的一列,因此它是为大多数实体定义的字段).我想通过允许在存储库方法上使用注释(如@Protected)来实现这一点,然后当调用这些方法而不是调用 findByField 时,方法 findByFieldAndGroup 是在幕后调用.使用 AOP(拦截用我的 @Protected 标记注释的方法)可以在方法有效执行之前分配组.

Say I've got a few interfaces extending CRUDRepositor. There are methods in there like 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). 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. With the use of AOP (which intercepts methods annotated with my @Protected tag) the group can be assigned before the method is effectively executed.

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();

}

有没有办法做到这一点?在最坏的情况下,我要么手动添加所有方法,要么完全切换到示例查询方法(您可以更轻松地动态添加组)或使用 ASM 使用 Java 代理生成方法(操作字节码)……但这些方法不太实用,需要大量重构.

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.

发现这些相关问题Spring data jpa - 执行前修改查询Spring Data JPA 和 spring-安全性:在数据库级别过滤(尤其是分页)其他相关参考资料包括 GitHub 上的这张票(没有进展,QueryDSL 的一种解决方案,它排除了使用基于方法名称的查询)和 这个主题.

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.

推荐答案

您可以使用 过滤器,一个特定的 Hibernate 特性,用于解决这个问题.

You can use filters, a specific Hibernate feature, for this problem.

想法如下.

首先,您需要使用要应用的不同过滤器对实体进行注释,在您的情况下,例如:

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 { 
  // ...
}

然后,您需要访问底层的EntityManager,因为您需要与关联的Hibernate Session 进行交互.您有几种方法可以做到这一点.例如,您可以为任务定义一个自定义事务管理器,例如:

Then, you need access to the underlying EntityManager because you need to interact with the associated 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;
  }
}

然后,在您的数据库配置中注册这个 TransationManager 而不是默认的 JpaTransactionManager:

Then, register this TransationManager in your database configuration instead of the default JpaTransactionManager:

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

您还可以通过创建自定义JpaRepository 或注入@PersistenceContextEntityManager 和关联的Sessioncode> 在你的 bean 中,但我认为上述方法是更简单的方法,尽管它有总是被应用的缺点.

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.

这篇关于拦截 Spring Data 中的存储库方法调用,即时优化查询的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆