如何在Spring MVC控制器中应用Spring Data投影?

时间:2015-03-31 18:42:14

标签: spring spring-security spring-data

直接调用数据存储库方法时是否可以指定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}}可以以某种方式传递到直接调用的存储库方法吗?

3 个答案:

答案 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 一样按名称查找投影(而不是将其硬连线到控制器中),这就是您要执行的操作必须做:

  1. RepositoryRestConfiguration注入控制器。此bean使您可以访问名为ProjectionDefinitions(请参见getProjectionConfiguration())的类,该类充当投影元数据目录。
  2. 使用ProjectionDefinitions,您可以获取投影类的名称和相关的绑定类。
  3. 稍后,您可以使用@Oliver详细介绍的方法来创建投影实例...

这是一个实现我所描述的小型控制器:

@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 版本中轻松完成!

您需要做的就是:

  1. 将投影名称作为请求参数传递

    `/api/users/search/findEmployeeUsers?projection=userView`
    
  2. 从您的服务方法中返回 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>