First, let me say i've been using Hibernate for about 6 months now and really appreciate not having to write my own DB-access code. I'm writing because i came across something that surprised me. I have a work-around, but am curious why my first approach didn't work.
Summary:
Let there be 3 classes: A, B, C. Class A has a List of B. B has a reference to C. The B-C relationship is one-to-one and one-way (B knows C, but not vice versa). Class A is modeled in Hibernate as an entity object, as is C. (In the real code, the object represented by C is very complex and it would not be appropriate to model it as a value object.) My original approach had B as a value object, but this led to a T.O.E. when saving A. By making B an entity object, i no longer received the T.O.E. and A,B,C were persisted properly.
What follows is a phony example that parallels my real code enough to demonstrate the behavior.
Here is the mapping that works:
Code:
<hibernate-mapping package="toe">
<class name="Foot">
<id name="id" type="long" access="field">
<generator class="native"/>
</id>
<property name="isLeft" access="field"/>
<property name="stinkLevel" access="field"/>
<list name="toes" cascade="all-delete-orphan" access="field">
<key column="FOOT_ID"/>
<index column="INDEX"/>
<one-to-many class="Toe"/>
</list>
</class>
<class name="Toe">
<id name="id" type="long" access="field">
<generator class="native"/>
</id>
<property name="name" access="field"/>
<property name="millimeters" access="field"/>
<many-to-one name="nail"
class="Nail"
column="NAIL_ID"
cascade="all"
unique="true"
access="field"/>
</class>
<class name="Nail">
<id name="id" type="long" access="field">
<generator class="native"/>
</id>
<property name="isPainted" access="field"/>
<property name="color" access="field"/>
</class>
</hibernate-mapping>
Here is the mapping that causes a TransientObjectException:Code:
<hibernate-mapping package="toe">
<class name="Foot">
<id name="id" type="long" access="field">
<generator class="native"/>
</id>
<property name="isLeft" access="field"/>
<property name="stinkLevel" access="field"/>
<list name="toes" access="field">
<key column="FOOT_ID"/>
<index column="INDEX"/>
<composite-element class="Toe">
<property name="name" access="field"/>
<property name="millimeters" access="field"/>
<many-to-one name="nail"
class="Nail"
column="NAIL_ID"
cascade="all"
unique="true"
access="field"/>
</composite-element>
</list>
</class>
<class name="Nail">
<id name="id" type="long" access="field">
<generator class="native"/>
</id>
<property name="isPainted" access="field"/>
<property name="color" access="field"/>
</class>
</hibernate-mapping>
This is the java code that corresponds to the above xml:Code:
package toe;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
/**
* This is like SwitchingScan in our real application.
* <p>
* The purpose of this class is to demonstrate that mapping class Toe
* as a value object in Hibernate gives a TransientObjectException,
* while mapping it as a entity object does not. In both situations
* class Nail is mapped as an entity object and there is a one-way
* (from Toe to Nail), one-to-one, relationship between Toe and Nail.</p>
*/
public class Foot
{
long id = -1L;
boolean isLeft;
int stinkLevel;
List<Toe> toes = new ArrayList<Toe>();
public static void main(String[] args)
{
Foot leftFoot = new Foot();
leftFoot.isLeft = true;
leftFoot.stinkLevel = 7;
String[] names = {"Porky", "Petunia", "Ms. Piggy", "Zaentz", "Little Piggy"};
String[] colors = {"Red", "White", "Blue", "Purple", "Black"};
double size = 40.0;
for (int t=0; t < 5; t++)
{
Nail nail = new Nail();
nail.isPainted = true;
nail.color = colors[t];
Toe toe = new Toe();
toe.name = names[t];
toe.millimeters = size;
toe.nail = nail;
leftFoot.toes.add(toe);
size -= 5.0;
}
Dao dao = new Dao();
dao.save(leftFoot);
dao.close();
}
}
/** This is like SwitchSetting in our real application. */
class Toe
{
//id is here only because crafting Toe as a value object didn't work as hoped
long id = -1L;
String name;
double millimeters;
Nail nail;
}
/** This is like Source in our real application. */
class Nail
{
long id = -1L;
boolean isPainted;
String color;
}
class Dao
{
private SessionFactory sessionFactory;
private SessionFactory getSessionFactory()
{
if (sessionFactory == null)
{
try
{
Configuration conf = new Configuration();
conf.configure("Feet.cfg.xml");
sessionFactory = conf.buildSessionFactory();
}
catch (HibernateException ex)
{
System.out.println("Problem getting SessionFactory.");
ex.printStackTrace();
System.exit(-1);
}
}
return sessionFactory;
}
void save(Foot foot)
{
Session session = getSessionFactory().openSession();
Transaction tran = null;
try
{
tran = session.beginTransaction();
session.saveOrUpdate(foot);
tran.commit();
}
catch (Exception ex)
{
if (tran != null)
tran.rollback();
System.out.println("Problem saving Foot.");
ex.printStackTrace();
System.exit(-1);
}
finally
{
session.close();
}
}
void close()
{
getSessionFactory().close();
}
}