【问题标题】:RestRepositoryController hide REST repository endpointsRestRepositoryController 隐藏 REST 存储库端点
【发布时间】:2020-11-02 01:32:18
【问题描述】:

我正在使用带有 SDR、HATEOAS、Hibernate 的 Spring Boot 2.3.1。在我的项目中,我通过 Spring Data REST 公开了存储库。 我已经在其他项目中使用了 SDR,但在这个项目中,我的一个控制器有一个奇怪的问题。事实上,如果我在这个控制器中添加一些自定义端点,相关实体的所有默认端点都将被隐藏并且不再可用。

让我解释得更好。我有这个实体:

@EntityListeners(TenantListener.class)
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
public class Tenant extends AbstractEntity {

    @Enumerated(value = EnumType.STRING)
    @Column(nullable = false, updatable = false)
    private TenantType type;

    @NotBlank
    @Column(nullable = false)
    private String fullName;

    @NotBlank
    @Column(nullable = false)
    private String lastName;

    @NotBlank
    @Column(nullable = false)
    private String firstName;

    @Username
    @Size(min = 4, max = 16)
    @Column(nullable = false, unique = true)
    @ColumnTransformer(write = "LOWER(?)")
    private String tenantId;

    //other fields

这是存储库:

@Transactional
@IsManagementUser
public interface TenantRepository extends JpaRepository<Tenant, Long>, JpaSpecificationExecutor {

    @Caching(evict = {
            @CacheEvict(value = "tenants", allEntries = true),
            @CacheEvict(value = "tenants#id", allEntries = true),
            @CacheEvict(value = "tenants#sid", allEntries = true),
            @CacheEvict(value = "tenants#exists", allEntries = true),
    })
    @Override
    <S extends Tenant> S save(S s);

    @Caching(evict = {
            @CacheEvict(value = "tenants", allEntries = true),
            @CacheEvict(value = "tenants#id", allEntries = true),
            @CacheEvict(value = "tenants#sid", allEntries = true),
            @CacheEvict(value = "tenants#exists", allEntries = true),
    })
    @Override
    void deleteById(Long aLong);

  
    @Caching(evict = {
            @CacheEvict(value = "tenants", allEntries = true),
            @CacheEvict(value = "tenants#id", allEntries = true),
            @CacheEvict(value = "tenants#sid", allEntries = true),
            @CacheEvict(value = "tenants#exists", allEntries = true),
    })
    @Modifying
    void deleteByTenantId(String tenantId);


  
    @Cacheable(cacheNames = "tenants#id")
    Optional<Tenant> findByTenantId(@Param("tenantId") String tenantId);

    @Cacheable(cacheNames = "tenants#sid")
    @Query("SELECT sid FROM Tenant t WHERE t.tenantId=:tenantId")
    String findSidByTenantId(@Param("tenantId") String tenantId);

  
    @Cacheable(cacheNames = "tenants#exists")
    @Query("SELECT case WHEN (COUNT(*) > 0)  THEN true ELSE false end FROM Tenant WHERE tenantId=:tenantId")
    boolean existsTenantId(@Param("tenantId") String tenantId);

    @Caching(evict = {
            @CacheEvict(value = "tenants", allEntries = true),
            @CacheEvict(value = "tenants#id", allEntries = true),
            @CacheEvict(value = "tenants#exists", allEntries = true),
    })
    @Modifying
    @Query("UPDATE Tenant t SET t.verified=:verified, t.version=t.version+1, t.lastModifiedDate=UTC_TIMESTAMP() WHERE t.tenantId=:tenantId")
    void setVerified(@Param("tenantId") String tenantId, @Param("verified") boolean verified);

    @Caching(evict = {
            @CacheEvict(value = "tenants", allEntries = true),
            @CacheEvict(value = "tenants#id", allEntries = true),
            @CacheEvict(value = "tenants#exists", allEntries = true),
    })
    @Modifying
    @Query("UPDATE Tenant t SET t.enabled=:enabled, t.version=t.version+1, t.lastModifiedDate=UTC_TIMESTAMP() WHERE t.tenantId=:tenantId")
    void setEnabled(@Param("tenantId") String tenantId, @Param("enabled") boolean enabled);
}

这是 REST 控制器:

@RepositoryRestController
@RequestMapping(path = "/api/v1")
@PreAuthorize("isAuthenticated()")
public class TenantController {

    @Autowired
    private LocalValidatorFactoryBean validator;

    @Autowired
    private TenantRepository tenantRepository;


    @Autowired
    private DbmsManager dbmsManager;

    @Autowired
    private PagedResourcesAssembler pagedResourcesAssembler;

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addValidators(validator);
    }


    @IsManagementUser
    @DeleteMapping(path = "/tenants/{id}")
    public ResponseEntity<?> deleteTenant(@PathVariable("id") Long id) {
        Optional<Tenant> optionalTenant = tenantRepository.findById(id);
        if (optionalTenant.isPresent()) {
            dbmsManager.dropTenant(optionalTenant.get().getTenantId());
        }
        return ResponseEntity.noContent().build();
    }

}

如果我调用端点GET http://localhost/api/v1/tenants,我会得到完整的租户列表。如果我尝试调用任何其他方法,例如 GET http://localhost/api/v1/tenants/1PATCH http://localhost/api/v1/tenants/1,我会在控制台中收到警告:

12/07/2020 21:51:01,440  WARN http-nio-9999-exec-2 PageNotFound:209 - Request method 'GET' not supported

如果我在我的 TenantController 中注释掉所有端点,那么一切正常。看来,无论我在控制器中创建什么端点,都会隐藏所有其他 SDR 默认端点。

这只是发生在这个实体和这个控制器上,但我没有看到任何特别的东西。任何提示都非常感谢,以便了解问题所在。

【问题讨论】:

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


    【解决方案1】:

    如果要融入 Spring Data REST 的 URI 空间,一定不能在类型级别使用@RequestMapping。如果这样做,控制器将包含在 Spring MVC 处理程序映射中,而不是 Spring Data REST 中。对于处理程序选择,这会导致找到 URI 匹配,然后 Spring MVC 只检查该特定映射中的处理程序,以获取有关 HTTP 方法的下游选择、生成和使用子句等。

    不幸的是,这种行为已经在 Spring MVC 中建立了很长时间,并且无法更改,因为它会破坏已知或不知情依赖于此的现有应用程序。

    我已经向 Spring MVC 提交了 this ticket 以重新考虑这一点(即使仅适用于 6.0)。

    顺便说一句。要简单地根据聚合的生命周期事件触发一些业务逻辑,另请参阅 Spring Data REST 发布的事件。在reference documentation 中阅读更多相关信息。首先,它们允许不必在此类场景中编写自定义控制器。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-05-14
      • 1970-01-01
      • 1970-01-01
      • 2019-10-02
      • 2013-09-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多