RestRepositoryController 隐藏 REST 存储库端点
RestRepositoryController hide REST repository endpoints
我正在使用 Spring 带有 SDR、HATEOAS、Hibernate 的 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/1
或 PATCH 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 默认端点。
这只发生在这个实体和这个控制器上,但我没有看到任何特别的东西。非常感谢任何提示,以便了解问题出在哪里。
如果要混入 Spring 数据 REST 的 URI space,则不能在类型级别上使用 @RequestMapping
。如果这样做,控制器将包含在 Spring MVC 处理程序映射中,而不是 Spring Data REST 的映射中。对于处理程序选择,这会导致找到 URI 匹配项,然后 Spring MVC 仅检查该特定映射中的处理程序以进行有关 HTTP 方法、生产和使用子句等的下游选择。
不幸的是,该行为已在 Spring MVC 中建立多年,无法更改,因为它会破坏已知或无意依赖于此的现有应用程序。
我已将 this ticket 提交给 Spring MVC 以重新考虑此问题(即使仅适用于 6.0)。
顺便说一句。要根据聚合的生命周期事件简单地触发一些业务逻辑,另请参阅事件 Spring Data REST 发布。在 reference documentation 中阅读更多相关信息。他们允许不必首先在这些场景中编写自定义控制器。
我正在使用 Spring 带有 SDR、HATEOAS、Hibernate 的 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/1
或 PATCH 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 默认端点。
这只发生在这个实体和这个控制器上,但我没有看到任何特别的东西。非常感谢任何提示,以便了解问题出在哪里。
如果要混入 Spring 数据 REST 的 URI space,则不能在类型级别上使用 @RequestMapping
。如果这样做,控制器将包含在 Spring MVC 处理程序映射中,而不是 Spring Data REST 的映射中。对于处理程序选择,这会导致找到 URI 匹配项,然后 Spring MVC 仅检查该特定映射中的处理程序以进行有关 HTTP 方法、生产和使用子句等的下游选择。
不幸的是,该行为已在 Spring MVC 中建立多年,无法更改,因为它会破坏已知或无意依赖于此的现有应用程序。
我已将 this ticket 提交给 Spring MVC 以重新考虑此问题(即使仅适用于 6.0)。
顺便说一句。要根据聚合的生命周期事件简单地触发一些业务逻辑,另请参阅事件 Spring Data REST 发布。在 reference documentation 中阅读更多相关信息。他们允许不必首先在这些场景中编写自定义控制器。