操作数据库可能出现的三种副作用:
注意:幻读和不可重复读的区别在于,不可重复读着眼于同一条数据是否在第二次查询中被其他事务更新,幻读着眼于同一个表第二次查询时是否有新的记录被其他事务插入。脏读(Dirty Reads):一个事务读取了另一个事务未提交的数据。不可重复读(Non-reapeatable Reads):当一个事务再次读取曾经读过的数据时,发现要读的数据已经被另一个事务修改。幻读(Phantom Reads):当一个事务重新执行了一个查询时,返回的记录中包括其他事务提交的新记录。
为了避免上述3种副作用,在标准SQL中定义了如下4种隔离等级。
未提交读(Read Uncommited):最低等级的事务隔离。它仅仅能保证在读取数据的过程中不会读到非法数据。使用这种隔离机制,上述三种副作用都可能发生。已提交读(Read Commited):此级别保证了一个事务不会读到另一个事务已经修改但尚未提交的数据,避免了‘脏读’。可重复读(Repeatable Read ):此级别避免了‘脏读’和‘不可重复读’,也就是说使用了这种隔离等级,一个事务不可能读取到另一个事务已经修改但未提交的数据。可序列化(Serializable):最高的隔离级别。上面三种副作用都不可能发生。实际上这种隔离级别机制模拟了事务的串行执行。该隔离级别要付出高昂的性能代价,一般很少使用。
隔离等级 | 脏读 | 不可重复读 | 幻读 |
未提交读 | 可能 | 可能 | 可能 |
已提交读 | 不可能 | 可能 | 可能 |
可重复读 | 不可能 | 不可能 | 可能 |
可序列化 | 不可能 | 不可能 | 不可能 |
Hibernate所支持的事务管理:
Hibernate只是对JDBC、JTA事务的轻量级的封装,本身并不具备事务管理的能力。在事务管理层,Hibernate将其委托给了底层的JDBC和JTA(Java Transaction API)来管理事务。
Hibenate默认的事务处理机制是JDBC Transaction,但也可以使用JTA管理事务。要想使用JTA,必须在配置文件中设置相应的事务工厂类,代码如下:
<hibernate-configuration>
<property name="hibernate.transaction.factory-class">
org.hibernate.transaction.JTATransactionFactory
</property>
...
</hibernate-configuration>
JDBC事务管理:
基于JDBC的事务管理是最简单的方式,而Hibernate对其的封装也使得使用非常简单。用法如下:
//获取session对象
Session session = HibernateSessionFactory.getSession();
//开启事务
Transaction tx = session.beginTransaction();
...
//提交事务
tx.commit();
从JDBC层面上看,上面的代码实际上对应如下代码:
//获取连接对象
Connection conn = DriverManager.getConnection(...);
//设置为手动提交
conn.setAutoCommit(false);
...
//提交事务
conn.commit();
JTA事务管理:
JTA提供跨Session的事务管理能力,这是它和JDBC Transaction的最大区别。JDBC Transaction实际上是在Connection中实现的,事务的周期仅限于Connection的生命周期。而JTA事务管理则是由JTA容器实现的,JTA容器对当前加入的所有Connection进行调度,以实现其事务要求。因此,JTA的事务周期可以跨多个JDBC Connection的生命周期,而对于使用JTA管理事务的Hibernate而言,则可以跨多个Session。
学习锁(Locking)
在处理数据过程中,若不希望正在处理的数据由于外界因素改变,可以使用‘锁’。
Hibernate中支持两种锁机制:悲观锁(Pessimistic Locking),乐观锁(Optimistic Locking)。
悲观锁:该锁对来自外界的数据修改持悲观和谨慎的态度。在整个处理过程中数据将被锁定。悲观锁一般依赖于数据库本身的锁机制。在MySQL中,悲观锁的典型应用是使用for update子句:
select * from mytable where id = 1 for update;
在执行上面的SQL语句后,并在当前事务结束前,id等于1的记录中所有字段将被锁定,也就是在当前事务结束之前,在任何其他的事务中都无法修改id等于1的记录中的字段。
Hibernate中使用Query接口的setLockMode来获得悲观锁。
Hibernate支持如下6种锁机制:
LockMode.NONE:无加锁机制。
LockMode.READ:在读取记录时自动获取。
LockMode.WRITE:在插入和更新记录时自动获取。不能使用在Session的load和lock方法中。
LockMode.UPGRADE:利用数据库的for update子句加锁。
LockMode.FORCE:与LockMode.UPGRADE相似,只是它在数据库中通过强制增加对象版本,来表明它已经被当前事务修改。
LockMode.UPGRADE_NOWAIT:Oracle的特定实现,利用Oracle的for update nowait子句来加锁。如果是别的数据库,则会以for update子句加锁。
在Hibernate中除了Query的setLockMode(...)加锁外,还有一些其他方法,如下:
Criteria.setLockMode(...);
Session.load(...);
Session.lock(...);
Session.get(...);
乐观锁:悲观锁依赖于数据库本身的锁机制,但悲观锁也带来了性能上的巨大损失。而乐观锁一般是基于数据版本(version)实现的,因此对于同一个应用程序,它可以更好地实现锁机制。所谓数据版本,就是在表中表示version字段,字段类型可以说long,integer,short,timestamp或calendar。当一个事务中持久化某个对象时,并修改相应的属性值后,会将当前版本号和数据表的版本号进行比较,如果当前版本号大于version字段值,就正常更新记录。否则,提交请求被拒绝。