3

For a project I'm currently working on I need to dynamically add properties to a domain class and persist them later in the database. In general, I need a key/value store attached to a "normal" domain class. Sadly I cannot use a NoSQL database (e.g. Redis).

My approach would be to handle the additional properties on a save() by identifying them within afterInsert or afterUpdate and writing them to another table - I would prefer not to use a map property within the domain class but an additional "Field" table (to better support searches).

I tried to add properties using the metaClass approach:

person.metaClass.middlename = "Biterius"
assert person.middlename == "Biterius"    // OK

This works and I can identify the additional properties in the afterInsert/afterUpdate methods but it seems that I cannot change the value thereafter - i.e., the following does not work:

person.middlename = "Tiberius"
assert person.middlename == "Tiberius"    // FAIL

Then I tried an Expando approach by extending the Person class by the Expando class (directly ("Person extends Expando") and via an abstract intermediate class ("Person extends AbstractPerson" and "AbstractPerson extends Expando")).

def person = new Person()
assert person in Person          // OK
assert person in AbstractPerson  // OK
assert person in Expando         // OK

Both variants did not work - I could assign values to arbitrary "properties" but the values were not stored!

person.mynewproperty = "Tiberius"  // no MissingPropertyException is thrown 
println person.mynewproperty       // returns null

So how can I add properties to a domain class programmatically during runtime, change them and retrieve them during afterInsert or afterUpdate in order to "manually" store them in a "Fields" table?

Or am I doing something completely wrong? Are there other / simpler ways to do this?

Jörg Rech
  • 1,339
  • 2
  • 13
  • 25
  • 1
    Are you trying to save metadata or are you actually trying to add a column to a table dynamically? A bit confused – chrislovecnm Sep 20 '12 at 09:12
  • 1
    So the field table would generate one table for each new property? Is that what you are looking for? – Jeff Beck Sep 20 '12 at 11:48
  • @chrislovecnm I'm trying to add a column to tables dynamically. For example, adding a "Middlename" to a Person table. – Jörg Rech Sep 25 '12 at 07:24
  • @JeffBeck Not exactly. If an Object has an additional property it should generate one ROW per property within the Field table. If a Person object has no middlename - and therefore no entry/row in the Field table it would be equivalent to a null. – Jörg Rech Sep 25 '12 at 07:24
  • @JörgRech does hibernate even allow adding columns dynamically? I would store the data in json and monkey with it that way :( – chrislovecnm Sep 25 '12 at 08:36

2 Answers2

0

What about turning your DB into a "NoSQL" one?

In one of my projects, I just used a String-property to store a map as JSON-Object. For Groovy it's not a big problem to convert between a map and a JSON-Object. And since you can access a map just like an object with properties, I found this solution very convenient.

Only drawback: you have to plan the size of your String-property in advance...

Update: sorry, just read that you want to support searches...

what about

class Person {
  ...
  static hasMany = [extProperties:KeyValue]
  ...
  def invokeMethod(String name, args) {
    if (name.startsWith('get')) {
       //an unknown properties's getter is called
    }
    //add same for setter
  }
}


class KeyValue {
  String key
  String value
}

I guess such a schema would give you all freedom you need. Even without the hasMany, you can make use of invokeMethod to handle your external tables...

The getter and setter can save your values in a transient string propertie (static transients = ['myTransientProperty']). This property should be available in the afterInsert / `afterUpdate´ events.

rdmueller
  • 10,742
  • 10
  • 69
  • 126
  • 1
    Nice variant! I'm using it by setting the properties from within the `invokeMethod` function and persisting it from the `afterInsert`/`afterUpdate` functions. The only inconvinience is that I cannot access them the groovy way by `person.middlename` but have to use getters and setters (`person.getMiddlename()` etc.). – Jörg Rech Sep 26 '12 at 09:30
  • 1
    @JörgRech once you have the getters and setters you can list them as transients and use person.middlename – Jeff Beck Sep 26 '12 at 16:27
  • 1
    Jorg: To access them like that, use propertyMissing instead of invokeMethod/methodMissing. `def propertyMissing(String name) { KeyValue.findByName(name) ?: throw new MissingPropertyException(name, delegate.class) }` – Raphael Nov 09 '12 at 01:45
0

Why don't you just create a map of strings on the domain object and store your extra data there manually? Unless you're storing complex data you should be able to cast anything you need to/from a string.

Rick Mangi
  • 3,761
  • 1
  • 14
  • 17
  • Yes, this is another alternative to store additional properties. However, It makes it really hard to search within an individual property. I would prefer not to use a map property within the domain class but an additional "Field" table to better support searches. – Jörg Rech Sep 25 '12 at 07:33
  • Then you might want to create another domain class which acts as a lookup table of key/values for your dynamic fields and store this as a many-to-many association in your domain class. You can use criteria queries to find individual properties. – Rick Mangi Sep 25 '12 at 15:59