@OneToOne + Table-per-Concrete-Class = exception?

786 views Asked by At

I'm new to Hibernate, and I can't get @OneToOne to function in our code.
After much reading, I've constructed an isolates example, and thought to ask the help of the community.

Suppose 3 classes: 1 abstract (Class_A) and 2 inheriting from it (Class_B / Class_C). Class_C has a unidirectional pointer to Class_B.
(I've prepared a diagram but the site wont let me post it :-/).

Notes:

  1. Pure Java + Hibernate 3.6.0 Final + Oracle 11g.
  2. Inheritance strategy = Table per Concrete Class.
  3. Developed with hibernate.hbm2ddl.auto=update.
  4. In our code Class_B needs its own table, thus no @Embeddable.
  5. In our code Class_C is also abstract, not as presented here in the simplified example.

Code

Class_A

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Class_A {
    @Id
    public long myId = 0;
}

Class_B

@Entity
@Table(name = "Class_B")
public class Class_B extends Class_A {
    private String myString = "Hellos - I'm Class_B!";
}

Class_C

@Entity
@Table(name = "Class_C")
public class Class_C extends Class_A {
    private String myString = "Hellos - I'm Class_C!";

    @OneToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
    @NotNull
    private Class_B classB;

    public void setClassB(Class_B classB) {
        this.classB = classB;
    }
}

Hibernate Code

StatelessSession statelessSession = sessionFactory.openStatelessSession();
Class_C classC = new Class_C();
classC.myId = 92;
Class_B classB = new Class_B();
classB.myId = 8000;
classC.setClassB(classB);

statelessSession.beginTransaction();
statelessSession.insert(classC);
statelessSession.getTransaction().commit();
statelessSession.close();

Problems

  1. At insert(classC) Hibernate only issues an SQL to insert Class_C. There is no SQL to insert Class_B. I see Class_C's details in Oracle, but Class_B's table is empty.
    Here's the SQL:

    Hibernate: insert into Class_C (classB_myId, myString, myId) values (?, ?, ?)

  2. At getTransaction().commit() it explodes with

this:

java.sql.BatchUpdateException: ORA-02291: integrity constraint (NDP.FK9619CF1CAD47EF0F) violated - parent key not found
at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:17660)
at oracle.jdbc.driver.OracleStatementWrapper.executeBatch(OracleStatementWrapper.java:771)
at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeBatch(NewProxyPreparedStatement.java:1723)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)
at org.hibernate.impl.StatelessSessionImpl.managedFlush(StatelessSessionImpl.java:333)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133)
...

Questions Please

  1. Why doesn't this work... what am I doing wrong?
  2. In our legacy code, the application allocates unique id numbers and have no intention of using generated Ids. Thus @GenerateValue for our @Id isn't considered. Is this a reason why this fails?
  3. Whats the difference between @OneToOne(cascade = CascadeType.ALL) vs. @OneToOne + @Cascade({CascadeType.ALL})?

MUCH thanks!

  • Ten_of_a_Kind
2

There are 2 answers

4
axtavt On BEST ANSWER

I guess the cause is

StatelessSession statelessSession = sessionFactory.openStatelessSession(); 

Try to use a normal Session instead:

Session session = sessionFactory.openSession(); 

StatelessSession is a special purpose tool that should be used only in special circumstances. For regular operations you should always use Session. From Hibenrate docs:

Alternatively, Hibernate provides a command-oriented API that can be used for streaming data to and from the database in the form of detached objects. A StatelessSession has no persistence context associated with it and does not provide many of the higher-level life cycle semantics. In particular, a stateless session does not implement a first-level cache nor interact with any second-level or query cache. It does not implement transactional write-behind or automatic dirty checking. Operations performed using a stateless session never cascade to associated instances.

0
Wladimir On

Please check Hibernate docs. As stated there:

Operations performed using a stateless session never cascade to associated instances. http://docs.jboss.org/hibernate/core/3.3/reference/en-US/html/batch.html#batch-statelesssession

With org.hibernate.StatelessSession, you have deal with object inter-dependencies when inserting objects. That's not the case when you are using a org.hibernate.Session.

In this case, you must persist classB object before classC object to make it work. Persisting order do counts. If you change the persistence order you will have org.hibernate.exception.ConstraintViolationException. Choose carefully when to use stateless sessions, as it has no persistence context.

statelessSession.beginTransaction();

statelessSession.insert(classB); // <- Persisting classB

statelessSession.insert(classC);
statelessSession.getTransaction().commit();
statelessSession.close();

Tested with Hibernate 3.6.0 Final + MySQL 5.0.51a-24