乐观锁
乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
乐观锁一般来说有以下2种方式:
- 使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
- 使用时间戳(timestamp)。乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。
数据库设置乐观锁--作用
Hibernate支持乐观锁。当多个事务同时对数据库表中的同一条数据操作时,如果没有加锁机制的话,就会产生脏数据(duty data)。Hibernate有2种机制可以解决这个问题:乐观锁和悲观锁。这里我们只讨论乐观锁。
Hibernate乐观锁,能自动检测多个事务对同一条数据进行的操作,并根据先胜原则,提交第一个事务,其他的事务提交时则抛出org.hibernate.StaleObjectStateException异常。
Hibernate乐观锁是怎么做到的呢?
我们先从Hibernate乐观锁的实现说起。
要实现Hibenate乐观锁,我们首先要在数据库表里增加一个版本控制字段,字段名随意,比如就叫version,对应hibernate类型只能为long,integer,short,timestamp,calendar,也就是只能为数字或timestamp类型。然后在hibernate mapping里作如下类似定义:
告诉Hibernate version作为版本控制用,交由它管理。
当然在entity class里也需要给version加上定义,定义的方法跟其他字段完全一样。
Hibernate乐观锁的的使用:
在事务2提交时,因为它提交的数据比事务1提交后的数据旧,所以hibernate会抛出一个org.hibernate.StaleObjectStateException异常。
回到前面的问题,Hibernate怎么知道事务2提交的数据比事务1提交后的数据旧呢?
因为MyEntity有个version版本控制字段。
回头看看上面的源代码中的:
MyEntity et1 = session1.load(MyEntity.class, id);
MyEntity et2 = session2.load(MyEntity.class, id);
这里,et1.version==et2.version,比如此时version=1,
当事务1提交后,该数据的版本控制字段version=version+1=2,而事务2提交时version=1<2所以Hibernate认为事务2提交的数据为过时数据,抛出异常。
这就是Hibernate乐观锁的原理机制。
我们已经知道了Hibernate乐观锁是根据version的值来判断数据是否过时,也就是说,在向数据库update某数据时,必须保证该entity里的version字段被正确地设置为update之前的值,否则hibernate乐观锁机制将无法根据version作出正确的判断。
在我们的WEB应用中,尤其应该注意这个问题。
Hibernate乐观锁是怎么做到的呢?
我们先从Hibernate乐观锁的实现说起。
要实现Hibenate乐观锁,我们首先要在数据库表里增加一个版本控制字段,字段名随意,比如就叫version,对应hibernate类型只能为long,integer,short,timestamp,calendar,也就是只能为数字或timestamp类型。然后在hibernate mapping里作如下类似定义:
- <version name="version"
- column="VERSION"
- type="integer"
- />
- <version name="version"
- column="VERSION"
- type="integer"
- />
当然在entity class里也需要给version加上定义,定义的方法跟其他字段完全一样。
- private Integer version;
- …
- // setVersion() && getVersion(Integer)
- private Integer version;
- …
- // setVersion() && getVersion(Integer)
Hibernate乐观锁的的使用:
- <DIV class=tf_edit_html_lineno id=tf_edit_html_lineno><SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">1</SPAN>Session session1 = sessionFactory.openSession();
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">2</SPAN>Session session2 = sessionFactory.openSession();
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">3</SPAN>
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">4</SPAN>MyEntity et1 = session1.load(MyEntity.class, id);
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">5</SPAN>MyEntity et2 = session2.load(MyEntity.class, id);
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">6</SPAN>//这里 et1, et2为同一条数据
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">7</SPAN>
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">8</SPAN>Transaction tx1 = session1.beginTransaction();
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">9</SPAN>//事务1开始
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">10</SPAN>et1.setName(“Entity1”);
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">11</SPAN>//事务1中对该数据修改
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">12</SPAN>
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">13</SPAN>tx1.commit();
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">14</SPAN>session1.close();
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">15</SPAN>//事务1提交
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">16</SPAN>
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">17</SPAN>Transaction tx2 = session2.beginTransaction();
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">18</SPAN>//事务2开始
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">19</SPAN>et2.setName(“Entity2”);
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">20</SPAN>//事务2中对该数据修改
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">21</SPAN>
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">22</SPAN>tx2.commit();
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">23</SPAN>session2.close();
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">24</SPAN>//事务2提交
- <SPAN style="PADDING-RIGHT: 3px; WIDTH: 1.5em; COLOR: #aaaadd; FONT-FAMILY: verdana">25</SPAN>
- </DIV>
- <div id="tf_edit_html_lineno" class="tf_edit_html_lineno"><span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">1</span>Session session1 = sessionFactory.openSession();
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">2</span>Session session2 = sessionFactory.openSession();
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">3</span>
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">4</span>MyEntity et1 = session1.load(MyEntity.class, id);
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">5</span>MyEntity et2 = session2.load(MyEntity.class, id);
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">6</span>//这里 et1, et2为同一条数据
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">7</span>
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">8</span>Transaction tx1 = session1.beginTransaction();
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">9</span>//事务1开始
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">10</span>et1.setName(“Entity1”);
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">11</span>//事务1中对该数据修改
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">12</span>
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">13</span>tx1.commit();
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">14</span>session1.close();
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">15</span>//事务1提交
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">16</span>
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">17</span>Transaction tx2 = session2.beginTransaction();
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">18</span>//事务2开始
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">19</span>et2.setName(“Entity2”);
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">20</span>//事务2中对该数据修改
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">21</span>
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">22</span>tx2.commit();
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">23</span>session2.close();
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">24</span>//事务2提交
- <span style="padding-right: 3px; 1.5em; color: #aaaadd; font-family: verdana;">25</span>
- </div>
在事务2提交时,因为它提交的数据比事务1提交后的数据旧,所以hibernate会抛出一个org.hibernate.StaleObjectStateException异常。
回到前面的问题,Hibernate怎么知道事务2提交的数据比事务1提交后的数据旧呢?
因为MyEntity有个version版本控制字段。
回头看看上面的源代码中的:
MyEntity et1 = session1.load(MyEntity.class, id);
MyEntity et2 = session2.load(MyEntity.class, id);
这里,et1.version==et2.version,比如此时version=1,
当事务1提交后,该数据的版本控制字段version=version+1=2,而事务2提交时version=1<2所以Hibernate认为事务2提交的数据为过时数据,抛出异常。
这就是Hibernate乐观锁的原理机制。
我们已经知道了Hibernate乐观锁是根据version的值来判断数据是否过时,也就是说,在向数据库update某数据时,必须保证该entity里的version字段被正确地设置为update之前的值,否则hibernate乐观锁机制将无法根据version作出正确的判断。
在我们的WEB应用中,尤其应该注意这个问题。