-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 13 posts ] 
Author Message
 Post subject: Composite ids smoke!
PostPosted: Mon Aug 28, 2006 6:04 pm 
Beginner
Beginner

Joined: Thu Mar 18, 2004 8:11 am
Posts: 38
Location: Italy
Today, composite ids are named "legacy" composite ids. The Hibernate docs and even the Ejb-3.0 specs relegate them to a kind of quaint and deprecated habit. More or less like smoking: you do it at your own risk and please avoid to expose someone else to it.

Why? Why the world is going to ban composite ids? Isn't the power of SQL in its inherent relational capabilities? Why do we have to create a lot of useless single-field primary keys and throw our core references into non-primary unique keys? Why do we have to steal orthogonality from our database?

Please note I'm not speaking of true "legacy composite ids", alias "speaking-codes" and the like (i.e.: smoking with no filter). I'm talking about having in a db things like that:

Code:
A[id]  B[id]
    ^      ^
    |      |
AB[idA,  idB]


translated into the Java persistence layer. I would like to have this:

Code:
class A implements Serializable {
  Integer id;

  Set<AB> setAbs;
}

class B implements Serializable {
  Integer id;

  Set<AB> setAbs;
}

class AB implements Serializable {
  A a;
  B b;
}


No, it can't be. At least with annotations (the "new way") there never was a single hibernate release that was capable of doing this the right and simple way. It simply didn't work at all. Not even to mention all the whole bunch on features that may arise with composites through class inheritance.

And now the ejb-3.0 specs also dictate "A primary key class must be defined to represent a composite primary key. Composite primary keys typically arise when mapping from legacy databases when the database key is comprised of several columns." (Linda & Michael, Ejb-3.0 specs, par.2.1.4).

Why in the world? What if I'm going to access my AB instances always through A.setAbs or B.setAbs, or maybe through queries? Why do I have to create a java file and make a java class to represent something I don't care at all to have represented? And also, why this case "typically" arise when mapping legacy (tr: bad) databases? Typically for what? Is it perhaps typical in the a-couple-of-tables database case? Nobody used foreign keys as part of primaries in the world? What's wrong with it?

Ok, don't worry. No blasfemy: I would like to have immutable composite keys. Yeah, absolutely: I want to instantiate my ABs, save them, maybe update some field inside them, maybe delete them. I will never, never, ever modify an AB.a or AB.b value. I swear!

So, where's the problem? Who is the knowledgeful-beyond-my-understanding that dictated composite ids are evil?

If you think I'm not that wrong in this crusade in behalf of composite ids, please ask to Mrs. Linda & Mr. Michael to remove the funny phrase about legacy whatever from ejb specs. And, by the way, from hibernate docs.

Get shake the ORM world a bit! :)

_________________
Giampaolo Tomassoni
Italy


Top
 Profile  
 
 Post subject:
PostPosted: Mon Aug 28, 2006 6:20 pm 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 6:10 am
Posts: 8615
Location: Neuchatel, Switzerland (Danish)
hibernate maps that fine. its called one-to-many and key-many-to-one.

go read the docs ;0)

_________________
Max
Don't forget to rate


Top
 Profile  
 
 Post subject:
PostPosted: Tue Aug 29, 2006 5:23 am 
Beginner
Beginner

Joined: Thu Mar 18, 2004 8:11 am
Posts: 38
Location: Italy
max wrote:
hibernate maps that fine. its called one-to-many and key-many-to-one.

go read the docs ;0)


I used that with xml mapping and it works (although there are some limitats). It doesn't work with annotations at all.

_________________
Giampaolo Tomassoni
Italy


Top
 Profile  
 
 Post subject:
PostPosted: Tue Aug 29, 2006 7:23 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 6:10 am
Posts: 8615
Location: Neuchatel, Switzerland (Danish)
and your point is ?

(and of course there are limitations...because of its nature...and hey, could that be why it is not recommended ? :)

_________________
Max
Don't forget to rate


Top
 Profile  
 
 Post subject:
PostPosted: Tue Aug 29, 2006 8:21 am 
Beginner
Beginner

Joined: Thu Mar 18, 2004 8:11 am
Posts: 38
Location: Italy
max wrote:
(and of course there are limitations...because of its nature...and hey, could that be why it is not recommended ? :)


You mean, the hibernate's nature or the composite id's one?

Because, in the latter, I would really like to know which are the limitations you speak about.

The only limit I see is in the composite id design under xml mappings. There, one must define a <composite-id> entry which can't be completed or overriden later in the class tree.

In example, the following tables:

Code:
CREATE TABLE basetype (
   id INTEGER NOT NULL,
   payload1 VARCHAR(32),

   PRIMARY KEY(id)
);

CREATE TABLE atype (
   id INTEGER NOT NULL,
   payload2 VARCHAR(32),

   PRIMARY KEY(id),
   FOREIGN KEY(id) REFERENCES basetype(id)
);

CREATE TABLE btype (
   id INTEGER NOT NULL,
   payload3 VARCHAR(32),

   PRIMARY KEY(id),
   FOREIGN KEY(id) REFERENCES basetype(id)
);


CREATE TABLE version (
   id INTEGER NOT NULL,
   payload4 VARCHAR(32),

   PRIMARY KEY(id)
);


CREATE TABLE aversioned (
   idversion INTEGER NOT NULL,
   ida INTEGER NOT NULL,
   payload5 VARCHAR(32),
   payload6 VARCHAR(32),

   PRIMARY KEY(idversion, ida),
   FOREIGN KEY(idversion) REFERENCES version(id),
   FOREIGN KEY(ida) REFERENCES atype(id)
);

CREATE TABLE bversioned (
   idversion INTEGER NOT NULL,
   idb INTEGER NOT NULL,
   payload5 VARCHAR(32),
   payload7 VARCHAR(32),

   PRIMARY KEY(idversion, ida),
   FOREIGN KEY(idversion) REFERENCES version(id),
   FOREIGN KEY(idb) REFERENCES btype(id)
);


may be mapped (and I would like to) by the following class tree:


Code:
class BaseType {
   Integer id;
   String payload1;

   Set<BaseVersioned> versionedTypes;
}

class AType extends BaseType {
   String payload2;
}

class BType extends BaseType {
   String payload3;
}

class Version {
   Integer id;
   String payload4;
}

abstract class BaseVersioned {
   Version version;
   String payload5;
}

class AVersioned extends BaseVersioned {
   AType a;
   String payload6;
}

class BVersioned extends BaseVersioned {
   BType b;
   String payload7;
}


Well. The problem is at the "versioned" tree.

Under xml mapping you would specify BaseVersioned as an abstract <class>, in which AVersioned and BVersioned are defined as <union-class>es. The problem is that you must define a <composite-id> entry at the BaseVersioned level and can't simply define just a partial primary key in it, to be completed in AVersioned and BVersioned <union-class>es. Xml mapping is somehow imperfect in dealing with composite ids. The hibernate team didn't explore composites to an enough powerful extent, I believe.

On the other hand, annontation would theoretically allow such a design: you define an @Id @ManyToOne on the version field in the BaseVersioned, then AVersioned completes the composite id thanks to the "a" field, while BVersioned completes the composite id with the "b" field.

A step toward perfection? Well, no: it doesn't work. Hibernate doesn't even want to deal with such a case reporting an error at configure time because of a "id overriding" (?!?!). A "composites are evil" message would fit better.

Also please note that this simple, fallback case:

Code:
class AVersioned {
   Version version;
   AType a;
   String payload5;
   String payload6;
}

class BVersioned {
   Version version;
   BType b;
   String payload5;
   String payload7;
}


won't work either with annotations. When building suitable (and useless) AVersioned and BVersioned primary-key classes and using the @IdClass annotation in AVersioned and BVersioned, hibernate complains spitting an "org.hibernate.AnnotationException: AVersionedPK has no persistent id property".

The same happens when you want to reduce coding by marking the PK classes with @MappedSuperclass and attempting to extend your AVersioned and BVersioned with them (which would make sense to me).

So, where's the very bad design of this simple case? Am I so guilty in just posting it? Isn't instead that the hibernate staff and, maybe, all the ejb3 designers, fear the inherent complexity of composite keys and prefer to relegate them to the "legacy" jail instead of taking a serious approach on the matter?

_________________
Giampaolo Tomassoni
Italy


Top
 Profile  
 
 Post subject:
PostPosted: Tue Aug 29, 2006 8:28 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 6:10 am
Posts: 8615
Location: Neuchatel, Switzerland (Danish)
well, this is not a composite id "limitation" it is a natural limition of inheritance.

You can't change the type of the id for specific subclasses; that also goes for single field id's.

you need to seperate these classes mapping wise. they cannot be mapped as subclasses in the persistence sense since they are not (e.g. they don't have an common id type)

_________________
Max
Don't forget to rate


Top
 Profile  
 
 Post subject:
PostPosted: Tue Aug 29, 2006 8:45 am 
Beginner
Beginner

Joined: Thu Mar 18, 2004 8:11 am
Posts: 38
Location: Italy
max wrote:
well, this is not a composite id "limitation" it is a natural limition of inheritance.

You can't change the type of the id for specific subclasses; that also goes for single field id's.


Well, I'm not: in a java-sense I'm just completing a partial one -defined in an abstract class- with one -defined in a concrete class-.

You may see that, apart for trivial code compression, the BaseVersioned class is only meant to allow for the instantiation of the BaseType.versionedTypes field, maybe through a @OneToMany(mappedBy = "version"). It has no equivalent table in sql. It is a table-per-concrete-class case, so the complete composites make sense only at concrete-class level, not at the BaseVersioned one. Why building fenches on this?


max wrote:
you need to seperate these classes mapping wise. they cannot be mapped as subclasses in the persistence sense since they are not (e.g. they don't have an common id type)


They share part of a primary key, so they are subclasses in the persistence sense. They are not unrelated. Hibernate has a too narrow idea of the persistence layer, to my opinion.

_________________
Giampaolo Tomassoni
Italy


Top
 Profile  
 
 Post subject:
PostPosted: Tue Aug 29, 2006 9:20 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 6:10 am
Posts: 8615
Location: Neuchatel, Switzerland (Danish)
sorry, but if you belive that then try and go and implement you self and come back and prove me wrong.

its pretty hard to generate a sql string that will get both one or the other and handle id's efficiently/correctly when the types are changing for subclasses.

hibernate allows you to map this as seperate class hiearchies - have you tried ?

_________________
Max
Don't forget to rate


Top
 Profile  
 
 Post subject:
PostPosted: Tue Aug 29, 2006 9:37 am 
Beginner
Beginner

Joined: Thu Mar 18, 2004 8:11 am
Posts: 38
Location: Italy
max wrote:
sorry, but if you belive that then try and go and implement you self and come back and prove me wrong.

its pretty hard to generate a sql string that will get both one or the other and handle id's efficiently/correctly when the types are changing for subclasses.


It is a union select on both AVersioned and BVersioned with idVersion=(a-number) in the where clausole.


max wrote:
hibernate allows you to map this as seperate class hiearchies - have you tried ?


Yes, it is the "fallback case". It works with xml mappings, it doesn't with annotations.

In summary, this:

Code:
class AVersionedPK implements Serializables {
@Id
@ManyToOne(optional = false)
@JoinColumn(name = "idVersion")
Versions version;

@Id
@ManyToOne(optional = false)
@JoinColumn(name = "idA")
AType a;

... hasCode, equals and toString ...

AVersioned(Version version, AType a) {
  this.version = version;
  this.a = a;
}
}

@Entity
@Table(name = "aversioned")
@IdClass(AVersionedPK.class)
class AVersioned implements Serializable {
@Id
@ManyToOne(optional = false)
@JoinColumn(name = "idVersion")
Versions version;

@Id
@ManyToOne(optional = false)
@JoinColumn(name = "idA")
AType a;

@Column
String payload5;

@Column
String payload6;

... hasCode, equals and toString ...

AVersioned(Version version, AType a) {
  this.version = version;
  this.a = a;
}
}


throws a org.hibernate.AnnotationException: AVersionedPK has no persistent id property.

The same happens with this other version, which would be really nice:

Code:
@MappedSuperclass
class AVersionedPK implements Serializables {
@Id
@ManyToOne(optional = false)
@JoinColumn(name = "idVersion")
Versions version;

@Id
@ManyToOne(optional = false)
@JoinColumn(name = "idA")
AType a;

... hasCode, equals and toString ...

AVersioned(Version version, AType a) {
  this.version = version;
  this.a = a;
}
}

@Entity
@Table(name = "aversioned")
@IdClass(AVersionedPK.class)
class AVersioned extends AVersionedPK {
@Column
String payload5;

@Column
String payload6;

AVersioned(Version version, AType a)
{ super(versione, a); }
}



Maybe they work even with annots not unsing the @ManyToOne but two integer ids in the classes, then using a @OneToOne to get the AVersioned{PK}.version field and AVersioned.a instance. But it is ugly.

_________________
Giampaolo Tomassoni
Italy


Top
 Profile  
 
 Post subject:
PostPosted: Tue Aug 29, 2006 9:52 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 6:10 am
Posts: 8615
Location: Neuchatel, Switzerland (Danish)
annotations can't map/express "non-clean" mappings...that is what orm.xml (even though i don't think it can handle this specific case) or hbm.xml is for.

you do know hbm.xml and annotations can be used *together* ?

_________________
Max
Don't forget to rate


Top
 Profile  
 
 Post subject:
PostPosted: Tue Aug 29, 2006 10:44 am 
Beginner
Beginner

Joined: Thu Mar 18, 2004 8:11 am
Posts: 38
Location: Italy
max wrote:
annotations can't map/express "non-clean" mappings...that is what orm.xml (even though i don't think it can handle this specific case) or hbm.xml is for.


Why this (an)notation is unclean? The @ManyToOne and @JoinColumn clearly state that the field is a reference to an entity, and which should be the "real" table column.


max wrote:
you do know hbm.xml and annotations can be used *together* ?


Yes. And I'm going with it.

But, Max, please note that I urged to post this because I really believe that ejb and hibernate is going in the wrong direction, clearly creating a dicotomy between the real thing and the hibernate/ejb view.

When an important feature, which may easily be expressed both in sql and in java, is missing in the middle layer, it is simply unfair to tag it as legacy and go ahead. Composite primaries are not legacy: they are the heart of a well-designed db.

I know that the more complex example I posted can't work with hibernate. That's because hibernate was born with the single <composite-id> xml element in mind and has no provision to scale a primary key up in the class hierarchy. This doesn't mean that such a feature wouldn't be useful or that it is a mistake to attempt.

Also, FOREIGNs in sql are meant to state that an object (an integer, in example) is after all just a reference to another object (a row in a table, in example). And Java too does know what a reference to an object is. So, when the Ejb specs forget to mention "a reference to another entity" in the list of types allowed for a primary key, they are effectively impairing the power of both sql and java. Xml mappings in Hibernate defined the <key-many-to-one> element, which is one of the most interesting feature I see of hibernate. The @Id @ManyToOne @JoinColumn would univocally express no more, no less than that. Forgetting this in implementing the ejb3 annots is a backward step for the hibernate community itself.

_________________
Giampaolo Tomassoni
Italy


Top
 Profile  
 
 Post subject:
PostPosted: Tue Aug 29, 2006 10:50 am 
Hibernate Team
Hibernate Team

Joined: Tue Aug 26, 2003 6:10 am
Posts: 8615
Location: Neuchatel, Switzerland (Danish)
i don't have time to discuss these details ;)

Hibernate still can and will provide the functionallity you asked for.

and no, "expanding" keys downwards in the persistence hiearchy (note i'm not saying class hiearchy here) is *Bad*.

so map it as seperate constructs as i informed you.

wether or not annotations can be made to express this is a feature request you are more than willingly to supply a patch for.

_________________
Max
Don't forget to rate


Top
 Profile  
 
 Post subject:
PostPosted: Tue Aug 29, 2006 11:08 am 
Beginner
Beginner

Joined: Thu Mar 18, 2004 8:11 am
Posts: 38
Location: Italy
max wrote:
and no, "expanding" keys downwards in the persistence hiearchy (note i'm not saying class hiearchy here) is *Bad*.


Ok. When you'll get some spare time which you would like to spend unwisely, please tell me why.

_________________
Giampaolo Tomassoni
Italy


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 13 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.