【问题标题】:Spring Data Rest - Repository inheritance creates strange search endpointsSpring Data Rest - 存储库继承创建奇怪的搜索端点
【发布时间】:2019-04-19 12:38:33
【问题描述】:

基于 stackoverflow 的不同线程,我正在尝试使用 Spring Data Rest 实现软删除行为。基本上,许多 JPA 查询都需要使用 @Query 注释来覆盖。当我在我的实际存储库上使用@Query 和所有@PreAuthorize、@PostFilter 等注释时,这一切都很好,但是我想在我自己的存储库类型中概括软删除,我想从中派生那些通过导出的存储库Spring 数据休息。

这是我所做的: 1) BaseEntity 以便 SoftDeleteRepository 中的 @Query 注释知道如何识别实体类型 2) SoftDeletable 签订关于如何使用软删除标志的合同 3) SoftDeletionRepository 将所有@Query 注释放到方法中 4) TrainingRequestRepository 扩展 SoftDeletionRepository,添加安全注解,然后由 Spring Data Rest 导出。

public interface BaseEntity {
    public Long getId();    
    public void setId(Long id); 
}

public interface SoftDeletable {
    public Boolean getDeleted();
    public void setDeleted(Boolean deleted);
}

@RepositoryRestResource
public interface SoftDeleteRepository<T extends BaseEntity & SoftDeletable, I extends Serializable> extends CrudRepository<T, I> {

    @Query("update #{#entityName} e set e.deleted = true where e.id = ?#{#request.id}")
    @Modifying
    @Override
    public void delete(@Param("request") T entity);

    @Transactional
    @Query("update #{#entityName} e set e.deleted = true where e.id = ?1")
    @Modifying
    @Override
    public void deleteById(I id);

    @Query("update #{#entityName} e set e.deleted = true")
    @Transactional
    @Modifying
    @Override
    public void deleteAll();

    @Query("select e from #{#entityName} e where e.deleted = false")
    @Override
    public Iterable<T> findAll();

    @Transactional(readOnly = true)
    @Query("select e from #{#entityName} e where e.id in ?1 and e.deleted = false")
    @Override
    public Iterable<T> findAllById(Iterable<I> requests);

    @Transactional(readOnly = true)
    @Query("select e from #{#entityName} e where e.id = ?1 and e.deleted = false")
    @Override
    public Optional<T> findById(@Param("id") I id);

    @Transactional(readOnly = true)
    @Query("select e from #{#entityName} e where e.deleted = true")
    public Iterable<T> findDeleted();

    @Override
    @Transactional(readOnly = true)
    @Query("select count(e) from #{#entityName} e where e.deleted = false")
    public long count();

}

@RepositoryRestResource
public interface TrainingRequestRepository extends SoftDeleteRepository<TrainingRequest, Long> {

    @PreAuthorize("hasAuthority('ADMIN') or principal.company.id == #request.owner.id")
    @Override
    public void delete(@Param("request") TrainingRequest request);

    @PreAuthorize("hasAuthority('ADMIN') or requests.?[owner.id != principal.company.id].empty")
    @Override
    public void deleteAll(Iterable<? extends TrainingRequest> entities);

    @PreAuthorize("hasAuthority('ADMIN') or @companyService.isOwnerOfRequest(id, principal)")
    @Override
    public void deleteById(Long id);

    @PreAuthorize("hasAuthority('ADMIN')")
    @Override
    public void deleteAll();

    @PreAuthorize("isFullyAuthenticated()")
    @PostFilter("hasAuthority('ADMIN') or hasAuthority('TRAINER') or filterObject.owner.id == principal.company.id")
    @Override
    public Iterable<TrainingRequest> findAll();

    @PreAuthorize("isFullyAuthenticated()")
    @PostFilter("hasAuthority('ADMIN') or hasAuthority('TRAINER') or !filterObject.owner.?[id == #root.principal.company.id].empty")
    @Override
    public Iterable<TrainingRequest> findAllById(Iterable<Long> requests);

    @PreAuthorize("isFullyAuthenticated()")
    @PostAuthorize("hasAuthority('ADMIN') or hasAuthority('TRAINER') or @ownershipValidator.isOwnerOf(principal.company, returnObject.orElse(null))")
    @Override
    public Optional<TrainingRequest> findById(@Param("id") Long id);

    @PreAuthorize("isFullyAuthenticated()")
    @PostFilter("hasAuthority('ADMIN') or hasAuthority('TRAINER') or filterObject.owner.id == principal.company.id")
    @Query("select e from #{#entityName} e where e.deleted = true")
    public Iterable<TrainingRequest> findDeleted();

    @PreAuthorize("hasAuthority('ADMIN') or (requests.?[id != null].empty or requests.?[owner.id != principal.owner.id].empty)")
    @Override
    public <S extends TrainingRequest> Iterable<S> saveAll(Iterable<S> requests);

    @PreAuthorize("hasAuthority('ADMIN') or (hasAuthority('CUSTOMER') and (#request.id == null or #request.owner.id == principal.owner.id))")
    @Override
    public <S extends TrainingRequest> S save(@Param("request") S request);

}

一切都很好!我可以使用 HTTP DELETE 删除实例,并且可以验证数据库中仅更改了“已删除”标志。甚至安全注释也得到了尊重,因此我们可以得出结论,两个 repos(父和子)中的注释都变得有效。

但是:当我点击存储库的 /search 端点时,我可以看到存储库中提到的所有方法的端点。我看起来像 TrainingRequestRepository 中的所有方法都被列为搜索端点:

curl -s -XGET -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" http://localhost:2222/trainingRequests/search
{
  "_links" : {
    "findById" : {
      "href" : "http://localhost:2222/trainingRequests/search/findById{?id}",
      "templated" : true
    },
    "deleteById" : {
      "href" : "http://localhost:2222/trainingRequests/search/deleteById{?id}",
      "templated" : true
    },
    "count" : {
      "href" : "http://localhost:2222/trainingRequests/search/count"
    },
    "delete" : {
      "href" : "http://localhost:2222/trainingRequests/search/delete{?request}",
      "templated" : true
    },
    "findAllById" : {
      "href" : "http://localhost:2222/trainingRequests/search/findAllById{?requests}",
      "templated" : true
    },
    "findAll" : {
      "href" : "http://localhost:2222/trainingRequests/search/findAll"
    },
    "deleteAll" : {
      "href" : "http://localhost:2222/trainingRequests/search/deleteAll"
    },
    "findOwn" : {
      "href" : "http://localhost:2222/trainingRequests/search/findOwn"
    },
    "findByOwner" : {
      "href" : "http://localhost:2222/trainingRequests/search/findByOwner{?owner}",
      "templated" : true
    },
    "findForeign" : {
      "href" : "http://localhost:2222/trainingRequests/search/findForeign"
    },
    "findByTraining" : {
      "href" : "http://localhost:2222/trainingRequests/search/findByTraining{?training}",
      "templated" : true
    },
    "findDeleted" : {
      "href" : "http://localhost:2222/trainingRequests/search/findDeleted"
    },
    "self" : {
      "href" : "http://localhost:2222/trainingRequests/search"
    }
  }
}

如果有人能指出我的方向,那就太好了!

编辑:问题是:为什么我在 /trainingRequests/search 端点中看到诸如 findAll、delete、deleteAll 等方法,而只有 findDeleted、findByTraining、findForeign、findByOwner、findOwn 应该在列表中。如果没有 SoftDeletionRepository 作为 TrainingRequestRepository 的父级,则它们不会出现在应有的列表中。

【问题讨论】:

  • 您的问题到底是什么,因为我在您发布的内容中没有看到?
  • 我在上面进行了编辑以希望清除
  • 我是否不太清楚为什么只有一部分暴露的端点会出现在列表中。
  • 好的,这是我目前对 SDR 的理解:SDR 采用 JPA 存储库并为其构建 REST 端点。有一个映射将某些 HTTP 请求链接到 JPA 存储库方法。示例:GET /foo/5 转到 findById(Long) 等此外,每个 repo (/foo/search) 都有一个 /search 端点,但只要我不使用自定义查询方法扩展 repo,它就是空的(docs.spring.io/spring-data/jpa/docs/current/reference/html/…)。在上面的示例中,我们在 /search 中看到了非自定义查询方法的方法。现在有意义吗?
  • 文档指出“所有查询方法资源都暴露在搜索资源下”。如果要隐藏或自定义链接,可以在方法上使用 @RepositoryRestResource 注释。

标签: java spring-data-jpa spring-data-rest


【解决方案1】:

问题在于 SpringDataRest 自动为每个模型生成 CRUD 端点,并按照 HATEOS 范式公开它们。

如果您不需要此功能,只需删除 SpringDataRest 依赖项即可。 [编辑] 我刚刚重新阅读了问题标题。 @RepositoryRestResource 是引入自动生成的端点,而不是继承。[/EDIT]

如果您需要此功能,您应该配置要公开的内容。有官方文档here,下面的例子取自here

# Exposes all public repository interfaces but considers @(Repository)RestResource\u2019s `exported flag.
spring.data.rest.detection-strategy=default

# Exposes all repositories independently of type visibility and annotations.
spring.data.rest.detection-strategy=all

# Only repositories annotated with @(Repository)RestResource are exposed, unless their exported flag is set to false.
spring.data.rest.detection-strategy=annotated

# Only public repositories annotated are exposed.
spring.data.rest.detection-strategy=visibility

【讨论】:

  • 也许我误解了你的意思。问题不在于 Spring Data Rest 将 TrainingRequestRepository 导出为休息资源。问题是存在不应该存在的 /search/* 端点。
猜你喜欢
  • 1970-01-01
  • 2016-07-11
  • 1970-01-01
  • 2017-09-07
  • 2015-12-23
  • 2018-11-23
  • 1970-01-01
  • 2017-04-30
  • 1970-01-01
相关资源
最近更新 更多