0

我们正在使用 Spring MVC 3.2、Jackson 1.9.12、Hibernate 4.2.2 构建 REST-API。

昨天我需要将一个小对象图传递给 API,它应该保存在数据库中。它有些工作,我可以为这些问题构建解决方法,但我认为必须有一个更好的解决方案。

所以这就是我们所拥有的:

  • 班级Bill
  • 班级Contact
  • 班级Address
  • 班级Item

关系是:

  • Bill与 有两个 OneToOne关系Contact。一叫sender,一叫receiver
  • Bill与 有ManyToOne关系Item,称为item
  • Contact与 有OneToOne关系Address,称为address

(这可能看起来有点愚蠢,但我省略了一些字段和其他关系,以解决问题)

第一个问题 - 简单案例 - 覆盖数据库条目:

让我们忽略所有这些依赖和关系,只使用普通Bill类。如果有一个不包含对象 ID 的传入 JSON 请求,一切都会按预期工作,并且 Spring/Hibernate 在数据库中创建一个条目:

{ ...
  "createdAt":"2013-05-29"
}

但是如果我在 JSON 请求中指定一个 id,Spring/Hibernate 将更新包含给定 id 的行:

{ ...
  "id":"c5a562d0-c8ab-1c42-8399-080012345678"
  "createdAt":"2013-05-29"
}

如果我在 JSON 请求中指定了一个 ID,但该 ID 在数据库中不存在,它将创建一个新条目。

我使用的代码没什么特别的。这是控制器动作:

@RequestMapping(value = "/create", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.CREATED)
public Bill create(@RequestBody Bill bill) {
    bill = service.createBill(bill);
    return bill;
}

以及服务方法:

@PreAuthorize("isAuthenticated()")
@Transactional
public Bill createBill(Bill bill) {
    bill = billDao.store(bill);
    return bill;
}

如您所见,我想使用/create路径来创建新条目,而不是更新现有条目。作为一种解决方法,我当然可以setId(null)控制器操作的第一行使用,但是有没有更好/更好的方法?

第二个问题 - 另一个 id 的东西 - 如何正确设计实体?

我们再次得到上面的对象图:

"bill":{ ...
  "sender":{ ...
    "address":{ ... }
  },
  "receiver":{ ...
    "address":{ ... }
  },
  "item":{ ... }
}

Contact(and Address) 和之间存在逻辑上的区别Item

  • A Contact(and Address) 由一堆数据组成。意味着:GUI 将显示很多输入字段,用户必须填写这些字段。
  • AnItem 已经在数据库中,我基本上只需要它id,而不是整个对象。这意味着:GUI 将显示一个包含所有现有Items 的下拉列表,用户必须选择其中一个。

现在我当然想使用(和重用)我的实体,但是我的Bill实体例如需要一个Item对象,而不是item_id. 作为一种解决方法,我当然可以创建另一个 bean,它与类几乎相同Bill,但item它包含一个item_id

"the_new_special_bill_for_incoming_json":{ ...
  "sender":{ ...
    "address":{ ... }
  },
  "receiver":{ ...
    "address":{ ... }
  },
  "item_id":{ ... }
}

再说一遍,同样的问题:有更好/更好的方法吗?

感谢你们!

4

2 回答 2

0
  1. 不,您应该将 id 设置为空。在我看来,这是使用 Hibernate 最简单/最好的方法。

但是,还有其他选择。如果您使用的是 Hibernate 会话,它具有三个显着方法persistmergeupdatesave,正如您从javadocs中看到的那样。

过去关于堆栈溢出的问题中可以看出,我最初所说的是不正确的。(我一直在使用 Spring Data,情况就是如此。)如果您使用的是休眠会话,您应该使用persistand-- update

  1. 至于您的第二个问题,一个选项是您可以创建一个特殊的反序列化器来使用 itemIditem在您的对象中设置一个对象。bill

像这样的东西:

public class CustomDeserializer<Item> extends StdDeserializer<Item> {

    protected CustomDeserializer(Class<Item> vc) {
        super(vc);
        // TODO Auto-generated constructor stub
    }

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

    @Override
    public Item deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {

        // use context to deserialize id into item?
        return null;
    }
}

或者对于 2 号,更简单更好的方法可能是:

添加到Bill

public class Bill {

private Item item;

[bill stuff ...]

@JsonProperty(value="item_id")
public void setItem_id(String item_id) {
   this.item = itemDao.getItem(item_id);
}
}
于 2013-05-29T08:20:01.117 回答
0

对于 #1,您可以在创建时忽略 JSON 中的 id 属性:

class Bill {
    @JsonIgnore
    public String id;
}

如果您需要在获取账单时公开 id 属性,请添加:

class Bill {
    ...
    @JsonProperty("id")
    public String id() {
        return id;
    }
}

然后,如果您有更新方法,例如PUT /bills/{id},明确设置 id:

@RequestMapping(value = "/bills/{id}", method = PUT)
public void update(@PathVariable String id, @RequestBody Bill bill) {
    bill.id = id;
    // ...
}

另一种选择是使用 DTO 类(在您的控制器类旁边)。

对于#2,给定

class Bill {
    ...
    @ManyToOne
    public Item item;
}

您应该能够仅使用 id 传入一个 Item:

{
    ...
    item : {
        "id" : "123"
    }
}

并且这种关系将与法案一起被保存。

测试一下。

于 2013-05-29T20:57:18.457 回答