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

时间:2014-12-05 10:41:45

标签: 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>

我尝试做的是更新(也许我应该说&#34;替换&#34;)此资源。

    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));

@Id注释设置为&#34;名称&#34;在&#34;客户&#34;上课,所以路径&#34;之前&#34;应该作为ID工作,第一个资源(名为&#34; Before&#34;)应该替换为第二个资源(名为&#34; After&#34;)。

然而,在执行上述编码后,&#34;之前&#34;资源仍然存在,并且有一个新的&#34; After&#34; resrouce。 似乎PUT方法可以创建一个新资源,而不是更新某些东西。 (即&#34;之前&#34;以及&#34;之后&#34;资源,并且没有任何更新)

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

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

修改

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

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

这是一个名为CustomerFacadeREST.java的类,在我从Database&#34;创建一个&#34; RESTful服务后自动创建。

根据NetBeans&#39; document,super.edit()方法最初是这样的。

 public void edit(T entity) {
 getEntityManager().merge(entity);
 }

在&#34;客户&#34; class,@ Id设置为&#34; name&#34;这样的价值。

 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...
     }

1 个答案:

答案 0 :(得分:5)

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

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

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

如果我们想让世界变得更美好,并为拯救世界免于彻底崩溃而尽自己的本分,我们应该学习这些语义,并遵循它们成为好公民。除了幂等性之外,还有更多关于动词语义的知识,但要理解这一点,是一个好的开始。我建议你可以拿一本关于REST的好书来学习一些好的做法。或者,如果你想要成为一个很酷的孩子,take time to read the bible (actually the Fielding Dissertation)

所有这一切,它是我们的工作作为开发人员创建遵循这些语义的代码。您的方法创建新资源的原因可能是因为您 使用您的代码创建新资源。也许这样的事情似乎更合适:

@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();
}

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

另外只保留一个FYI:在很多情况下是这样的情况,会发生的是客户端请求(GET)资源,比如客户,并更新该客户表示,然后将其作为PUT请求发送回更新顾客。在服务器上,它应该使用该信息来更新特定客户的数据,如上例所示。


更新

根据修改。你完全忽略了这应该如何工作的重点。

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

target.path("Before").request().put(Entity.xml(cust));

这有什么问题,使用新的Customer,您将标识符设置为"After",这与请求路径中的标识符不同,您正在使用"Before"。所以路径变量{id}是“之前”。使用此请求URI,您表示要访问ID为“之前”的客户。如我的代码所示,您有责任检查数据库中是否存在ID为“之前”的客户。如果没有,您应该返回404 Not Found。您为新name设置的Customer(id)应该是数据库中预期的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));

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