How to apply Spring Data projections in a Spring MVC controllers?

前端 未结 2 664
被撕碎了的回忆
被撕碎了的回忆 2020-11-30 22:29

Is it possible to specify projection when calling data repository method directly? Here\'s repository code - note I would not like to expose it via REST, instea

相关标签:
2条回答
  • 2020-11-30 23:20

    No it's not, especially as projections are usually applied to the result of a query execution on a case by case basis. Thus they're currently designed to be selectively applied to domain types.

    As of the latest Spring Data Fowler release train GA release the projection infrastructure can be used programmatically in Spring MVC controllers. Simply declare a Spring bean for SpelAwareProxyProjectionFactory:

    @Configuration
    class SomeConfig {
    
      @Bean
      public SpelAwareProxyProjectionFactory projectionFactory() {
        return new SpelAwareProxyProjectionFactory();
      }
    }
    

    Then inject it into your controller and use it:

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

    See how as of the latest release Page has a map(…) method that can be used to transform the page content on the fly. We use a JDK 8 lambda to provide a conversion step using the ProjectionFactory.

    0 讨论(0)
  • 2020-11-30 23:21

    Additionally to the @Oliver's answer, if you want to lookup the Projections by name as SpringDataRest does (instead of hardwired them in your controller), this is what you have to do:

    1. Inject RepositoryRestConfiguration into your controller. This bean gives you access to a class called ProjectionDefinitions (see, getProjectionConfiguration()) which acts a projection metadata directory.
    2. Using ProjectionDefinitions you can retrieve Projection Classes given their names and their associated bound classes.
    3. Later, you can use the method detailed by @Oliver to create the projections instances ...

    This is a small Controller that implements what I describe:

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

    Happy coding!

    0 讨论(0)
提交回复
热议问题