-->
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.  [ 9 posts ] 
Author Message
 Post subject: Need help with Many to Many relationship
PostPosted: Fri Jul 27, 2007 1:34 am 
Beginner
Beginner

Joined: Mon May 07, 2007 11:12 pm
Posts: 20
Hello,

I am trying to implement a many to many relationship and getting the exception below. I was hoping to get some suggestions on how to get this working.

- Should I be creating a mapping between the two tables below or should I be using the data access classes to do most of handling.

- If I should be using a mapping, how should I set up the maps when I have a composite key involved?

schema

Code:


CREATE TABLE ADDRESS_LINK(
       ADDRESS_ID           varchar(20) NOT NULL,
       PARENT_ID            varchar(20) NOT NULL,
       PARENT_TYPE          varchar(30) NOT NULL,
       PRIMARY KEY (PARENT_ID, PARENT_TYPE, ADDRESS_ID)
);


CREATE TABLE ADDRESS (
       ADDRESS_ID           varchar(20) NOT NULL,
       COUNTRY              varchar(30) NULL,
       ADDRESS1             varchar(45) NULL,
       ADDRESS2             varchar(45) NULL,
       CITY                 varchar(30) NULL,
       STATE                varchar(15) NULL,
       POSTAL_CD            varchar(10) NULL,
       IS_DEFAULT           int NULL DEFAULT 0,
       DESCRIPTION          varchar(25) NULL,
       PRIMARY KEY (ADDRESS_ID)
);




Mapping documents:

AddressLink map file:

Code:
<hibernate-mapping>
    <class name="org.openiam.idm.srvc.continfo.dto.AddressLink" table="ADDRESS_LINK" >
        <comment></comment>
        <composite-id name="id" class="org.openiam.idm.srvc.continfo.dto.AddressLinkId">
            <key-property name="parentId" type="string">
                <column name="PARENT_ID" length="20" />
            </key-property>
            <key-property name="parentType" type="string">
                <column name="PARENT_TYPE" length="30" />
            </key-property>
            <key-property name="addressId" type="string">
                <column name="ADDRESS_ID" length="20" />
            </key-property>
        </composite-id>
       
     
    <set name="addresses" cascade="all-delete-orphas">
       <key>
          <column name="addressId"  />
       </key>
      <many-to-many class="org.openiam.idm.srvc.continfo.dto.Address" column="addressId" />
   </set>

 
    </class>
</hibernate-mapping>



Address map:

Code:
  <class name="org.openiam.idm.srvc.continfo.dto.Address" table="ADDRESS">
        <comment></comment>
        <id name="addressId" type="string">
            <column name="ADDRESS_ID" length="20" />
            <generator class="assigned" />
        </id>
        <property name="country" type="string">
            <column name="COUNTRY" length="30">
                <comment></comment>
            </column>
        </property>
        <property name="address1" type="string">
            <column name="ADDRESS1" length="45">
                <comment></comment>
            </column>
        </property>
        <property name="address2" type="string">
            <column name="ADDRESS2" length="45">
                <comment></comment>
            </column>
        </property>
        <property name="city" type="string">
            <column name="CITY" length="30">
                <comment></comment>
            </column>
        </property>
        <property name="state" type="string">
            <column name="STATE" length="15">
                <comment></comment>
            </column>
        </property>
        <property name="postalCd" type="string">
            <column name="POSTAL_CD" length="10">
                <comment></comment>
            </column>
        </property>
        <property name="isDefault" type="java.lang.Integer">
            <column name="IS_DEFAULT">
                <comment></comment>
            </column>
        </property>
        <property name="description" type="string">
            <column name="DESCRIPTION" length="25">
                <comment></comment>
            </column>
        </property>
    </class>
</hibernate-mapping>




Code between sessionFactory.openSession() and session.close():

Unit test code:

Code:
   public void testAddAddress() {
      
      Set addressSet = new HashSet();
      addressSet.add(adr1);
      AddressLink link = new AddressLink(adr1.getParentId(),adr1.getParentType(),adr1.getAddressId());
      
      linkDAO.add(link);         
   }


AddressLinkDAO code:

public void add(AddressLink transientInstance) {
log.debug("persisting AddressLink instance");
try {
sessionFactory.getCurrentSession().persist(transientInstance);
log.debug("persist successful");
} catch (RuntimeException re) {
re.printStackTrace();
log.error("persist failed", re);
throw re;
}
}

Full stack trace of any exception that occurs:

Code:
nested exception is org.hibernate.MappingException: Foreign key (FK34207BA2E3118199:addresses [addressId])) must have same number of columns as the referenced primary key (ADDRESS_LINK [PARENT_ID,PARENT_TYPE,ADDRESS_ID])
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'serviceDAO' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'sessionFactory' while setting bean property 'sessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in class path resource [applicationContext.xml]: Invocation of init method failed; nested exception is org.hibernate.MappingException: Foreign key (FK34207BA2E3118199:addresses [addressId])) must have same number of columns as the referenced primary key (ADDRESS_LINK [PARENT_ID,PARENT_TYPE,ADDRESS_ID])
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in class path resource [applicationContext.xml]: Invocation of init method failed; nested exception is org.hibernate.MappingException: Foreign key (FK34207BA2E3118199:addresses [addressId])) must have same number of columns as the referenced primary key (ADDRESS_LINK [PARENT_ID,PARENT_TYPE,ADDRESS_ID])
Caused by: org.hibernate.MappingException: Foreign key (FK34207BA2E3118199:addresses [addressId])) must have same number of columns as the referenced primary key (ADDRESS_LINK [PARENT_ID,PARENT_TYPE,ADDRESS_ID])
   at org.hibernate.mapping.ForeignKey.alignColumns(ForeignKey.java:90)
   at org.hibernate.mapping.ForeignKey.alignColumns(ForeignKey.java:73)
   at org.hibernate.cfg.Configuration.secondPassCompileForeignKeys(Configuration.java:1263)
   at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1170)
   at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1286)
   at org.springframework.orm.hibernate3.LocalSessionFactoryBean.newSessionFactory(LocalSessionFactoryBean.java:805)
   at org.springframework.orm.hibernate3.LocalSessionFactoryBean.buildSessionFactory(LocalSessionFactoryBean.java:745)
   at org.springframework.orm.hibernate3.AbstractSessionFactoryBean.afterPropertiesSet(AbstractSessionFactoryBean.java:134)
   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1202)
   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1172)
   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:428)
   at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:251)
   at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:156)
   at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:248)
   at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:160)
   at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:261)
   at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:109)
   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1100)
   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:862)
   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:424)
   at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:251)
   at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:156)
   at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:248)
   at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:160)
   at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:261)
   at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:109)
   at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:389)
   at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:120)
   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:800)
   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:720)
   at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:387)
   at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:251)
   at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:156)
   at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:248)
   at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:160)
   at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:287)
   at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:352)
   at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:91)
   at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:75)
   at org.openiam.idm.srvc.continfo.AddressLinkDAOTest.onSetUp(AddressLinkDAOTest.java:41)
   at org.springframework.test.AbstractSingleSpringContextTests.setUp(AbstractSingleSpringContextTests.java:89)
   at junit.framework.TestCase.runBare(TestCase.java:128)
   at org.springframework.test.ConditionalTestCase.runBare(ConditionalTestCase.java:69)
   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)



[/code]


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 27, 2007 3:16 am 
Expert
Expert

Joined: Thu Jul 05, 2007 9:38 am
Posts: 287
Hi I think you have the

<set> mapping on the wrong side.

the address has a set of addresslinks, but you've got it the other way round. which makes hibernate try to turn address.address_id into a Reference to address_link.(address_id, parent_id, parent_type) which of course fails.

Hope that helps
Jens


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 27, 2007 8:38 am 
Beginner
Beginner

Joined: Mon May 07, 2007 11:12 pm
Posts: 20
Perhaps I was looking at it from a different perspective. Address_links is a cross reference table that would allow me to have 1 or more addresses for a either user or company. If that is the case, can't I create a DAO on AddressLink and use that to navigate to the collection of addresses? If so, do I still need to have the set realtionship on the opposite side?

thanks


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 27, 2007 11:18 am 
Expert
Expert

Joined: Fri Jul 13, 2007 8:18 am
Posts: 370
Location: london
There's actually a much simpler solution to this. Its possible to link multiple Address objects to both User and Company without an intermediate table. Simply define a one to many relationship to address in both User and Company, using different key columns for each.

e.g.
Address.java
Code:
package test.addresses;

public class Address {
   private Long id;
   private String country;

   public Long getId() {
      return id;
   }

   public void setId(Long id) {
      this.id = id;
   }

   public String getCountry() {
      return country;
   }

   public void setCountry(String country) {
      this.country = country;
   }
   
   public String toString() {
      return country;
   }
   
}


Address.hbm.xml
Code:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
  "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="test.addresses">

<class name="Address" table="addr_address">
   <id name="id" column="id">
      <generator class="native"/>
   </id>

   <!-- Single address property for simple example. -->
   <property name="country"/>
</class>

</hibernate-mapping>


User.java
Code:
package test.addresses;

import java.util.Set;

public class User {
   private Long id;
   private Set<Address> addresses;
   
   public Long getId() {
      return id;
   }

   public void setId(Long id) {
      this.id = id;
   }

   public Set<Address> getAddresses() {
      return addresses;
   }

   public void setAddresses(Set<Address> addresses) {
      this.addresses = addresses;
   }

   
}


User.hbm.xml
Code:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
  "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="test.addresses">

<class name="User" table="addr_user">
   <id name="id" column="id">
      <generator class="native"/>
   </id>

   <set name="addresses">
      <key column="user_id"/>
      <one-to-many class="Address"/>
   </set>
</class>

</hibernate-mapping>


Company.java
Code:
package test.addresses;

import java.util.Set;

public class Company {
   private Long id;
   private Set<Address> addresses;

   public Long getId() {
      return id;
   }

   public void setId(Long id) {
      this.id = id;
   }

   public Set<Address> getAddresses() {
      return addresses;
   }

   public void setAddresses(Set<Address> addresses) {
      this.addresses = addresses;
   }

   
}


Company.hbm.xml
Code:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
  "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="test.addresses">

<class name="Company" table="addr_company">
   <id name="id" column="id">
      <generator class="native"/>
   </id>

   <set name="addresses">
      <key column="company_id"/>
      <one-to-many class="Address"/>
   </set>
   
</class>

</hibernate-mapping>


TestIt.java
Code:
package test.addresses;

import java.util.HashSet;
import java.util.Set;

import junit.framework.TestCase;

import org.hibernate.Session;


public class TestIt extends TestCase {

   public void testIt() {
      Long userId = createUser();
      Long companyId = createCompany();
      
      Session session = HibernateUtil.getSession();
      User user = (User)session.load(User.class, userId);
      Company company = (Company)session.load(Company.class, companyId);
      
      System.out.println("user addresses: "+user.getAddresses());
      System.out.println("company addresses: "+company.getAddresses());
      
      session.close();
   }

   private Long createCompany() {
      Session session = HibernateUtil.getSession();
      session.beginTransaction();
      
      Address addr1 = new Address();
      addr1.setCountry("UK");
      session.save(addr1);
      
      Address addr2 = new Address();
      addr2.setCountry("France");
      session.save(addr2);

      Set<Address> addresses = new HashSet<Address>();
      addresses.add(addr1);
      addresses.add(addr2);
      
      Company company = new Company();
      company.setAddresses(addresses);
      Long id = (Long)session.save(company);
      
      session.getTransaction().commit();
      session.close();

      return id;
   }

   private Long createUser() {
      Session session = HibernateUtil.getSession();
      session.beginTransaction();

      Address addr1 = new Address();
      addr1.setCountry("Spain");
      session.save(addr1);
      
      Address addr2 = new Address();
      addr2.setCountry("Bolivia");
      session.save(addr2);

      Set<Address> addresses = new HashSet<Address>();
      addresses.add(addr1);
      addresses.add(addr2);

      User user = new User();
      user.setAddresses(addresses);
      Long id = (Long)session.save(user);
      
      session.getTransaction().commit();
      session.close();
      
      return id;
   }
}


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 27, 2007 11:50 am 
Beginner
Beginner

Joined: Mon May 07, 2007 11:12 pm
Posts: 20
Actually that wont work because i want to be able to reuse this with other objects as they come up.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 27, 2007 12:21 pm 
Expert
Expert

Joined: Fri Jul 13, 2007 8:18 am
Posts: 370
Location: london
Actually its easily extended to new objects - every time you add a new object type with addresses you simply map the addresses with a different key column.

Obviously I don't know your precise requirements but your solution is much more complicated than this - is it worth it? I couldn't think of many more real world things that have multiple addresses.

e.g. AnotherClassWithAddresses
Code:
<class name="AnotherClassWithAddresses" table="addr_another">
   <id name="id" column="id">
      <generator class="native"/>
   </id>

   <set name="addresses">
<!-- Simply use a different key column. -->
      <key column="another_class_with_addresses_id"/>
      <one-to-many class="Address"/>
   </set>
</class>


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 27, 2007 12:36 pm 
Expert
Expert

Joined: Thu Jul 05, 2007 9:38 am
Posts: 287
suneetshah wrote:
Perhaps I was looking at it from a different perspective. Address_links is a cross reference table that would allow me to have 1 or more addresses for a either user or company. If that is the case, can't I create a DAO on AddressLink and use that to navigate to the collection of addresses? If so, do I still need to have the set realtionship on the opposite side?

thanks


Your tables look ok, it is just that the adress_link class is the M side of the 1:M mapping so the <set> tag needs to be on the adress side.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 27, 2007 3:34 pm 
Beginner
Beginner

Joined: Mon May 07, 2007 11:12 pm
Posts: 20
Thanks.

The scenario is that you have companies, users, accounts, etc. Each may have addresses associated with them. I may want to track different addresses like - branch office, HQ, etc.

I dont want to have to recreate the Addresses class each time I have a different type of parent object. So in my key, i have parent Id and a parent type to help resolve this.

I think your solution may work. I can just embed the address DAO into each of the parent objects if I understand correctly. Is that right?

thanks for your help.



thatmikewilliams wrote:
Actually its easily extended to new objects - every time you add a new object type with addresses you simply map the addresses with a different key column.

Obviously I don't know your precise requirements but your solution is much more complicated than this - is it worth it? I couldn't think of many more real world things that have multiple addresses.

e.g. AnotherClassWithAddresses
Code:
<class name="AnotherClassWithAddresses" table="addr_another">
   <id name="id" column="id">
      <generator class="native"/>
   </id>

   <set name="addresses">
<!-- Simply use a different key column. -->
      <key column="another_class_with_addresses_id"/>
      <one-to-many class="Address"/>
   </set>
</class>


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jul 27, 2007 5:11 pm 
Expert
Expert

Joined: Fri Jul 13, 2007 8:18 am
Posts: 370
Location: london
Quote:
I dont want to have to recreate the Addresses class each time I have a different type of parent object.

Exactly. What I propose doesn't require the Address class to be changed or recreated when a new parent object is added. The new parent object gets its addresses by way of a foreign key in the address table refering to the new parent's primary key.

For each parent there is simply a different foreign key column in address. You don't need to manually add this to the XML definition of address, Hibernate automatically adds the foreign key columns to the address table when you define the address <set> in the parent.

Quote:
I can just embed the address DAO into each of the parent objects

There's no need to have an address DAO at all. An objects addresses will be automatically loaded (lazily by default) by hibernate. Also, if you configure the cascade options appropriately, addresses will be created, updated and deleted in the database without having to call a hibernate API.

One of the downsides to this approach is it doesn't allow sharing of address objects between parents. For example, if a User and Company had the same address then 2 address records would be created.

The link table that you've implemented would allow more than one parent to reference the same address record. Whether this is a good idea or not depends on your scenarios. e.g. It might not be desirable for a Company's address to be changed as a side effect of a User moving house!

Mike


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