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)等等
而博主的問題顯然是由於外來鍵約束問題導致的。