为啥@RequestBody 得到一个具有空属性的对象

Posted

技术标签:

【中文标题】为啥@RequestBody 得到一个具有空属性的对象【英文标题】:Why is the @RequestBody getting an object with null attributes为什么@RequestBody 得到一个具有空属性的对象 【发布时间】:2020-01-18 15:39:55 【问题描述】:

我有一个 springboot REST 控制器,它带有 PATCH 和 PUT 的请求方法,如图所示。出于某种原因,@RequestBody“公司”的字段/属性以空值的形式出现。我错过了什么?

我在前端使用 angular8,它正在执行 PATCH 调用。

我尝试了其他一些帖子建议,但没有运气。如果这是一个重复的问题,请指出答案。

Spring Tool Suite 4 

版本:4.1.0.RELEASE 内部版本号:201812201347

版权 (c) 2007 - 2018 Pivotal, Inc. 版权所有。访问http://spring.io/tools4

我正在为 postgres 使用 pgAdmin 4.12。

这是我从 Angular 发出的调用:

    this.companyService.patch$(this.currentId, this.appMenu.currentObject).subscribe(selectedCompany => this.appMenu.currentObject = selectedCompany);

这是如上所述调用的角度服务:

    import  Injectable  from '@angular/core';
import  HttpClient  from '@angular/common/http';
import  Observable, BehaviorSubject  from 'rxjs';
import  Company  from '../models/company.model';

@Injectable(
  providedIn: 'root'
)
export class CompanyService 
  private url: string;

  constructor(private http: HttpClient) 
    this.url = "http://localhost:8080/niche/company";
  

  getOne$ = (companyId: number): Observable<Company> => this.http.get<Company>(`$this.url/$companyId`);

  get$ = (): Observable<Company[]> => this.http.get<Company[]>(this.url);

  post$ = (company: Company): Observable<Company> => this.http.post<Company>(this.url,  company );

  patch$ = (companyId: number, company: Company): Observable<Company> => this.http.patch<Company>(`$this.url/$companyId`,  company );

  delete$ = (companyId: number): Observable<Company> => this.http.delete<Company>(`$this.url/$companyId`);

从 Angular 前端请求有效负载:

    company: createdBy: "denis", createdDate: "2019-04-14T04:00:00.000+0000", updatedBy: "denis",…
company: createdBy: "denis", createdDate: "2019-04-14T04:00:00.000+0000", updatedBy: "denis",…
companyName: "Bull Winkle"
createdBy: "denis"
createdDate: "2019-04-14T04:00:00.000+0000"
email: "bullwinkle@mail.com"
id: 2
notes: "test"
phone: "999999999"
products: []
updatedBy: "denis"
updatedDate: "2019-05-14T04:00:00.000+0000"
webSite: "bullwilkle.com"

这是实际的 JSON:

"company":"createdBy":"denis","createdDate":"2019-04-14T04:00:00.000+0000","updatedBy":"denis","updatedDate":"2019-05-14T04:00:00.000+0000","id":2,"email":"bullwinkle@mail.com","companyName":"Bull Winkle","webSite":"bullwilkle.com","phone":"999999999","notes":"test","products":[]

springboot 后端控制器:

    /**
 * 
 */
package com.ebusiness.niche.controller;

import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;

import com.ebusiness.niche.entity.Company;
import com.ebusiness.niche.service.CompanyService;
//import com.sun.istack.internal.logging.Logger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author denisputnam
 *
 */
@RestController
@RequestMapping( value = "/niche" )
public class CompanyController 
    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private CompanyService companyService;

    @ResponseBody
    @RequestMapping(value = "/company", method =  RequestMethod.GET )
    @CrossOrigin(origins = "http://localhost:4200")
    public ResponseEntity<List<Company>> getCompanys() 
        log.info("getCompanys(): Called...");

        List<Company> companyList = null;

        companyList = this.companyService.findAll();

        if( companyList == null || companyList.isEmpty() ) 
            log.info("getCompanys(): returned a null or empty list.");
            ResponseEntity<List<Company>> rVal = new ResponseEntity<List<Company>>(companyList, HttpStatus.NO_CONTENT);
            return rVal;
        
        return new ResponseEntity<List<Company>>(companyList, HttpStatus.OK);       
    

    @ResponseBody
    @RequestMapping(value = "/company/id", method =  RequestMethod.GET )
    @CrossOrigin(origins = "http://localhost:4200")
    public ResponseEntity<Company> getCompany(@PathVariable("id") Long id) 
        log.info("getCompany(): Called...");
        log.info("id=" + id);

//      List<Company> companyList = null;
        Optional<Company> optcompany = null;
        Company company = null;

        optcompany = this.companyService.findById(id);

        if( optcompany == null  ) 
            log.info("getCompany(): returned a null.");
            ResponseEntity<Company> rVal = new ResponseEntity<Company>(company, HttpStatus.NO_CONTENT);
            return rVal;
         else 
            company = optcompany.get();
        
        return new ResponseEntity<Company>(company, HttpStatus.OK);     
    

    @ResponseBody
//  @RequestMapping(value = "/company/id", headers = 
//    "content-type=application/json" , consumes = MediaType.APPLICATION_JSON_VALUE, method =  RequestMethod.PATCH, RequestMethod.PUT, RequestMethod.POST )
    @RequestMapping(value = "/company/id", method =  RequestMethod.PATCH, RequestMethod.PUT )
    @CrossOrigin(origins = "http://localhost:4200")
    public ResponseEntity<Company> updateCompany(@PathVariable("id") Long id, @RequestBody Company company) 
        log.info("updateCompany(): Called...");
        log.info("id=" + id);

        Optional<Company> currentCompany = this.companyService.findById(id);
        Company dbCompany = null;

        if( currentCompany == null ) 
            log.error("Unable to update.  The company with id  not found.", id);
            ResponseEntity<Company> rVal = new ResponseEntity<Company>(company, HttpStatus.NO_CONTENT);
            return rVal;
        

        dbCompany = currentCompany.get();
        dbCompany.setCompanyName(company.getCompanyName());
        dbCompany.setEmail(company.getEmail());
        dbCompany.setNotes(company.getNotes());
        dbCompany.setPhone(company.getPhone());
        dbCompany.setWebSite(company.getWebSite());

        this.companyService.update(dbCompany);

        return new ResponseEntity<Company>(dbCompany, HttpStatus.OK);       
    


springboot后端实体bean:

    /**
 * 
 */
package com.ebusiness.niche.entity;

import java.io.Serializable;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.NotNull;

/**
 * @author denisputnam
 *
 */
@Entity
@Table(
        uniqueConstraints = 
                @UniqueConstraint(columnNames = "email")
        
)
public class Company extends History implements Serializable

    /**
     * 
     */
    private static final long serialVersionUID = 1329304564033025946L;

    @Id
    @GeneratedValue
    private Long id;

    @Column
    @NotNull
    private String email;

    @Column
    @NotNull
    private String companyName;

    @Column
    @NotNull
    private String webSite;

    @Column
    @NotNull
    private String phone;

    @Column(length=4096)
    private String notes;

    @ManyToMany(mappedBy="companys")
    Set<Product> products;

    public String getNotes() 
        return notes;
    

    public void setNotes(String notes) 
        this.notes = notes;
    

    public Long getId() 
        return id;
    

    public void setId(Long id) 
        this.id = id;
    

    public String getEmail() 
        return email;
    

    public void setEmail(String email) 
        this.email = email;
    

    public String getCompanyName() 
        return companyName;
    

    public void setCompanyName(String companyName) 
        this.companyName = companyName;
    

    public String getWebSite() 
        return webSite;
    

    public void setWebSite(String webSite) 
        this.webSite = webSite;
    

    public String getPhone() 
        return phone;
    

    public void setPhone(String phone) 
        this.phone = phone;
    

    public Set<Product> getProducts() 
        return products;
    

    public void setProducts(Set<Product> products) 
        this.products = products;
    


这个 History 基类实体 bean:

    /**
 * 
 */
package com.ebusiness.niche.entity;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.validation.constraints.NotNull;

/**
 * @author denisputnam
 *
 */
//@Entity
@MappedSuperclass
public class History implements Serializable 
    /**
     * 
     */
    private static final long serialVersionUID = -1136283585074348099L;
    private String createdBy;
    @Column(nullable = false)
    @NotNull
    private Date createdDate = new Date();
    private String updatedBy;
    private Date updatedDate;
    /**
     * @return the createdBy
     */
    public String getCreatedBy() 
        return createdBy;
    
    /**
     * @param createdBy the createdBy to set
     */
    public void setCreatedBy(String createdBy) 
        this.createdBy = createdBy;
    
    /**
     * @return the createdDate
     */
    public Date getCreatedDate() 
        return createdDate;
    
    /**
     * @param createdDate the createdDate to set
     */
    public void setCreatedDate(Date createdDate) 
        this.createdDate = createdDate;
    
    /**
     * @return the updatedBy
     */
    public String getUpdatedBy() 
        return updatedBy;
    
    /**
     * @param updatedBy the updatedBy to set
     */
    public void setUpdatedBy(String updatedBy) 
        this.updatedBy = updatedBy;
    
    /**
     * @return the updatedDate
     */
    public Date getUpdatedDate() 
        return updatedDate;
    
    /**
     * @param updatedDate the updatedDate to set
     */
    public void setUpdatedDate(Date updatedDate) 
        this.updatedDate = updatedDate;
    



我对此进行了编辑以添加来自控制台日志的 TRACE 输出。我还简化了 Company 实体,使其不包含任何日期或与其他实体的任何其他关系,因此现在只传递字符串。

这是 TRACE 输出:

   2019-09-18 15:52:19.591 TRACE 47732 --- [nio-8080-exec-1] o.h.r.j.i.ResourceRegistryStandardImpl   : Releasing JDBC resources
2019-09-18 15:52:19.591 TRACE 47732 --- [nio-8080-exec-1] cResourceLocalTransactionCoordinatorImpl : ResourceLocalTransactionCoordinatorImpl#afterCompletionCallback(true)
2019-09-18 15:52:19.591 TRACE 47732 --- [nio-8080-exec-1] .t.i.SynchronizationRegistryStandardImpl : SynchronizationRegistryStandardImpl.notifySynchronizationsAfterTransactionCompletion(3)
2019-09-18 15:52:19.591 TRACE 47732 --- [nio-8080-exec-1] org.hibernate.internal.SessionImpl       : SessionImpl#afterTransactionCompletion(successful=true, delayed=false)
2019-09-18 15:52:19.594 DEBUG 47732 --- [nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Using 'application/json', given [application/json, text/plain, */*] and supported [application/json, application/*+json, application/json, application/*+json]
2019-09-18 15:52:19.594 TRACE 47732 --- [nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor  : Writing [[com.ebusiness.niche.entity.Company@ddb52f3, com.ebusiness.niche.entity.Company@73d5674e]]
2019-09-18 15:52:19.619 TRACE 47732 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : No view rendering, null ModelAndView returned.
2019-09-18 15:52:19.619 TRACE 47732 --- [nio-8080-exec-1] org.hibernate.internal.SessionImpl       : Closing session [f1652eeb-71a2-4776-8d94-9573336d60f3]
2019-09-18 15:52:19.619 TRACE 47732 --- [nio-8080-exec-1] o.h.e.jdbc.internal.JdbcCoordinatorImpl  : Closing JDBC container [org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl@6effa4c8]
2019-09-18 15:52:19.619 TRACE 47732 --- [nio-8080-exec-1] o.h.r.j.i.ResourceRegistryStandardImpl   : Releasing JDBC resources
2019-09-18 15:52:19.619 TRACE 47732 --- [nio-8080-exec-1] o.h.r.j.i.LogicalConnectionManagedImpl   : Closing logical connection
2019-09-18 15:52:19.620 TRACE 47732 --- [nio-8080-exec-1] o.h.r.j.i.ResourceRegistryStandardImpl   : Releasing JDBC resources
2019-09-18 15:52:19.620 TRACE 47732 --- [nio-8080-exec-1] o.h.r.j.i.LogicalConnectionManagedImpl   : Logical connection closed
2019-09-18 15:52:19.620 DEBUG 47732 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed 200 OK, headers=masked
2019-09-18 15:52:31.192 TRACE 47732 --- [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public org.springframework.http.ResponseEntity<com.ebusiness.niche.entity.Company> com.ebusiness.niche.controller.CompanyController.updateCompany(java.lang.Long,com.ebusiness.niche.entity.Company)
2019-09-18 15:52:31.264 TRACE 47732 --- [nio-8080-exec-3] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public org.springframework.http.ResponseEntity<com.ebusiness.niche.entity.Company> com.ebusiness.niche.controller.CompanyController.updateCompany(java.lang.Long,com.ebusiness.niche.entity.Company)
2019-09-18 15:52:31.266 TRACE 47732 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet        : PUT "/niche/company/2", parameters=, headers=masked in DispatcherServlet 'dispatcherServlet'
2019-09-18 15:52:31.267 TRACE 47732 --- [nio-8080-exec-3] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public org.springframework.http.ResponseEntity<com.ebusiness.niche.entity.Company> com.ebusiness.niche.controller.CompanyController.updateCompany(java.lang.Long,com.ebusiness.niche.entity.Company)
2019-09-18 15:52:31.268 TRACE 47732 --- [nio-8080-exec-3] .i.SessionFactoryImpl$SessionBuilderImpl : Opening Hibernate Session.  tenant=null, owner=null
2019-09-18 15:52:31.268 TRACE 47732 --- [nio-8080-exec-3] org.hibernate.internal.SessionImpl       : Opened Session [740dabea-2970-4491-8ff7-d373afc649f6] at timestamp: 1568836351268
2019-09-18 15:52:31.268 TRACE 47732 --- [nio-8080-exec-3] o.s.web.cors.DefaultCorsProcessor        : Skip: response already contains "Access-Control-Allow-Origin"
2019-09-18 15:52:31.312 TRACE 47732 --- [nio-8080-exec-3] m.m.a.RequestResponseBodyMethodProcessor : Read "application/json;charset=UTF-8" to [com.ebusiness.niche.entity.Company@46388186]
2019-09-18 15:52:31.320 TRACE 47732 --- [nio-8080-exec-3] .w.s.m.m.a.ServletInvocableHandlerMethod : Arguments: [2, com.ebusiness.niche.entity.Company@46388186]

【问题讨论】:

在这行companyService.update(dbCompany); 之前,您是否将 bdCompany 设为空?您可以调试并让我们知道这是否没有属性为 null 则意味着当公司服务更新时 dbCompany 正在那里做某事。 从 find 调用返回的 currentCompany 中设置 dbCompany。这不是空的。问题是“company”参数具有 null 属性,这会导致在 update() 调用上为 null 值触发约束。我需要知道为什么属性作为公司参数的空值出现。 payload 显示 angular 正在设置 json 请求的属性。 那你是在假设后端的问题吗?正确的? Optional&lt;Company&gt; currentCompany = this.companyService.findById(id); 让我们知道这个变量是否有一些带有值(非空)的字段,如果这是服务中的问题。 嗨乔纳森,我没有假设任何事情。我只知道调试器显示@RequestBody 公司具有空属性。 currentCompany 和 dbCompany 不是问题。它是传递给请求的参数。从 Angular 发送带有给定有效负载的请求的时间,到它到达 updateCompany() 的请求方法的时间,出现了问题。只要“company”参数属性为空,dbCompany 变量就会设置错误。 【参考方案1】:

好的,这个问题是因为从Front End application 发送了一个 JSON 主体作为嵌套了公司的 JSON 对象。 company

patch$ = (companyId: number, company: Company): Observable<Company> 
          => this.http.patch<Company>(`$this.url/$companyId`,  company );

然后当请求到达Back End application 时,字段值是null,因为它是company 中的一个JSON 对象。已通过删除括号 以将 company 作为对象来解决此问题。

patch$ = (companyId: number, company: Company): Observable<Company> 
          => this.http.patch<Company>(`$this.url/$companyId`, company);

【讨论】:

有效载荷需要看起来像,"createdBy":"denis","createdDate":"2019-04-14","updatedBy":"denis","updatedDate":"2019 -05-14","id":2,"email":"bullwinkle@mail.com","companyName":"Bull Winkle","webSite":"bullwilkle.com","phone":"999999999" ,"notes":"test" 而不是 "company":"createdBy":"denis","createdDate":"2019-04-14","updatedBy":"denis","updatedDate": "2019-05-14","id":2,"email":"bullwinkle@mail.com","companyName":"Bull Winkle","webSite":"bullwilkle.com","电话":" 999999999","notes":"test" 感谢乔纳森,感谢您的帮助。【参考方案2】:

我的 Spring Boot 后端代码

@PutMapping("updateFile")
    public ResponseEntity<String> updateAttachment(@RequestBody Attachment attachment) 
        storageService.updateAttachment(attachment);
        return ResponseEntity.ok("file updated successfully");
    

我之前尝试过下面的代码,并且在后端 attachment 整个对象发生 null

  const message = await axiosInstance.put<string>(
        `localhost:8080/files/updateFile`,
         attachment:attachment,
      );

下面的代码有效。传递对象时,只需传递整个对象而不使用括号。它将被视为数据。

const message = await axiosInstance.put<string>(
    `localhost:8080/files/updateFile`,
      attachment,
  );

【讨论】:

以上是关于为啥@RequestBody 得到一个具有空属性的对象的主要内容,如果未能解决你的问题,请参考以下文章

为啥在 ::after 伪元素上需要一个空的 `content` 属性? [复制]

@RequestBody 正在获取空值

猫鼬帮助,为啥我得到空值?

为啥 Flutter Floor DAO findAll 返回具有空 ID 的实体集?

为啥我用这些代码得到一个空的 Tableview? JSON

为啥 mongoosastic 填充/弹性搜索没有填充我的参考资料之一?我得到一个空对象