PUT方法(RESTful)不能用作更新资源的方法

Hir*_*oki 3 java rest jax-rs put http-method

根据这篇文章(http://restcookbook.com/HTTP%20Methods/put-vs-post/),PUT应该作为更新资源的方法.

但是,使用JAX_RS 2.0和Jersey 2.0练习RESTful,我认为它不会更新特定资源.(即我正在使用JAX_RS 2.0和Jersey 2.0学习RESTful)

这是一个这样的资源.

<customer>
    <name>Before</name>
    <postcode>111</postcode>
</customer>
Run Code Online (Sandbox Code Playgroud)

我想要做的是更新(也许我应该说"替换")这个资源.

    ClientConfig config = new ClientConfig();
    Client client = ClientBuilder.newClient(config);
    WebTarget target =  client.target("http://xxx/yyy/zzz/end.cust");

    Customer cust = new Customer();
    cust.setName("After");
    cust.setPostcode(222);

    target.path("Before").request().put(Entity.xml(cust));
Run Code Online (Sandbox Code Playgroud)

@Id注释在"Customer"类中设置为"Name",因此路径"Before"应该作为ID工作,第一个资源(名为"Before")应该替换为第二个资源(名为"After") ").

但是,在执行上面的编码之后,"之前"资源仍然存在,并且有一个新的"After"resrouce.似乎PUT方法可以创建一个新资源,而不是更新某些东西.(即有"之前"和"之后"资源,并且没有更新任何内容)

我测试了一个POST方法以创建一个新资源,并按照我的预期创建了一个新资源.

如果你看到我做错了什么或需要做什么,你能提出一些建议吗?

编辑

我将添加服务器端代码.用@PUT注释的方法是这样的.

 @PUT
 @Path("{id}")
 @Consumes({"application/xml", "application/json"})
 public void edit(@PathParam("id") String id, Customer entity) {
     super.edit(entity);
 }
Run Code Online (Sandbox Code Playgroud)

这是在一个名为CustomerFacadeREST.java的类中,在我创建"来自数据库的RESTful服务"后自动创建.

根据NetBeans的文档,super.edit()方法最初是这样的.

 public void edit(T entity) {
 getEntityManager().merge(entity);
 }
Run Code Online (Sandbox Code Playgroud)

在"Customer"类中,@ Id以这种方式设置为"name"值.

 public class Customer implements Serializable {
     private static final long serialVersionUID = 1L;
     @Id
     @Basic(optional = false)
     @NotNull
     @Size(min = 1, max = 80)
     @Column(name = "Name")
     private String name;
     // Other fields, such as Postcode...

     public Customer() {
     }

     // Other constructors and methods...
     }
Run Code Online (Sandbox Code Playgroud)

Pau*_*tha 5

"HTTP动词"背后的想法,如PUT,GET,POST,DELETE只是协议语义的问题.只是执行HTTP PUT操作不会做任何神奇的事情.这是开发人员在开发时应该理解的正确语义,因为这些语义是众所周知的(这就是协议存在的原因).如果没有人遵循这些语义,世界将介于大萧条和天启之间.

话虽这么说,这些动词(语义)是一种保证(或者可能是一个更好的词),对于执行请求的客户端,某个动词将具有一些已知的语义.一个主要因素是幂等性的概念.幂等性是这样的想法:无论我提出多少次请求,结果都是相同的(或具有相同的效果).

某些HTTP动词被认为是幂等的,例如PUT,DELETE,GET.无论多少次提出完全相同的请求,一般的想法是结果/效果应该是相同的.另一方面,POST被认为不是幂等的,因为完全相同的POST请求可能产生不同的结果,例如,提交订单,错误地,再次或创建新客户两次.

如果我们想让世界变得更美好,并为拯救世界免于完全崩溃而尽自己的本分,我们应该学习这些语义并遵循它们成为好公民.除了幂等性之外,还有更多关于动词语义的知识,但理解这一点,是一个好的开始.我建议你可以拿一本关于REST的好书来学习一些好的做法.或者,如果你想要成为一个很酷的孩子,花点时间阅读圣经(实际上是菲尔丁论文).

总而言之,作为开发人员,我们的工作是创建遵循这些语义的代码.您的方法创建新资源的原因可能是因为您正在使用代码创建新资源.也许这样的事情似乎更合适:

@PUT
@Path("/customers/{id}")
@Consumes(MediaType.APPLICATION_JSON)
public Response updateCustomer(@PathParam("id") long id, 
                               Customer updateCustomer) {

    Customer customer = customerService.getCustomerById(id);
    if (customer == null) {
        throw new WebApplicationException("Can't find it", 404);
    }

    customer.setFirstName(updateCustomer.getFirstName());
    customer.setLastName(updateCustomer.getLastName());
    ...

    return Response.noContent().build();
}
Run Code Online (Sandbox Code Playgroud)

所以我们只是更新我们数据库中已存在的客户.通常,在更新PUT请求时,应该知道特定的客户资源URI.所以说客户提出请求http://blah.com/api/customers/1234,我们的服务将使用id查找客户1234.如果找不到,我们返回404状态代码,因为资源不存在.如果确实存在,那么我们使用请求中提供的客户数据更新客户.如果您想创建一个新客户,其中URI未知,那么POST将是正确的,您将发送客户表示http://blah.com/api/customers.

还要保留一个仅供参考:在许多情况下这样的情况下,会发生的情况是客户端请求(GET)资源,比如客户,并更新该客户表示,然后将其作为PUT请求发送回更新的客户.在服务器上,它应该使用该信息来更新特定客户的数据,如上面的示例所示.


UPDATE

根据你的编辑.你完全忽略了这应该如何工作的重点.

Customer cust = new Customer();
cust.setName("After");
cust.setPostcode(222);

target.path("Before").request().put(Entity.xml(cust));
Run Code Online (Sandbox Code Playgroud)

这有什么问题,使用new Customer,您将设置标识符"After",这与您正在使用的请求路径中的标识符不同"Before".所以路径变量{id}是"之前".使用此请求URI,您表示要访问ID为"之前"的客户.如我的代码所示,您有责任检查数据库中是否存在ID为"之前"的客户.如果没有,你应该退回一个404 Not Found.name您为new设置的(id)Customer应该是数据库中预期的id.因此,如果您想在数据库"After"中更新id为id的客户.那么你应该把"After"放在路径中,而不是"之前".我们不应该尝试更改标识符.

就像我说的,当我们想要更新资源时,我们通常会获取资源,更新一些字段(但不是标识符),然后将其发回.序列可能看起来像

final String PATH = "http://hello.com/api/customers"
WebTarget target = client.target(PATH);
Customer customer = target.path("1234").request().get(Customer.class);
// where 1234 is the id (or in your case `name` of the customer.
// I would avoid using the name as the DB id, that's why my example uses numbers
customer.setPostalCode(...);
target = client.target(PATH).path(customer.getName()); // getName should be 1234
Response response = target.request().put(Entity.xml(customer));
Run Code Online (Sandbox Code Playgroud)

我们在路径中使用与我们提供的相同的id,因为这是在服务器中识别资源的方式.