views:

289

answers:

4

Is there a way to use Flyweight objects with the hibernating persistence mapping? My data model contains many objects that will be the same. Instead of having a separate instance for each of those same objects I'd like to use the Flyweight Design Pattern and reference always the same physical object. How to achieve this in hibernate?

Btw. do all JVMs optimize the usage of Strings in a way such that when the same string is used several times, it will always be the same physical instance?

A: 

If your objects implement equality by identity, Hibernate will only have the single instance associated with that primary key. I don't believe it's exactly the same idea as Flyweight, but the point is that you won't have many instances of the same Hibernate object.

duffymo
+2  A: 

Yes, you can implement the Flyweight pattern with Hibernate.

The flyweight pattern is way to minimize memory usage per instance. The strategy is to share as much state between flyweight instances as possible. In your case the shareable state is everything except the hibernate object identifier and some additional state to maintain object identity.

Each flyweight instance needs its own object identity. The additional state is the way to implement identity to distinguish between objects that share common state.

public boolean equals(Object obj){
  Fly other; [..]//check type
  //null checks ommitted
  return other.myState.equals(myState) && other.commonState.equals(commonState); 
}

If the object identity is shared between instances hibernate would interpret all physical instances (references) as the same instance. Hibernate uses the equals method to check object identity and your equal implementation would have to return (! a.equals(a) == true) which is illegal. Equal has to be reflexive. If you would break this contract all libraries that depend on the contract will be broken (collections, hibernate, etc.).

You cannot implement the equal method using the hibernate object identifier to distinguish between objects. This would make the object identity dependent on the persistence state (persisted or transient).

One way to model the common state in hibernate is a one-to-many association between shared state objects and flyweight objects. (Maybe someone has an idea how to map the data without joining two tables?)

String: Only internalized strings will be shared. This is not the best solution most of the time. It is appropriate for symbols (class name, method name, etc.). The internalized strings will never by garbage collected and you have to have a String instance that will to be garbage collected anyway new String("..").intern(). It will not save allocations. There is only the minor advantage that the base string will not survive a gc generation or could be allocated on the stack (with escape analysis in hot spot enabled and applicable).

Thomas Jung
+1  A: 

do all JVMs optimize the usage of Strings in a way such that when the same string is used several times, it will always be the same physical instance?

I doubt that very much. In the same class file, when definined like:

String s1 = "Yes";
String s2 = "Yes";

you'll probably have s1 == s1.

But if you have like:

String x = loadTextFromFile(); // file contains es
StringBuilder b = new StringBuilder();
s2 = b.append("Y").append(x).toString(); // s2 = "Yes"

I don't think the runtime is going to check and compare all the loaded strings to the return value of them builder.

So, always compare objects with equals(). That's good advice anyway since every good equals starts with:

if (this == o) {
    return true;
}
extraneon
It is not about comparison, but rather about memory consumption. As Thomas Jung mentions, you can use the String.intern() method to get the underlying String. Try it out!
lewap
What I wanted to say is that if a string value isn't known at compile time, or can't be precomputed, the object will differ (no ==) even if it has, by coincidence, the same value.
extraneon
+2  A: 

It depends.

For readonly values you can easily implement a flyweight pattern by creating a custom UserType which will return objects from a pool instead of new instances every time.

For entities Hibernate is by default sane and wants to be consistent across transactions and thus won't share entities between Sessions to avoid race conditions of your data - and I don't think that is what you want.

But in case it is (and this is totally non-recommended without really knowing what you are doing) you can implement Interceptor.getEntity() which is intended for second level caching. In that method you can return an entity (even some shared by other sessions) and you will effectively have a flyweight pattern for your entities.

BUT I highly recommend against this for the consistency of your data - much better to have actual immutable flyweight values referenced by entities than also try and flyweight the actual entities.

Max Rydahl Andersen
The user type is a good idea to map the common state of the flyweight instances. (Hibernate and JDBC will still initiate a lot of instance. There is no way around that. At least they will be garbage collection quite rapidly.) Could a CompositeUserType represent the whole common state in one object?
Thomas Jung
Yes, a composite user type could probably do that but then why have the entity as an entity in the first place ? Just have it be a value object (component) from the start.
Max Rydahl Andersen