如何在 Spring MVC 控制器中应用 Spring 数据投影?
How to apply Spring Data projections in a Spring MVC controllers?
是否可以在直接调用数据仓库方法时指定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
为projection
?
我意识到上面的代码对某些人来说可能看起来很奇怪...可以通过 REST 公开存储库并将 @PreAuthorize
东西放入存储库中。思想控制器是进行安全检查的更合适的地方 - 它更自然也更容易测试。
那么,projection
东西能否以某种方式传递到直接调用的存储库方法中?
不,不是,尤其是因为预测通常根据具体情况应用于查询执行的结果。因此,它们目前被设计为有选择地应用于域类型。
截至最新的 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
.
的转换步骤
除@Oliver 的回答外,如果您想按名称查找投影,如 SpringDataRest 所做的那样(而不是将它们硬连接到你的控制器),这是你必须做的:
- 将
RepositoryRestConfiguration
注入您的控制器。该 bean 使您可以访问名为 ProjectionDefinitions
的 class(参见 getProjectionConfiguration()
),它充当投影元数据目录。
- 使用
ProjectionDefinitions
您可以检索投影 类 给定它们的名称及其关联的边界 classes。
- 稍后,您可以使用@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);
}
}
后期可以轻松完成Spring Data Rest发布!
您需要做的就是:
将投影名称作为请求参数传递
`/api/users/search/findEmployeeUsers?projection=userView`
return PagedModel<PersistentEntityResource>
而不是您的服务方法中的 Page<User>
;
完成!
我假设您想从您的自定义控制器调用此服务方法,在这种情况下您需要从您的控制器方法 return ResponseEntity<PagedModel<PersistentEntityResource>>
。
不希望它可分页?只需 return ResponseEntity<CollectionModel<PersistentEntityResource>>
即可。
另请查看 example for single resoure projection.
Spring Data Rest 负责在 api 请求中将 @Projection
s 应用到 PersistentEntityResource
s,就像你不断地暴露你的 @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>
是否可以在直接调用数据仓库方法时指定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
为projection
?
我意识到上面的代码对某些人来说可能看起来很奇怪...可以通过 REST 公开存储库并将 @PreAuthorize
东西放入存储库中。思想控制器是进行安全检查的更合适的地方 - 它更自然也更容易测试。
那么,projection
东西能否以某种方式传递到直接调用的存储库方法中?
不,不是,尤其是因为预测通常根据具体情况应用于查询执行的结果。因此,它们目前被设计为有选择地应用于域类型。
截至最新的 Spring Data Fowler 发布序列 GA 版本,可以在 Spring MVC 控制器中以编程方式使用投影基础设施。只需为 SpelAwareProxyProjectionFactory
:
@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
.
除@Oliver 的回答外,如果您想按名称查找投影,如 SpringDataRest 所做的那样(而不是将它们硬连接到你的控制器),这是你必须做的:
- 将
RepositoryRestConfiguration
注入您的控制器。该 bean 使您可以访问名为ProjectionDefinitions
的 class(参见getProjectionConfiguration()
),它充当投影元数据目录。 - 使用
ProjectionDefinitions
您可以检索投影 类 给定它们的名称及其关联的边界 classes。 - 稍后,您可以使用@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);
}
}
后期可以轻松完成Spring Data Rest发布!
您需要做的就是:
将投影名称作为请求参数传递
`/api/users/search/findEmployeeUsers?projection=userView`
return
PagedModel<PersistentEntityResource>
而不是您的服务方法中的Page<User>
;
完成!
我假设您想从您的自定义控制器调用此服务方法,在这种情况下您需要从您的控制器方法 return ResponseEntity<PagedModel<PersistentEntityResource>>
。
不希望它可分页?只需 return ResponseEntity<CollectionModel<PersistentEntityResource>>
即可。
另请查看 example for single resoure projection.
Spring Data Rest 负责在 api 请求中将 @Projection
s 应用到 PersistentEntityResource
s,就像你不断地暴露你的 @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>