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