tags:

views:

36

answers:

1

Hi! I'm working on a program to store and manage test results. Tests have properties like date, duration, and also results. However, some tests measure pressure, others temperature... I hope you get the idea. So, not every test has the same attributes.

My first thought was to extract the test results out of the test table into individual result tables, thus creating a 1:1 relationship. And to keep the test results extensible, I wanted to have the results stored in Maps rather than Beans.

However, it seems that Hibernate doesn't like my approach. Does anyone have Experience with this kind of mapping? Is there a better alternative?


EDIT for Example and further explanation

 --------
|  Test  |
|id      |
+--------+
|date    |
|duration| 1       1  ---------- 
|...     | --------- |  Medium  |
 --------            |testID    |
                     +----------+
                     |medium    |
                     |pressure  |
                      ----------

Medium would be one property that a test can have. As other properties could be added, I don't want to hardcode the Medium class but rather map it as a Map, storing the three attributes. My thought was that Test would have a Map<String, Map<String, Object>> to represent all properties associated with it.

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"&gt;

<hibernate-mapping package="com.hoerbiger.versuchsdb.domain">
 <class name="Test" lazy="false">

  <id name="id">
   <generator class="native"/>
  </id>

  <property name="datum"/>

  <many-to-one name="material" class="Material"/>

  <set name="testgeraete" table="Test_Testgeraet" lazy="false">
   <key column="testID"/>
   <many-to-many column="geraetID" class="Testgeraet"/>
  </set>

  <!-- Here it's getting important -->

  <one-to-one name="medium" entity-name="Medium" constrained="false" lazy="false"
              access="com.hoerbiger.versuchsdb.hibernate.TestAccessor"/>

 </class>
</hibernate-mapping>

Where the com.hoerbiger.versuchsdb.hibernate.TestAccessor is a PropertyAccessor to access the map that I want to use for the relations

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"&gt;

<hibernate-mapping package="com.hoerbiger.versuchsdb.domain">
 <class entity-name="Medium" lazy="false">

  <id name="id" type="long">
   <generator class="foreign">
    <param name="property">id</param>
   </generator>
  </id>

  <one-to-one name="id" class="Test" constrained="true"/>

  <property name="medium" type="string"/>

  <property name="pressure" type="int"/>

 </class>
</hibernate-mapping>

To try what works, I used this main method. Note the last few lines

@SuppressWarnings("unchecked")
public static void main(String[] args) {
    Session s = Helper.getSessionfactory().getCurrentSession();
    s.beginTransaction();
    //Change to dynamic session
    s = s.getSession(EntityMode.MAP);

    Map<String, Object> test = (Map<String, Object>) s.createQuery("from Test").iterate().next();
    //for me, prints "25"
    System.out.println(test.get("id"));

    Map<String, Object> medium = new HashMap<String, Object>();
    medium.put("id", test);
    medium.put("medium", "Luft");
    medium.put("pressure", 40);

    s.save("Medium", medium);
    s.getTransaction().commit();
    System.out.println("---");
    s = Helper.getSessionfactory().getCurrentSession();
    s.beginTransaction();

    Test t = (Test) s.createQuery("from Test").iterate().next();
    //for me, prints "null, 2010-07-07 00:00:00.0" (Test.toString())
    System.out.println(t);
    //"25"
    System.out.println(t.getId());
    //"{medium={id=null, 2010-07-07 00:00:00.0, ...}}"
    System.out.println(t.getProperties());
    //"true"
    System.out.println(t.getProperties().get("medium").get("id") == t);

    //This is really weird - hibernate loads the map with a Test stored, but when
    //saving, it expects a Long. With this line, the Commit succeeds
    t.getProperties().get("medium").put("id", t.getId());
    s.getTransaction().commit();
}

What's up with these last lines? Is it a bug in hibernate or a misconfiguration?

A: 

After a little more readup, I worked out what my mistake was: Currently, my medium mapping looks like this:

<hibernate-mapping package="com.hoerbiger.versuchsdb.domain"> 
 <class entity-name="Medium" lazy="false"> 

  <id name="id" type="long"> 
   <generator class="foreign"> 
    <param name="property">id</param> 
   </generator> 
  </id> 

  <one-to-one name="id" class="Test" constrained="true"/> 

  <property name="medium" type="string"/> 

  <property name="pressure" type="int"/> 

 </class> 
</hibernate-mapping>

But it should look like this:

<hibernate-mapping package="com.hoerbiger.versuchsdb.domain"> 
 <class entity-name="Medium" lazy="false"> 

  <id name="id" type="long"> 
   <generator class="foreign"> 
    <param name="property">test</param> 
   </generator> 
  </id> 

  <one-to-one name="test" class="Test" constrained="true"/> 

  <property name="medium" type="string"/> 

  <property name="pressure" type="int"/> 

 </class> 
</hibernate-mapping>

the problem being to use "id" in the generator and one-to-one constraint

Hibernate uses two attributes for representing a primary key one-to-one constraint, in my case:

  • id is the PK & FK, i.e. test.getId(), a Long
  • test is the reference to the foreign object it self

Since the Map is not type safe, you can overwrite the Long stored first by a Test. Both values are simply read into the same map entry. By changing the two occurrences of id to test, the reference has its own entry and doesn't overwrite the id value any more

Silly Freak