-->
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.  [ 1 post ] 
Author Message
 Post subject: Mapping Overlapping PK/FK Problem
PostPosted: Sun Oct 15, 2006 3:46 am 
Newbie

Joined: Sun Sep 24, 2006 10:29 pm
Posts: 2
I am having some difficulty in mapping an attributed many-to-many relationship with overlapping Primary and Foreign keys. Following the recommended best practices I mapped the relationship using two one-to-many relationships, representing the “join” table as its own entity – which made a lot of sense given the additional attributes it held. However due to the existence of a compound key, components of which are in fact foreign keys I’ve had some difficulty in getting a valid set of mapping annotations. Additionally the objects at either end of the many-to-many relationship are using auto-generated keys, which I think is complicating matters.

Probably best illustrated by an example so here goes:

Hibernate version: 3.2 CR4, HEM/HAN: 3.2 CR2

Mapping documents:

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/ ... ce_1_0.xsd"
version="1.0">
<persistence-unit name="Test" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>entity.TableOne</class>
<class>entity.TableTwo</class>
<class>entity.TableOneTwo</class>
<class>entity.TableOneTwoPK</class>
<exclude-unlisted-classes />
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.Oracle9Dialect" />
<property name="hibernate.connection.driver_class"
value="oracle.jdbc.driver.OracleDriver" />
<property name="hibernate.connection.username" value="greg" />
<property name="hibernate.connection.password" value="password" />
<property name="hibernate.connection.url" value="jdbc:oracle:thin:@localhost:1521:orcl" />
<property name="hibernate.cache.provider_class"
value="org.hibernate.cache.HashtableCacheProvider" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<property name="hibernate.use_sql_comments" value="true" />
</properties>
</persistence-unit>
</persistence>

Full stack trace of any exception that occurs:

javax.persistence.RollbackException: Error while commiting the transaction
at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:63)
at test.TableOneTwoTest.testCreateEntities(TableOneTwoTest.java:48)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at junit.framework.TestCase.runTest(TestCase.java:164)
at junit.framework.TestCase.runBare(TestCase.java:130)
at junit.framework.TestResult$1.protect(TestResult.java:110)
at junit.framework.TestResult.runProtected(TestResult.java:128)
at junit.framework.TestResult.run(TestResult.java:113)
at junit.framework.TestCase.run(TestCase.java:120)
at junit.framework.TestSuite.runTest(TestSuite.java:228)
at junit.framework.TestSuite.run(TestSuite.java:223)
at org.junit.internal.runners.OldTestClassRunner.run(OldTestClassRunner.java:35)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:38)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Caused by: org.hibernate.exception.SQLGrammarException: Could not execute JDBC batch update
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:67)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:249)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:235)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:139)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:297)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:53)
... 20 more
Caused by: java.sql.BatchUpdateException: ORA-00957: duplicate column name

at oracle.jdbc.driver.DatabaseError.throwBatchUpdateException(DatabaseError.java:343)
at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:10698)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:48)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:242)
... 28 more

Name and version of the database you are using: Oracle 10g

The generated SQL (show_sql=true):

/* insert entity.TableOneTwo
*/ insert
into
TABLEONE_TABLETWO
(DATA, TABLEONEID, TABLETWOID, tableOneId, tableTwoId)
values
(?, ?, ?, ?, ?)

DB Schema:

Code:
ALTER TABLE TableOne_TableTwo DROP CONSTRAINT FK_TableOne_TableTwo_1;
ALTER TABLE TableOne_TableTwo DROP CONSTRAINT FK_TableOne_TableTwo_2;

DROP TABLE TableOne;
DROP TABLE TableTwo;
DROP TABLE TableOne_TableTwo;
DROP SEQUENCE hibernate_sequence;

CREATE TABLE TableOne (
   TableOneId NUMBER (20) NOT NULL,
   data VARCHAR (255) NOT NULL,
   CONSTRAINT PK_TableOne PRIMARY KEY (TableOneId)
   );

CREATE TABLE TableTwo (
   TableTwoId NUMBER (20) NOT NULL,
   data VARCHAR (255) NOT NULL,
   CONSTRAINT PK_TableTwo PRIMARY KEY (TableTwoId)
   );

CREATE TABLE TableOne_TableTwo (
   TableOneId NUMBER (20) NOT NULL,
   TableTwoId NUMBER (20) NOT NULL,
   data VARCHAR (255) NOT NULL,
   CONSTRAINT FK_TableOne_TableTwo PRIMARY KEY (TableOneId, TableTwoId)
   );

ALTER TABLE TableOne_TableTwo ADD (CONSTRAINT FK_TableOne_TableTwo_1 FOREIGN KEY (TableOneId) REFERENCES TableOne (TableOneId));
ALTER TABLE TableOne_TableTwo ADD (CONSTRAINT FK_TableOne_TableTwo_2 FOREIGN KEY (TableTwoId) REFERENCES TableTwo (TableTwoId));

CREATE SEQUENCE hibernate_sequence NOCYCLE NOORDER CACHE 20 NOMAXVALUE MINVALUE 1 INCREMENT BY 1 START WITH 1000;


Mapping to the following set of classes:

Code:
package entity;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@SuppressWarnings("serial")
@Entity()
@Table(name = "TABLEONE", schema = "GREG")
public class TableOne implements Serializable {

   @Id()
   @GeneratedValue(strategy = GenerationType.AUTO)
   @Column(name = "TABLEONEID", unique = true, nullable = false, precision = 20)
   private Long tableOneId;

   @Basic()
   @Column(name = "DATA", nullable = false, length = 255)
   private String data;

   @OneToMany(mappedBy = "tableOne", cascade = CascadeType.ALL)
   private Set<TableOneTwo> tableOneTwos;

   public TableOne() {
      tableOneTwos = new HashSet<TableOneTwo>(0);
   }

   public String getData() {
      return data;
   }

   public void setData(String data) {
      this.data = data;
   }

   public Long getTableOneId() {
      return tableOneId;
   }

   public void setTableOneId(Long tableOneId) {
      this.tableOneId = tableOneId;
   }

   public Set<TableOneTwo> getTableOneTwos() {
      return tableOneTwos;
   }

   public void setTableOneTwos(Set<TableOneTwo> tableOneTwos) {
      this.tableOneTwos = tableOneTwos;
   }

}


package entity;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@SuppressWarnings("serial")
@Entity
@Table(name = "TABLETWO")
public class TableTwo implements Serializable {

   @Id
   @GeneratedValue(strategy = GenerationType.AUTO)
   @Column(name = "TABLETWOID", unique = true, nullable = false, precision = 20)
   private Long tableTwoId;

   @Basic
   @Column(name = "DATA", nullable = false, length = 255)
   private String data;

   @OneToMany(mappedBy = "tableTwo", cascade = CascadeType.ALL)
   private Set<TableOneTwo> tableOneTwos;

   public TableTwo() {
      tableOneTwos = new HashSet<TableOneTwo>(0);
   }

   public String getData() {
      return data;
   }

   public void setData(String data) {
      this.data = data;
   }

   public Set<TableOneTwo> getTableOneTwos() {
      return tableOneTwos;
   }

   public void setTableOneTwos(Set<TableOneTwo> tableOneTwos) {
      this.tableOneTwos = tableOneTwos;
   }

   public Long getTableTwoId() {
      return tableTwoId;
   }

   public void setTableTwoId(Long tableTwoId) {
      this.tableTwoId = tableTwoId;
   }

}

package entity;

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity()
@IdClass(TableOneTwoPK.class)
@Table(name = "TABLEONE_TABLETWO")
public class TableOneTwo {

   @Id()
   private Long tableOneId;

   @Id()
   private Long tableTwoId;

   @Basic()
   @Column(name = "DATA")
   private String data;

   @ManyToOne(cascade = CascadeType.ALL)
   @JoinColumn(name = "TABLEONEID", referencedColumnName = "TABLEONEID", nullable = false)
   private TableOne tableOne;

   @ManyToOne(cascade = CascadeType.ALL)
   @JoinColumn(name = "TABLETWOID", referencedColumnName = "TABLETWOID", nullable = false)
   private TableTwo tableTwo;

   public TableOneTwo() {

   }

   public Long getTableOneId() {
      return tableOneId;
   }

   public void setTableOneId(Long tableOneId) {
      this.tableOneId = tableOneId;
   }

   public Long getTableTwoId() {
      return tableTwoId;
   }

   public void setTableTwoId(Long tableTwoId) {
      this.tableTwoId = tableTwoId;
   }

   public String getData() {
      return data;
   }

   public void setData(String data) {
      this.data = data;
   }

   public TableOne getTableOne() {
      return tableOne;
   }

   public void setTableOne(TableOne tableOne) {
      this.tableOne = tableOne;
   }

   public TableTwo getTableTwo() {
      return tableTwo;
   }

   public void setTableTwo(TableTwo tableTwo) {
      this.tableTwo = tableTwo;
   }

}

package entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Embeddable;

@SuppressWarnings("serial")
@Embeddable()
public class TableOneTwoPK implements Serializable {

   @Column(nullable = false, insertable = false, updatable = false)
   private Long tableOneId;

   @Column(nullable = false, insertable = false, updatable = false)
   private Long tableTwoId;

   public TableOneTwoPK() {
   }

   public Long getTableOneId() {
      return tableOneId;
   }

   public void setTableOneId(Long tableOneId) {
      this.tableOneId = tableOneId;
   }

   public Long getTableTwoId() {
      return tableTwoId;
   }

   public void setTableTwoId(Long tableTwoId) {
      this.tableTwoId = tableTwoId;
   }

   @Override
   public boolean equals(Object obj) {
      if (obj == null)
         return false;
      if (!(obj instanceof TableOneTwoPK))
         return false;
      TableOneTwoPK other = (TableOneTwoPK) obj;
      return this.tableOneId.equals(other.tableOneId) && this.tableTwoId.equals(other.tableTwoId);
   }

   @Override
   public int hashCode() {
      return super.hashCode();
   }

}


Using the following JUnit test harness:

Code:
package test;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

import junit.framework.TestCase;
import entity.TableOne;
import entity.TableOneTwo;
import entity.TableTwo;

public class TableOneTwoTest extends TestCase {

   EntityManagerFactory emf;

   EntityManager em;

   @Override
   protected void setUp() throws Exception {
      emf = Persistence.createEntityManagerFactory("Test");
      em = emf.createEntityManager();
   }

   public void testCreateEntities() {

      EntityTransaction tx = em.getTransaction();
      tx.begin();

      TableOne tableOne = new TableOne();
      tableOne.setData("Table One Data");

      TableTwo tableTwo = new TableTwo();
      tableTwo.setData("Table Two Data");

      TableOneTwo tableOneTwo = new TableOneTwo();
      tableOneTwo.setData("Table One & Two Data");

      tableOne.getTableOneTwos().add(tableOneTwo);
      tableTwo.getTableOneTwos().add(tableOneTwo);
      tableOneTwo.setTableOne(tableOne);
      tableOneTwo.setTableTwo(tableTwo);

      em.persist(tableOne);
      em.persist(tableTwo);
      em.persist(tableOneTwo);

      tx.commit();
   }

   public void testCreateSingleEntity() {

      EntityTransaction tx = em.getTransaction();
      tx.begin();

      TableOne tableOne = em.find(TableOne.class, 10L);
      TableTwo tableTwo = em.find(TableTwo.class, 10L);

      TableOneTwo tableOneTwo = new TableOneTwo();
      tableOneTwo.setData("Table One & Two Data");
      tableOneTwo.setTableOne(tableOne);
      tableOneTwo.setTableTwo(tableTwo);

      em.persist(tableOneTwo);

      tx.commit();
   }

}


Using the above test harness results in a “duplicate column” exception from the DB as a result of the following generated SQL:

Quote:
/* insert entity.TableOneTwo
*/ insert
into
TABLEONE_TABLETWO
(DATA, TABLEONEID, TABLETWOID, tableOneId, tableTwoId)
values
(?, ?, ?, ?, ?)

15/10/2006 15:54:30 org.hibernate.util.JDBCExceptionReporter logExceptions
WARNING: SQL Error: 957, SQLState: 42000
15/10/2006 15:54:30 org.hibernate.util.JDBCExceptionReporter logExceptions
SEVERE: ORA-00957: duplicate column name


It seems to be ignoring the “updateable = false” in the TableOneTwoPK class. Interestingly enough if I include the “name” attribute in this class, then I get a “Repeated column in mapping” exception.

I’ve tried numerous approaches in the annotations, such as using an @Embedded compound key class. See the following code updates to the TableOneTwo class only:

Code:
package entity;

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity()
@Table(name = "TABLEONE_TABLETWO")
public class TableOneTwo {

   @Id()
   private TableOneTwoPK Id;

   @Basic()
   @Column(name = "DATA")
   private String data;

   @ManyToOne(cascade = CascadeType.ALL)
   @JoinColumn(name = "TABLEONEID", referencedColumnName = "TABLEONEID", nullable = false)
   private TableOne tableOne;

   @ManyToOne(cascade = CascadeType.ALL)
   @JoinColumn(name = "TABLETWOID", referencedColumnName = "TABLETWOID", nullable = false)
   private TableTwo tableTwo;

   public TableOneTwo() {

   }

   public TableOneTwoPK getId() {
      return Id;
   }

   public void setId(TableOneTwoPK id) {
      Id = id;
   }

   public String getData() {
      return data;
   }

   public void setData(String data) {
      this.data = data;
   }

   public TableOne getTableOne() {
      return tableOne;
   }

   public void setTableOne(TableOne tableOne) {
      this.tableOne = tableOne;
   }

   public TableTwo getTableTwo() {
      return tableTwo;
   }

   public void setTableTwo(TableTwo tableTwo) {
      this.tableTwo = tableTwo;
   }

}


Which results in the following error:
Quote:
“javax.persistence.PersistenceException: org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save(): entity2.TableOneTwo”


The only way I have been able to get this structure to persist is via the @Embedded PK annotation and removal of the Cascade settings, then to force multiple saves on each of the entities, eg a “save” on the TableOne and TableTwo classes so that they are assigned Id’s, which I then set into the TableOneTwo class then call save on it.

Is it possible to “Cascade” save this structure and have Hibernate resolve the generation and assignment of the generated Id’s into the Join table?


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

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.