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