【问题标题】:PUT method (RESTful) doesn't work as a way to update resourcesPUT 方法 (RESTful) 不能用作更新资源的方法
【发布时间】:2014-12-05 10:41:44
【问题描述】:

根据这篇文章(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>

我想做的是更新(也许我应该说“替换”)这个资源。

    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 注释在“Customer”类中设置为“Name”,因此路径“Before”应该用作 ID,第一个资源(名为“Before”)应该替换为第二个资源(命名为“之后”)。

但是,执行上述编码后,“Before”资源仍然存在,并且有一个新的“After”资源。 似乎 PUT 方法用于创建新资源,而不是更新某些内容。 (即有“之前”和“之后”资源,没有任何更新)

为了创建新资源,我测试了一个 POST 方法,它按我的预期创建了一个新资源。

如果您发现我做错了什么或需要做什么,请您给点建议吗?

编辑

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

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

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

根据NetBeans的文档,super.edit()方法原来是这样的。

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

在“客户”类中,@Id 以这种方式设置为“名称”值。

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

【问题讨论】:

  • 您的请求处理代码在哪里?
  • 粘贴您的服务器端代码。似乎它不允许您修改&lt;name/&gt; 标签。
  • >Smutje,恐怕我不确定您所说的请求处理代码到底是什么意思。和request()有关系吗?
  • >carlspring,我添加了一些服务器端代码。我希望他们会有所帮助。

标签: java rest jax-rs put http-method


【解决方案1】:

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 为“Before”的客户。如我的代码所示,您有责任检查数据库中是否存在 ID 为“Before”的客户。如果没有,您应该返回404 Not Found。您为新的Customer 设置的name (id) 应该是数据库中预期的id。因此,如果您想在数据库“之后”中使用 id 更新客户。那么你应该把“After”放在路径中,而不是“Before”。我们不应该尝试更改标识符。

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

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,因为这是在服务器中识别资源的方式。

【讨论】:

  • 非常感谢您的回复,是的,我应该阅读这篇论文。顺便说一句,听起来我的 PUT 不起作用的原因来自 ID,对吧? (在您的示例中,1234 用作 ID,在我的情况下,名称是 ID。我在上面添加了更多信息。)另外,您认为我应该修改 @PUT 方法(自动生成),以便它会和你的相似吗?
  • 至于你的最后一个问题,你应该首先检查数据库中是否存在{id},如果没有,是的,发回一个404,如果它确实存在,那么merge应该更新它
  • 另外,无论您使用带文字的名称(“After”)还是数字(“1234”),都无关紧要。我只是使用了一个在数据库 ID 中更常见的数字。在浏览器中查看 URL。你应该看到http://stackoverflow.com/questions/2731404927314049 很可能是该问题的数据库 ID。名称很常见,并且有很多重复,这就是名称数据库ID不好的原因。增加数字可能会更好。
  • 非常感谢。终于更新成功了,学到了很多!!
猜你喜欢
  • 2013-05-28
  • 2014-05-27
  • 2013-12-08
  • 2013-07-24
  • 2013-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多