什么是事务?
事务,是工作中的基本逻辑单元。一个事务可能包括数据库的一系列操作,而一个完整的事务保证这些操作都被正确地同步到数据库中,不会发生数据不完整或者错误,或者受到其他干扰。
事务的4个特性
原子性:即作为一个事务,它是不可分割的整体,只有全部操作都完成了,才算结束;其中任何一个操作执行失败,整个事务都要撤销。
一致性:即事务不能破坏数据库的完整性和业务逻辑的一致性。事务不管成功还是失败,事务结束时,整个数据库内部数据都是正确的。
隔离性:即在并发的数据库操作时,不同的事务操作相同的数据时,每个事务都有自己的完整的数据空间。一个事务不会看到或拿到另一个事务正修改到一半的数据,这些数据要么是另一个事务修改前的,要么是另一个事务修改后提交的。这个特性,是为了在数据库并发操作过程中,保证所有并发操作的正确性。
持久性:即事务成功提交后,数据就被永久地保存进数据库,重新启动数据库系统后,数据仍然保存在数据库系统中。
Hibernate支持的3种事务工厂
JDBCTransactionFactory:产生数据库JDBC的事务。通过JDBC的API来开始和结束事务,适用于任何Java环境,这是Hibernate的默认值。
JTATransactionFactory:产生托管环境下的事务。如果在上下文环境中存在运行的事务,则由容器管理,否则,启动一个新事务并使用bean管理。通过JTA来开始和事务结束,适用于JavaEE托管环境。
CMTTransactionFactory:产生托管环境下的事务。委托容器管理JTA事务。在Java应用程序中不必编写开始和结束事务的程序代码。
并发控制
更新丢失(Lost Update)
两个事务都更新同一行数据,如果一个事务执行失败,导致事务回滚,会把另一个事务的更新结果也丢失
脏读(Dirty read)
如果第二个事务读取了第一个事务修改的但是还没有提交的数据,结果第一个事务最后发生回滚,数据更新没有被保存到数据库中,那么第二个事务读到的数据就是不正确的,是被第一个事务弄脏的数据,所以这种现象叫脏读。
不可重复读(Unrepeatable read)
两个事务都读取同一行数据,一个事务修改数据并提交,另一个事务也修改数据并提交
幻读(Phantom read)
一个事务执行两次查询,结果发现第二次查询的结果比第一次查询的结果数据多了,这可能是因为另一个事务在两次查询之间插入了新行,这就是所谓的“幻读”问题。
数据库隔离级别
1.读未提交级别(readuncommitted isolation)
如果设置为这个级别,当某个事务新插入了数据,或者更新修改了数据,但是这个事务还没有提交。那么另一个事务能看到这些新插入或者修改的但未提交的数据。如果第一个事务结果是提交失败,这样第二个事务select语句的结果就会发生脏读。所以设置为读未提交级别不能避免脏读问题。
2.读已提交级别(read committed isolation)
如果设置为这个级别,那么一个事务只能读取被提交了的数据。所以如果一个数据在被另一个事务更改而事务未被提交,那么其他是看不到的。这能够避免脏读问题。但是,如果一个事务执行了两次同一个查询,但是在两次查询期间,其他事务更新或删除了数据,那么两个查询结果会不一样,可能出现不可重复读问题。
3.可重复读级别(repeatable read isolation)
如果设置为这个级别,那么一个事务对数据执行读取或写入操作时,锁定了这些数据行,其他事务不能对数据执行删除或者更新操作。所以它可以避免读已提交级别无法避免的不可重复读问题。但是,其他事务仍然可能插入新的数据,所以有可能发生幻读问题。
4.串行化事务(serializable)
执行多个串行事务和一个一个串行地执行,他们的结果一样,它使用完全与其他事务隔离的事务。比如:如果一个事务已经在操作某行数据了,那么另一个事务如果也想操作这行,那么不管它的操作会不会与第一个事务的操作发生冲突,它都必须停下来,等待第一个事务结束,这个事务才能恢复运行。
隔离级别的配置
在Hibernate的配置文件中,可以设置Hibernate.connection.isolation属性来设置全局的隔离级别。
[html] view plaincopyprint?
<session-factory>
<property name="hibernate.connection.isolation">4</property>
</session-factory>
<session-factory>
<propertyname="hibernate.connection.isolation">4</property>
</session-factory>
乐观锁(optimistic locking)
乐观锁并没有对任何操作加锁,它只是在事务更新记录时,对事务进行检查,看自从上次读取这条记录后,它是否被其他事务修改。如果被修改了,那么这个事务就会回滚。检查的开销比完成隔离的开销低多了,而且它只有在发现修改冲突时才会发生回滚的开销。
1.使用version实现乐观锁
(在数据库中,使用version字段来跟踪记录的修改。每次修改记录,记录的version都会递增,那么事务在更新记录时,会先检查记录此时的版本号是否与这个事务上次读取记录的版本号一致,如果不一样,说明数据已经被别的事务修改了)
注意:
使用version需要在数据库中增加一个整数类型的version列,相应的对应的实体对象模型中需要增加version属性;version元素必须跟在id元素的后面
使用version设置乐观锁需要在class元素下使用version元素
<version name="?" column="?">
2.使用时间戳实现乐观锁
(每次修改记录时,都会记录下这个修改操作的时间戳。那么事务在跟新记录时,先检查记录此时的时间戳是否与这个事务上读取记录的时间戳一致,如果不一致,说明数据已经被别的事务修改了)
注意:
使用timestamp设置乐观锁需要在class元素下使用timestamp元素,在数据库增加一个timestamp类型的相应字段,在对应的实体对象模型中需要增加相应字段;timestamp元素必须跟在id元素后面
<timestamp name="?" column="?">
3.对比字段值实现乐观锁
(此方法不推荐使用)
<class name="" dynamic-update="true"optimistic-lock="dirty" table="">
dynamic-update="true":Hibernate产生的sql update语句只更新变化字段。
optimistic-lock="dirty":where子句只包含被更新过的属性.
optimistic-lock="all":where子句包含对象的所有属性.
悲观锁
悲观锁是在更新数据时把记录锁住,防止其他事务读写这个记录。所以无论事务间操作是否有冲突,使用悲观锁都会有额外的开销
注意:
使用悲观锁的事务会锁住记录,其他访问这个记录的事务会被一直阻塞,直到这个使用锁的事务提交或者释放了锁。但是,悲观锁不阻止增加新记录,所以执行同样的查询,可能返回不同数量的结果
悲观锁的多个锁机制
LockMode.FORCE:类似于LockMode.UPGRADE,只是对于带版本号的实体,它迫使实体的版本递增。
LockMode.NONE:不加锁,这是默认的锁定模式。但是,如果检索的数据不是从缓存中得到,而是需要从数据库中读取时,会自动地采用READ共享锁模式。
LockMode.READ:当Hibernate被设置为可重复读级别或者可串行化级别时,Hibernate将自动使用这种锁模式来读取数据。
LockMode.WRITE:当向数据库中插入或者更新数据时,Hibernate会自动使用这种锁。这种锁机制只是作为内部使用,不能作为load()和lock()的参数。
LockMode.UPGRADE:使用悲观锁。默认情况下,数据库使用共享锁来执行查询操作。使用了悲观锁后,Hibernate检索数据时使用的SQL语句如下: select * from Tabel where .... for update for update子句代表为检索操作加上锁,这个时候,其他事务,无论读操作还是写操作,都无法访问相关的数据
LockMode.UPGRADE_NOWAIT:类似于LockMode.UPGRADE,在Oracle中select for update nowait子句中,nowait说明了执行该select语句时,如果不能立刻获得悲观锁,那么不会等待其他事务释放锁,而是抛出一个异常。