1. 程式人生 > >hibernate異常org.hibernate.exception.ConstraintViolationExceptio

hibernate異常org.hibernate.exception.ConstraintViolationExceptio

HTTP Status 500 - Request processing failed; nested exception is
org.springframework.dao.DataIntegrityViolationException: could not
execute statement; SQL [n/a]; constraint [null]; nested exception is
org.hibernate.exception.ConstraintViolationException: could not
execute statement

在使用hibernate執行save操作時出現此異常,最後通過斷點除錯,發現這個save之前還有其他的save,並且使用了其他save的返回的id,來作為外來鍵參照。
我們一般都是將service的一個方法放到一個事務中(查詢操作當然不需要事務了)
虛擬碼如下:

@Transactional//生命式事務,基於aop實現
viod saveSomething(){

Object a=new Object();
Object b=new Object();
Session hiberSession=getSession();
hiberSession.save(a);
b.setAId(a.getId);//將a物件的主鍵作為外來鍵參照,然而此時還沒有提交事務,a物件中還沒有id返回,也就是b物件引用了一個不存在的外來鍵作為外來鍵值
hiberSession.save(b);


}

正確操作如下(解決方法):

@Transactional//rolling back on runtime exceptions
viod saveSomething(){

Object a=new Object();
Object b=new Object();
Session hiberSession=getSession();
hiberSession.save(a);
hiberSession.flush();//重新整理快取,將sql執行,這個預設是在事務提交前自動執行的
b.setAId(a.getId);//將a物件的主鍵作為外來鍵參照
hiberSession.save(b);


}

這裡需要注意:session的flush與事務提交的區別:
程式碼:
先看看session的flush,最好的說明無疑是看原始碼了:

session的flush與事務commit(只是簡單理解)

session.flush

/**
	 * Force this session to flush. Must be called at the end of a
	 * unit of work, before committing the transaction and closing the
	 * session (depending on {@link #setFlushMode(FlushMode)},
	 * {@link Transaction#commit()} calls this method).
	 * <p/>
	 * <i>Flushing</i> is the process of synchronizing the underlying persistent
	 * store with persistable state held in memory.
	 *
	 * @throws HibernateException Indicates problems flushing the session or
	 * talking to the database.
	 */
	public void flush() throws HibernateException;

務必是在某個單元(一個整塊)的任務末尾,事物提交和session關閉之前 重新整理session

  • Tracnsaction的commit方法會自動呼叫session的flush操作
  • flush操作是同步底層持久化的過程,以儲存在記憶體中的可持久化狀態進行儲存(注意哦,只是存在記憶體,也就是快取中,並且有個狀態標記這條快取是可以持久化的,還沒存到磁碟(資料庫中))

transaction.commit

/**
	 * Commit this transaction.  This might entail a number of things depending on the context:<ul>
	 *     <li>
	 *         If this transaction is the {@link #isInitiator initiator}, {@link Session#flush} the {@link Session}
	 *         with which it is associated (unless {@link Session} is in {@link FlushMode#MANUAL}).
	 *     </li>
	 *     <li>
	 *         If this transaction is the {@link #isInitiator initiator}, commit the underlying transaction.
	 *     </li>
	 *     <li>
	 *         Coordinate various callbacks
	 *     </li>
	 * </ul>
	 *
	 * @throws HibernateException Indicates a problem committing the transaction.
	 */

問題:

既然事務的commit會自動執行flush,那麼為什麼不手動flush就會導致a物件的主鍵沒返回呢???

猜測:

博主暫時猜測:自動的flush操作可能會打亂正常得邏輯的sql執行順序,如果是手動flush就能保證a的sql在b的sql之前執行,
如此猜測的起點是:java虛擬機器為了優化可以對程式碼進行重排序,這點大家都是知道的,那麼hibernate這裡的事務有沒有可能是這樣的呢?進行某種程度上的優化,來改變sql的執行順序,我的pojo裡面雖然有外來鍵欄位但是是沒有指定參照於某個表的,只是讓他作為普通的欄位,資料庫裡面是建立了外來鍵約束的。(以上暫時只是猜測,後續研究再來更正,各位博友如果知道原由,還望留言給出,謝謝!)

博主當時是沒注意這個外來鍵關聯的問題,自己單獨測試沒有出現此異常,當時將系統投入使用時,發現出現此異常。當時以為是網路原因,當時生產伺服器配置很高,這點排除;除此之外這個異常看名字也是和網路沒半毛錢關係,再仔細看異常名字,包含volatile字樣,這應該是和原子操作或者是事務的操作有點關係了。
找到該異常定義處:


## DataIntegrityViolationException.java

/**
 * Exception thrown when an attempt to insert or update data
 * results in violation of an integrity constraint. Note that this
 * is not purely a relational concept; unique primary keys are
 * required by most database types.
 *
 * @author Rod Johnson
 * */
 public class DataIntegrityViolationException extends NonTransientDataAccessException
 

大意是說當嘗試insert或者update資料時導致違反完整性約束。
什麼是完整性約束呢?

完整性約束

完整性約束簡單來說就是為了保證只允許正確的資料存入到資料庫。
什麼是正確的資料?
不違反資料庫裡面定義的約束,比如非空約束(不允許該欄位為null),主鍵約束(唯一且不空),外來鍵約束(非null)等等
而博主的問題顯然是由於外來鍵約束問題導致的。