-->
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.  [ 6 posts ] 
Author Message
 Post subject: Having problem with a bidirectional OneToOne relationship
PostPosted: Mon May 30, 2011 6:32 pm 
Newbie

Joined: Thu May 19, 2011 12:59 am
Posts: 13
I feel really stupid right now, because I am struggling with a problem with a bidirectional OneToOne relationship which is supposed to by simple to perform but I am just not understanding it because I cannot get it to work.

Basically, the problem is between two tables, lets call them table A and table B.

A has primary key of A_ID.
B is a bit strange because it has a primary key called B_ID which is also a foreign key to table A's A_ID.

Now my classes:

Code:
@Entity
@Table(name = "A")
public class A {

  public B b;
}

@Entity
@Table(name = "B")
public class B {

  public A a;
}


Now how am I supposed to annotate those A and B fields to properly point to eachother?
I have tried so many variations on OneToOne but I'll either get a Hibernate initialization error or it will run fine but Hibernate never inserts the entry for B.

For example I'll instantiate an object for A, fill in some fields, then instantiate A's B and fill ins one of its fields, then I call my DAO to save A. The result? A new row is inserted for A. B is ignored.

I can also verify on debug that no insert is ever attempted for B. Hibernate just completely ignores B.

Here for example is an annotation I tried:

Code:
------ A's class: ------

@OneToOne(fetch=FetchType.LAZY)
@JoinColumn(name="A_ID", referencedColumnName="B_ID")
public B b;

------ B's class ------

@OneToOne(fetch=FetchType.LAZY)
@JoinColumn(name="B_ID", referencedColumnName="A_ID")
public A a;


I also tried "mappedBy" where I put mappedBy("a") in B's field but Hibernate threw an error that it was an unknown mappedBy. That was quite curious, I thought mappedBy operates off of class name, no?

And both classes are referenced in my annotated classes property.

Any ideas?

I am really new to Hibernate

Edit: Also what makes this interesting is... when I load from DB using Hibernate, it populates B just fine. It's only when saving it seems to ignore B


Top
 Profile  
 
 Post subject: Re: Having problem with a bidirectional OneToOne relationship
PostPosted: Tue May 31, 2011 12:44 am 
Newbie

Joined: Thu May 19, 2011 12:59 am
Posts: 13
OK, made some progress mostly by trial and error.

I got to the point where now Hibernate is trying to at least insert the record, however I get an ORACLE error due to the primary key not being set.

See this second table is like an extension of the first table, and it shares the same ID. So it's primary key is the foreign key to the first table's PK. Not my choice of design, but I need to deal with it.

Here's how I set it up now, and to maybe make it easier I changed some names. Main is the main table that has a sequence to generate it's ID. Then it has the extension object as a member, and it is supposed to share the same ID. The problem is, I cannot figure out how to set up my annotations to get hibernate to properly set Extension's ID when they are both inserted at same time.

Code:
@Entity
@Table(name = "Main")
public class Main {

  @SequenceGenerator(name = "generator", sequenceName="MAIN_SEQ")
  @Id
  @GeneratedValue(strategy = SEQUENCE, generator = "generator")
  @Column(name = "MAIN_ID", unique = true, nullable = false)
  private Long mainId;

  @JoinColumn(name="MAIN_ID", referencedColumnName="EXTENSION_ID")
  @OneToOne(optional=false, cascade=CascadeType.ALL)
  private Extension extension;
}


Code:
@Entity
@Table(name = "Extension")
public class Extension {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "EXTENSION_ID", nullable = false, insertable=false, updatable=false)
  private Long extensionId;

  @OneToOne
  @JoinColumn(name="EXTENSION_ID", referencedColumnName="MAIN_ID")
  private Main main;
}


And when I run it with this I get the error:

ORA-01400: cannot insert NULL into ("TEST"."EXTENSION"."EXTENSION_ID")

So, it's at least now trying to insert the Extension object into the right table, but I cant figure out how to have Hibernate automatically set the extension id on the extension object when the main is inserted.

Basically, I do:

Main main = new Main();
// set main's fields
main.extension = new Extension();
// other fields
DAO.save(main);

Edit:
Basically, what I think I need to do is set my Id generation for my Extension class to be of type "foreign", but I am not sure how to do this with annotation. I found this document here: http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/mapping.html#d0e5617 with my situation described under "5.1.2.2.7. Identity copy (foreign generator)".

However trying this I cannot get it to work. First of all, I see from the debug that Hibernate actually beings with performing a SELECT from Extension table- wtf? And the select fails because it thinks there is a column named "Extension". How did it get to that???


Top
 Profile  
 
 Post subject: Re: Having problem with a bidirectional OneToOne relationship
PostPosted: Tue May 31, 2011 3:43 am 
Newbie

Joined: Wed May 04, 2011 2:24 am
Posts: 10
Hey trellised,

Haven't encountered this specific situation myself, but I'd consider trying to get the mappedby property to work. The relationship needs an 'owner'. The mappedby property operates off the field/property name, depending on which one you map the annotations.

There is a nice example at http://download.oracle.com/javaee/5/api/javax/persistence/OneToOne.html, i hope it helps.


Top
 Profile  
 
 Post subject: Re: Having problem with a bidirectional OneToOne relationship
PostPosted: Tue May 31, 2011 10:35 am 
Newbie

Joined: Thu May 19, 2011 12:59 am
Posts: 13
Anne Buit wrote:
Hey trellised,

Haven't encountered this specific situation myself, but I'd consider trying to get the mappedby property to work. The relationship needs an 'owner'. The mappedby property operates off the field/property name, depending on which one you map the annotations.

There is a nice example at http://download.oracle.com/javaee/5/api/javax/persistence/OneToOne.html, i hope it helps.


Hi Anne,

Thanks for your response!

I now have the mappedBy attribute working (I didn't know I was supposed to use the name of the property on the main class, instead of the name of the main class itself) but alas, I am back to my original problem of the compilation working, execution working - no errors thrown - yet the EXTENSION insertion is totally ignored. It never happens.

I am starting to get really stressed over this.

I changed my classes as follows:

Code:
@Entity
@Table(name = "Main")
public class Main {

  @SequenceGenerator(name = "generator", sequenceName="MAIN_SEQ")
  @Id
  @GeneratedValue(strategy = SEQUENCE, generator = "generator")
  @Column(name = "MAIN_ID", unique = true, nullable = false)
  private Long mainId;

  @OneToOne(optional=false)
  @JoinColumn(name="MAIN_ID", unique=true, nullable=false, updatable=false)
  private Extension extension;
}


Code:
@Entity
@Table(name = "Extension")
public class Extension {

  @Id
  @Column(name = "EXTENSION_ID", nullable = false)
  private Long extensionId;

  @OneToOne(optional=false, mappedBy="extension")
  private Main main;
}


I suppose the issue IO have that complicate this scenario is the ID column names between my MAIN and EXTENSION table are not the same. One is MAIN_ID the other is EXTENSION_ID. For MAIN_ID as you can see in annotation above it is setup as a PK with a sequence generator. For EXTENSION, EXTENSION_ID is the PK and is a FK to MAIN_ID (when a MAIN record is created, it may or may not get an EXTENSION record created for it, which would have an EXTENSION_ID set to the value of MAIN_ID.

If I leave my Extension class without any Generator annotation, then Hibernate simply ignores it, never attempts to insert it when I save Main.

If I do provide some Generator annotation (for example I tried IDENTITY earlier) then Hibernate does attempt to insert it, however it did not set the ID as I had hoped so I get an error from Hibernate that I am supposed to assign the value before calling Save().

If the Main entry is brand new, and uses a sequence to generate it's PK, then how can I expect to provide Extension with this ID before I call Save() ???

Edit: Also, since the example did not mention having the id in a separate field with @Id annotated above it, like in my class. I just tried removing it from my class, so all I have is the reference to Extension. This fails however:

Quote:
org.hibernate.AnnotationException:No identifier specified for entity: com.test.Extension


So apparently the example does not even work


Top
 Profile  
 
 Post subject: Re: Having problem with a bidirectional OneToOne relationship
PostPosted: Tue May 31, 2011 3:26 pm 
Newbie

Joined: Thu May 19, 2011 12:59 am
Posts: 13
Ok, finally got it working - after wasting so much time on this!

I have tried sooo many different possible solutions nothing ever works.

Here is what I had to do, and unfortunately I had to use hibernate specific annotation to do it.... if you can suggest a better way using only JPA annotations I would love to hear it!

Code:
@Entity
@Table(name = "Main")
public class Main {

  @SequenceGenerator(name = "generator", sequenceName="MAIN_SEQ")
  @Id
  @GeneratedValue(strategy = SEQUENCE, generator = "generator")
  @Column(name = "MAIN_ID", unique = true, nullable = false)
  private Long mainId;

  @OneToOne(cascade=CascadeType.ALL) 
  @PrimaryKeyJoinColumn(name="MAIN_ID", referencedColumnName="EXTENSION_ID")
  private Extension extension;
}


Code:
   
@Entity
@Table(name = "Extension")
public class Extension {

  @Id
  @Column(name = "EXTENSION_ID", nullable = false)
  @GeneratedValue(generator = "foreignGenerator")
  @GenericGenerator(name = "foreignGenerator", strategy = "foreign",
        parameters = { @Parameter(name = "property", value = "main") })
  private Long extensionId;
  @OneToOne(mappedBy="extension")
  private Main main;
}


Yep, that did it. Now Hibernate not only attempts to perform the insert, but also populates the extensionId with the primary key generated for mainId in it's Save() call.

Edit:

Looking over what I had to do, it looks like basically I am telling the generator for Extension to use the same generator in Main. Is that correct? If so, is there a more direct way of doing this, perhaps using JPA standard tags? If only I could for example, name my generator in Main with name="MainGenerator" and then in Extension also just tell it to use "MainGenerator". Without all that extra verbose annotations I had to do.


Top
 Profile  
 
 Post subject: Re: Having problem with a bidirectional OneToOne relationship
PostPosted: Sat Jun 04, 2011 1:00 am 
Newbie

Joined: Wed Jun 24, 2009 9:10 pm
Posts: 10
Yes, you are right. Basically, you are telling the generator for Extension to use the Main's generator to get its primary key value via parameters attribute.

And as mentioned in "Java Persistence with Hibernate" book, there is no standard way in JPA for shared primary key generation. And that is why we have to use Hibernate specific annotation. Not sure JPA2.0 has something for this.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 6 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.