5.2 Working with application transactions
In our CaveatEmptor application, both the user who posted a comment and any system administrator can open an Edit Comment screen to delete or edit the text of a comment. Suppose two different administrators open the edit screen to view the same comment simultaneously. Both edit the comment text and submit their changes. At this point, we have three ways to handle the concurrent attempts to
write to the database:
■ Last commit wins—Both updates succeed, and the second update overwrites the changes of the first. No error message is shown.
■ First commit wins—The first modification is persisted, and the user submitting the second change receives an error message. The user must restart the business process by retrieving the updated comment. This option is often called optimistic locking.
■ Merge conflicting updates—The first modification is persisted, and the second modification may be applied selectively by the user.
在我們的CavertEmptor中,用戶(hù)和系統(tǒng)管理員都可以打開(kāi)“編輯cooment”功能,刪除或者編輯評(píng)論。假設(shè)同時(shí)有兩個(gè)管理員對(duì)同一個(gè)評(píng)論打開(kāi)編輯功能,他們都對(duì)評(píng)論進(jìn)行編輯然后提交。這是,我們發(fā)現(xiàn)可能有三種方式對(duì)此并發(fā)操作進(jìn)行處理、寫(xiě)入數(shù)據(jù)庫(kù):
后者提交獲勝-兩次更新都成功,但是第二次更新將會(huì)覆蓋第一次的更新,不顯示任何錯(cuò)誤。
前者提交獲勝-第一個(gè)提交者成功,更新數(shù)據(jù)庫(kù),后一個(gè)提交失敗,并返回錯(cuò)誤信息。用戶(hù)必須重新開(kāi)始整個(gè)業(yè)務(wù)過(guò)程,取得被更新的評(píng)論。者就是常說(shuō)的“樂(lè)觀(guān)鎖”。
合并更新沖突-第一個(gè)更新被保存,而第二個(gè)更新則由用戶(hù)選擇是否保存。
The first option, last commit wins, is problematic; the second user overwrites the changes of the first user without seeing the changes made by the first user or even knowing that they existed. In our example, this probably wouldn’t matter, but it would be unacceptable for some other kinds of data. The second and third options are usually acceptable for most kinds of data. From our point of view, the third option is just a variation of the second—instead of showing an error message, we show the message and then allow the user to manually merge changes. There is no single best solution. You must investigate your own business requirements to decide among these three options.
上面的第一個(gè)方案是有問(wèn)題的:第二個(gè)用戶(hù)在不知道改變、甚至不知道第一個(gè)用戶(hù)已經(jīng)操作的情況下,直接重寫(xiě)了數(shù)據(jù)庫(kù)。在我們的例子中,也許是沒(méi)關(guān)系的,但是對(duì)于某些應(yīng)用是不可接受的。第二個(gè)和第三個(gè)方案對(duì)于絕大部分?jǐn)?shù)據(jù)是可以結(jié)束的。按照我們的觀(guān)點(diǎn),第三個(gè)是第二個(gè)的變種,它不是顯式錯(cuò)誤信息,而是給用戶(hù)進(jìn)行選擇,允許用戶(hù)手工合并對(duì)數(shù)據(jù)的修改。這里并沒(méi)有一個(gè)最完美的解決方案。你必須根據(jù)你自己項(xiàng)目的需要自行選擇方案。
The first option happens by default if you don’t do anything special in your application。On the other hand, Hibernate can help you implement the second and third strategies, using managed versioning for optimistic locking.
如果在你的項(xiàng)目中不采取任何措施,那么會(huì)默認(rèn)使用第一個(gè)方案。但是,hibernate可以通過(guò)使用對(duì)“樂(lè)觀(guān)鎖”使用“管理版本”,幫助你實(shí)現(xiàn)第二個(gè)或第三個(gè)方案。
Managed versioning relies on either a version number that is incremented or a timestamp that is updated to the current time, every time an object is modified. For Hibernate managed versioning, we must add a new property to our Comment class and map it as a version number using the <version> tag. First, let’s look at the changes to the Comment class:
public class Comment {
...
private int version;
...
void setVersion(int version) {
this.version = version;
}
int getVersion() {
return version;
}
}
“管理版本”使用版本號(hào)或者時(shí)間戳對(duì)數(shù)據(jù)進(jìn)行標(biāo)記,每次數(shù)據(jù)在更新的時(shí)候,它都會(huì)被更新。對(duì)于hibernate,我們必須給Comment類(lèi),添加新的屬性,然后使用<version>標(biāo)簽對(duì)其進(jìn)行映射。首先讓我們來(lái)看一下Comment類(lèi)的改變:
public class Comment {
...
private int version;
...
void setVersion(int version) {
this.version = version;
}
int getVersion() {
return version;
}
}
You can also use a public scope for the setter and getter methods. The <version> property mapping must come immediately after the identifier property mapping in the mapping file for the Comment class:
<class name="Comment" table="COMMENTS">
<id ...
<version name="version" column="VERSION"/>
...
</>
你可以使用public的setter或getter方法,這里的<version>映射的定義必須緊跟在映射文件中comment類(lèi)的id后面。
<class name="Comment" table="COMMENTS">
<id ...
<version name="version" column="VERSION"/>
...
</>
You don’t need to set the value of the version or timestamp property yourself; Hibernate will initialize the value when you first save a Comment, and increment or reset it whenever the object is modified. Whenever Hibernate updates a comment, it uses the version column in the SQLWHERE clause:
update COMMENTS set COMMENT_TEXT='New comment text', VERSION=3
where COMMENT_ID=123 and VERSION=2
你無(wú)需自己手工去修改這里的版本號(hào)或者時(shí)間戳,hibernate將會(huì)在你保存Commment的時(shí)候?qū)ζ涑跏蓟?,然后在?duì)象每次被更新的時(shí)候即使更新其版本號(hào)和時(shí)間戳。無(wú)論hibernate何時(shí)更新comment,它都會(huì)在SQL語(yǔ)句的where后面加上version列:
update COMMENTS set COMMENT_TEXT='New comment text', VERSION=3
where COMMENT_ID=123 and VERSION=2
If another application transaction would have updated the same item since it was read by the current application transaction, the VERSION column would not contain the value 2, and the row would not be updated. Hibernate would check the row count returned by the JDBC driver—which in this case would be the number of rows updated, zero—and throw a StaleObjectStateException. Using this exception, we might show the user of the second application transaction an error message (“You have been working with stale data because another user modified it!”) and let the first commit win. Alternatively, we could catch the exception and show the second user a new screen, allowing the user to manually merge changes between the two versions.
如果某個(gè)事務(wù)已經(jīng)更新了某個(gè)數(shù)據(jù),而另外一個(gè)事務(wù)也準(zhǔn)備更新此數(shù)據(jù),但是當(dāng)時(shí)它取得數(shù)據(jù)的版本號(hào)是2,而此時(shí)由于該數(shù)據(jù)已經(jīng)被更新過(guò),新版本號(hào)是3,因此將無(wú)法更新數(shù)據(jù)庫(kù)中的任何的數(shù)據(jù)。這時(shí)候hibernate將會(huì)檢查jdbc的返回行數(shù)-如果未有任何數(shù)據(jù)行被更新,則返回0-因此將會(huì)拋出StaleObjectStateException異常。
Using this exception, we might show the user of the second application transaction an error message (“You have been working with stale data because another user modified it!”) and let the first commit win. Alternatively, we could catch the exception and show the second user a new screen, allowing the user to manually merge changes between the two versions.
使用此異常,就可以給第二個(gè)應(yīng)用事務(wù)的用戶(hù)顯式錯(cuò)誤信息(你正使用的數(shù)據(jù)已經(jīng)被其他人更新了),讓第一個(gè)提交者獲勝。當(dāng)然,你也可以捕捉異常,然后給第二個(gè)用戶(hù)一個(gè)新的平面,允許它手工合并這兩個(gè)版本的數(shù)據(jù)。