0

we're building a REST-API using Spring MVC 3.2, Jackson 1.9.12, Hibernate 4.2.2.

Yesterday I needed to pass a small object graph to the API, which should get persisted in the database. It some kind of works and I could build workarounds for those problems, but I think there must be a nicer solution.

So here is what we have:

  • Class Bill
  • Class Contact
  • Class Address
  • Class Item

Relations are:

  • Bill has two OneToOne relations to Contact. One called sender, one called receiver.
  • Bill has a ManyToOne relation to Item, called item.
  • Contact has a OneToOne relation to Address, called address.

(This may seem a bit dumb, but I left out some fields and other relations, to bring the problem to the point)

First Problem - Simple case - Overriding database entries:

Let's ignore all those dependencies and relations and just take the plain Bill class. If there's an incoming JSON request which doesn't contain an object id everything works as expected and Spring/Hibernate creates a new entry in database:

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

But if I specify an id in the JSON request, Spring/Hibernate will update the row containing the given id:

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

If I specify an id in the JSON request, but that id doesn't exist in database, it will create a new entry.

The code I use is nothing special. Here's the controller action:

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

And the service method:

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

As you can see, I'd like to use the /create path to create new entries and not update already existing ones. As a workaround I could of course use setId(null) in the first line of the controller action, but is there any nicer/better way?

Second Problem - Another id thing - How to correctly design entities?

We again have the object graph from above:

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

There's a logical difference between Contact(and Address) and Item:

  • A Contact (and Address) consists of a bunch of new data. Means: The GUI will display a lot input fields, which the user has to fill in.
  • An Item is already in the database, and I basically would just need its id, not the whole object. This means: The GUI will display a dropdown list with all existing Items and the user has to choose one of them.

Now of course I'd like to use (and reuse) my entities, but my Bill entity for example expects an Item object, not an item_id. As a workaround I could of course create another bean, which is nearly the same as the Bill class, but instead of item it contains an item_id:

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

So again, the same question: Is there a nicer/better way?

Thank you guys!

Benjamin M
  • 23,599
  • 32
  • 121
  • 201
  • For first case we use different paths (and methods). For ex for create the path would be /create with POST for update it would be /update (PUT).Id check is required for Update Also We accept dtos(json data) from the client not the direct Entities. So there is overhead of converting to dto(and from dto) But this separates DAO and presentation layers. For second approach as i said earlier we use DTO's(light weight) that have just the id and name and we use the id to retrieve the child objects(if required) – srikanth yaradla May 29 '13 at 09:06
  • Yeah sure this would be an option. I already considered it in my question posting. But as I also said: I would have to write a second bean (DTO) that is nearly the same as my Entity Bean. This would mean to copy 10-20 fields per Entity. Doesn't look/feel clean to me... And of course we have separate paths for `create` and `update`. It's just strange that I can't distinguish between those two actions ?! – Benjamin M May 29 '13 at 09:11
  • We use saveOrUpdate of SpringSessionContext to save or update. For create we really don't care if the client sends the id because our dto to do conversion never sets the id to do(whether new or existing) But for update we actually retrieve the domain object using the id passed by client( in update path) and after doing all validation we set the relevant fields to the 'retrieved' domain object (in service transaction). This is what my co workers have been practicing. – srikanth yaradla May 29 '13 at 09:47
  • Also is it a good idea to directly apply json serializers on domain objects?.They are hibernate managed objects and the moment you use any setters their state is changed(if not dettached) and they could become 'dirty') Also i think if you directly update the desirialized object it wont be 'managed' by hibernate. So you have to retrieve and then update.I think people have used direct domain objects using opensessionview filters and similar solutions but then you have to be careful in handling the scenarios where there is a possibility of modifying the domain objects outside the transaction. – srikanth yaradla May 29 '13 at 10:27

2 Answers2

0
  1. No, you should set the id null. It's the easiest / best way to do it with Hibernate in my opinion.

However, there are alternatives. If you're using a Hibernate session, it has three salient methods persist, merge, update, and save, as you can see from the javadocs.

As you can see from past questions asked on stack overflow, what I originally said is incorrect. (I've been using Spring Data a lot, where that is the case.) You should use persist and update--if you're using a hibernate session.

  1. As for your second issue, one option is you could create a special deserializer to set an item object in your bill object using the itemId.

Something like this:

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

OR for number 2, a simpler better way might be:

Add to 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);
}
}
Community
  • 1
  • 1
CorayThan
  • 17,174
  • 28
  • 113
  • 161
  • I'm fine with the solution for problem *#2*. But for problem *#1* there must a way to distinguish between *insert* and *update* somehow. It's not really intuitive to keep track of all ids in an object graph and set each one to `null`. Maybe someone else has a neat solution for this. – Benjamin M May 29 '13 at 08:38
  • @Benjamin M I added an easier, alternative way to implement #2. Also, you should provide the implementation details of your dao layer, as it potentially makes a big difference if you're using a JPA entity manager, Hibernate session, or some other abstraction. – CorayThan May 29 '13 at 08:57
  • Thanks for the second idea, but my coworker will kill me, if I access the DAO from within the Entity ;) The DAO basically is `@Repository("BillDAO") @Transactional public class BillDAOImpl extends AbstractJpaDao { ... }` – Benjamin M May 29 '13 at 08:58
  • Hey, I think I found exactly what I want (for #2). Or am I missing something? It automatically (de-)serializes `id <-> object`, both ways. Have a look here: http://www.runningasroot.com/blog/2012/05/02/autowiring-jackson-deserializers-in-spring/ – Benjamin M May 29 '13 at 09:07
0

For #1, you can ignore the id property in the JSON upon creation:

class Bill {
    @JsonIgnore
    public String id;
}

If you need to expose the id property when GETting a Bill, add:

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

And then if you have an update method, e.g. PUT /bills/{id}, set the id explicitly:

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

Another option would be to use a DTO class (next to your controller class).

For #2, given

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

you ought to be able to pass in an Item with just the id:

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

and the relationship would be saved along with the Bill.

Test it.

Jukka
  • 4,583
  • 18
  • 14