Search code examples
javahibernateooppojo

Object Oriented Design for generic hibernate object


I'm currently working good object oriented principles, and hibernate, I have this POJO, in which the properties would be dynamically populated. This is a design pattern I've read for good Object Oriented Design, in which it would be easy to add attributes to specificic object without breaking the application. My question is, how can you map this to a table, when your attributes is supposedly dynamic, I'm using an enum to limit the key value pairs for the map, but ideally it can still grow. I am only using in-memory database (h2) and I'm not going to be using the code for production use. This is for learning purposes only. see code below:

public class Transaction {

    private static Map<Object, Object> properties;

    public Transaction(){
        if(null != properties)
            properties = new LinkedHashMap<Object, Object>();
    }

    public Transaction(Map<Object, Object> properties){
        if(null != properties)
            setProperties(properties);
    }

    public void setProperties(Map<Object, Object> prop){
        properties = prop;
    }
    public void setProperties(Properties property, String value){
        properties.put(property, value);
    }

    public Map<Object, Object> getProperties(){
        return properties;
    }

    public String getProperties(Properties property){
        return (String) properties.get(property);
    }


}

So I want to be able to create a table that would have this properties, dynamically, My Enum:

public enum Properties {
    Entry("Entry"), Id("Entry_ID"), Name("Name"), Credit("Credit");

    private final String description;

    private Properties(final String description){
        this.description = description;
    }
    @Override
    public String toString(){
        return description;
    }
}

I have this hibernate mapping, but as you can see this would be need to be updated everytime a field is updated, I need a generic mapping so that when I change/add the attributes, annotation or xml would be okay, see below:

 <class name="Transaction" table="TRANSACTION">
    <id name="id" column="ENTRY_ID">
        <generator class="increment"/>
    </id>
    <property name="name"/>
     <property name="credit" type="boolean" column="IS_CREDIT"/>
</class>

Solution

  • UserDefinedField on Marin Fowler's web site may be a perfect starting point for exploring general answers to this question.

    As for Hibernate: It's really designed for statically binding tables to objects and you may have significant problems if you change the schema while running. You can implement the following solutions, though:

    • Serialized LOB (you serialize your Map into a binary field or - using JSON/XML - a text field). This is a half-and-half approach - half tabular/normal form/SQL and half not-SQL. So, if this approach is attractive, you might want to consider going all-in with a NoSQL database as discussed later
    • Attribute table, where your customized attributes are stored in a key-value pair table that joins to to the master table. This can be mapped in Hibernate using Indexed Collections (see section 7.2.2.2 Maps) and you would end up with something quite like in your question:

      @Entity 
      public class Transaction {
      
          @Id @GeneratedValue public Integer getId() { return id; }
          public void setId(Integer id) { this.id = id; }
          private Integer id;
      
          // ... snip ...
      
          @OneToMany(mappedBy="transaction")
          @MapKey(name="name")
          public Map<String, String> getProperties(){
          return properties;
          }
          public void setProperties(Map<String, String> prop){
          properties = prop;
          }
          private Map<String, String> properties; // NB: Type has to be <String, String> because the column name is a String and you have defined the property value to be a String.
      
          public void setProperty(Properties property, String value){
          properties.put(property, value);
          }
          public String getProperty(String name){
          return (String) properties.get(property);
          }
      }
      
      @Entity
      public class Property {
          @Id @GeneratedValue public Integer getId() { return id; }
          public void setId(Integer id) { this.id = id; }
          private Integer id;
      
          @ManyToOne
          public Transaction getTransaction() { return transaction; }
          public void setTransaction(Transaction transaction) { this.transaction = transaction; }
          private Transaction transaction;
      
          public String getName() { return name; }
          public void setName(String name) { this.name = name; }
          private String name;
      
          public String getDescription() { return description; }
          public void setDescription(String description) { this.description = description; }
          private String description;
      
      }
      
    • Pre-defined custom-fields, where you start with a really wide table with loads of unused columns. In this implementation you end up defining a mapping between your arbitrary property names and the pre-defined column names (getString1(), getString10(), etc)

    However, a much better solution for you may be to use a NoSQL database - specifically a document-based one. These allow you to store and retrieve arbitrary data-structures (maps and lists). Interestingly, using such an approach makes binding to the data store significantly easier.

    MongoDB or Redis (Java bindings at Jedis) are examples.