Prevent orphaned objects in DB4O when updating fields

895 views Asked by At

I want to store Person objects in DB4O. The Person Location field can be changed over time. So I retrieve a person from the DB and call a method to set the location field to a new Location object. (I want the Location objects to be immutable i.e. DDD Value Objects).

This works, however the previously assigned Location objects remain the database. How can I configure DB4O to remove these orphaned Location objects? Or do I need some custom process to garbage collect?

Simplified classes for this example:

class Person {
    Location location;
    public void Move(Location newLocation) { 
        location = newLocation;
    }
}

class Location {
    public Location(string city) { 
        this.City = city;
        //etc
    }
    public readonly string City;
    /// more fields...
}

EDIT: Some more information - Person is meant to be an DDD aggregate root. So there are no external references to a person's internal state. If Person updates its location, then the old location should cease to exist.

5

There are 5 answers

1
Gamlor On BEST ANSWER

I think there's no perfect solution available. But with some work you can nearly achieve this behavior. A similar topic is already discussed here.

The first step would be to activate cascade-deletion on the location-field. So when a Person is deleted the location is also deleted.

configuration.common().objectClass(Person.class).objectField("location").cascadeOnDelete(true);

Now we need to handle the changing location case. The idea is this:

  1. Register on the activate-event. There you 'remember' which object was embedded
  2. Register on the update-event. There you check if it's still the same embedded object. If not, you delete the old on.
  3. Very important: Never ever 'share' the embedded object, otherwise it will be deleted for everyone. A simple solution is to create a copy of the Location-object whenever it's assigned.

There is Java-Demo which implements this behavior.

Well, this is only the concept, it's a long way to an acceptable solution:

  1. Utilize Attributes or other configuration to specify which objects are such
  2. Build a robust implementation of the event-handlers etc.
  3. A solid solution to ensure that a 'shared' location is not deleted
2
AudioBubble On

Have you thought about making it a value type?

1
German On

How about using db4o's delete() first on the old location and then storing the new one?

Best!

German

2
mnemosyn On

This really looks like a transaction to me.

Like German said, you will have to delete the old, store and assign the new one and make sure these steps can be committed in one go.

In a RDBMS, you'd have to come up with a transaction for this, too. However, many RDBMS systems support you here with triggers and events. Note that db4o also offers certain callbacks.

I'm currently working on a ref-count abstraction for cases like this, but it is very tricky to handle generically. On the other hand, you could write a specific Update method that simplifies the transaction for you and compares old and new objects' references. If they don't match and you can be sure that nobody else references that type of address object, you can delete it.

Also note that were you using a language without garbage collection, you'd also have to keep track of this manually and delete the old object.

That 'aggregate root' concept seems very vague to me - after all, it depends on the perspective, but that is another issue.

0
szcoder On

According to db4o 8.0 API reference of cascadeOnDelete(boolean), the old object should be deleted automatically. Here is a copy of the doc, check the given example.

sets cascaded delete behaviour. 

Setting cascadeOnDelete to true will result in the deletion of all member objects of instances of this class, if they are passed to ObjectContainer.delete(Object). 

Caution !
This setting will also trigger deletion of old member objects, on calls to ObjectContainer.store(Object).

An example of the behaviour:

ObjectContainer con;
Bar bar1 = new Bar();
Bar bar2 = new Bar();
foo.bar = bar1;
con.store(foo); // bar1 is stored as a member of foo
foo.bar = bar2;
con.store(foo); // bar2 is stored as a member of foo 

The last statement will also delete bar1 from the ObjectContainer, no matter how many other stored objects hold references to bar1. 

The default setting is false.

In client-server environment this setting should be used on both client and server. 

This setting can be applied to an open object container. 

Parameters:
flag - whether deletes are to be cascaded to member objects.
See Also:
ObjectField.cascadeOnDelete(boolean), ObjectContainer.delete(Object), Using callbacks

However it doesn't work as printed, weird.