Deserializing a many-to-one relation with FlexJSON

2019-08-30 11:20发布

问题:

I am adding RESTful support to my web application (with Spring) so I can do CRUD operations like this: Read info about a participant (JSONSerialize):

$ curl -i -H "Accept: application/json" http://localhost:8080/dancingwithstars/participant/1
{
    "birthDate": "2013-01-23",
    "code": "JS0001",
    "firstName": "Julia",
    "mainFunction": {
        "code": "AA"
        "name": "Principal Dancer"
    },
    "gender": "F",
    "height": 160.00,
    "id": 1,
    "lastName": "Smith",
    "version": 1,
    "weight": 55.00
}

Or create a new participant (JSONDeserialize)

$ curl -i -X POST -H "Content-Type: application/json" -H "Accept: application/json" -d '{"birthDate":"2013-01-15","code":"AT0001","firstName":"Angela","mainFunction":{"code": "AB", "name":"Choreografer"},"gender":"F","height":189.00,"lastName":"Wright","version":0,"weight":76.00}' http://localhost:8080/dancingwithstars/participants

But the thing is, I don't need to know all the fields of the mainFunction object to create a participant I just need to know the code which is unique. For the serialization I can use:

return new JSONSerializer().include("mainFunction.code").exclude("mainFunction.*").exclude("*.class").transform(new DateTransformer("yyyy-MM-dd"), Date.class).prettyPrint(true).serialize(this);

And I get the following json:

{
    "birthDate": "2013-01-23",
    "code": "JS0001",
    "firstName": "Julia",
    "mainFunction": {
        "code": "AA"
    },
    "gender": "F",
    "height": 160.00,
    "id": 1,
    "lastName": "Smith",
    "version": 1,
    "weight": 55.00
}

For the deserialization I don't know. The requirement seems pretty common so I wanted to ask in the forum. Any ideas?

To sum up, there is a parent object with a reference (many-to-one) to a child object. If I provide the parent with all the information of the child, everything works fine. But hold on a second, with the ID of the child should be enough to safe the parent. I try to save the parent with just the ID of the child and then I get this exception:

org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing

Any thoughts?

Agustin

UPDATE 27-Jan-2013: It looks like the issue comes from Hibernate. Let say we have products and categories. The relation is that products have a reference to a category (many-to-one relation). Given a category previously saved in a database. You cannot create a product and just give the ID of the category to create the product. You will need the version as well even though Hibernate won't look at the version to create the product. See below:

/*
 * Create one category before the product
 */
Category cat1 = new Category();
cat1.setCode("KIT");
cat1.setName("Kitchen products");
session.save(cat1);
session.flush();
session.clear();
Category cat1db = (Category) session.get(Category.class, cat1.getCode());
logger.info(cat1.toString());
assertNotNull(cat1db.getVersion());

/*
 * Create the product and assign it a category
 */
Product p1 = new Product();
p1.setName("Kettle");
p1.setPrice(4D);
Category c1 = new Category();
c1.setCode("KIT");
//c1.setVersion(new Integer(666));
p1.setCategory(c1);
session.save(p1);
session.flush();
session.clear();
Product p1db = (Product) session.get(Product.class, p1.getId());
logger.info(p1.toString());
assertNotNull(p1db.getId());

That will fail. But if you uncomment the version of the category, the product will be created. Also, Hibernate will do an extra query to the database asking for that category.

Two questions here:

  1. Why Hibernate needs the field version even though it doesn't look a it in the end?
  2. Why Hibernate does an extra query asking for the category?

Your thoughts?

回答1:

When you are working with objects that are coming in from JSON or external representations you are always working with detached objects from Hibernate's perspective.

It's been a while since I've done any serious hibernate, but if you are creating an object Hibernate doesn't know about you have to tell it by re-attaching the object with the session. It will then pull the information it needs from the database to associate it with the session, including any versioning information. This is referred to as a detached object because it was created outside of the Hibernate session, and Hibernate doesn't know if this object is a new object or an object that exists in the DB. You can re-attach objects with session.merge(someObject). You only need to reattach the parent btw. For a better explanation:

What is the proper way to re-attach detached objects in Hibernate?

The version information is used for optimistic locking. So in a multi-client environment it's possible two request could modify the same object in the DB and it's your job as the programmer to order those modifications and make sure one doesn't overwrite the other. Optimistic locking is one way to do this where objects are given versions when someone modifies the object the version is incremented. However, before a new version is saved you have to check and make sure the object being given to the DB is the same version as the one stored in the DB. That makes sure that what mods you're about to save were done on the latest version stored in the DB. That means you can't overwrite the object with an old version of that object. Without that version information Hibernate can't performing optimistic locking.