[英]concurrency in hibernate
我有一個servlet為用戶做一些工作,然后減少用戶的信用。 當我實時查看數據庫中用戶的信用時,如果來自同一用戶的並發請求很多,則由於並發控制而導致信用額被錯誤地扣除。 T假設我有一個服務器,並且使用的數據庫管理是休眠。 我正在使用事務控制來跨越整個請求,請參閱代碼以獲取詳細信息。 我有幾個問題:
為什么當面對來自同一用戶的許多並發請求時,db中的信用計數器會跳到各處? 為什么我的交易控制不起作用?
如果我在檢索用戶帳戶后修改了基礎數據,然后嘗試更新它,為什么我沒有得到任何HibernateException(eg.StaleObjectException)
?
我有整個用戶請求的事務跨度,有更好的方法嗎? 請批評。 如果您覺得我做錯了,請隨意重寫示例代碼結構。
Main servlet class: protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try{ Manager.beginTransaction(); cmdDowork(request, response); Manager.commitTransaction(); }catch(Exception exp){ Manager.rollbackTransaction(); exp.printStackTrace(); } finally{ Manager.closeSession(); } } public void cmdDowork(){ try{ UserAccount userAccount = lazyGetUserAccount(request.getParameter("userName")); doWorkForUser(userAccount);//time and resource consuming process if(userAccount!=null) { decUserAccountQuota(userAccount); } }catch (HibernateException e){ e.printStackTrace(); } } public static UserAccount lazyGetUserAccount(String userName) { UserAccount userAccount = Manager.getUserAccount(userName); if(userAccount == null){ userAccount = new UserAccount(userName); userAccount.setReserve(DEFAULT_USER_QUOTA); userAccount.setBalance(DEFAULT_USER_QUOTA); Manager.saveUserAccount(userAccount); } return userAccount; } private boolean decUserAccountQuota(UserAccount userAccount) { if(userAccount.getBalance()Edit: code I used to test optimistic locking as suggested by the answer, I am not getting a any StaleObjectException, the update were committed successfully.. Session em1=Manager.sessionFactory.openSession(); Session em2=Manager.sessionFactory.openSession();
em1.getTransaction().begin(); em2.getTransaction().begin(); UserAccount c1 = (UserAccount)em1.get( UserAccount.class, "jonathan" ); UserAccount c2 = (UserAccount)em2.get( UserAccount.class, "jonathan" ); c1.setBalance( c1.getBalance() -1 ); em1.flush(); em1.getTransaction().commit(); System.out.println("balance1 is "+c2.getBalance()); c2.setBalance( c2.getBalance() -1 ); em2.flush(); // fail em2.getTransaction().commit(); System.out.println("balance2 is "+c2.getBalance());
您有兩種方法可以處理這種情況:使用悲觀鎖定或使用樂觀主義鎖定。 但是你似乎沒有使用兩者,這可能解釋了不正確的行為。
通過樂觀鎖定,Hibernate將檢查用戶帳戶在讀取和保存之間是否未被更改。 然后,並發事務可能會失敗並被回滾。
使用悲觀鎖定時,您在讀取行時會鎖定該行,並且只有在事務完成時才會解鎖該行。 這可以防止並發事務讀取過時的數據。
刷新實體可以根據當前事務是否已經提交來讀取或不讀取新數據,但也不是解決方案。 因為您似乎也創建了用戶帳戶(如果它不存在),所以您無法輕松應用悲觀鎖定。 我建議你使用樂觀鎖定(並使用例如時間戳來檢測並發修改)。
閱讀關於悲觀主義和樂觀主義鎖定的其他問題 。 另請參閱hibernate章節“ 事務和並發 ”和“ 休眠注釋 ”。
它應該像在相應字段上添加@Version
一樣簡單, optimisticLockStrategy
默認值是VERSION
(使用單獨的列)。
- 更新 -
您可以測試它是否在測試用例中有效。 我創建了一個帶有ID
, value
和version
字段的簡單實體Counter
。
public class Counter implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Basic(optional = false)
@Column(name = "ID")
private Integer id;
@Column(name = "VALUE")
private Integer value;
@Column(name = "VERSION")
@Version
private Integer version;
...
}
如果按順序更新一個實體,則可以:
id = insertEntity( ... );
em1.getTransaction().begin();
Counter c1 = em1.find( Counter.class, id );
c1.setValue( c1.getValue() + 1 );
em1.flush();
em1.getTransaction().commit();
em2.getTransaction().begin();
Counter c2 = em2.find( Counter.class, id );
c2.setValue( c2.getValue() + 1 );
em2.flush(); // OK
em2.getTransaction().commit();
我得到一個value=2
且version=2
實體。
如果我模擬兩個並發更新:
id = insertEntity( ... );
em1.getTransaction().begin();
em2.getTransaction().begin();
Counter c1 = em1.find( Counter.class, id );
Counter c2 = em2.find( Counter.class, id );
c1.setValue( c1.getValue() + 1 );
em1.flush();
em1.getTransaction().commit();
c2.setValue( c2.getValue() + 1 );
em2.flush(); // fail
em2.getTransaction().commit();
然后第二次沖洗失敗:
Hibernate: update COUNTER set VALUE=?, VERSION=? where ID=? and VERSION=?
Hibernate: update COUNTER set VALUE=?, VERSION=? where ID=? and VERSION=?
Dec 23, 2009 11:08:46 AM org.hibernate.event.def.AbstractFlushingEventListener performExecutions
SEVERE: Could not synchronize database state with session
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [org.ewe.Counter#15]
at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1765)
這是因為SQL語句中的實際參數是:
update COUNTER set VALUE=1, VERSION=1 where ID=xxx and VERSION=0
--> 1 row updated
update COUNTER set VALUE=1, VERSION=1 where ID=xxx and VERSION=0
--> 0 row updated, because version has been changed in between
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.