【问题标题】:How to post a list to Spring Data Rest?如何将列表发布到 Spring Data Rest?
【发布时间】:2015-10-01 04:55:22
【问题描述】:

我关注了this example,它允许发布一个独特的Person 对象。我想要一个 REST 服务,我可以在其中一次发布 Person 的集合,例如一个名为 Team 的列表/任何集合,只需一次调用即可包含大量 Person 对象。

我的意思是,我的问题不完全是关于 OneToMany 关系,您在其中向每个人发送 REST 请求。这个话题是well answered

我想利用 @RepositoryRestResource 或 Spring Data Rest 的其他功能发送 Person 对象的集合。这是否可以通过 Spring Data Rest 实现,还是我应该通过创建控制器、接收列表并解析 Team 列表以插入每个 Person 来解决?

我找到了this feature request,这似乎回答了现在 Spring Rest Data 缺少我正在寻找的东西,但我不确定。

在我的业务需求中,应用程序 A 将向应用程序 B 发布订单列表,我必须将其保存在数据库中以供将来处理,因此,在阅读了 Spring Data Rest 并制作了一些示例后,我发现它的简洁架构令人惊叹非常适合我的要求,只是我不知道如何发布列表。

【问题讨论】:

    标签: java spring rest spring-mvc spring-data-rest


    【解决方案1】:

    嗯,AFAIK,你不能用 spring data rest 做到这一点,只需阅读文档,你就会看到,没有提到将列表发布到集合资源。

    我不清楚其原因,但一方面 - REST 本身并没有真正指定您应该如何进行批处理操作。 所以目前还不清楚应该如何处理该功能,例如您是否应该将列表发布到集合资源?或者您应该导出像/someentity/batch 这样能够在一批中修补、删除和添加实体的资源?如果要添加列表,您应该如何返回 ID?对于单个 POST 来收集位置标头中的 spring-data-rest 返回 id。对于批量添加,这是无法完成的。

    这并不能证明 spring-data-rest 缺少批处理操作是合理的。他们应该实施这个恕我直言,但至少它可以帮助理解他们为什么会错过它。

    不过我可以说的是,您始终可以将自己的控制器添加到可以正确处理 /someentity/batch 的项目中,甚至可以从中创建一个库,以便您可以在其他项目中使用它。甚至 fork spring-data-rest 并添加此功能。尽管我试图了解它是如何工作的,但到目前为止失败了。 但你可能都知道,对吧?

    There is a feature request for this.

    【讨论】:

    • 我会将您的答案视为最终答案:“Spring Data Rest 不能用于接收列表”。显然,我尊重它,但对我来说,如果 Spring Data Rest 接收列表并插入数据库,它会更有用。当然,我可以在没有 Spring Data Rest 的情况下做到这一点。只需使用 @RequestBody List 创建一个控制器,然后在数据库中添加所有 pojo。不过,如果我可以将列表发布到我的服务,我会经常使用 Spring Data Rest。
    • @JimC 是的,我也是。我认为他们的 JIRA 中的批处理操作存在问题。我试图创建另一个问题 - SDR 不支持 OrderColumn 注释 - 但我没有收到任何答案。
    【解决方案2】:

    基于 user1685095 answer,您可以制作自定义控制器 PersonRestController 并公开帖子 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 修复:

    无法从 [Source: java.io.PushbackInputStream@3298b722; 的 START_ARRAY 令牌中反序列化 org.springframework.hateoas.Resources 的实例行:1,列: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 
    }
    

    【讨论】:

    • 你能澄清一下什么是 Wrapper 类吗?谢谢
    【解决方案3】:

    我尝试使用@RequestBody List&lt;Resource&lt;MyPojo&gt;&gt;。 当请求正文不包含任何链接时,它运行良好,但是 如果元素带有链接,则服务器无法反序列化请求正文。

    然后我尝试使用@RequestBody Resources&lt;MyPojo&gt;,但我无法确定列表的默认名称。

    最后,我尝试了一个包含List&lt;Resource&lt;MyPojo&gt;&gt; 的包装器,它可以工作。

    这是我的解决方案:

    首先为List&lt;Resource&lt;MyPojo&gt;&gt;创建一个包装类:

    public class Bulk<T> {
        private List<Resource<T>> bulk;
        // getter and setter
    }
    

    然后使用@RequestBody Resource&lt;Bulk&lt;MyPojo&gt;&gt;作为参数。

    最后,带有链接的示例 json 用于在一个请求中创建批量数据:

    {
        "bulk": [
            {
                "title": "Spring in Action",
                "author": "http://localhost:8080/authors/1"
            },
            {
                "title": "Spring Quick Start",
                "author": "http://localhost:8080/authors/2"
            }
        ]
    }
    

    【讨论】:

    • 这应该是公认的答案,对我帮助很大。供将来参考:-关于Resources&lt;MyPojo&gt;,我尝试了“内容”(因为它是类代码中列表的名称)和“实体”,但没有一个起作用:反序列化的内容列表总是空的(我没有时间深入研究它)-关于List&lt;Resource&lt;MyPojo&gt;&gt;,您将得到的异常类似于:JSON 解析错误:无法构造MyPojo 的实例(尽管至少存在一个创建者):否从字符串值反序列化的字符串参数构造函数/工厂方法('/my-link')
    • 资源现在是EntityModel,见这里:docs.spring.io/spring-hateoas/docs/current/reference/html/…
    【解决方案4】:
    @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) );
    }
    

    【讨论】:

    • 只包含没有描述的代码的答案无济于事
    【解决方案5】:

    基于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);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2016-08-19
      • 2015-08-13
      • 2018-07-23
      • 1970-01-01
      • 2017-03-15
      • 1970-01-01
      • 2015-07-06
      • 2014-08-25
      相关资源
      最近更新 更多