-->
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 a Circular Reference
PostPosted: Thu Oct 29, 2009 3:38 pm 
Newbie

Joined: Mon Apr 09, 2007 12:25 pm
Posts: 19
Location: Boston, MA
I have the following domain model:
Image

The basic business rules:
    An AccountManager will have 1 or many accounts (at least one is required).
    An AccountManager will have 1 and only 1 primary account.
    An Account will belong to 1 and only 1 AccountManager.

As you can see, I have a bit of a circular reference here when it comes to creating a new AccountManager. This isn't a problem in java:

Code:
public class AccountManager extends IDObject {

   public static final String DEFAULT_PRIMARY_ACCOUNT_NAME = "Primary";
   private final String name;
   private final Set<Account> accounts = new HashSet<Account>();
   private Account primaryAccount;

   public AccountManager(String name) {
      this.name = name;
      this.primaryAccount = this.addAccount(DEFAULT_PRIMARY_ACCOUNT_NAME);
   }

   public Account addAccount(String name) {
      Account account = new Account(name, this);

      this.accounts.add(account);

      return account;
   }

   public Set<Account> getAccounts() {
      return this.accounts;
   }

   public String getName() {
      return this.name;
   }

   public Account getPrimaryAccount() {
      return this.primaryAccount;
   }

   public void setPrimaryAccount(Account primaryAccount) {
      this.primaryAccount = primaryAccount;
   }
}

public class Account extends IDObject {

   private final String name;
   private final AccountManager accountManager;

   public Account(String name, AccountManager accountManager) {
      this.name = name;
      this.accountManager = accountManager;
   }

   public AccountManager getAccountManager() {
      return this.accountManager;
   }

   public String getName() {
      return this.name;
   }
}

public class AccountManagerUseCaseTest extends BaseRepositoryTest {

   @Autowired
   private AccountManagerRepository accountManagerRepository;

   @Test
   public void createAndSaveAccountManagerTest() {
      AccountManager accountManager = new AccountManager("Leo");

      this.accountManagerRepository.saveOrUpdate(accountManager);
      this.accountManagerRepository.flush();

      AccountManager foundAccountManager = this.accountManagerRepository
            .get(accountManager.getId());

      Assert.assertEquals("ID should match: ", accountManager.getId(),
            foundAccountManager.getId());
      Assert.assertEquals("Name should match: ", accountManager.getName(),
            foundAccountManager.getName());
      Assert.assertEquals("One account should have been created: ", 1,
            foundAccountManager.getAccounts().size());
      Assert.assertEquals("The one account should have been made primary: ",
            true,
            foundAccountManager.getPrimaryAccount() == foundAccountManager
                  .getAccounts().toArray()[0]);
      Assert.assertEquals("The primary account's name was incorrect: ",
            AccountManager.DEFAULT_PRIMARY_ACCOUNT_NAME,
            foundAccountManager.getPrimaryAccount().getName());

      Assert
            .assertEquals(
                  "Account did not contain AccountManager reference before save: ",
                  accountManager, accountManager.getPrimaryAccount()
                        .getAccountManager());
      Assert
            .assertEquals(
                  "Account did not contain AccountManager reference after save: ",
                  foundAccountManager, foundAccountManager
                        .getPrimaryAccount().getAccountManager());
   }
}


But things get a little confusing when I start to map these classes. Initially I had the following:

Code:
<hibernate-mapping>
   <class name="com.fidelity.shares.domain.leo.AccountManager"
      table="account_manager_leo">

      <id name="id" column="id" type="long">
         <generator class="sequence">
            <param name="sequence">account_manager_leo_seq</param>
         </generator>
      </id>

      <version name="version" column="version" />

      <property name="name" column="name" length="100" access="field"
         not-null="true" />

      <many-to-one name="primaryAccount" column="primary_account_id"
         cascade="all" not-null="true" foreign-key="account_manager_fk1"/>

      <set name="accounts" inverse="true" cascade="all" access="field" >
         <key column="account_manager_id" foreign-key="account_fk1" not-null="true" />
         <one-to-many class="com.fidelity.shares.domain.leo.Account" />
      </set>
   </class>
</hibernate-mapping>

<hibernate-mapping>
   <class name="com.fidelity.shares.domain.leo.Account" table="account_leo">
      <id name="id" column="id" type="long">
         <generator class="sequence">
            <param name="sequence">account_leo_seq</param>
         </generator>
      </id>

      <version name="version" column="version" />

      <property name="name" column="name" length="100" access="field"
         not-null="true" />
   </class>   
</hibernate-mapping>


Which creates the following DDL:
Code:
create table account_leo (
    id number(19,0) not null,
    version number(10,0) not null,
    name varchar2(100 char) not null,
    account_manager_id number(19,0) not null,
    primary key (id)
);

create table account_manager_leo (
    id number(19,0) not null,
    version number(10,0) not null,
    name varchar2(100 char) not null,
    primary_account_id number(19,0) not null,
    primary key (id)
);

alter table account_leo
    add constraint account_fk1
    foreign key (account_manager_id)
    references account_manager_leo;

alter table account_manager_leo
    add constraint account_manager_fk1
    foreign key (primary_account_id)
    references account_leo;

create sequence account_leo_seq;

create sequence account_manager_leo_seq;


Notice the not-nulls on account.account_manager_id and account_manager.primary_account_id. This mapping file presents some challenges when creating a new AccountManager: I need to insert into account_manager, but I need to satisfy primary_account_id. I can't insert into account first because I need to satisfy account.account_manager_id. Hence the circular reference and hence the following error:

Quote:
java.sql.BatchUpdateException: ORA-01400: cannot insert NULL into ("A122695"."ACCOUNT_LEO"."ACCOUNT_MANAGER_ID")


Now I could make one of these two fields nullable:

Code:
<hibernate-mapping>
   <class name="com.fidelity.shares.domain.leo.AccountManager"
      table="account_manager_leo">

      <id name="id" column="id" type="long">
         <generator class="sequence">
            <param name="sequence">account_manager_leo_seq</param>
         </generator>
      </id>

      <version name="version" column="version" />

      <property name="name" column="name" length="100" access="field"
         not-null="true" />

      <many-to-one name="primaryAccount" column="primary_account_id"
         cascade="all" not-null="false" foreign-key="account_manager_fk1"/>

      <set name="accounts" inverse="true" cascade="all" access="field" >
         <key column="account_manager_id" foreign-key="account_fk1" not-null="true" />
         <one-to-many class="com.fidelity.shares.domain.leo.Account" />
      </set>
   </class>
</hibernate-mapping>


but that doesn't work as Account gets created first:

Quote:
DEBUG [29/10/2009 14:41:15.320] AbstractBatcher[ 401] - insert into A122695.account_leo (version, name, id) values (?, ?, ?)
TRACE [29/10/2009 14:41:15.320] NullableType[ 133] - binding '0' to parameter: 1
TRACE [29/10/2009 14:41:15.320] NullableType[ 133] - binding 'Primary' to parameter: 2
TRACE [29/10/2009 14:41:15.336] NullableType[ 133] - binding '1' to parameter: 3
ERROR [29/10/2009 14:41:15.336] JDBCExceptionReporter[ 78] - ORA-01400: cannot insert NULL into ("A122695"."ACCOUNT_LEO"."ACCOUNT_MANAGER_ID")


I can relax the not-null constraint on account:

Code:
<hibernate-mapping>
   <class name="com.fidelity.shares.domain.leo.Account" table="account_leo">
      <id name="id" column="id" type="long">
         <generator class="sequence">
            <param name="sequence">account_leo_seq</param>
         </generator>
      </id>

      <version name="version" column="version" />

      <property name="name" column="name" length="100" access="field"
         not-null="false" />

   </class>   
</hibernate-mapping>


But now account.account_manager_id is not getting populated:

Quote:
DEBUG [29/10/2009 14:46:48.675] AbstractBatcher[ 401] - select A122695.account_manager_leo_seq.nextval from dual
DEBUG [29/10/2009 14:46:48.738] AbstractBatcher[ 401] - select A122695.account_leo_seq.nextval from dual
DEBUG [29/10/2009 14:46:48.785] AbstractBatcher[ 401] - insert into A122695.account_leo (version, name, id) values (?, ?, ?)
TRACE [29/10/2009 14:46:48.785] NullableType[ 133] - binding '0' to parameter: 1
TRACE [29/10/2009 14:46:48.800] NullableType[ 133] - binding 'Primary' to parameter: 2
TRACE [29/10/2009 14:46:48.800] NullableType[ 133] - binding '1' to parameter: 3
DEBUG [29/10/2009 14:46:48.816] AbstractBatcher[ 401] - insert into A122695.account_manager_leo (version, name, primary_account_id, id) values (?, ?, ?, ?)
TRACE [29/10/2009 14:46:48.816] NullableType[ 133] - binding '0' to parameter: 1
TRACE [29/10/2009 14:46:48.816] NullableType[ 133] - binding 'Leo' to parameter: 2
TRACE [29/10/2009 14:46:48.831] NullableType[ 133] - binding '1' to parameter: 3
TRACE [29/10/2009 14:46:48.831] NullableType[ 133] - binding '1' to parameter: 4


I am also now leaving an opening for someone to potentially break the business rules via the back end.

In addition, I've gotten feedback from the DBAs that they aren't a big fan of the table design as it requires that one defers constraints in order to insert data into these tables (outside of hibernate). That may or may not be justified and I can certainly make an argument if it is not.

My question is: is there a best practice around modeling such a relationship via the Hibernate mapping files? I feel like the object model makes sense (it seems to fit the biz requirements.

Thanks!


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.