Hi,
I have been trying to move away from OJB and on to hibernate for our application. Our application is a web application that I do not want to work with eager loading and with detached objects (to simplify migration and because there is little gained from it because our application displays all data up front).
I have started with the unit tests and everything was working fine, until I added a collection to my object. This collection is best represented as a bag/idbag. However, neither works.
I will start with a bag.
If I have a bag, all my test cases run, but I get the following error when I try to retrieve my object (Brand), add a new element to my collection (ExtraEnqField), it comes up with this error:
Code:
java.util.NoSuchElementException
   at java.util.AbstractList$Itr.next(AbstractList.java:427)
   at org.hibernate.collection.AbstractPersistentCollection$IteratorProxy.next(AbstractPersistentCollection.java:555)
   at au.com.tt.ccm.dao.BrandDAOTest.testUpdatingExistingBrandWithNewExtraEnq(BrandDAOTest.java:174)
Here is the test that fails:
Code:
public void testUpdatingExistingBrandWithNewExtraEnq() throws FileNotFoundException, IOException, Exception
    {
   SessionFactory sessionFactory = getSessionFactory(new HelperRoutines().getTestHibernateConfig("test.hibernate.cfg.xml"));
   BrandDAO brandDao = new BrandDAO(sessionFactory);
   
   Brand saveBrand = brandDao.retrieve("test9");
   ExtraEnqField extraEnqField = (ExtraEnqField)saveBrand.getExtraEnqFields().iterator().next();
   brandDao.save(saveBrand);
   
   Brand retrieveBrand = brandDao.retrieve("test9");   
   assertEquals(BrandTestHelper.equals(retrieveBrand, saveBrand), true);
   assertEquals(saveBrand.getExtraEnqFields().size(), retrieveBrand.getExtraEnqFields().size());
    } 
It is using TestNG BTW. The tests use hypersonic, but the target database is Postgres.
Here is a cut down version of my binding:
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="au.com.tt.ccm.beans">
   <class name="Brand" table="brand" schema="public" lazy="false">
      <id name="brandId" column="brand_id">
         <generator class="assigned" />
      </id>
<!-- More fields here -->
      
      <bag name="extraEnqFields" table="extra_enq_field" lazy="false" cascade="all" fetch="join">
         <key column="brand"/>
         <composite-element class="ExtraEnqField" >
            <property name="label" column="label" type="string" />
            <property name="displayType" column="display_type"
               type="string" />
         </composite-element>
      </bag>
   </class>
</hibernate-mapping>
It looks like it is using a proxy, but I thought that using lazy="false" stops it from using a proxy. Why isn't Hibernate just loading the data directly into the Collection?
Here is my DAO, bean and SQL (cut down again)
BEAN Code:
package au.com.tt.ccm.beans;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;
import java.util.Date;
import java.util.Set;
public class Brand implements Serializable
{
    public Brand(String inBrandId)
    {
        mBrandId = inBrandId;
        mExtraEnqFields = new ArrayList<ExtraEnqField>();
    }
    public Brand()
    {
        mExtraEnqFields = new ArrayList<ExtraEnqField>();
    }
    public String getBrandId()
    {
        return (mBrandId);
    }
    public Collection<ExtraEnqField> getExtraEnqFields()
    {
        return (mExtraEnqFields);
    }
    public void setBrandId(String inBrandId)
    {
        mBrandId = inBrandId;
    }
    public void setExtraEnqFields(Collection<ExtraEnqField> inExtraEnqFields)
    {
        mExtraEnqFields = inExtraEnqFields;
    }
    
    public void addExtraEnqFields(ExtraEnqField inExtraEnqField)
    {        
        mExtraEnqFields.add(inExtraEnqField);        
    }
    
    @Override
    public int hashCode()
    {
   final int prime = 31;
   int result = 1;
   result = prime * result + ((mBrandId == null) ? 0 : mBrandId.hashCode());
   return result;
    }
    @Override
    public boolean equals(Object obj)
    {
   if (this == obj) return true;
   if (obj == null) return false;
   if (getClass() != obj.getClass()) return false;
   final Brand other = (Brand) obj;
   if (mBrandId == null)
   {
       if (other.getBrandId() != null) return false;
   }
   else if (!mBrandId.equals(other.getBrandId())) return false;
   return true;
    }
    String mBrandId;
    Collection<ExtraEnqField> mExtraEnqFields;
}
Code:
package au.com.tt.ccm.beans;
import java.io.Serializable;
import org.apache.commons.lang.builder.ToStringBuilder;
import au.com.tt.utils.CalypsoToStringStyles;;
public class ExtraEnqField implements Serializable
{
    public ExtraEnqField()
    {
    }    
    
    public String getLabel() { return(mLabel); }
    
    public String getDisplayType() { return(mDisplayType); }
      
    public void setLabel(String inLabel) { mLabel = inLabel; }
   
    public void setDisplayType(String inDisplayType) { mDisplayType = inDisplayType; }
    
    public int getId()
    {
        return mId;
    }
    public void setId(int inId)
    {
        mId = inId;
    }
    @Override
    public int hashCode()
    {
   final int prime = 31;
   int result = 1;
   result = prime * result + mId;
   return result;
    }
    @Override
    public boolean equals(Object obj)
    {
   if (this == obj) return true;
   if (obj == null) return false;
   if (getClass() != obj.getClass()) return false;
   final ExtraEnqField other = (ExtraEnqField) obj;
   if (mId != other.mId) return false;
   return true;
    }
    private String   mLabel;
    private String   mDisplayType;   
    private int      mId;   
}
DAOCode:
package au.com.tt.ccm.dao;
import java.io.Serializable;
import java.util.List;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.classic.Session;
import org.hibernate.criterion.ExistsSubqueryExpression;
import au.com.tt.ccm.beans.*;
import au.com.tt.ccm.exceptions.DAOException;
public class BrandDAO implements Serializable
{
    public BrandDAO(SessionFactory inSessionFactory) throws DAOException
    {
   if (inSessionFactory == null)
       throw new DAOException("SessionFactory cannot be null");   
   mSessionFactory = inSessionFactory;
   
    }
    public SessionFactory getSessionFactory()
    {
        return mSessionFactory;
    }
    public boolean exists(String inBrandId) 
    {
   Session existsSession = null;
   try
   {
       existsSession = mSessionFactory.openSession();
       
       String sql = "SELECT true FROM brand WHERE brand.brand_id = :brand_id LIMIT 1";
       
       Query existsQuery = existsSession.createSQLQuery(sql).setParameter("brand_id", inBrandId);
       boolean ret = existsQuery.list().iterator().hasNext();
       return(ret); 
   }
   finally
   {
       if (existsSession != null)
      existsSession.close();
   }
    }
    public Brand retrieve(String inBrandId) 
    {
   Session retrieveSession = null;
   Transaction tx = null;   
   Brand result = null;
   
   try
   {
       retrieveSession = mSessionFactory.openSession();
       tx = retrieveSession.beginTransaction();
       result = (Brand) retrieveSession.get(Brand.class, inBrandId, LockMode.NONE);
       tx.commit();
   }
   catch (RuntimeException e)
   {
       onRuntimeException(tx);
   }
   finally
   {
       retrieveSession.close();
   }
   
        return (result);
    }
    
    public void save(Brand inBrand) 
    {
   Transaction tx = null;
   Session saveSession = null;
   try
   {
       saveSession = mSessionFactory.openSession();
       tx = saveSession.beginTransaction();
       saveSession.saveOrUpdate(inBrand);
       tx.commit();
   }
   catch (RuntimeException e)
   {
       onRuntimeException(tx);
       throw e;
   }
   finally
   {
       saveSession.close();
   }
    }    
    
    private void onRuntimeException(Transaction tx)
    {
   tx.rollback();    
    }
    
    SessionFactory mSessionFactory;
}
SQLCode:
CREATE TABLE brand (
    brand_id character varying(8) NOT NULL,
);
CREATE TABLE extra_enq_field (
    id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL,
    brand character varying(8) NOT NULL,
    label character varying(30) NOT NULL,
    display_type character varying(15) NOT NULL
);
Now, I would love to use idbag, but that even more problems. For example, I setup the idbag like so:
Code:
<idbag name="extraEnqFields" table="extra_enq_field" cascade="all" lazy="false" fetch="join">
    <collection-id type="integer" column="id" >
      <generator class="identity"></generator>
    </collection-id>
    <key column="brand"/>
   <composite-element class="ExtraEnqField" >
       <property name="label" column="label" type="string" />     
          <property name="displayType" column="display_type" type="string" />
   </composite-element>
</idbag>
And I got errors like the following:
Code:
FAILED: testUpdatingExistingBrandWithNewExtraEnq
java.util.NoSuchElementException
   at java.util.AbstractList$Itr.next(AbstractList.java:427)
   at org.hibernate.collection.AbstractPersistentCollection$IteratorProxy.next(AbstractPersistentCollection.java:555)
   at au.com.tt.ccm.dao.BrandDAOTest.testUpdatingExistingBrandWithNewExtraEnq(BrandDAOTest.java:174)
... Removed 22 stack frames
FAILED: testSaveExistingBrandWithNewExtraEnq
java.lang.ClassCastException: org.hibernate.id.IdentifierGeneratorFactory$2
   at org.hibernate.type.IntegerType.set(IntegerType.java:41)
   at org.hibernate.type.NullableType.nullSafeSet(NullableType.java:136)
   at org.hibernate.type.NullableType.nullSafeSet(NullableType.java:116)
   at org.hibernate.persister.collection.AbstractCollectionPersister.writeIdentifier(AbstractCollectionPersister.java:807)
   at org.hibernate.persister.collection.AbstractCollectionPersister.insertRows(AbstractCollectionPersister.java:1359)
   at org.hibernate.action.CollectionUpdateAction.execute(CollectionUpdateAction.java:66)
   at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:279)
   at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:263)
   at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:170)
   at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298)
   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 au.com.tt.ccm.dao.BrandDAO.save(BrandDAO.java:106)
   at au.com.tt.ccm.dao.BrandDAOTest.testSaveExistingBrandWithNewExtraEnq(BrandDAOTest.java:159)
... Removed 22 stack frames