事务隔离
使用封锁技术,事务对申请的资源加锁,但是会影响数据库性能。根据数据对象封锁的程度,可以分成多种不同的事务隔离级别。
数据并发执行时,产生不一致的现象:
1,丢失更新(Lost Update)
两个事务读入同一数据并修改,然后提交修改,T2 提交的结果破坏了 T1 提交的结果,导致 T1 的修改丢失。
2, 不可重复读
事务T1 读取数据后,事务T2 执行了同一数据的跟新操作,使得事务 T1 无法再现前一次读取的结果。
事务1 读取某一数据后,事务2 对该数据作了修改,事务1 再次读取时,得到数据和前一次不一致。
① 事务1 读取某一些记录后,事务2 删除了同一数据源的部分数据,事务1 再次读取时,发现某些记录丢失。
① 事务1 读取某一些记录后,事务2 插入了同一数据源的新数据,事务1 再次读取时,发现某些记录增加。
3, 读“脏”数据
事务T1 修改某一数据,并将其写回物理数据库。事务T2 读取同一数据后,事务T1 由于某种原因被撤销,数据库将已经修改的数据恢复原值,导致事务T2 保持的数据和数据库中的数据产生了不一致。
ANSI SQL-99 标准定义了下列隔离级别:
Hibernate 在配置文件中声明事务的隔离级别,Hibenate 获取数据库连接后,将根据隔离级别自动设置数据库连接为指定的事务隔离级别。
<property name="connection.isolation">8</property>
● 未提交读(Read Uncommitted):隔离事务的最低级别,只能保证不会读取到物理上损坏的数据。Hibernate配置:1;允许产生:1,2,3
● 已提交读(Read Committed):常见数据库引擎的默认级别,保证一个事务不会读取到另一个事务已修改但未提交的数据。Hibernate配置:2;允许产生:1,2
● 可重复读(Repeatable Read):保证一个事务不能更新已经由另一个事务读取但是未提交的数据。相当于应用中的已提交读和乐观并发控制。Hibernate配置:4;允许产生:1
● 可串行化(Serializable):隔离事务的最高级别,事务之间完全隔离。系统开销最大。Hibernate配置:8;这种情况很容易造成死锁的问题,hibernate表现为:
Deadlock found when trying to get lock; try restarting transaction 2-3、并发控制类型 根据使用的锁定策略和隔离等级,可以把事务的并发控制分为两种: ① 悲观并发控制 用户使用时锁定数据。主要应用于数据争用激烈的环境中,以及发生并发冲突时用锁保护数据的成本低于回滚事务成本的环境中。 Hibernate 的悲观锁定不在内存中锁定数据,由底层数据库负责完成。 ② 乐观并发控制 用户读取数据时不锁定数据。当一个用户更新数据时,系统将进行检查该用户读取数据后其他用户是否更改了该数据,是则产生一个错误,一般情况下,收到错误信息的用户将回滚事务并重新开始。主要用户数据争用不大,且偶尔回滚事务的成本低于读取数据时锁定数据的成本的环境中。 Hibernate 中使用元素 version 和 timestamp 实现乐观并发控制模式的版本控制,并提供多种编程方式。版本是数据库表中的一个字段,可以是一个递增的整数,也可以是一个时间戳,它们对应 Java 持久化类的一个属性。事务提交成功后,Hibernate 自动修改版本号。如果另外一个事务同时访问同一数据,若发现提交前的版本号和事前载入的版本号有出入,则认为发生了冲突,事务停止执行,撤销操作,并抛出异常。应用程序必须捕捉该异常并做出一定的处理。 ⒈应用程序级别的版本控制 ⒉长生命周期会话的自动化版本控制 ⒊托管对象的自动化版本控制 ⒋定制自动化版本控制
二 实际应用中我们使用Read Committed作为我们的事务级别 也就是 已提交读(Read Committed),因为如果用户 有权限 修改一个 数据 ,那 那么 就可以提交这个事务,这是系统需要解决的权限问题,由于这种情况会造成第二类数据丢失的情况,因此要配备乐观锁的机制,这种事物方法使用比较多。下面 举例实验:
1,例如A持久化后会被事务关联(事务针对修改,插入,不针对查询,如果对于一般不需要修改的表,如字典表,可以不配置事务,而是配置二级缓存来提高性能)
class A代码(先不加乐观锁,会造成第二类数据丢失):
@Entity public class A { private int id; private int anum; private String aname; @Id @GeneratedValue(strategy=GenerationType.IDENTITY) public int getId() { return id; } //get..set }
hibernate配置:<property name="connection.isolation">2</property>
事先插入一条数据(a.setAnum(1);)
测试代码:
public static void test(){ new Thread(new Runnable(){//线程2,启动事务后不提交,然后让线程1启动 public void run() { Session session = HibernateSessionFactory.getSession(); Transaction tt = session.beginTransaction(); System.out.println("t2 statr...."); A a = (A) session.get(A.class, 1); System.out.println("未修改之前的num:"+a.getAnum()); a.setAnum(a.getAnum()+1); session.update(a); try { Thread.sleep(5000);//让出时间让线程1执行 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } tt.commit(); session.clear();//清除一级缓存 System.out.println("t2 cummit end...."); A a1 = (A) session.get(A.class, 1); System.out.println("修改之后的num:"+a1.getAnum()); session.close(); } }).start();//正确结果为1+1=2 new Thread(new Runnable(){ public void run() { Session session = HibernateSessionFactory.getSession(); Transaction tt = session.beginTransaction(); System.out.println("t1 statr...."); A a = (A) session.get(A.class, 1); a.setAnum(a.getAnum()+2); session.update(a); tt.commit(); System.out.println("1 cummit end...."); session.clear();//清除一级缓存 session.close(); } }).start();//正确结果为1+1=3 }
打印信息:
t2 statr.... Hibernate: select a0_.id as id0_0_, a0_.aname as aname0_0_, a0_.anum as anum0_0_ from A a0_ where a0_.id=?//让出时间让t1执行 t1 statr.... Hibernate: select a0_.id as id0_0_, a0_.aname as aname0_0_, a0_.anum as anum0_0_ from A a0_ where a0_.id=? 未修改之前的num:1 Hibernate: update A set aname=?, anum=? where id=? 1 cummit end.... Hibernate: update A set aname=?, anum=? where id=? t2 cummit end.... Hibernate: select a0_.id as id0_0_, a0_.aname as aname0_0_, a0_.anum as anum0_0_ from A a0_ where a0_.id=? 修改之后的num:2
可以看出他t1本来应该是3但是被t2覆盖
为此我们加上锁:
修改A(锁可以有两种,version,和时间锁,但是时间锁有个精确度问题)使用version:
private int version; ... @Version//使用version注释来表明版本控制 public int getVersion() { return version; }
测试(同样的测试代码):
打印信息:
t2 statr.... Hibernate: select a0_.id as id0_0_, a0_.aname as aname0_0_, a0_.anum as anum0_0_, a0_.version as version0_0_ from A a0_ where a0_.id=? t1 statr.... Hibernate: select a0_.id as id0_0_, a0_.aname as aname0_0_, a0_.anum as anum0_0_, a0_.version as version0_0_ from A a0_ where a0_.id=? 未修改之前的num:1 Hibernate: update A set aname=?, anum=?, version=? where id=? and version=? 1 cummit end.... Hibernate: update A set aname=?, anum=?, version=? where id=? and version=? Exception in thread "Thread-0" org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.eric.po.A#1] at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1792) at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2435) at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2335) at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2635) at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:115) at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:279) at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:263) at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:168) at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321) at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50) at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1027) at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:365) at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137) at com.eric.dao.Testcreate$1.run(Testcreate.java:139) at java.lang.Thread.run(Thread.java:619)
此时乐观所就起作用了,防止了数据的丢失。针对异常可自行捕获。