3

I know that when using Wicket with JPA frameworks it is not advisable to serialize entities that have already been persisted to the database (because of problems with lazy fields and to save space). In such cases we are supposed to use LoadableDetachableModel. But what about the following use-case?

Suppose we want to create a new entity (say, a Contract) which will consist, among other things, of persisted entities (say, a Client which is selected from a list of clients stored in the DB). The entity under creation is a model object of some Wicket component (say, a Wizard). In the end (when we finish our wizard) we save the new entity to the DB. So my question is: what is the best generic solution to the serialization problem of such model objects? We can't use LDM because the entity is not in the DB yet but we don't want our inner entities (like Client) to be serialized wholly, too.

My idea was to implement a custom wicket serializer that checks if the object is an entity and if it is persisted. If so, store only its id, otherwise use the default serialization. Similarly, when deserializing use the stored id and get the entity from the DB or deserialize using the default mechanism. Not sure, though, how to do that in a generic way. My next thought was that if we can do it, then we do not need any LDM anymore, we can just store all our entities in simple org.apache.wicket.model.Model models and our serialization logic will take care of them, right?

Here's some code:

  @Entity
  Client {
     String clientName;

     @ManyToOne(fetch = FetchType.LAZY)
     ClientGroup group;
  }

  @Entity
  Contract {
     Date date;

     @ManyToOne(fetch = FetchType.LAZY)
     Client client;
  }

  ContractWizard extends Wizard {
     ContractWizard(String markupId, IModel<Contract> model) {
        super(markupId);
        setDefaultModel(model);
     }
  }

  Contract contract = DAO.createEntity(Contract.class);
  ContractWizard wizard = new ContractWizard("wizard", ?); 

How to pass the contract? If we just say Model.of(contract) the whole contract will be serialized along with inner client (and it can be big), moreover if we access contract.client.group after deserialization we can bump into the problem: https://en.wikibooks.org/wiki/Java_Persistence/Relationships#Serialization.2C_and_Detaching

So I wonder how people go about solving such issues, I'm sure it's a fairly common problem.

koszek
  • 55
  • 7
  • Almost the same problem (diferent point of view) https://stackoverflow.com/questions/7070644/how-do-i-keep-entities-or-their-associations-attached-to-the-current-persisten - – Jacek Cz Aug 21 '17 at 22:37

2 Answers2

1

I guess there are 2 approaches to your problem:

a.) Only save the stuff the user actually sees in Models. In your example that might be "contractStartDate", "contractEndDate", List of clientIds. That's the main approach if you don't want your DatabaseObjects in your view.

b.) Write your own LoadableDetachableModel and make sure you only serialize transient objects. For example like: (assuming that any negative id is not saved to the database)

public class MyLoadableDetachableModel extends LoadableDetachableModel {

private Object myObject;

private Integer id;

public MyLoadableDetachableModel(Object myObject) {
    this.myObject = myObject;
    this.id = myObject.getId();
}

@Override
protected Object load() {
    if (id < 0) {
        return myObject;
    }

    return myObjectDao.getMyObjectById(id);
}

@Override
protected void onDetach() {
    super.onDetach();
    id = myObject.getId();

    if (id >= 0) {
        myObject = null;
    }
}
}

The downfall of this is that you'll have to make your DatabaseObjects Serializable which is not really ideal and can lead to all kind of problems. You would also need to decouple the references to other entities from the transient object by using a ListModel.

Having worked with both approaches I personally prefer the first. From my expierence the whole injecting dao objects into wicket can lead to disaster. :) I would only use this in view-only projects that aren't too big.

  • Well, I understand the first approach but in our project database objects are already used extensively and they are all serializable, too. As for the second approach, I don't quite understand how you mean to deal with inner persisted entities. Do it on a field-by-field basis? We will end up with a new LDM implementation everytime, right? How about a more generic approach, like the one I mentioned with a custom serializer? – koszek Sep 27 '16 at 12:54
  • You don't really need a new LDM for each entity but you'll need to store inner persisted entities not in your entity directly but in a seperate Model (like LoadableDetachableListModel for example). But you'll only need this if you actually need to change the values of the inner peristent fields in your view. – Thorsten Wendelmuth Sep 27 '16 at 12:59
0

Most projects I know of just accept serializing referenced entities (e.g. your Clients) along with the edited entity (Contract).

Using conversations (keeping a Hibernate/JPA session open over several requests) is a nice alternative for applications with complex entity relations: The Hibernate session and its entities is kept separate from the page and is never serialized. The component just keeps an identifier to fetch its conversation.

svenmeier
  • 5,681
  • 17
  • 22
  • 1
    And that's what we do too. But I'm beginning to doubt if it is a good idea. There are two problems with it. Firstly, JPA entities can be big and we want our pages to be as light as possible. Secondly, there is a [well-known problem](https://en.wikibooks.org/wiki/Java_Persistence/Relationships#Serialization.2C_and_Detaching) with lazily fetched fields. So looks like the first approach of Thorsten Wendelmuth is the way to go?.. – koszek Sep 27 '16 at 15:03
  • IMHO Thorsten's suggested solution a) is too complicated if you have many entities and want to edit relationships between them. Google for "wicket cdi conversation" to get an idea of my alternative solution. – svenmeier Sep 27 '16 at 16:50
  • I understand your idea, but can you explain how the problem with lazy fields is addressed in your solution? You don't want your users to see an error page if they press the Back button in the browser and somewhere in the code a lazy field was accessed that had not been accessed before serialization took place, do you? – koszek Sep 28 '16 at 12:26
  • There is no serialization of entities, see my edited answer. – svenmeier Sep 29 '16 at 05:53
  • Sorry, I don't think I understand exactly how this will work. I added some code to my question to clarify it. Can you explain your idea in terms of code? – koszek Sep 29 '16 at 07:38
  • Why don't you have an IModel and pass that during the saving of the Contract? Like let wicket help you filling out all the usual Contract fields (like I said contractStartDate, endDate, etc) and only when the user actually wants to save it, pass it to your serviceLayer/persistanceLayer and make the connection (+ whatever else you may need). – Thorsten Wendelmuth Sep 29 '16 at 22:59