如何 post 列表到 Spring 数据休息?
How to post a list to Spring Data Rest?
我关注了 this example,它允许 post 一个独特的 Person
对象。我想要一个 REST 服务,我可以在其中一次 post Person
的集合,例如一个名为 Team
的 list/any 集合,在一次调用中包含大量 Person
个对象。
我的意思是,我的问题不完全是关于 OneToMany
关系,您向每个人发送 REST 请求。本主题是 well answered.
我想发送 Person
个对象的集合,利用 @RepositoryRestResource
或 Spring Data Rest 的其他功能。这可能与 Spring Data Rest 一起使用,还是我应该通过创建一个控制器来解决,接收列表并解析 Team
列表以插入每个 Person
?
我找到了 this feature request,这似乎回答了如今 Spring Rest Data 缺少我正在寻找的内容,但我不确定。
在我的业务需求中,应用程序 A 将 post 订单列表发送给应用程序 B,我必须将其保存在数据库中以供将来处理,因此,在阅读了 Spring Data Rest 和做了一些样本,我发现它干净的架构令人惊叹并且非常适合我的要求,除了我没有弄清楚如何 post 一个列表。
好吧,AFAIK 你不能用 spring 数据 rest 做到这一点,只要阅读文档你就会看到,没有提到将列表发布到 collection 资源。
我不清楚其中的原因,但一方面 - REST 本身并没有真正指定您应该如何进行批处理操作。
所以不清楚应该如何使用该功能,比如你应该 POST 一个列表到 collection 资源吗?或者您是否应该导出像 /someentity/batch
这样的资源,以便能够批量修补、删除和添加实体?如果您要添加列表,您应该如何 return ID?对于位置 header 中的单个 POST 到 collection spring-data-rest return id。对于批量添加,这是无法完成的。
这并不能证明 spring-data-rest 缺少批处理操作。他们应该实施这个恕我直言,但至少它可以帮助理解为什么他们可能会错过它。
不过我可以说的是,您始终可以将自己的控制器添加到可以正确处理 /someentity/batch 的项目中,您甚至可以从中创建一个库,以便您可以在另一个项目。或者甚至 fork spring-data-rest 并添加此功能。尽管我试图了解它是如何工作的,但到目前为止都失败了。
但你可能知道所有这些,对吧?
我尝试使用 @RequestBody List<Resource<MyPojo>>
。
当请求体不包含任何 links 时,它工作正常,但是
如果元素携带 link,服务器无法反序列化请求正文。
然后我尝试使用@RequestBody Resources<MyPojo>
,但我无法确定列表的默认名称。
最后,我尝试了一个包含 List<Resource<MyPojo>>
的包装器,它起作用了。
这是我的解决方案:
首先为 List<Resource<MyPojo>>
创建一个包装器 class:
public class Bulk<T> {
private List<Resource<T>> bulk;
// getter and setter
}
然后使用@RequestBody Resource<Bulk<MyPojo>>
作为参数。
最后,示例 json 和 links 用于在一个请求中创建批量数据:
{
"bulk": [
{
"title": "Spring in Action",
"author": "http://localhost:8080/authors/1"
},
{
"title": "Spring Quick Start",
"author": "http://localhost:8080/authors/2"
}
]
}
基于 user1685095 ,您可以制作自定义控制器 PersonRestController
并公开 post collection of Person
,因为它似乎不是Spring-date-rest
尚未曝光
@RepositoryRestController
@RequestMapping(value = "/persons")
public class PersonRestController {
private final PersonRepository repo;
@Autowired
public AppointmentRestController(PersonRepository repo) {
this.repo = repo;
}
@RequestMapping(method = RequestMethod.POST, value = "/batch", consumes = "application/json", produces = "application/json")
public @ResponseBody ResponseEntity<?> savePersonList(@RequestBody Resource<PersonWrapper<Person>> personWrapper,
PersistentEntityResourceAssembler assembler) {
Resources<Person> resources = new Resources<Person>(repo.save(personWrapper.getContent()));
//TODO add extra links `assembler`
return ResponseEntity.ok(resources);
}
}
PersonWrapper to fix:
Can not deserialize instance of org.springframework.hateoas.Resources out of START_ARRAY token\n at [Source: java.io.PushbackInputStream@3298b722; line: 1, column: 1]
更新
public class PersonWrapper{
private List<Person> content;
public List<Person> getContent(){
return content;
}
public void setContent(List<Person> content){
this.content = content;
}
}
public class Person{
private String name;
private String email;
// Other fields
// GETTER & SETTER
}
@RequestMapping(method=RequestMethod.POST, value="/batchInsert", consumes = "application/json", produces = "application/json")
@ResponseBody
public ResponseEntity<?> batchInsert(@RequestBody Resources<Person> people, PersistentEntityResourceAssembler assembler) throws Exception {
Iterable<Person> s = repo.save( people.getContent() ); // save entities
List<PersistentEntityResource> list = new ArrayList<PersistentEntityResource>();
Iterator<Sample> itr = s.iterator();
while(itr.hasNext()) {
list.add( assembler.toFullResource( itr.next() ) );
}
return ResponseEntity.ok( new Resources<PersistentEntityResource>(list) );
}
基于totran的答案,这是我的代码:
存在依赖关系:
springBootVersion = '2.4.2'
springDependencyManagement = '1.0.10.RELEASE'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
代码:
import icu.kyakya.rest.jpa.model.Address;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@RepositoryRestResource(collectionResourceRel = "address", path = "address")
public interface AddressRepository extends PagingAndSortingRepository<Address, Long> {
//...
}
import lombok.Data;
import java.util.List;
@Data
public class Bulk<T> {
private List<T> bulk;
}
import lombok.RequiredArgsConstructor;
import org.springframework.data.rest.webmvc.BasePathAwareController;
import org.springframework.data.rest.webmvc.RepositoryRestController;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.server.ExposesResourceFor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
@BasePathAwareController // if base url exists, it needs to be added
@RepositoryRestController
@RequiredArgsConstructor
@ExposesResourceFor(Address.class)
public class AddressController {
private final AddressRepository repo;
@PostMapping("/address/saveAll")
public ResponseEntity<Iterable<Address>> saveAll(@RequestBody EntityModel<Bulk<Address>> bulk) {
List<Address> addresses = Objects.requireNonNull(bulk.getContent()).getBulk();
Iterable<Address> resp = repo.saveAll(addresses);
return new ResponseEntity<>(resp,HttpStatus.CREATED);
}
}
方式更像Spring数据休息:
import lombok.RequiredArgsConstructor;
import org.springframework.data.rest.webmvc.BasePathAwareController;
import org.springframework.data.rest.webmvc.RepositoryRestController;
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.server.ExposesResourceFor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@BasePathAwareController // if base url exists, it needs to be added
@RepositoryRestController
@RequiredArgsConstructor
@ExposesResourceFor(Address.class)
public class AddressController {
private final AddressRepository repo;
private final RepositoryEntityLinks entityLinks; //get link
/**
* curl -i -X POST -H "Content-Type:application/json" -d '{ "bulk": [ {"country" : "Japan" , "city" : "Tokyo" }, {"country" : "Japan" , "city" : "Osaka" }]} ' http://localhost:8080/api/v1/address/saveAll
*
* @return 201 https://docs.spring.io/spring-data/rest/docs/current/reference/html/#repository-resources.default-status-codes
*/
@PostMapping("/address/saveAll")
public ResponseEntity<CollectionModel<EntityModel<Address>>> List<Address> data = Objects.requireNonNull(bulk.getContent()).getBulk();
Iterable<Address> addresses = repo.saveAll(data);
ArrayList<EntityModel<Address>> models = new ArrayList<>();
addresses.forEach(i->{
Link link = entityLinks.linkToItemResource(Address.class, i.getId()).withRel("self");
models.add(EntityModel.of(i).add(link));
});
return new ResponseEntity<>(CollectionModel.of(models),HttpStatus.CREATED);
}
}
我关注了 this example,它允许 post 一个独特的 Person
对象。我想要一个 REST 服务,我可以在其中一次 post Person
的集合,例如一个名为 Team
的 list/any 集合,在一次调用中包含大量 Person
个对象。
我的意思是,我的问题不完全是关于 OneToMany
关系,您向每个人发送 REST 请求。本主题是 well answered.
我想发送 Person
个对象的集合,利用 @RepositoryRestResource
或 Spring Data Rest 的其他功能。这可能与 Spring Data Rest 一起使用,还是我应该通过创建一个控制器来解决,接收列表并解析 Team
列表以插入每个 Person
?
我找到了 this feature request,这似乎回答了如今 Spring Rest Data 缺少我正在寻找的内容,但我不确定。
在我的业务需求中,应用程序 A 将 post 订单列表发送给应用程序 B,我必须将其保存在数据库中以供将来处理,因此,在阅读了 Spring Data Rest 和做了一些样本,我发现它干净的架构令人惊叹并且非常适合我的要求,除了我没有弄清楚如何 post 一个列表。
好吧,AFAIK 你不能用 spring 数据 rest 做到这一点,只要阅读文档你就会看到,没有提到将列表发布到 collection 资源。
我不清楚其中的原因,但一方面 - REST 本身并没有真正指定您应该如何进行批处理操作。
所以不清楚应该如何使用该功能,比如你应该 POST 一个列表到 collection 资源吗?或者您是否应该导出像 /someentity/batch
这样的资源,以便能够批量修补、删除和添加实体?如果您要添加列表,您应该如何 return ID?对于位置 header 中的单个 POST 到 collection spring-data-rest return id。对于批量添加,这是无法完成的。
这并不能证明 spring-data-rest 缺少批处理操作。他们应该实施这个恕我直言,但至少它可以帮助理解为什么他们可能会错过它。
不过我可以说的是,您始终可以将自己的控制器添加到可以正确处理 /someentity/batch 的项目中,您甚至可以从中创建一个库,以便您可以在另一个项目。或者甚至 fork spring-data-rest 并添加此功能。尽管我试图了解它是如何工作的,但到目前为止都失败了。 但你可能知道所有这些,对吧?
我尝试使用 @RequestBody List<Resource<MyPojo>>
。
当请求体不包含任何 links 时,它工作正常,但是
如果元素携带 link,服务器无法反序列化请求正文。
然后我尝试使用@RequestBody Resources<MyPojo>
,但我无法确定列表的默认名称。
最后,我尝试了一个包含 List<Resource<MyPojo>>
的包装器,它起作用了。
这是我的解决方案:
首先为 List<Resource<MyPojo>>
创建一个包装器 class:
public class Bulk<T> {
private List<Resource<T>> bulk;
// getter and setter
}
然后使用@RequestBody Resource<Bulk<MyPojo>>
作为参数。
最后,示例 json 和 links 用于在一个请求中创建批量数据:
{
"bulk": [
{
"title": "Spring in Action",
"author": "http://localhost:8080/authors/1"
},
{
"title": "Spring Quick Start",
"author": "http://localhost:8080/authors/2"
}
]
}
基于 user1685095 PersonRestController
并公开 post collection of Person
,因为它似乎不是Spring-date-rest
@RepositoryRestController
@RequestMapping(value = "/persons")
public class PersonRestController {
private final PersonRepository repo;
@Autowired
public AppointmentRestController(PersonRepository repo) {
this.repo = repo;
}
@RequestMapping(method = RequestMethod.POST, value = "/batch", consumes = "application/json", produces = "application/json")
public @ResponseBody ResponseEntity<?> savePersonList(@RequestBody Resource<PersonWrapper<Person>> personWrapper,
PersistentEntityResourceAssembler assembler) {
Resources<Person> resources = new Resources<Person>(repo.save(personWrapper.getContent()));
//TODO add extra links `assembler`
return ResponseEntity.ok(resources);
}
}
PersonWrapper to fix:
Can not deserialize instance of org.springframework.hateoas.Resources out of START_ARRAY token\n at [Source: java.io.PushbackInputStream@3298b722; line: 1, column: 1]
更新
public class PersonWrapper{
private List<Person> content;
public List<Person> getContent(){
return content;
}
public void setContent(List<Person> content){
this.content = content;
}
}
public class Person{
private String name;
private String email;
// Other fields
// GETTER & SETTER
}
@RequestMapping(method=RequestMethod.POST, value="/batchInsert", consumes = "application/json", produces = "application/json")
@ResponseBody
public ResponseEntity<?> batchInsert(@RequestBody Resources<Person> people, PersistentEntityResourceAssembler assembler) throws Exception {
Iterable<Person> s = repo.save( people.getContent() ); // save entities
List<PersistentEntityResource> list = new ArrayList<PersistentEntityResource>();
Iterator<Sample> itr = s.iterator();
while(itr.hasNext()) {
list.add( assembler.toFullResource( itr.next() ) );
}
return ResponseEntity.ok( new Resources<PersistentEntityResource>(list) );
}
基于totran的答案,这是我的代码:
存在依赖关系:
springBootVersion = '2.4.2'
springDependencyManagement = '1.0.10.RELEASE'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
代码:
import icu.kyakya.rest.jpa.model.Address;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@RepositoryRestResource(collectionResourceRel = "address", path = "address")
public interface AddressRepository extends PagingAndSortingRepository<Address, Long> {
//...
}
import lombok.Data;
import java.util.List;
@Data
public class Bulk<T> {
private List<T> bulk;
}
import lombok.RequiredArgsConstructor;
import org.springframework.data.rest.webmvc.BasePathAwareController;
import org.springframework.data.rest.webmvc.RepositoryRestController;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.server.ExposesResourceFor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
@BasePathAwareController // if base url exists, it needs to be added
@RepositoryRestController
@RequiredArgsConstructor
@ExposesResourceFor(Address.class)
public class AddressController {
private final AddressRepository repo;
@PostMapping("/address/saveAll")
public ResponseEntity<Iterable<Address>> saveAll(@RequestBody EntityModel<Bulk<Address>> bulk) {
List<Address> addresses = Objects.requireNonNull(bulk.getContent()).getBulk();
Iterable<Address> resp = repo.saveAll(addresses);
return new ResponseEntity<>(resp,HttpStatus.CREATED);
}
}
方式更像Spring数据休息:
import lombok.RequiredArgsConstructor;
import org.springframework.data.rest.webmvc.BasePathAwareController;
import org.springframework.data.rest.webmvc.RepositoryRestController;
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.server.ExposesResourceFor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@BasePathAwareController // if base url exists, it needs to be added
@RepositoryRestController
@RequiredArgsConstructor
@ExposesResourceFor(Address.class)
public class AddressController {
private final AddressRepository repo;
private final RepositoryEntityLinks entityLinks; //get link
/**
* curl -i -X POST -H "Content-Type:application/json" -d '{ "bulk": [ {"country" : "Japan" , "city" : "Tokyo" }, {"country" : "Japan" , "city" : "Osaka" }]} ' http://localhost:8080/api/v1/address/saveAll
*
* @return 201 https://docs.spring.io/spring-data/rest/docs/current/reference/html/#repository-resources.default-status-codes
*/
@PostMapping("/address/saveAll")
public ResponseEntity<CollectionModel<EntityModel<Address>>> List<Address> data = Objects.requireNonNull(bulk.getContent()).getBulk();
Iterable<Address> addresses = repo.saveAll(data);
ArrayList<EntityModel<Address>> models = new ArrayList<>();
addresses.forEach(i->{
Link link = entityLinks.linkToItemResource(Address.class, i.getId()).withRel("self");
models.add(EntityModel.of(i).add(link));
});
return new ResponseEntity<>(CollectionModel.of(models),HttpStatus.CREATED);
}
}