2011年3月9日 星期三

Hibernate 教學 - org.hibernate.TransientObjectException: object references an unsaved transient instance

以下介紹一下在寫 Hibernate 時常見的例外情況,以下 PO 出完整的訊息

Exception in thread "main" org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: org.pojo.Group
at org.hibernate.engine.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:219)
at org.hibernate.type.EntityType.getIdentifier(EntityType.java:397)
at org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:242)
at org.hibernate.type.TypeFactory.findDirty(TypeFactory.java:597)
at org.hibernate.persister.entity.AbstractEntityPersister.findDirty(AbstractEntityPersister.java:3123)
at org.hibernate.event.def.DefaultFlushEntityEventListener.dirtyCheck(DefaultFlushEntityEventListener.java:479)
at org.hibernate.event.def.DefaultFlushEntityEventListener.isUpdateNecessary(DefaultFlushEntityEventListener.java:204)
at org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:127)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:196)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:76)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:26)
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 org.control.AccountTest.main(AccountTest.java:37)


說到這個例外的問題,看第一行就大概明瞭了

object references an unsaved transient instance

看到這個大致上的可能情況就是 "處於持久狀態的物件不能引用(reference)暫態物件(transition)"

他的理由很簡單   一個受 session 控管的物件根本無法參照到一個處於暫態的物件

因為處於暫態的物件不可能會在資料庫後方有對應的資料存在,故無法參照。

以下以一個多對一的例子來看一下,我僅顯示兩個物件 User(多) 和 Group(一) 的映射檔

  
      
          
      

      
  



  
      
          
      

      
      
  


以下以 JUint 進行這個錯誤的測試

package org.test;
import org.pojo.*;
import java.util.Date;
import org.control.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.model.Account;
import junit.framework.TestCase;
public class test extends TestCase   //繼承 JUnit 的 TestCase
{
    public void testManyToOne()
    {
        SessionFactory factory = HibernateUtil.getSessionFactory();
        Session session = factory.openSession();
        Transaction tx = session.beginTransaction();
 
        //底下建立兩個 Group  
        Group g1 = new Group();
        g1.setName("group1");
        Group g2 = new Group();
        g2.setName("group2");
  
        User u;
        for(int i=0;i<5;i++){
            u = new User();
            u.setName("Username" + i);
            if(i < 3){
                u.setGroup(g1);
            }
            else{
                u.setGroup(g2);
            }
            session.save(u);
        }
        

        //以上程式過程中都未執行session.save(g1)&session.save(g2)
        //所以這兩個物件都處於Transition狀態 
        tx.commit();
        session.close();
    }
}

以上的範例如果執行時應該會產生五個 SQL 的 INSERT 語句
Hibernate: insert into t_user (name, group_id) values (?, ?)
Hibernate: insert into t_user (name, group_id) values (?, ?)
Hibernate: insert into t_user (name, group_id) values (?, ?)
Hibernate: insert into t_user (name, group_id) values (?, ?)
Hibernate: insert into t_user (name, group_id) values (?, ?)
接著在拋出 object references an unsaved transient instance 的例外

解決問題很簡單阿 就把 g1和g2物件 save() 即可,所以在以上程式的迴圈加上

session.save(g1) 和 session.save(g2) 就好了

這時候你可能有個問題,如果我把這個 save()動作移動到迴圈後再執行呢

他最後結果還是會成功 只是會多跑幾個SQL語句 如下所示

Hibernate: insert into t_user (name, group_id) values (?, ?)
Hibernate: insert into t_user (name, group_id) values (?, ?)
Hibernate: insert into t_user (name, group_id) values (?, ?)
Hibernate: insert into t_user (name, group_id) values (?, ?)
Hibernate: insert into t_user (name, group_id) values (?, ?)
Hibernate: insert into t_group (name) values (?)
Hibernate: insert into t_group (name) values (?)
Hibernate: update t_user set name=?, group_id=? where id=?
Hibernate: update t_user set name=?, group_id=? where id=?
Hibernate: update t_user set name=?, group_id=? where id=?
Hibernate: update t_user set name=?, group_id=? where id=?
Hibernate: update t_user set name=?, group_id=? where id=?

以上顯示出了 Hibernate 會先執行t_user 的 INSERT 但是外鍵欄位則尚未賦值

待 t_Group INSERT 之後再去對 t_user 執行 UPDATE的動作 以更新外來建

沒有留言:

張貼留言