直接调用数据存储库方法时是否可以指定projection
?这里的存储库代码 - 注意我不想通过REST公开它,而是希望能够从服务或控制器中调用它:
@RepositoryRestResource(exported = false)
public interface UsersRepository extends PagingAndSortingRepository<User, Long> {
@Query(value = "SELECT u FROM User u WHERE ....")
public Page<User> findEmployeeUsers(Pageable p);
}
然后在控制器中我这样做:
@PreAuthorize(value = "hasRole('ROLE_ADMIN')")
@RequestMapping(value = "/users/employee")
public Page<User> listEmployees(Pageable pageable) {
return usersRepository.findEmployeeUsers(pageable);
}
如果直接调用projection
方法,有没有办法为findEmployeeUsers
方法指定@PreAuthorize
?
我意识到上面的代码对于某些人来说可能看起来很奇怪......可以通过REST公开存储库并将projection
内容放在存储库中。思想控制器是进行安全检查的最佳位置 - 测试更自然,更简单。
那么,{{1}}可以以某种方式传递到直接调用的存储库方法吗?
答案 0 :(得分:50)
不,不是,尤其是因为投影通常会根据具体情况应用于查询执行的结果。因此,它们目前被设计为有选择地应用于域类型。
从最新的Spring Data Fowler发布列车GA版本开始,可以在Spring MVC控制器中以编程方式使用投影基础架构。只需为SpelAwareProxyProjectionFactory
声明一个Spring bean:
@Configuration
class SomeConfig {
@Bean
public SpelAwareProxyProjectionFactory projectionFactory() {
return new SpelAwareProxyProjectionFactory();
}
}
然后将其注入您的控制器并使用它:
@Controller
class SampleController {
private final ProjectionFactory projectionFactory;
@Autowired
public SampleController(ProjectionFactory projectionFactory) {
this.projectionFactory = projectionFactory;
}
@PreAuthorize(value = "hasRole('ROLE_ADMIN')")
@RequestMapping(value = "/users/employee")
public Page<?> listEmployees(Pageable pageable) {
return usersRepository.findEmployeeUsers(pageable).//
map(user -> projectionFactory.createProjection(Projection.class, user);
}
}
了解最新版本Page
中的map(…)
如何使用ProjectionFactory
方法可以动态转换页面内容。我们使用JDK 8 lambda来使用{{1}}提供转换步骤。
答案 1 :(得分:1)
除了@Oliver的答案,如果您要像 SpringDataRest 一样按名称查找投影(而不是将其硬连线到控制器中),这就是您要执行的操作必须做:
RepositoryRestConfiguration
注入控制器。此bean使您可以访问名为ProjectionDefinitions
(请参见getProjectionConfiguration()
)的类,该类充当投影元数据目录。ProjectionDefinitions
,您可以获取投影类的名称和相关的绑定类。这是一个实现我所描述的小型控制器:
@RestController
@RequestMapping("students")
public class StudentController {
/**
* {@link StudentController} logger.
*/
private static final Logger logger =
LoggerFactory.getLogger(StudentController.class);
/**
* Projections Factory.
*/
private ProjectionFactory p8nFactory;
/**
* Projections Directory.
*/
private ProjectionDefinitions p8nDefs;
/**
* {@link Student} repository.
*/
private StudentRepository repo;
/**
* Class Constructor.
*
* @param repoConfig
* {@code RepositoryRestConfiguration} bean
* @param p8nFactory
* Factory used to create projections
* @param repo
* {@link StudentRepository} instance
*/
@Autowired
public StudentController(
RepositoryRestConfiguration repoConfig,
ProjectionFactory p8nFactory,
StudentRepository repo
) {
super();
this.p8nFactory = p8nFactory;
this.p8nDefs = repoConfig.getProjectionConfiguration();
this.repo = repo;
}
...
/**
* Retrieves all persisted students.
*
* @param projection
* (Optional) Name of the projection to be applied to
* students retrieved from the persistence layer
* @return
* {@code ResponseEntity} whose content can be a list of Students
* or a projected view of them
*/
@GetMapping(path = "", produces = APPLICATION_JSON_VALUE)
public ResponseEntity<Object> retrieveAll(
@RequestParam(required = false) String projection
) {
Class<?> type; // Kind of Projection to be applied
List<?> rawData; // Raw Entity Students
List<?> pjData; // Projected students (if applies)
rawData = this.repo.findAll();
pjData = rawData;
if (projection != null) {
type = this.p8nDefs.getProjectionType(Student.class, projection);
pjData = rawData
.stream()
.map(s -> this.p8nFactory.createProjection(type, s))
.collect(Collectors.toList());
}
return new ResponseEntity<>(pjData, HttpStatus.OK);
}
}
祝您编程愉快!
答案 2 :(得分:0)
可以在最新的 Spring Data Rest 版本中轻松完成!
您需要做的就是:
将投影名称作为请求参数传递
`/api/users/search/findEmployeeUsers?projection=userView`
从您的服务方法中返回 PagedModel<PersistentEntityResource>
而不是 Page<User>
;
完成!
我假设您想从自定义控制器调用此服务方法,在这种情况下,您需要从控制器方法返回 ResponseEntity<PagedModel<PersistentEntityResource>>
。
不希望它可分页?只需返回 ResponseEntity<CollectionModel<PersistentEntityResource>>
即可。
另请查看example for single resoure projection。
Spring Data Rest 负责在 api 请求上将 @Projection
应用到 PersistentEntityResource
,就像您不断从 @RestResource
中暴露 @RepositoryRestResource
一样;相同的投影行为,保持相同的命名约定,基本相同的 URI(对于当前示例)。
带有一些业务逻辑的服务方法可能如下所示:
@Override
@Transactional(readOnly = true)
public PagedModel<PersistentEntityResource> listEmployees(Pageable pageable, PersistentEntityResourceAssembler resourceAssembler) {
Page<User> users = userRepository.findEmployeeUsers(pageable);
List<User> entities = users.getContent();
entities.forEach(user -> user.setOnVacation(isUserOnVacationNow(user)));
CollectionModel<PersistentEntityResource> collectionModel = resourceAssembler.toCollectionModel(entities);
return PagedModel.of(collectionModel.getContent(), new PagedModel.PageMetadata(
users.getSize(),
users.getNumber(),
users.getTotalElements(),
users.getTotalPages()));
}
您的控制器方法可能如下所示:
@BasePathAwareController
public class UsersController {
@GetMapping(value = "/users/search/findEmployeeUsers")
ResponseEntity<PagedModel<PersistentEntityResource>> findEmployeeUsers(Pageable pageable,
PersistentEntityResourceAssembler resourceAssembler) {
return ResponseEntity.status(HttpStatus.OK)
.body(userService.listEmployees(pageable, resourceAssembler));
}
}
我使用 spring-boot-starter-data-rest:2.3.4.RELEASE 和 spring-data-rest-webmvc:3.3.4.RELEASE 和 spring-data-rest-webmvc:3.3.4.RELEASE作为依赖项,将其配置为我的 pom.xml 的父项
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>