【问题标题】:How to POST nested entities with Spring Data REST如何使用 Spring Data REST 发布嵌套实体
【发布时间】:2014-08-25 12:05:43
【问题描述】:

我正在构建一个 Spring Data REST 应用程序,当我尝试发布它时遇到了一些问题。主实体嵌套了另外两个相关实体。

有一个“问题”对象有很多答案,每个答案都有很多回复。

我从前端应用程序生成一个这样的 JSON 来发布问题:

{
    "user": "http://localhost:8080/users/1",
    "status": 1,
    "answers": [
        {
            "img": "urlOfImg",
            "question": "http://localhost:8080/question/6",
            "replies": [
                {
                    "literal": "http://localhost:8080/literal/1",
                    "result": "6"
                },
                {
                    "literal": "http://localhost:8080/literal/1",
                    "result": "6"
                }
            ]
        },
        {
            "img": "urlOfImg",
            "question": "http://localhost:8080/question/6",
            "replies": [
                {
                    "literal": "http://localhost:8080/literal/3",
                    "result": "10"
                }
            ]
        }
    ]
}

但是当我尝试发布它时,我收到以下错误响应:

{

    "cause" : {
        "cause" : {
          "cause" : null,
          "message" : "Template must not be null or empty!"
        },
        "message" : "Template must not be null or empty! (through reference chain: project.models.Questionary[\"answers\"])"
      },
      "message" : "Could not read JSON: Template must not be null or empty! (through reference chain: project.models.Questionary[\"answers\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Template must not be null or empty! (through reference chain: project.models.Questionary[\"answers\"])"
}

编辑:

我还添加了我的存储库:

@RepositoryRestResource(collectionResourceRel = "questionaries", path = "questionaries")
public interface InspeccionRepository extends JpaRepository<Inspeccion, Integer> {
    @RestResource(rel="byUser", path="byUser")
    public List<Questionary> findByUser (@Param("user") User user);
}

我的实体问题类是:

@Entity @Table(name="QUESTIONARY", schema="enco" )
public class Questionary implements Serializable {
     private static final long serialVersionUID = 1L;
     //----------------------------------------------------------------------
     // ENTITY PRIMARY KEY ( BASED ON A SINGLE FIELD )
     //----------------------------------------------------------------------
     @Id
     @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEC_QUESTIONARY")
     @SequenceGenerator(name = "SEC_QUESTIONARY", sequenceName = "ENCO.SEC_QUESTIONARY", allocationSize = 1)
     @Column(name="IDQUES", nullable=false)
     private Integer idques        ;

     //----------------------------------------------------------------------
     // ENTITY DATA FIELDS 
     //----------------------------------------------------------------------    

     @Column(name="ESTATUS")
     private Integer estatus       ;


     //----------------------------------------------------------------------
     // ENTITY LINKS ( RELATIONSHIP )
     //----------------------------------------------------------------------

     @ManyToOne
     @JoinColumn(name="IDUSER", referencedColumnName="IDUSER")
     private User user;

     @OneToMany(mappedBy="questionary", targetEntity=Answer.class)
     private List<Answer> answers;



     //----------------------------------------------------------------------
     // CONSTRUCTOR(S)
     //----------------------------------------------------------------------
     public Questionary()
     {
        super();
     }


     //----------------------------------------------------------------------
     // GETTERS & SETTERS FOR FIELDS
     //----------------------------------------------------------------------

     //--- DATABASE MAPPING : IDNSE ( NUMBER ) 
     public void setIdnse( Integer idnse )
     {
         this.idnse = idnse;
     }
     public Integer getIdnse()
     {
         return this.idnse;
     }

     //--- DATABASE MAPPING : ESTADO ( NUMBER ) 
     public void setEstatus Integer estatus )
     {
         this.estatus = estatus;
     }
     public Integer getEstatus()
     {
         return this.estatus;
     }      
     //----------------------------------------------------------------------
     // GETTERS & SETTERS FOR LINKS
     //----------------------------------------------------------------------
     public void setUser( Usuario user )
     {
         this.user = user;
     }
     public User getUser()
     {
         return this.user;
     }


     public void setAnswers( List<Respuesta> answers )
     {
         this.answers = answer;
     }
     public List<Answer> getAnswers()
     {
         return this.answers;
     }


     // Get Complete Object method      public List<Answer>
     getAnswerComplete() {
         List<Answer> answers = this.answers;
         return answers;
    }
}

我的答案实体:

 @Entity @Table(name="ANSWER", schema="enco" ) public class Answer
 implements Serializable {
     private static final long serialVersionUID = 1L;

     //----------------------------------------------------------------------
     // ENTITY PRIMARY KEY ( BASED ON A SINGLE FIELD )
     //----------------------------------------------------------------------
     @Id
     @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEC_ANSWER")
     @SequenceGenerator(name = "SEC_ANSWER", sequenceName = "ENCOADMIN.SEC_ANSWER", allocationSize = 1)
     @Column(name="IDANS", nullable=false)
     private Integer idans        ;


     //----------------------------------------------------------------------
     // ENTITY DATA FIELDS 
     //----------------------------------------------------------------------    

     @Column(name="IMG", length=100)
     private String     img       ;


     //----------------------------------------------------------------------
     // ENTITY LINKS ( RELATIONSHIP )
     //----------------------------------------------------------------------
     @ManyToOne
     @JoinColumn(name="IDQUES", referencedColumnName="IDQUES")
     private Questionary questionary  ;

     @OneToMany(mappedBy="answer", targetEntity=Reply.class)
     private List<Reply> replies;

     @ManyToOne
     @JoinColumn(name="IDQUE", referencedColumnName="IDQUE")
     private Question Question    ;


     //----------------------------------------------------------------------
     // CONSTRUCTOR(S)
     //----------------------------------------------------------------------
     public Answer()
     {
        super();
     }

     //----------------------------------------------------------------------
     // GETTER & SETTER FOR THE KEY FIELD
     //----------------------------------------------------------------------
     public void setIdans( Integer idans )
     {
         this.idans = idans ;
     }
     public Integer getIdans()
     {
         return this.idans;
     }

     //----------------------------------------------------------------------
     // GETTERS & SETTERS FOR FIELDS
     //----------------------------------------------------------------------

     //--- DATABASE MAPPING : IMAGEN ( VARCHAR2 ) 
     public void setImg( String img )
     {
         this.img = img;
     }
     public String getImg()
     {
         return this.img;
     }


     //----------------------------------------------------------------------
     // GETTERS & SETTERS FOR LINKS
     //----------------------------------------------------------------------
     public void setQuestionary( Questionary questionary )
     {
         this.questionary = questionary;
     }
     public Questionary getQuestionary()
     {
         return this.questionary;
     }

     public void setReplies( List<Reply> contestaciones )
     {
         this.replies = replies;
     }
     public List<Reply> getReplies()
     {
         return this.replies;
     }

     public void setQuestion( Question question )
     {
         this.question = question;
     }
     public Question getQuestion()
     {
         return this.question;
     }


}

这是错误控制台:

Caused by: com.fasterxml.jackson.databind.JsonMappingException:
Template must not be null or empty! (through reference chain:
project.models.Questionary["answers"])  at
  com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:232)
 ~[jackson-databind-2.3.3.jar:2.3.3]    at *snip*

【问题讨论】:

  • 你能不能也展示你的控制器方法、堆栈跟踪和POJO 类?
  • 我已经用您要求的所有信息编辑了主帖。感谢您的帮助。
  • 也许问题出在错误的数据中?可以吗:"question": "http://localhost:8080/question/6"。也许它应该看起来像这样:"question": 6。为什么要发送URLs 而不是 id?
  • 因为问题和答案之间有关系,所以这部分是可以的,我个人尝试过,没有问题。问题是当我在列表中的另一个中嵌套和对象时。例如,如果我首先做这个帖子,只有检查和每个答案的一个帖子和每个回复的另一个帖子都有效,但是当我有一个超过 350 个答案的问题并且每个问题都有 3 或 4 个回复时,我的事情是荒谬的。 ...它必须是一种在一个嵌套对象中执行此操作的方法...
  • 你有想过这个吗?

标签: spring rest post jackson


【解决方案1】:

尝试在类Questionary 的字段answers 上添加@RestResource(exported = false)

据我说,发生此错误是因为反序列化程序希望 URI 从中获取答案,而不是将答案嵌套在 JSON 中。添加注释会告诉它改为查看 JSON。

【讨论】:

  • 这个解决方案非常适合我。进行此更改后,我能够将非持久化项添加到一对多资源的集合中。
  • 但是现在您不能使用 Questionary-repository 的 CRUD 方法,因为它们不会被暴露。问题的创建将始终由 Answer-repository 处理,因为在 Questionary-repository 上它是不允许的。对吗?
  • 不确定这是否对任何人都有帮助,但在我正在使用的实现中 @RestResource(exported = false) 需要添加到存储库文件中,例如QuestionaryController, QuestionaryModel, QuestionaryRepo。我不确定这是否是春季的标准,我只使用了几个月的基本后端服务,但认为它可能会对处于类似情况的人有所帮助。
【解决方案2】:

我在 2.3.0.M1 中仍然看到此错误,但我终于找到了解决方法。

基本问题是这样的:如果您在 JSON 中发布嵌入实体的 url,它就可以工作。如果您发布实际的嵌入式实体 JSON,则不会。它尝试将实体 JSON 反序列化为 URI,这当然失败了。

看起来问题出在 spring data rest 在其配置中创建的两个 TypeConstrainedMappingJackson2HttpMessageConverter 对象(在 RepositoryRestMvcConfiguration.defaultMessageConverters() 中)。

我终于通过配置 messageConverters 支持的媒体类型解决了这个问题,以便它跳过这两个并点击普通的 MappingJackson2HttpMessageConverter,它适用于嵌套实体。

例如,如果您扩展 RepositoryRestMvcConfiguration 并添加此方法,那么当您发送内容类型为 'application/json' 的请求时,它将命中普通的 MappingJackson2HttpMessageConverter,而不是尝试反序列化为 URI:

@Override
public void configureHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
    ((MappingJackson2HttpMessageConverter) messageConverters.get(0))
            .setSupportedMediaTypes(asList(MediaTypes.HAL_JSON));
    ((MappingJackson2HttpMessageConverter) messageConverters.get(2))
            .setSupportedMediaTypes(asList(MediaType.APPLICATION_JSON));
}

配置 RepositoryRestMvcConfiguration 中 defaultMessageConverters() 生成的消息转换器。

请记住,普通的 objectMapper 无法处理 JSON 中的 URI - 在您传递嵌入式实体的 URI 时,您仍然需要点击两个预配置的消息转换器之一。

【讨论】:

  • 你的意思是你不能 POST 一个有一个字段作为嵌入实体而另一个字段作为 URI 的对象?
  • 是的,直到这个问题得到修复,它才会起作用。如果您像我建议的那样覆盖 configureHttpMessageConverters ,您仍然需要为每个请求选择一种策略 - 将所有嵌套实体嵌入为 JSON 或为所有实体发送 URI - 并适当地设置请求的内容类型,使其命中正确的 messageConverter。
  • 您是否知道是否有针对此的错误报告?
【解决方案3】:

您的 JSON 的一个问题是您试图将字符串反序列化为问题:

"question": "http://localhost:8080/question/6"

在您的 Answer 对象中,Jackson 期待有一个问题对象。看来您正在使用 URL 作为 ID,因此您需要为您的问题传递类似这样的内容,而不是字符串:

"question": {
    "id": "http://localhost:8080/question/6"
}

【讨论】:

  • Sam,感谢您的回答,但它仍然无法正常工作。我有同样的错误。还有其他想法吗?
【解决方案4】:

尝试更新“Spring Boot Data REST Starter”库。为我工作。

【讨论】:

  • 我正在使用 spring-boot-starter-data-rest-1.2.5.RELEASE.jar。但我还是有问题。我必须升级哪个版本?
猜你喜欢
  • 2018-05-28
  • 1970-01-01
  • 1970-01-01
  • 2017-11-13
  • 1970-01-01
  • 2018-07-23
  • 2014-07-09
  • 2018-07-06
相关资源
最近更新 更多