關於Hibernate基於version的樂觀鎖

剛剛接觸SSH框架,雖然可能這個框架已經比較過期了,可是我的認爲,SSH做爲一個成熟的框架,做爲框架的入門仍是能夠的。html

馬馬虎虎學完了Hibernate的基礎,總結一點心得之類的。java

學習Hibernate的樂觀鎖時:mysql

  • 首先要知道爲何要用樂觀鎖。之因此要用樂觀鎖,就是爲了不髒數據。這很像數據庫原理中的共享鎖(讀鎖)和排它鎖(寫鎖)。無論是樂觀鎖、共享鎖、排它鎖,其目的都是爲了保證數據的一致性,也能夠說是保證數據的正確性。全部鎖的本質都是同樣的。
  • 其次要知道何時要用樂觀鎖。通常有多個事務要對同一數據進行操做時,就須要使用樂觀鎖。好比很經典的銀行取錢問題,只有用鎖來保證數據只能被一個事務所使用,在該事務結束使用以前,別的事務都不能對它作任何事,不然就可能出現丟失修改、讀髒數據、數據不一致等問題。
  • 第三要知道爲何能夠使用樂觀鎖。Hibernate自己是對JDBC的輕量級封裝,其目的是爲了使開發人員能夠像操做對象同樣操做數據庫,其本質就是數據庫操做,因此數據庫的鎖機制是能夠實現和使用的。
  • 第四要知道樂觀鎖的實現機制。version元素是利用一個遞增的整數來跟蹤數據表中記錄的版本的。在讀取數據時,會將version一同讀取出來,而在更新時,將version+1(使用hql在update時不校驗version)。將提交數據的version與數據庫庫表中對應記錄的version進行比較,若是提交的數據的version大於數據庫庫表中記錄的version,則執行更新,不然便認爲是過時或無效數據,不執行更新,並拋出異常。
  • 最後要知道怎麼使用樂觀鎖。
  • 先要有一個entity class。在該類裏面增長一個version屬性,設爲int類型,這個字段表示版本信息。

      【注:代碼中有@的能夠不用管,這個是註解方式。即直接在類上使用註解,來達到相同的配置效果。】sql

 1 package hibernate;
 2  
 3 import java.util.Set;
 4  
 5 import javax.persistence.Column;
 6 import javax.persistence.Entity;
 7 import javax.persistence.GeneratedValue;
 8 import javax.persistence.GenerationType;
 9 import javax.persistence.Id;
10 import javax.persistence.Table;
11  
12 @Entity
13 @Table(name = "product_")
14 public class Product {
15     int id;
16     String name;
17     float price;
18     Category category;
19     Set<User> users;
20     int version;
21     
22     @Column(name = "version")
23     public int getVersion() {
24         return version;
25     }
26     public void setVersion(int version) {
27         this.version = version;
28     }
29     
30     @Column(name = "users")
31     public Set<User> getUsers() {
32         return users;
33     }
34     public void setUsers(Set<User> users) {
35         this.users = users;
36     }
37     
38     @Column(name = "category")
39     public Category getCategory() {
40         return category;
41     }
42     public void setCategory(Category category) {
43         this.category = category;
44     }
45     
46     @Id
47     @GeneratedValue(strategy = GenerationType.IDENTITY)
48     @Column(name = "id")
49     public int getId() {
50         return id;
51     }
52     public void setId(int id) {
53         this.id = id;
54     }
55     
56     @Column(name = "name")
57     public String getName() {
58         return name;
59     }
60     public void setName(String name) {
61         this.name = name;
62     }
63     
64     @Column(name = "price")
65     public float getPrice() {
66         return price;
67     }
68     public void setPrice(float price) {
69         this.price = price;
70     }
71 }

 

  • 接下來就是進行修改entity class的配置文件。在"類名.hbm.xml"文件中,增長一個version字段,用於版本信息控制,這就是樂觀鎖的核心機制。

      【注:version標籤必須跟在id標籤後面,不然會有錯,運行程序會報錯,沒法讀取XML文件。 hibernate須要訪問的屬性必定要在"類名.hbm.xml"中定義】數據庫

1 <?xml version="1.0"?> 2 <!DOCTYPE hibernate-mapping PUBLIC 3 "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 4 "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> 5 6 <hibernate-mapping package="hibernate"> 7 8 <!-- *****表示類Product對應表product_***** --> 9 <class name="Product" table="product_"> 10 11 <!-- *****表示屬性id,映射表裏的字段id***** --> 12 <id name="id" column="id"> 13 <!-- *****id的自增加方式採用數據庫的本地方式***** --> 14 <generator class="native"> 15 </generator> 16 </id> 17 18 <!--version標籤必須跟在id標籤後面 --> 19 <version name="version" column="ver" type="int"></version> 20 21 <!-- *****只寫了屬性name,沒有經過column="name" 顯式的指定字段,那麼字段的名字也是name.***** --> 22 <property name="name" /> 23 <property name="price" /> 24 25 <!-- 使用標籤many-to-one設置多對一關係 --> 26 <!-- name="category" 對應Product類中的category屬性 --> 27 <!-- class="Category" 表示對應Category類 --> 28 <!-- column="cid" 表示指向category_表的外鍵 --> 29 <many-to-one name="category" class="Category" column="cid"/> 30 31 <set name="users" table="user_product" lazy="false"> 32 <key column="pid"/> 33 <many-to-many column="uid" class="User"/> 34 </set> 35 </class> 36 37 </hibernate-mapping>app

 

  • 還有在"包名.cfg.xml"文件中要配置好映射。
<mapping resource="hibernate/Product.hbm.xml" />

 

  • 最後測試一下就行了。
 1 public class TestHibernate {
 2  
 3     public static void main(String[] args) {
 4     SessionFactory sf = new Configuration().configure().buildSessionFactory();
 5         
 6         Session s1 = sf.openSession();
 7         Session s2 = sf.openSession();
 8  
 9         s1.beginTransaction();
10         s2.beginTransaction();
11  
12         Product p1 = (Product) s1.get(Product.class, 1);
13         System.out.println("產品本來價格是: " + p1.getPrice());
14         p1.setPrice(p1.getPrice() + 1000);
15  
16         Product p2 = (Product) s2.get(Product.class, 1);
17         p2.setPrice(p2.getPrice() + 1000);
18  
19         s1.update(p1);
20         s2.update(p2);
21  
22         s1.getTransaction().commit();
23         s2.getTransaction().commit();
24  
25         Product p = (Product) s1.get(Product.class, 1);
26         System.out.println("通過兩次價格增長後,價格變爲: " + p.getPrice());
27  
28         sf.close();
29     }
30  
31 }

 

  • 運行結果:在main線程中出現了報錯,由於s1已經修改了數據,可是s2也想修改,可是version的值已經由1變爲2了,因此s2不能執行,報錯Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect),意思是"行被另外一事務更新或刪除(或未保存的值映射不正確)",即s1已經修改了該行,產品本來價格是: 10000.0,程序執行完以後爲11000.0,s2沒有執行。
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
八月 05, 2018 10:49:36 上午 com.mchange.v2.log.MLog <clinit>
信息: MLog clients using java 1.4+ standard logging.
八月 05, 2018 10:49:38 上午 com.mchange.v2.c3p0.C3P0Registry banner
信息: Initializing c3p0-0.9.1 [built 16-January-2007 14:46:42; debug? true; trace: 10]
八月 05, 2018 10:49:38 上午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource getPoolManager
信息: Initializing c3p0 pool... com.mchange.v2.c3p0.PoolBackedDataSource@7cf67e4e [ connectionPoolDataSource -> com.mchange.v2.c3p0.WrapperConnectionPoolDataSource@3c314b76 [ acquireIncrement -> 2, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, debugUnreturnedConnectionStackTraces -> false, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 2x00zq9x27t6vc4ficmh|4d1b0d2a, idleConnectionTestPeriod -> 3000, initialPoolSize -> 5, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 50000, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 20, maxStatements -> 100, maxStatementsPerConnection -> 0, minPoolSize -> 5, nestedDataSource -> com.mchange.v2.c3p0.DriverManagerDataSource@ce96782d [ description -> null, driverClass -> null, factoryClassLocation -> null, identityToken -> 2x00zq9x27t6vc4ficmh|52feb982, jdbcUrl -> jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8, properties -> {user=******, password=******} ], preferredTestQuery -> null, propertyCycle -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false; userOverrides: {} ], dataSourceName -> null, factoryClassLocation -> null, identityToken -> 2x00zq9x27t6vc4ficmh|4fcd19b3, numHelperThreads -> 3 ]
Hibernate: select product0_.id as id0_0_, product0_.ver as ver0_0_, product0_.name as name0_0_, product0_.price as price0_0_, product0_.cid as cid0_0_ from product_ product0_ where product0_.id=?
Hibernate: select users0_.pid as pid0_1_, users0_.uid as uid1_, user1_.id as id3_0_, user1_.name as name3_0_ from user_product users0_ inner join user_ user1_ on users0_.uid=user1_.id where users0_.pid=?
Hibernate: select products0_.uid as uid3_1_, products0_.pid as pid1_, product1_.id as id0_0_, product1_.ver as ver0_0_, product1_.name as name0_0_, product1_.price as price0_0_, product1_.cid as cid0_0_ from user_product products0_ inner join product_ product1_ on products0_.pid=product1_.id where products0_.uid=?
Hibernate: select products0_.uid as uid3_1_, products0_.pid as pid1_, product1_.id as id0_0_, product1_.ver as ver0_0_, product1_.name as name0_0_, product1_.price as price0_0_, product1_.cid as cid0_0_ from user_product products0_ inner join product_ product1_ on products0_.pid=product1_.id where products0_.uid=?
Hibernate: select products0_.uid as uid3_1_, products0_.pid as pid1_, product1_.id as id0_0_, product1_.ver as ver0_0_, product1_.name as name0_0_, product1_.price as price0_0_, product1_.cid as cid0_0_ from user_product products0_ inner join product_ product1_ on products0_.pid=product1_.id where products0_.uid=?
產品本來價格是: 10000.0
Hibernate: select product0_.id as id0_0_, product0_.ver as ver0_0_, product0_.name as name0_0_, product0_.price as price0_0_, product0_.cid as cid0_0_ from product_ product0_ where product0_.id=?
Hibernate: select users0_.pid as pid0_1_, users0_.uid as uid1_, user1_.id as id3_0_, user1_.name as name3_0_ from user_product users0_ inner join user_ user1_ on users0_.uid=user1_.id where users0_.pid=?
Hibernate: select products0_.uid as uid3_1_, products0_.pid as pid1_, product1_.id as id0_0_, product1_.ver as ver0_0_, product1_.name as name0_0_, product1_.price as price0_0_, product1_.cid as cid0_0_ from user_product products0_ inner join product_ product1_ on products0_.pid=product1_.id where products0_.uid=?
Hibernate: select products0_.uid as uid3_1_, products0_.pid as pid1_, product1_.id as id0_0_, product1_.ver as ver0_0_, product1_.name as name0_0_, product1_.price as price0_0_, product1_.cid as cid0_0_ from user_product products0_ inner join product_ product1_ on products0_.pid=product1_.id where products0_.uid=?
Hibernate: select products0_.uid as uid3_1_, products0_.pid as pid1_, product1_.id as id0_0_, product1_.ver as ver0_0_, product1_.name as name0_0_, product1_.price as price0_0_, product1_.cid as cid0_0_ from user_product products0_ inner join product_ product1_ on products0_.pid=product1_.id where products0_.uid=?
Hibernate: update product_ set ver=?, name=?, price=?, cid=? where id=? and ver=?
Hibernate: update product_ set ver=?, name=?, price=?, cid=? where id=? and ver=?
Exception in thread "main" org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [hibernate.Product#1]
    at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1950)
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2594)
    at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2494)
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2821)
    at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:113)
    at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:273)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:265)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:185)
    at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216)
    at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383)
    at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133)
    at hibernate.TestHibernate.main(TestHibernate.java:404)

 

  • PS:除了樂觀鎖還有悲觀鎖,弄懂了樂觀鎖以後能夠研究一下悲觀鎖。
相關文章
相關標籤/搜索