【问题标题】:How to apply Spring Data projections in a Spring MVC controllers?如何在 Spring MVC 控制器中应用 Spring Data 投影?
【发布时间】:2015-06-05 05:34:27
【问题描述】:

直接调用数据仓库方法时可以指定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);
}

像上面那样直接调用findEmployeeUsers方法时,有什么方法可以指定projection?

我意识到上面的代码对某些人来说可能看起来很奇怪...可以通过 REST 公开存储库并将 @PreAuthorize 东西放入存储库中。思想控制器是进行安全检查的更合适的地方 - 它更自然且测试更简单。

那么,projection 可以以某种方式传递到直接调用的存储库方法中吗?

【问题讨论】:

    标签: spring spring-security spring-data


    【解决方案1】:

    不,不是,尤其是预测通常会根据具体情况应用于查询执行的结果。因此,它们目前被设计为选择性地应用于域类型。

    从最新的 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(…) 方法,可用于动态转换页面内容。我们使用 JDK 8 lambda 来提供使用 ProjectionFactory 的转换步骤。

    【讨论】:

    • 谢谢,这看起来不错!不幸的是,我无法测试它,因为我被 SpringBoot 1.2.2(3) 锁定 - 它没有 spring-data 项目的最新 GA 版本......我意识到这是一个不同的问题 - 但有没有办法将 GA 放入 SpringBoot 1.2.2 或 1.2.3 应用程序来测试这个东西?
    • 这是如何做到的 - spring.io/blog/2015/03/26/what-s-new-in-spring-data-fowler,将尝试新的投影基础设施。
    • @OliverGierke 看来这在当前版本的 Spring Data 中不再有效?使用提供的示例,杰克逊抛出一个com.fasterxml.jackson.core.JsonGenerationException: Can not start an object, expecting field name
    • 如上例所示转换存储库查询的结果与使用返回投影本身的存储库查询相比如何?在性能等方面?
    • @OliverGierke 这是我可以遵循的最后一项改进,还是您对此有一些新的改进?
    【解决方案2】:

    除了@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);
        }
    }
    

    【讨论】:

      【解决方案3】:

      在最新的 Spring Data Rest 版本中可以轻松完成!

      您需要做的就是:

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

        `/api/users/search/findEmployeeUsers?projection=userView`
        
      2. 从您的服务方法中返回 PagedModel&lt;PersistentEntityResource&gt; 而不是 Page&lt;User&gt;;

      完成!

      我假设你想从你的自定义控制器中调用这个服务方法,在这种情况下你需要从你的控制器方法中返回ResponseEntity&lt;PagedModel&lt;PersistentEntityResource&gt;&gt;。

      不希望它可分页?只需返回 ResponseEntity&lt;CollectionModel&lt;PersistentEntityResource&gt;&gt; 即可。 另请查看example for single resoure projection。

      Spring Data Rest 负责将 @Projections 应用于 api 请求上的 PersistentEntityResources,就像您不断从 @RepositoryRestResource 暴露您的 @RestResource;投影的相同行为,保持相同的命名约定,基本相同的 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>
      

      【讨论】:

        猜你喜欢
        • 2018-03-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-01-08
        • 2021-03-22
        • 2017-07-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多