-->
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.  [ 2 posts ] 
Author Message
 Post subject: list index column updated with null when moving associations
PostPosted: Mon Dec 29, 2008 11:04 am 
Newbie

Joined: Thu Oct 05, 2006 3:26 am
Posts: 8
Summary:
List index column is updated with null when moving from one list to another list of different class, thus following query results in org.hibernate.HibernateException: null index column for collection.

Problem Description:
I have Company and Department. A company has many departments. A departments has many child departments. So, a department has a many-to-one association to either a company or a department.

Let's say we have InternetSales department under SalesDepartment, which is a top department under TestCompany:
TestCompany
- SalesDepartment
-- InternetSales

Now, InternetSales becames a top department of TestCompany:
TestCompany
- SalesDepartment
- InternetSales

In Hibernate, this is achieved by removing InternetSales from SalesDepartment's list collection, and adding it to TestCompany's list. (Of course, many-to-one association from InternetSales should also be modified.) Hibernate then takes care of updating database, but the order of updates is incorrect. Hibernate first updates index column with index of SalesDepartment in TestCompany's list, then updates it with null (index of SalesDepartment in SalesDepartment is cleared.) Following is the complete test case. Is this a bug/limitation, or can I workaround this by changing mapping/codes?

Note: This issue doesn't occur when moving from one list to another of the same class.

Hibernate version:
This issue exists for both 3.2.6GA and 3.3.1GA.

Database Definition:
Code:
CREATE TABLE companies (
   company_id NUMBER NOT NULL PRIMARY KEY,
   company_name VARCHAR2(100) NOT NULL,
   data_version NUMBER NOT NULL
);
CREATE TABLE departments (
   department_id NUMBER NOT NULL PRIMARY KEY,
   company_id NUMBER REFERENCES companies (company_id),
   parent_department_id NUMBER REFERENCES departments (department_id),
   index_number NUMBER,
   department_name VARCHAR2(100) NOT NULL,
   data_version NUMBER NOT NULL
);
CREATE SEQUENCE company_id;
CREATE SEQUENCE department_id;


Class Definitions:
Code:
package test.bo;

import java.util.List;

public class Company {

   // attributes
   private Long companyId;
   private String companyName;
   private Long dataVersion;
   // associations
   private List<Department> departments;

   public Company() {
   }

   public List<Department> getDepartments() {
      return departments;
   }

   public void setDepartments(List<Department> departments) {
      this.departments = departments;
   }

   public Long getCompanyId() {
      return companyId;
   }

   public void setCompanyId(Long companyId) {
      this.companyId = companyId;
   }

   public String getCompanyName() {
      return companyName;
   }

   public void setCompanyName(String companyName) {
      this.companyName = companyName;
   }

   public Long getDataVersion() {
      return dataVersion;
   }

   public void setDataVersion(Long dataVersion) {
      this.dataVersion = dataVersion;
   }

}

Code:
package test.bo;

import java.util.List;

public class Department {

   // attributes
   private Long departmentId;
   private String departmentName;
   private Long dataVersion;
   // associations
   private Company company;
   private Department parentDepartment;
   private List<Department> childDepartments;

   public Department() {
   }

   public Long getDepartmentId() {
      return departmentId;
   }

   public void setDepartmentId(Long departmentId) {
      this.departmentId = departmentId;
   }

   public String getDepartmentName() {
      return departmentName;
   }

   public void setDepartmentName(String departmentName) {
      this.departmentName = departmentName;
   }

   public Long getDataVersion() {
      return dataVersion;
   }

   public void setDataVersion(Long dataVersion) {
      this.dataVersion = dataVersion;
   }

   public Company getCompany() {
      return company;
   }

   public void setCompany(Company company) {
      this.company = company;
   }

   public Department getParentDepartment() {
      return parentDepartment;
   }

   public void setParentDepartment(Department parentDepartment) {
      this.parentDepartment = parentDepartment;
   }

   public List<Department> getChildDepartments() {
      return childDepartments;
   }

   public void setChildDepartments(List<Department> childDepartments) {
      this.childDepartments = childDepartments;
   }

}


Mapping documents:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="test.bo">
   <class name="Company" table="companies" optimistic-lock="version">
      <id name="companyId" column="company_id" type="long" unsaved-value="null">
         <generator class="sequence">
            <param name="sequence">company_id</param>
         </generator>
      </id>
      <version name="dataVersion" column="data_version" type="long" />
      
      <property name="companyName" column="company_name" not-null="true" />
      
      <list name="departments" cascade="all">
         <key column="company_id" />
         <list-index column="index_number" base="1" />
         <one-to-many class="Department" />
      </list>
   </class>
</hibernate-mapping>

Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="test.bo">
   <class name="Department" table="departments" optimistic-lock="version">
      <id name="departmentId" column="department_id" type="long" unsaved-value="null">
         <generator class="sequence">
            <param name="sequence">department_id</param>
         </generator>
      </id>
      <version name="dataVersion" column="data_version" type="long" />
      
      <property name="departmentName" column="department_name" not-null="true" />
      
      <many-to-one name="company" column="company_id" class="Company" update="false" insert="false" />
      <many-to-one name="parentDepartment" column="parent_department_id" class="Department" update="false" insert="false" />
      
      <list name="childDepartments" cascade="all">
         <key column="parent_department_id" />
         <list-index column="index_number" base="1" />
         <one-to-many class="Department" />
      </list>
   </class>
</hibernate-mapping>


Test Case:
Code:
package test;

import java.sql.Connection;
import java.util.ArrayList;

import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.criterion.Restrictions;

import test.bo.Company;
import test.bo.Department;

public class Test {
   
   private static Logger logger = Logger.getLogger(Test.class);

   public static void main(String[] args) {
      Configuration configuration = (new Configuration()).configure();
      SessionFactory sessionFactory = configuration.buildSessionFactory();
      try {
         clean(sessionFactory);
         create(sessionFactory);
         update(sessionFactory);
         select(sessionFactory);
      } catch (RuntimeException e) {
         logger.error("Exception occurred", e);
      } finally {
         sessionFactory.close();
      }
   }
   
   @SuppressWarnings("deprecation")
   private static void clean(SessionFactory sessionFactory) {
      logger.debug("CLEAN:");
      Session session = sessionFactory.openSession();
      try {
         Connection connection = session.connection();
         connection.createStatement().executeUpdate("DELETE FROM departments");
         connection.createStatement().executeUpdate("DELETE FROM companies");
         connection.commit();
      } catch (Exception e) {
         throw new RuntimeException(e);
      } finally {
         session.close();
      }
   }
   
   private static void create(SessionFactory sessionFactory) {
      logger.debug("CREATE:");
      Session session = sessionFactory.openSession();
      Transaction transaction = session.beginTransaction();
      try {
         Company testCompany = new Company();
         testCompany.setCompanyName("TestCompany");
         testCompany.setDepartments(new ArrayList<Department>());
         
         Department salesDepartment = new Department();
         salesDepartment.setDepartmentName("SalesDepartment");
         salesDepartment.setChildDepartments(new ArrayList<Department>());
         // association
         salesDepartment.setCompany(testCompany);
         testCompany.getDepartments().add(salesDepartment);
         
         Department internetSales = new Department();
         internetSales.setDepartmentName("InternetSales");
         internetSales.setChildDepartments(new ArrayList<Department>());
         // association
         internetSales.setParentDepartment(salesDepartment);
         salesDepartment.getChildDepartments().add(internetSales);
         
         session.save(testCompany);
         transaction.commit();
         printCompany(testCompany);
      } catch (RuntimeException e) {
         transaction.rollback();
         throw e;
      } finally {
         session.close();
      }
   }
   
   private static void update(SessionFactory sessionFactory) {
      logger.debug("UPDATE:");
      Session session = sessionFactory.openSession();
      Transaction transaction = session.beginTransaction();
      try {
         Criteria testCompanyCriteria = session.createCriteria(Company.class);
         testCompanyCriteria.add(Restrictions.eq("companyName", "TestCompany"));
         Company testCompany = (Company)testCompanyCriteria.uniqueResult();

         Criteria internetSalesCriteria = session.createCriteria(Department.class);
         internetSalesCriteria.add(Restrictions.eq("departmentName", "InternetSales"));
         Department internetSales = (Department)internetSalesCriteria.uniqueResult();
         
         // remove association
         internetSales.getParentDepartment().getChildDepartments().remove(internetSales);
         // new association
         internetSales.setCompany(testCompany);
         testCompany.getDepartments().add(internetSales);
         
         transaction.commit();
         printCompany(testCompany);
      } catch (RuntimeException e) {
         transaction.rollback();
         throw e;
      } finally {
         session.close();
      }
   }
   
   private static void select(SessionFactory sessionFactory) {
      logger.debug("SELECT:");
      Session session = sessionFactory.openSession();
      try {
         Criteria criteria = session.createCriteria(Company.class);
         criteria.add(Restrictions.eq("companyName", "TestCompany"));
         Company testCompany = (Company)criteria.uniqueResult();
         
         printCompany(testCompany);
      } finally {
         session.close();
      }
   }

   private static void printCompany(Company company) {
      logger.debug("<DATA>");
      logger.debug(String.format("[Company] id:%d, name:%s", company.getCompanyId(), company.getCompanyName()));
      for (Department department : company.getDepartments()) {
         printDepartment(department, 1);
      }
   }
   
   private static void printDepartment(Department department, int indentSize) {
      StringBuilder indent = new StringBuilder();
      for (int i = 0; i < indentSize; i++) indent.append(" ");
      logger.debug(String.format("%s[Department] id:%d, name:%s", indent, department.getDepartmentId(), department.getDepartmentName()));
      for (Department childDepartment : department.getChildDepartments()) {
         printDepartment(childDepartment, indentSize + 1);
      }
   }
   
}


Log:
Code:
23:25:10,046 DEBUG Test:56 - CLEAN:
23:25:10,203 DEBUG Test:71 - CREATE:
23:25:10,218 DEBUG SQL:111 - select company_id.nextval from dual
23:25:10,343 DEBUG SQL:111 - select department_id.nextval from dual
23:25:10,343 DEBUG SQL:111 - select department_id.nextval from dual
23:25:10,375 DEBUG SQL:111 - insert into companies (data_version, company_name, company_id) values (?, ?, ?)
23:25:10,375 DEBUG SQL:111 - insert into departments (data_version, department_name, department_id) values (?, ?, ?)
23:25:10,375 DEBUG SQL:111 - insert into departments (data_version, department_name, department_id) values (?, ?, ?)
23:25:10,375 DEBUG SQL:111 - update departments set company_id=?, index_number=? where department_id=?
23:25:10,375 DEBUG SQL:111 - update departments set parent_department_id=?, index_number=? where department_id=?
23:25:10,390 DEBUG Test:148 - <DATA>
23:25:10,390 DEBUG Test:149 - [Company] id:24, name:TestCompany
23:25:10,390 DEBUG Test:158 -  [Department] id:47, name:SalesDepartment
23:25:10,390 DEBUG Test:158 -   [Department] id:48, name:InternetSales
23:25:10,390 DEBUG Test:105 - UPDATE:
23:25:10,406 DEBUG SQL:111 - select this_.company_id as company1_0_0_, this_.data_version as data2_0_0_, this_.company_name as company3_0_0_ from companies this_ where this_.company_name=?
23:25:10,421 DEBUG SQL:111 - select this_.department_id as department1_1_0_, this_.data_version as data2_1_0_, this_.department_name as department3_1_0_, this_.company_id as company4_1_0_, this_.parent_department_id as parent5_1_0_ from departments this_ where this_.department_name=?
23:25:10,437 DEBUG SQL:111 - select department0_.department_id as department1_1_0_, department0_.data_version as data2_1_0_, department0_.department_name as department3_1_0_, department0_.company_id as company4_1_0_, department0_.parent_department_id as parent5_1_0_ from departments department0_ where department0_.department_id=?
23:25:10,437 DEBUG SQL:111 - select childdepar0_.parent_department_id as parent5_1_, childdepar0_.department_id as department1_1_, childdepar0_.index_number as index6_1_, childdepar0_.department_id as department1_1_0_, childdepar0_.data_version as data2_1_0_, childdepar0_.department_name as department3_1_0_, childdepar0_.company_id as company4_1_0_, childdepar0_.parent_department_id as parent5_1_0_ from departments childdepar0_ where childdepar0_.parent_department_id=?
23:25:10,453 DEBUG SQL:111 - select department0_.company_id as company4_1_, department0_.department_id as department1_1_, department0_.index_number as index6_1_, department0_.department_id as department1_1_0_, department0_.data_version as data2_1_0_, department0_.department_name as department3_1_0_, department0_.company_id as company4_1_0_, department0_.parent_department_id as parent5_1_0_ from departments department0_ where department0_.company_id=?
23:25:10,546 DEBUG SQL:111 - update companies set data_version=?, company_name=? where company_id=? and data_version=?
23:25:10,546 DEBUG SQL:111 - update departments set data_version=?, department_name=? where department_id=? and data_version=?
23:25:10,546 DEBUG SQL:111 - update departments set company_id=?, index_number=? where department_id=?
23:25:10,593 DEBUG SQL:111 - update departments set parent_department_id=null, index_number=null where parent_department_id=?
23:25:10,593 DEBUG Test:148 - <DATA>
23:25:10,593 DEBUG Test:149 - [Company] id:24, name:TestCompany
23:25:10,609 DEBUG Test:158 -  [Department] id:47, name:SalesDepartment
23:25:10,609 DEBUG Test:158 -  [Department] id:48, name:InternetSales
23:25:10,609 DEBUG SQL:111 - select childdepar0_.parent_department_id as parent5_1_, childdepar0_.department_id as department1_1_, childdepar0_.index_number as index6_1_, childdepar0_.department_id as department1_1_0_, childdepar0_.data_version as data2_1_0_, childdepar0_.department_name as department3_1_0_, childdepar0_.company_id as company4_1_0_, childdepar0_.parent_department_id as parent5_1_0_ from departments childdepar0_ where childdepar0_.parent_department_id=?
23:25:10,609 DEBUG Test:134 - SELECT:
23:25:10,609 DEBUG SQL:111 - select this_.company_id as company1_0_0_, this_.data_version as data2_0_0_, this_.company_name as company3_0_0_ from companies this_ where this_.company_name=?
23:25:10,609 DEBUG Test:148 - <DATA>
23:25:10,609 DEBUG Test:149 - [Company] id:24, name:TestCompany
23:25:10,609 DEBUG SQL:111 - select department0_.company_id as company4_1_, department0_.department_id as department1_1_, department0_.index_number as index6_1_, department0_.department_id as department1_1_0_, department0_.data_version as data2_1_0_, department0_.department_name as department3_1_0_, department0_.company_id as company4_1_0_, department0_.parent_department_id as parent5_1_0_ from departments department0_ where department0_.company_id=?
23:25:10,625  WARN LoadContexts:132 - fail-safe cleanup (collections) : org.hibernate.engine.loading.CollectionLoadContext@d3db51<rs=oracle.jdbc.driver.OracleResultSetImpl@11381e7>
23:25:10,625  WARN CollectionLoadContext:348 - On CollectionLoadContext#cleanup, localLoadingCollectionKeys contained [1] entries
23:25:10,625 ERROR Test:48 - Exception occurred
org.hibernate.HibernateException: null index column for collection: test.bo.Company.departments
   at org.hibernate.persister.collection.AbstractCollectionPersister.readIndex(AbstractCollectionPersister.java:732)
   at org.hibernate.collection.PersistentList.readFrom(PersistentList.java:402)
   at org.hibernate.loader.Loader.readCollectionElement(Loader.java:1031)
   at org.hibernate.loader.Loader.readCollectionElements(Loader.java:669)
   at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:614)
   at org.hibernate.loader.Loader.doQuery(Loader.java:724)
   at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:259)
   at org.hibernate.loader.Loader.loadCollection(Loader.java:2015)
   at org.hibernate.loader.collection.CollectionLoader.initialize(CollectionLoader.java:59)
   at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:587)
   at org.hibernate.event.def.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:83)
   at org.hibernate.impl.SessionImpl.initializeCollection(SessionImpl.java:1743)
   at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:366)
   at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:108)
   at org.hibernate.collection.PersistentList.iterator(PersistentList.java:138)
   at test.Test.printCompany(Test.java:150)
   at test.Test.select(Test.java:141)
   at test.Test.main(Test.java:46)


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


Top
 Profile  
 
 Post subject: Re: list index column updated with null when moving associat
PostPosted: Sat Jan 03, 2009 12:42 am 
Newbie

Joined: Thu Oct 05, 2006 3:26 am
Posts: 8
I figured that sharing a index column between two different list collections is a cause of the problem. A department can have a many-to-one association to either Company or Department, but the application must take care of this constraint. Hibernate sees that a department can have many-to-one associations both to Company and Department, list index must be mapped to two separate columns.


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