views:

673

answers:

3

I was wondering if there is a way to do Hibernate mapping for the following. My two days spent on this say there isn't.

There are two tables, having one to many relationship.

+---------------------+       +-----------------+
|  versioned_entity   |       |  basic_entity   |
+---------------------+       +-----------------+
| fk_BasicEntityId    | *   1 | id              |
| version             |-------| active_version  |
| ...                 |       | ...             |
+---------------------+       +-----------------+

Hibernate mapping:

<class name="my.VersionedEntity" table="versioned_entity">
  <composite-id>
    <key-many-to-one name="basicEntity" column="fk_BasicEntityId"/>
    <key-property name="version"/>
  </composite-id>
  ...
</class>

<class name="my.BasicEntity" table="basic_entity">
  <id name="id">
  <property name="activeVersion" not-null="true">
    <column name="active_version"/>
  </property>
  ...
</class>

Brief explanation: fk_BasicEntityId is a foreign key pointing to basic_entity.id. An entry in versioned_entity can be identified by its foreign key and version (there can be many versions having same fk_BasicEntityId). active_version is a number pointing to one of versioned_entity entries and tells which version is "active". Hope that's clear enough.

Now the problem I am solving is that I need quick access in Java like this:

my.BasicEntity basic = my.BasicEntityDAO.get();
my.VersionedEntity activeVersion = basic.getActiveVersionedEntity();

The current implementation is done in Java and simply iterates over all versioned entities of given basic entity and checks if its version matches with active version. Needless to say, this doesn't scale and results performance problems.

So I went on to move this onto database level. Looks easy? So I thought! I went on and added following into Hibernate mapping of basic_entity:

<many-to-one name="activeVersionedEntity" not-null="false" insert="false" update="false">
  <column name="id" unique="true" not-null="true"/>
  <column name="activeVersion"/>
</many-to-one>

The double column is required here for versioned_entity has composite key. Unfortunately, this doesn't work - JVM dies without a trace when a call to be.getActiveVersionedEntity() is invoked (on colleague's machine it throws StackOverflowError inside Hibernate). I suspect this happens because "id" is also an identifier, as I have very similar mapping for the same class where column is not an identifier and this works correctly.

I could probably do this with HQL, but that would require me to move this method into the DAO class which is particularly awkward as would require changes in API (not an option). Using DAO call inside my.BasicEntity::getActiveVersionedEntity() is another big no-no: I can't mix up different abstraction layers. Another way of solving this would be looking into Interceptors/events, however this also probably one of those options which beat the purpose of the framework (I shouldn't care about low level things like this, do I?).

I am running Hibernate version: 3.2.7GA / Spring 2.5.6. The documentation is unbelievably poor - I have looked at three different Hibernate books, online documentation, forums, etc.

Any help is appreciated.

+1  A: 

Why do you map active_version as a property? I suspect it's an integer, right? Why don't you change the active_version to be of type VersionedEntity? This way, it would be a simple one-to-one relationship.

Update:

We are using this mapping for one of our sites:

<many-to-one class="my.VersionedEntity" name="currentVersion" lazy="false" />
sfussenegger
I need to have both - version as integer and versioned object as object. I believe having the former doesn't prevent me having the later, does it?
mindas
Why both if you could simply do getActiveVersion().getVersion() to get the version? Otherwise, you risk that both properties get out of sync (and probably quite odd behavior as a result). We are using the exact same approach for one of our sites and it works perfectly.
sfussenegger
What you describe here gives an org.hibernate.MappingException: Foreign key (FK4F44784EF87DDA8D:basic_entities [active_version])) must have same number of columns as the referenced primary key (versioned_entity [fk_BasicEntityId ,version])with the data model described above.
Sindri Traustason
Thanks a lot for your help. I have tried the approach you suggested, but I still get JVM crash. I assume it is likely caused by the composite id being used as a many-to-one column parameter (see comments above). Are you using similar combination in your environment by a chance?
mindas
@Sindri I didn't say it's gonna work for the desribed situation. I just pasted what we are using.Just double checked and found out that we're neither using a composite-id nor incrementing version numbers at all. We just sort by date where needed and use "regular" autoincrementing id instead. Do you really need consecutive version numbering for each BasicEntity? If yes, I'd still rather use a simple id for your VersionedEntity and add a unique key consisting of fk_basicEntityId and version
sfussenegger
A: 

It sounds like you are trying to double-map a certain column. I haven't understood your problem in detail but have you considered using update=false and insert=false on one of the mappings? That should enable you to do a double mapping of the same column as both a relationship and just an integer. More info here.

magnusvk
Yes, I have tried update=false and insert=false (otherwise the system doesn't even start up - validation exception is thrown). You are correctly assuming that I am trying to double-map a column. In fact, this is not only a simple column but also part of composite key. I believe this is the root of the problem as this is why StackOverflowException is thrown.
mindas
In my experience, using Hibernate with composite keys is extremely unstable. I have seen NullPointerExceptions thrown deep down in the Hibernate core -- and this was for a simple query -- all because of the composite keys. If you can, avoid them, though you probably are locked into this or wouldn't be going to all this trouble...
magnusvk
A: 

Are you modeling a bi-directional referential relationship or an inheritance hierarchy?

If the object model is an inheritance hierarchy, then the relationship should not need to be bi-directional, and the solution is to obtain the sub-class instance from the sub-class DAO. This removes the need to navigate from the base class to the sub-class in the base class DAO. This is what I was attempting to explain in my original answer (below).

If the object model is a bi-directional relationship, then you need to set inverse="true" on the set or bag at the "one" end of the relationship - in this case BasicEntity.

Hibernate 3 allows the use of indexed collections at the "one" end of the relationship, but the Hibernate 2 functionality with set or bag should be enough to test the theory.

This link might help: https://www.hibernate.org/193.html Also: Hibernate Quickly pp142 - 145; Hibernate in Action pp108 - 111.


If I understand the problem correctly, you want to build a VersionedEntity from two tables, using data obtained like this:

select * from basic_entity be, versioned_entity ve where ve.id = be.id and ve.id = <id> and ve.active_version=<version>

Given your constraints on not calling one DAO from another, I wonder whether the chosen method correctly expresses the intent:

public my.VersionedEntity my.BasicEntityDAO.getActiveVersionedEntity();

Could you implement a method like this instead:

public my.VersionedEntity my.VersionedEntityDAO.getActive();
richj
Please read all comments already posted here. The problem is not how to name Java methods and how they should look like, but how to get Hibernate mapping right.
mindas
The comments (e.g. from Sindri) identify that you have a circular mapping. In the paragraph that starts "I could probably do this with HQL ..." it seems that you have identified a promising solution, but rejected it for reasons of programming style - calling one DAO from another. If you move the method to VersionedEntityDAO then you could use your own solution without breaking your programming style rule. The style problem is due to the use of BasicEntityDAO to create a VersionedEntity instead of VersionedEntityDAO. You didn't say if one of these exists. Sorry if this wasn't clear.
richj
Added section on bi-directional relationships.
richj