hibernate 控制并发访问
2011-11-01 22:25
405 查看
在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同:
◆未授权读取(Read Uncommitted):允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个数据则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。
◆授权读取(Read Committed):允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
◆可重复读取(Repeatable Read):禁止不可重复读取和脏读取,但是有时可能出现幻影数据。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
◆序列化(Serializable):提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。
如果一个事务读取由另一个还没有被提交的事务进行的改变,就发生脏读取(dirty read)。这很危险,因为由其他事务进行的改变随后
可能回滚,并且低一个事务可能编写无效的数据。
如果一个事物读取一个行两次,并且每次读取不同的状态,就会发生不可重复读取(unrepeatable read)。例如,另一个事务可能已经
写到这个行,并已经在两次读取之间提交。
不可重复读取的一个特殊案例是二次丢失更新问题(second lost updates problem)。想象两个并发事务都读取一个行:一个写到行并
提交,然后第二个也写到行并提交。有第一个事务所做的改变丢失了。
幻读(phantom read)发生在一个事务执行一个查询两次,并且第二个结果集包括第一个结果集中不可见的行,或者包括已经删除的行时。
这种情况是由另一个事务在两次查询执行之间插入或者删除行照成的。
ANSI事务隔离性级别:
允许脏读取但不允许丢失更新的系统,要在读取未提交(read uncommitted)的隔离性中操作。如果一个为提交事务已经写到一个行,
另一个事务就不可能再写到这个行。但任何事务都可以读取任何行。这个隔离性级别可以在数据库管理系统中通过专门的写锁来实现。
允许不可重复读取但不允许脏读取系统,要实现读取提交(read committed)的事务隔离性。这可以用共享的读锁和专门的写锁来实现。
读取事务不会阻塞其他事务访问行。但是为提交的鞋事务隔赛所有其他的事务访问改行。
在可重复读(repeatable read)隔离性模式中操作的系统既不允许不可重复读取,也不允许脏读取。幻读可能发生。
可序列化 (serializable)提供最严格的事务隔离性。这个隔离性级别模拟连续的事务执行,好像事务是连续的一个接一个的执行,而不是
并发的执行。
选择隔离性级别:
首先,消除读取为提交隔离性级别。在不同的事务中使用一个为提交的事务变化是很危险的。一个事务的回滚或者失败将影响其他的并发事务。
甚至由一个终止回滚的事务所做的改变也可能在任何地方被提交,因为他们可能读取,然后由另一个成功的事务传播!
其次,大多数应用程序不需要可序列化的隔离级别(幻读通常不成问题),并且这个隔离性往往难以伸缩。
这样就把选择读取提交还是可重复读取交给你了。
Hibernate配置选项:
hibernate.connection.isolation = 4
1-读取为提交隔离性
2-读取提交隔离性
3-可重复读取隔离性
4-可序列化隔离性
乐观并发控制:
乐观的方法始终假设一切都会很好,并且很少有冲突的数据修改。在编写数据时,乐观并发控制值在工作单元结束时才出现错误。多用户
的应用程序通常默认为使用读取提交隔离性级别的乐观并发控制和数据库连接。只有适当的时候才获得额外的隔离性保证;这种方法保证
了最佳的性能和可伸缩性。
对于如何处理对话中这些第二个事务中的丢失更新,你有3种选择:
最晚提交生效(last commit wins)--两个事物提交都成功,第二个提交覆盖第一个变化。没有显示错误消息。
最早提交生效(fist commit wins)--对话A的事务被提交,并且在对话B中提交事务的用户得到一条错误信息。用户必须获取新数据来
重启会话,并在此利用没有失效的数据完成对话的所有步骤。
合并冲突更新(merge conflicting updates)--第一个修改被提交,并且对话B中的事务在提交时终止,带有一条错误消息。但是失败
的对话B用户可以选择性的应用变化,而不是再次在对话中完成所有工作。
在hibernate中启用版本控制:
hibernate提供自动的版本控制。每个实体实例都有一个版本,它可以是一个数字或者是一个时间戳。当对象修改时,hibernate就增加
他的版本号,自动比较版本,如果侦测到冲突就抛出异常。因此,你给所有持久化的实体类都添加这个版本属性,来启动乐观锁:
version:
view
plaincopy
to clipboardprint?
public class Item implements Serializable
{
private Integer
itemId;
private String
itemName;
/**
版本控制 */
private int version;
配置:
view
plaincopy
to clipboardprint?
<class name="Item" table="ITEM">
<id
name="itemId" column="ITEM_ID" type="integer">
<generator class="native"/>
</id>
<!--
XML格式的version属性映射必须立即放在标示符属性映射之后 -->
<version
name="version" access="field" column="OBJ_VERSION" />
<property
name="itemName" type="string" column="ITEM_NAME"/>
</class>
代码:
view
plaincopy
to clipboardprint?
/**
* 报错:
* org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
* @param itemName
*/
private static void update1(String
itemName) {
Configuration
configuration = new Configuration().configure();
SessionFactory
sessionFactory = configuration.buildSessionFactory();
Session
session = sessionFactory.openSession();
Transaction
tr = session.beginTransaction();
Item
item = (Item) session.get(Item.class, 1);
item.setItemName(itemName);
Session
session1 = sessionFactory.openSession();
Transaction
tr1 = session1.beginTransaction();
Item
item1 = (Item) session1.get(Item.class, 1);
// 一样会报错
// item1.setItemName(itemName);
item1.setItemName(itemName+"1");
tr.commit();
session.close();
//org.hibernate.StaleObjectStateException:
//Row
was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
tr1.commit();
session1.close();
sessionFactory.close();
}
/**
* 提交成功
* @param itemName
*/
private static void update2(String
itemName) {
Configuration
configuration = new Configuration().configure();
SessionFactory
sessionFactory = configuration.buildSessionFactory();
Session
session = sessionFactory.openSession();
Transaction
tr = session.beginTransaction();
Item
item = (Item) session.get(Item.class, 1);
item.setItemName(itemName);
tr.commit();
session.close();
Session
session1 = sessionFactory.openSession();
Transaction
tr1 = session1.beginTransaction();
Item
item1 = (Item) session1.get(Item.class, 1);
item1.setItemName(itemName+"1");
tr1.commit();
session1.close();
sessionFactory.close();
}
timestamp(理论上讲,时间戳更不安全。且在集群环境中更不安全):
view
plaincopy
to clipboardprint?
public class Item implements Serializable
{
private Integer
itemId;
private String
itemName;
/**
版本控制 */
private Date
lastUpdated;
配置:
view
plaincopy
to clipboardprint?
<class name="Item" table="ITEM">
<id
name="itemId" column="ITEM_ID" type="integer">
<generator class="native"/>
</id>
<timestamp
name="lastUpdated" access="field" column="LAST_UPDATED"></timestamp>
<property
name="itemName" type="string" column="ITEM_NAME"/>
</class>
代码:
view
plaincopy
to clipboardprint?
/**
* 报错:
* org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
* @param itemName
*/
private static void update1(String
itemName) {
Configuration
configuration = new Configuration().configure();
SessionFactory
sessionFactory = configuration.buildSessionFactory();
Session
session = sessionFactory.openSession();
Transaction
tr = session.beginTransaction();
Item
item = (Item) session.get(Item.class, 1);
item.setItemName(itemName);
Session
session1 = sessionFactory.openSession();
Transaction
tr1 = session1.beginTransaction();
Item
item1 = (Item) session1.get(Item.class, 1);
// 一样会报错
// item1.setItemName(itemName);
item1.setItemName(itemName+"1");
tr.commit();
session.close();
//org.hibernate.StaleObjectStateException:
//Row
was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
tr1.commit();
session1.close();
sessionFactory.close();
}
/**
* 提交成功
* @param itemName
*/
private static void update2(String
itemName) {
Configuration
configuration = new Configuration().configure();
SessionFactory
sessionFactory = configuration.buildSessionFactory();
Session
session = sessionFactory.openSession();
Transaction
tr = session.beginTransaction();
Item
item = (Item) session.get(Item.class, 1);
item.setItemName(itemName);
tr.commit();
session.close();
Session
session1 = sessionFactory.openSession();
Transaction
tr1 = session1.beginTransaction();
Item
item1 = (Item) session1.get(Item.class, 1);
item1.setItemName(itemName+"1");
tr1.commit();
session1.close();
sessionFactory.close();
}
hibernate检查机制:
org.hibernate.persister.entity.AbstractEntityPersister.check:
view
plaincopy
to clipboardprint?
protected boolean check(int rows,
Serializable id, int tableNumber,
Expectation expectation, PreparedStatement statement) throws HibernateException
{
try {
expectation.verifyOutcome(
rows, statement, -1 );
}
catch(
StaleStateException e ) {
if (
!isNullableTable( tableNumber ) ) {
if (
getFactory().getStatistics().isStatisticsEnabled() ) {
getFactory().getStatisticsImplementor()
.optimisticFailure(
getEntityName() );
}
throw new StaleObjectStateException(
getEntityName(), id );
}
return false;
}
catch(
TooManyRowsAffectedException e ) {
throw new HibernateException(
"Duplicate
identifier in table for: " +
MessageHelper.infoString( this,
id, getFactory() )
);
}
catch (
Throwable t ) {
return false;
}
return true;
}
org.hibernate.jdbc.Expectation:
view
plaincopy
to clipboardprint?
/**
*
Perform verification of the outcome of the RDBMS operation based on
*
the type of expectation defined.
*
*
@param rowCount The RDBMS reported "number of rows affected".
*
@param statement The statement representing the operation
*
@param batchPosition The position in the batch (if batching)
*
@throws SQLException Exception from the JDBC driver
*
@throws HibernateException Problem processing the outcome.
*/
public void verifyOutcome(int rowCount,
PreparedStatement statement, int batchPosition) throws SQLException,
HibernateException;
view
plaincopy
to clipboardprint?
/**
*
我用了两个factory,也是可以提交成功的
*
你想想,这证明了什么
*
@param itemName
*/
private static void update3(String
itemName) {
Configuration
configuration = new Configuration().configure();
SessionFactory
sessionFactory = configuration.buildSessionFactory();
Session
session = sessionFactory.openSession();
Transaction
tr = session.beginTransaction();
Item
item = (Item) session.get(Item.class, 1);
item.setItemName(itemName);
tr.commit();
session.close();
sessionFactory.close();
Configuration
configuration1 = new Configuration().configure();
SessionFactory
sessionFactory1 = configuration1.buildSessionFactory();
Session
session1 = sessionFactory1.openSession();
Transaction
tr1 = session1.beginTransaction();
Item
item1 = (Item) session1.get(Item.class, 1);
item1.setItemName(itemName+"11");
tr1.commit();
session1.close();
sessionFactory1.close();
}
版本控制的自动管理:
涉及目前被版本控制的Item对象的每一个DML操作都包括版本检查。例如,假设在一个工作单元中,你从版本为1的数据库中加载一个Item。然后修改它的其中一个值类型属性。当持久化
上下文被清除时,hibernate侦测到修改,并把Item的版本增加到2。然后执行SQL UPDATE使这一修改在数据库中永久化:
update item set intial_price = '11.23',obj_version=2 where item_id = 123 and obj_version=1
如果另一个并发的工作单元更新和提交了同一个行,obj_version列就不包含值1,行也不会被更新。hibernate检查由jdbc驱动器返回这个语句所更新
的行数---在这个例子中,被更新的行数为0--并抛出StaleObjectStateException。加载Item时呈现的状态,清除时不再在数据库中呈现;
因而,你真正使用失效的数据,必须通知应用程序的用户。可以捕捉这个异常,并显示一条错误消息,或者显示帮助用户给应用程序重启会话
的一个提示框。
如果你想要禁用对特定值类型属性或者集合的自动增加,就用optimistic-lock="false"属性映射他。
没有版本号或者时间戳的版本控制(如果你用的遗留表,不方便再添加字段):
view
plaincopy
to clipboardprint?
<!--
org.hibernate.MappingException: optimistic-lock=all|dirty requires dynamic-update="true" -->
<!--
dirty的区别在于,只有当某个属性共同修改时,才报错;all是只要有修改就报错 -->
<class name="Item" table="ITEM" optimistic-lock="all" dynamic-update="true">
<id name="itemId" column="ITEM_ID" type="integer">
<generator class="native"/>
</id>
<property name="itemName" type="string" column="ITEM_NAME"/>
</class>
用Java Persistence版本控制:
version:
实体:
view
plaincopy
to clipboardprint?
@Entity
public class Item implements Serializable
{
@Id
@GeneratedValue
@Column(name="ITEM_ID")
private Integer
itemId;
@Column(name="ITEM_NAME")
private String
itemName;
/**
版本控制 */
@Version
@Column(name="OBJ_VERSION")
private int version;
更新:
view
plaincopy
to clipboardprint?
private static void update(String
itemName) {
EntityManagerFactory
factory = Persistence.createEntityManagerFactory("partner4java");
EntityManager
em = factory.createEntityManager();
EntityTransaction
tr = em.getTransaction();
tr.begin();
Item
item = em.find(Item.class, 1);
item.setItemName(itemName);
EntityManager
em1 = factory.createEntityManager();
EntityTransaction
tr1 = em1.getTransaction();
tr1.begin();
Item
item1 = em1.find(Item.class, 1);
item1.setItemName(itemName+"he");
tr.commit();
em.close();
//Caused
by: javax.persistence.OptimisticLockException
tr1.commit();
em1.close();
factory.close();
}
报错:
Caused by: javax.persistence.OptimisticLockException
optimisticLock:
实体:
view
plaincopy
to clipboardprint?
@Entity
//optimistic-lock=all|dirty
requires dynamic-update="true"
@org.hibernate.annotations.Entity(
optimisticLock=OptimisticLockType.ALL,
dynamicUpdate=true)
public class Item implements Serializable
{
@Id
@GeneratedValue
@Column(name="ITEM_ID")
private Integer
itemId;
@Column(name="ITEM_NAME")
private String
itemName;
//版本检测排除下面的属性
@OptimisticLock(excluded=true)
private Double
price;
public Item()
{
super();
}
调用:
view
plaincopy
to clipboardprint?
/**
*
报错
*
Caused by: javax.persistence.OptimisticLockException
*
@param itemName
*/
private static void update1(String
itemName) {
EntityManagerFactory
factory = Persistence.createEntityManagerFactory("partner4java");
EntityManager
em = factory.createEntityManager();
EntityTransaction
tr = em.getTransaction();
tr.begin();
Item
item = em.find(Item.class, 1);
item.setItemName(itemName);
EntityManager
em1 = factory.createEntityManager();
EntityTransaction
tr1 = em1.getTransaction();
tr1.begin();
Item
item1 = em1.find(Item.class, 1);
item1.setItemName(itemName+"he");
tr.commit();
em.close();
//Caused
by: javax.persistence.OptimisticLockException
tr1.commit();
em1.close();
factory.close();
}
/**
*
不会报错
*
@param price
*/
private static void update2(Double
price) {
EntityManagerFactory
factory = Persistence.createEntityManagerFactory("partner4java");
EntityManager
em = factory.createEntityManager();
EntityTransaction
tr = em.getTransaction();
tr.begin();
Item
item = em.find(Item.class, 1);
item.setPrice(price);
EntityManager
em1 = factory.createEntityManager();
EntityTransaction
tr1 = em1.getTransaction();
tr1.begin();
Item
item1 = em1.find(Item.class, 1);
item1.setPrice(price+1D);
tr.commit();
em.close();
tr1.commit();
em1.close();
factory.close();
}
获得额外的隔离性保证:
1、显示的悲观锁
当我们关注应用程序的课伸缩性时。例如,对标量查询可能需要可重复读取。
不是把所有的数据库事务转换为一个更高的、不可伸缩的隔离性级别,而是在必要时,在Hibernate Session中使用lock()方法获得
更高的隔离性:
view
plaincopy
to clipboardprint?
/**
*
session.lock(item, LockMode.UPGRADE);
*
@param itemName
*/
private static void update1(String
itemName) {
Configuration
configuration = new Configuration().configure();
SessionFactory
sessionFactory = configuration.buildSessionFactory();
Session
session = sessionFactory.openSession();
Transaction
tr = session.beginTransaction();
Item
item = (Item) session.get(Item.class, 1);
item.setItemName(itemName);
//指定锁级别
session.lock(item,
LockMode.UPGRADE);
Session
session1 = sessionFactory.openSession();
Transaction
tr1 = session1.beginTransaction();
Item
item1 = (Item) session1.get(Item.class, 1);
item1.setItemName(itemName+"1");
//停在这步,因为碰到for
update的锁了,他要等到tr提交了之后才会执行,但是,我们这个demo当然无法执行到这步
tr1.commit();
session1.close();
tr.commit();
session.close();
sessionFactory.close();
}
/**
*
只锁定指定列的数据,不会锁定表
*
但是,以前这种方式是悲观锁,会锁定整个表,如果指定锁定某一行,如下:
*
Item item = (Item) session.get(Item.class, 1, LockMode.UPGRADE);
*
@param itemName
*/
private static void update2(String
itemName) {
Configuration
configuration = new Configuration().configure();
SessionFactory
sessionFactory = configuration.buildSessionFactory();
Session
session = sessionFactory.openSession();
Transaction
tr = session.beginTransaction();
Item
item = (Item) session.get(Item.class, 1);
item.setItemName(itemName);
//指定锁级别
session.lock(item,
LockMode.UPGRADE);
Session
session1 = sessionFactory.openSession();
Transaction
tr1 = session1.beginTransaction();
Item
item1 = (Item) session1.get(Item.class, 2);
item1.setItemName(itemName+"1");
//执行通过
tr1.commit();
session1.close();
tr.commit();
session.close();
sessionFactory.close();
}
Java Persistence 的hibernate也实现了这个技术,但是规范并没有要求实现这个。
hibernate锁模式:
LockMode.NONE--别到数据库中去,除非对象不处于任何高速缓存中。
LockMode.READ--绕过所有高速缓存,并执行版本检查,来验证内存中的对象是否与当前数据中村中的版本相同。
LockMode.UPGRADE--绕过所有高速缓存,做一个版本检查,如果支持的话,就获得数据库级的悲观升级锁。相当于Java Persistence中的LockModeType.READ。如果数据库方言不支持
select...for update选项,这个模式就透明的退回到LockMode.READ。
LockMode.UPGRADE_NOWAIT--与LockMode.UPGRADE相同,但如果支持的话,就使用select...for update nowait。他禁用了等待并发所释放,因为如果无法获得锁,就立即抛出锁异常。
如果数据库SQL方言不支持nowait选择,这个模式就透明的退回到LockMode.UPGRADE。
LockMode.FORCE--在数据库中强制增加对象的版本,来表明他已经被当前事务修改。相当于Java Persistence中的LockModeType.WRITE。
LockMode.WRITE--当hibernate已经在当前事务中写到一个行时,就自动获得他。(内部模式)
默认情况下load()和get()使用NONE。
◆未授权读取(Read Uncommitted):允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个数据则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。
◆授权读取(Read Committed):允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
◆可重复读取(Repeatable Read):禁止不可重复读取和脏读取,但是有时可能出现幻影数据。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
◆序列化(Serializable):提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。
如果一个事务读取由另一个还没有被提交的事务进行的改变,就发生脏读取(dirty read)。这很危险,因为由其他事务进行的改变随后
可能回滚,并且低一个事务可能编写无效的数据。
如果一个事物读取一个行两次,并且每次读取不同的状态,就会发生不可重复读取(unrepeatable read)。例如,另一个事务可能已经
写到这个行,并已经在两次读取之间提交。
不可重复读取的一个特殊案例是二次丢失更新问题(second lost updates problem)。想象两个并发事务都读取一个行:一个写到行并
提交,然后第二个也写到行并提交。有第一个事务所做的改变丢失了。
幻读(phantom read)发生在一个事务执行一个查询两次,并且第二个结果集包括第一个结果集中不可见的行,或者包括已经删除的行时。
这种情况是由另一个事务在两次查询执行之间插入或者删除行照成的。
ANSI事务隔离性级别:
允许脏读取但不允许丢失更新的系统,要在读取未提交(read uncommitted)的隔离性中操作。如果一个为提交事务已经写到一个行,
另一个事务就不可能再写到这个行。但任何事务都可以读取任何行。这个隔离性级别可以在数据库管理系统中通过专门的写锁来实现。
允许不可重复读取但不允许脏读取系统,要实现读取提交(read committed)的事务隔离性。这可以用共享的读锁和专门的写锁来实现。
读取事务不会阻塞其他事务访问行。但是为提交的鞋事务隔赛所有其他的事务访问改行。
在可重复读(repeatable read)隔离性模式中操作的系统既不允许不可重复读取,也不允许脏读取。幻读可能发生。
可序列化 (serializable)提供最严格的事务隔离性。这个隔离性级别模拟连续的事务执行,好像事务是连续的一个接一个的执行,而不是
并发的执行。
选择隔离性级别:
首先,消除读取为提交隔离性级别。在不同的事务中使用一个为提交的事务变化是很危险的。一个事务的回滚或者失败将影响其他的并发事务。
甚至由一个终止回滚的事务所做的改变也可能在任何地方被提交,因为他们可能读取,然后由另一个成功的事务传播!
其次,大多数应用程序不需要可序列化的隔离级别(幻读通常不成问题),并且这个隔离性往往难以伸缩。
这样就把选择读取提交还是可重复读取交给你了。
Hibernate配置选项:
hibernate.connection.isolation = 4
1-读取为提交隔离性
2-读取提交隔离性
3-可重复读取隔离性
4-可序列化隔离性
乐观并发控制:
乐观的方法始终假设一切都会很好,并且很少有冲突的数据修改。在编写数据时,乐观并发控制值在工作单元结束时才出现错误。多用户
的应用程序通常默认为使用读取提交隔离性级别的乐观并发控制和数据库连接。只有适当的时候才获得额外的隔离性保证;这种方法保证
了最佳的性能和可伸缩性。
对于如何处理对话中这些第二个事务中的丢失更新,你有3种选择:
最晚提交生效(last commit wins)--两个事物提交都成功,第二个提交覆盖第一个变化。没有显示错误消息。
最早提交生效(fist commit wins)--对话A的事务被提交,并且在对话B中提交事务的用户得到一条错误信息。用户必须获取新数据来
重启会话,并在此利用没有失效的数据完成对话的所有步骤。
合并冲突更新(merge conflicting updates)--第一个修改被提交,并且对话B中的事务在提交时终止,带有一条错误消息。但是失败
的对话B用户可以选择性的应用变化,而不是再次在对话中完成所有工作。
在hibernate中启用版本控制:
hibernate提供自动的版本控制。每个实体实例都有一个版本,它可以是一个数字或者是一个时间戳。当对象修改时,hibernate就增加
他的版本号,自动比较版本,如果侦测到冲突就抛出异常。因此,你给所有持久化的实体类都添加这个版本属性,来启动乐观锁:
version:
view
plaincopy
to clipboardprint?
public class Item implements Serializable
{
private Integer
itemId;
private String
itemName;
/**
版本控制 */
private int version;
配置:
view
plaincopy
to clipboardprint?
<class name="Item" table="ITEM">
<id
name="itemId" column="ITEM_ID" type="integer">
<generator class="native"/>
</id>
<!--
XML格式的version属性映射必须立即放在标示符属性映射之后 -->
<version
name="version" access="field" column="OBJ_VERSION" />
<property
name="itemName" type="string" column="ITEM_NAME"/>
</class>
代码:
view
plaincopy
to clipboardprint?
/**
* 报错:
* org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
* @param itemName
*/
private static void update1(String
itemName) {
Configuration
configuration = new Configuration().configure();
SessionFactory
sessionFactory = configuration.buildSessionFactory();
Session
session = sessionFactory.openSession();
Transaction
tr = session.beginTransaction();
Item
item = (Item) session.get(Item.class, 1);
item.setItemName(itemName);
Session
session1 = sessionFactory.openSession();
Transaction
tr1 = session1.beginTransaction();
Item
item1 = (Item) session1.get(Item.class, 1);
// 一样会报错
// item1.setItemName(itemName);
item1.setItemName(itemName+"1");
tr.commit();
session.close();
//org.hibernate.StaleObjectStateException:
//Row
was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
tr1.commit();
session1.close();
sessionFactory.close();
}
/**
* 提交成功
* @param itemName
*/
private static void update2(String
itemName) {
Configuration
configuration = new Configuration().configure();
SessionFactory
sessionFactory = configuration.buildSessionFactory();
Session
session = sessionFactory.openSession();
Transaction
tr = session.beginTransaction();
Item
item = (Item) session.get(Item.class, 1);
item.setItemName(itemName);
tr.commit();
session.close();
Session
session1 = sessionFactory.openSession();
Transaction
tr1 = session1.beginTransaction();
Item
item1 = (Item) session1.get(Item.class, 1);
item1.setItemName(itemName+"1");
tr1.commit();
session1.close();
sessionFactory.close();
}
timestamp(理论上讲,时间戳更不安全。且在集群环境中更不安全):
view
plaincopy
to clipboardprint?
public class Item implements Serializable
{
private Integer
itemId;
private String
itemName;
/**
版本控制 */
private Date
lastUpdated;
配置:
view
plaincopy
to clipboardprint?
<class name="Item" table="ITEM">
<id
name="itemId" column="ITEM_ID" type="integer">
<generator class="native"/>
</id>
<timestamp
name="lastUpdated" access="field" column="LAST_UPDATED"></timestamp>
<property
name="itemName" type="string" column="ITEM_NAME"/>
</class>
代码:
view
plaincopy
to clipboardprint?
/**
* 报错:
* org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
* @param itemName
*/
private static void update1(String
itemName) {
Configuration
configuration = new Configuration().configure();
SessionFactory
sessionFactory = configuration.buildSessionFactory();
Session
session = sessionFactory.openSession();
Transaction
tr = session.beginTransaction();
Item
item = (Item) session.get(Item.class, 1);
item.setItemName(itemName);
Session
session1 = sessionFactory.openSession();
Transaction
tr1 = session1.beginTransaction();
Item
item1 = (Item) session1.get(Item.class, 1);
// 一样会报错
// item1.setItemName(itemName);
item1.setItemName(itemName+"1");
tr.commit();
session.close();
//org.hibernate.StaleObjectStateException:
//Row
was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
tr1.commit();
session1.close();
sessionFactory.close();
}
/**
* 提交成功
* @param itemName
*/
private static void update2(String
itemName) {
Configuration
configuration = new Configuration().configure();
SessionFactory
sessionFactory = configuration.buildSessionFactory();
Session
session = sessionFactory.openSession();
Transaction
tr = session.beginTransaction();
Item
item = (Item) session.get(Item.class, 1);
item.setItemName(itemName);
tr.commit();
session.close();
Session
session1 = sessionFactory.openSession();
Transaction
tr1 = session1.beginTransaction();
Item
item1 = (Item) session1.get(Item.class, 1);
item1.setItemName(itemName+"1");
tr1.commit();
session1.close();
sessionFactory.close();
}
hibernate检查机制:
org.hibernate.persister.entity.AbstractEntityPersister.check:
view
plaincopy
to clipboardprint?
protected boolean check(int rows,
Serializable id, int tableNumber,
Expectation expectation, PreparedStatement statement) throws HibernateException
{
try {
expectation.verifyOutcome(
rows, statement, -1 );
}
catch(
StaleStateException e ) {
if (
!isNullableTable( tableNumber ) ) {
if (
getFactory().getStatistics().isStatisticsEnabled() ) {
getFactory().getStatisticsImplementor()
.optimisticFailure(
getEntityName() );
}
throw new StaleObjectStateException(
getEntityName(), id );
}
return false;
}
catch(
TooManyRowsAffectedException e ) {
throw new HibernateException(
"Duplicate
identifier in table for: " +
MessageHelper.infoString( this,
id, getFactory() )
);
}
catch (
Throwable t ) {
return false;
}
return true;
}
org.hibernate.jdbc.Expectation:
view
plaincopy
to clipboardprint?
/**
*
Perform verification of the outcome of the RDBMS operation based on
*
the type of expectation defined.
*
*
@param rowCount The RDBMS reported "number of rows affected".
*
@param statement The statement representing the operation
*
@param batchPosition The position in the batch (if batching)
*
@throws SQLException Exception from the JDBC driver
*
@throws HibernateException Problem processing the outcome.
*/
public void verifyOutcome(int rowCount,
PreparedStatement statement, int batchPosition) throws SQLException,
HibernateException;
view
plaincopy
to clipboardprint?
/**
*
我用了两个factory,也是可以提交成功的
*
你想想,这证明了什么
*
@param itemName
*/
private static void update3(String
itemName) {
Configuration
configuration = new Configuration().configure();
SessionFactory
sessionFactory = configuration.buildSessionFactory();
Session
session = sessionFactory.openSession();
Transaction
tr = session.beginTransaction();
Item
item = (Item) session.get(Item.class, 1);
item.setItemName(itemName);
tr.commit();
session.close();
sessionFactory.close();
Configuration
configuration1 = new Configuration().configure();
SessionFactory
sessionFactory1 = configuration1.buildSessionFactory();
Session
session1 = sessionFactory1.openSession();
Transaction
tr1 = session1.beginTransaction();
Item
item1 = (Item) session1.get(Item.class, 1);
item1.setItemName(itemName+"11");
tr1.commit();
session1.close();
sessionFactory1.close();
}
版本控制的自动管理:
涉及目前被版本控制的Item对象的每一个DML操作都包括版本检查。例如,假设在一个工作单元中,你从版本为1的数据库中加载一个Item。然后修改它的其中一个值类型属性。当持久化
上下文被清除时,hibernate侦测到修改,并把Item的版本增加到2。然后执行SQL UPDATE使这一修改在数据库中永久化:
update item set intial_price = '11.23',obj_version=2 where item_id = 123 and obj_version=1
如果另一个并发的工作单元更新和提交了同一个行,obj_version列就不包含值1,行也不会被更新。hibernate检查由jdbc驱动器返回这个语句所更新
的行数---在这个例子中,被更新的行数为0--并抛出StaleObjectStateException。加载Item时呈现的状态,清除时不再在数据库中呈现;
因而,你真正使用失效的数据,必须通知应用程序的用户。可以捕捉这个异常,并显示一条错误消息,或者显示帮助用户给应用程序重启会话
的一个提示框。
如果你想要禁用对特定值类型属性或者集合的自动增加,就用optimistic-lock="false"属性映射他。
没有版本号或者时间戳的版本控制(如果你用的遗留表,不方便再添加字段):
view
plaincopy
to clipboardprint?
<!--
org.hibernate.MappingException: optimistic-lock=all|dirty requires dynamic-update="true" -->
<!--
dirty的区别在于,只有当某个属性共同修改时,才报错;all是只要有修改就报错 -->
<class name="Item" table="ITEM" optimistic-lock="all" dynamic-update="true">
<id name="itemId" column="ITEM_ID" type="integer">
<generator class="native"/>
</id>
<property name="itemName" type="string" column="ITEM_NAME"/>
</class>
用Java Persistence版本控制:
version:
实体:
view
plaincopy
to clipboardprint?
@Entity
public class Item implements Serializable
{
@Id
@GeneratedValue
@Column(name="ITEM_ID")
private Integer
itemId;
@Column(name="ITEM_NAME")
private String
itemName;
/**
版本控制 */
@Version
@Column(name="OBJ_VERSION")
private int version;
更新:
view
plaincopy
to clipboardprint?
private static void update(String
itemName) {
EntityManagerFactory
factory = Persistence.createEntityManagerFactory("partner4java");
EntityManager
em = factory.createEntityManager();
EntityTransaction
tr = em.getTransaction();
tr.begin();
Item
item = em.find(Item.class, 1);
item.setItemName(itemName);
EntityManager
em1 = factory.createEntityManager();
EntityTransaction
tr1 = em1.getTransaction();
tr1.begin();
Item
item1 = em1.find(Item.class, 1);
item1.setItemName(itemName+"he");
tr.commit();
em.close();
//Caused
by: javax.persistence.OptimisticLockException
tr1.commit();
em1.close();
factory.close();
}
报错:
Caused by: javax.persistence.OptimisticLockException
optimisticLock:
实体:
view
plaincopy
to clipboardprint?
@Entity
//optimistic-lock=all|dirty
requires dynamic-update="true"
@org.hibernate.annotations.Entity(
optimisticLock=OptimisticLockType.ALL,
dynamicUpdate=true)
public class Item implements Serializable
{
@Id
@GeneratedValue
@Column(name="ITEM_ID")
private Integer
itemId;
@Column(name="ITEM_NAME")
private String
itemName;
//版本检测排除下面的属性
@OptimisticLock(excluded=true)
private Double
price;
public Item()
{
super();
}
调用:
view
plaincopy
to clipboardprint?
/**
*
报错
*
Caused by: javax.persistence.OptimisticLockException
*
@param itemName
*/
private static void update1(String
itemName) {
EntityManagerFactory
factory = Persistence.createEntityManagerFactory("partner4java");
EntityManager
em = factory.createEntityManager();
EntityTransaction
tr = em.getTransaction();
tr.begin();
Item
item = em.find(Item.class, 1);
item.setItemName(itemName);
EntityManager
em1 = factory.createEntityManager();
EntityTransaction
tr1 = em1.getTransaction();
tr1.begin();
Item
item1 = em1.find(Item.class, 1);
item1.setItemName(itemName+"he");
tr.commit();
em.close();
//Caused
by: javax.persistence.OptimisticLockException
tr1.commit();
em1.close();
factory.close();
}
/**
*
不会报错
*
@param price
*/
private static void update2(Double
price) {
EntityManagerFactory
factory = Persistence.createEntityManagerFactory("partner4java");
EntityManager
em = factory.createEntityManager();
EntityTransaction
tr = em.getTransaction();
tr.begin();
Item
item = em.find(Item.class, 1);
item.setPrice(price);
EntityManager
em1 = factory.createEntityManager();
EntityTransaction
tr1 = em1.getTransaction();
tr1.begin();
Item
item1 = em1.find(Item.class, 1);
item1.setPrice(price+1D);
tr.commit();
em.close();
tr1.commit();
em1.close();
factory.close();
}
获得额外的隔离性保证:
1、显示的悲观锁
当我们关注应用程序的课伸缩性时。例如,对标量查询可能需要可重复读取。
不是把所有的数据库事务转换为一个更高的、不可伸缩的隔离性级别,而是在必要时,在Hibernate Session中使用lock()方法获得
更高的隔离性:
view
plaincopy
to clipboardprint?
/**
*
session.lock(item, LockMode.UPGRADE);
*
@param itemName
*/
private static void update1(String
itemName) {
Configuration
configuration = new Configuration().configure();
SessionFactory
sessionFactory = configuration.buildSessionFactory();
Session
session = sessionFactory.openSession();
Transaction
tr = session.beginTransaction();
Item
item = (Item) session.get(Item.class, 1);
item.setItemName(itemName);
//指定锁级别
session.lock(item,
LockMode.UPGRADE);
Session
session1 = sessionFactory.openSession();
Transaction
tr1 = session1.beginTransaction();
Item
item1 = (Item) session1.get(Item.class, 1);
item1.setItemName(itemName+"1");
//停在这步,因为碰到for
update的锁了,他要等到tr提交了之后才会执行,但是,我们这个demo当然无法执行到这步
tr1.commit();
session1.close();
tr.commit();
session.close();
sessionFactory.close();
}
/**
*
只锁定指定列的数据,不会锁定表
*
但是,以前这种方式是悲观锁,会锁定整个表,如果指定锁定某一行,如下:
*
Item item = (Item) session.get(Item.class, 1, LockMode.UPGRADE);
*
@param itemName
*/
private static void update2(String
itemName) {
Configuration
configuration = new Configuration().configure();
SessionFactory
sessionFactory = configuration.buildSessionFactory();
Session
session = sessionFactory.openSession();
Transaction
tr = session.beginTransaction();
Item
item = (Item) session.get(Item.class, 1);
item.setItemName(itemName);
//指定锁级别
session.lock(item,
LockMode.UPGRADE);
Session
session1 = sessionFactory.openSession();
Transaction
tr1 = session1.beginTransaction();
Item
item1 = (Item) session1.get(Item.class, 2);
item1.setItemName(itemName+"1");
//执行通过
tr1.commit();
session1.close();
tr.commit();
session.close();
sessionFactory.close();
}
Java Persistence 的hibernate也实现了这个技术,但是规范并没有要求实现这个。
hibernate锁模式:
LockMode.NONE--别到数据库中去,除非对象不处于任何高速缓存中。
LockMode.READ--绕过所有高速缓存,并执行版本检查,来验证内存中的对象是否与当前数据中村中的版本相同。
LockMode.UPGRADE--绕过所有高速缓存,做一个版本检查,如果支持的话,就获得数据库级的悲观升级锁。相当于Java Persistence中的LockModeType.READ。如果数据库方言不支持
select...for update选项,这个模式就透明的退回到LockMode.READ。
LockMode.UPGRADE_NOWAIT--与LockMode.UPGRADE相同,但如果支持的话,就使用select...for update nowait。他禁用了等待并发所释放,因为如果无法获得锁,就立即抛出锁异常。
如果数据库SQL方言不支持nowait选择,这个模式就透明的退回到LockMode.UPGRADE。
LockMode.FORCE--在数据库中强制增加对象的版本,来表明他已经被当前事务修改。相当于Java Persistence中的LockModeType.WRITE。
LockMode.WRITE--当hibernate已经在当前事务中写到一个行时,就自动获得他。(内部模式)
默认情况下load()和get()使用NONE。
相关文章推荐
- hibernate之控制并发访问(乐观并发控制之外获得额外的隔离性保证--使用LockMode.UPGRADE的实例)
- hibernate之控制并发访问(事务隔离性问题)
- Hibernate--多事务并发访问控制
- hibernate之控制并发访问(ANSI事务隔离性级别)
- hibernate之控制并发访问(选择隔离性级别)
- hibernate之控制并发访问(乐观并发控制之在hibernate中启用版本控制)
- hibernate之控制并发访问(设置隔离性级别)
- hibernate之控制并发访问(乐观并发控制之理解乐观策略)
- Hibernate温习--多事务并发访问控制
- Hibernate多事务并发访问控制
- hibernate之控制并发访问(乐观并发控制之在hibernate中启用版本控制--数字版本实例)
- (十一)Hibernate之多事务并发访问控制
- hibernate之控制并发访问(乐观并发控制之在hibernate中启用版本控制--时间戳版本实例)
- hibernate之控制并发访问(乐观并发控制之外获得额外的隔离性保证)
- JPA/Hibernate:基于版本的乐观锁并发控制
- 控制并发访问资源 -- Semaphore
- 数据访问模式:数据并发控制(Data Concurrency Control)
- Hibernate二级缓存的并发访问策略
- MySQL中的事务及读写锁实现并发访问控制
- JavaEE Tutorials (13) - 使用锁定控制对实体数据的并发访问