一.Hibernate事务处理方式
Hibernate是对JDBC的封装,本身不具备事务处理能力,它将事务处理交给底层的JDBC(默认)或者JTA处理,修改Hibernate.cfg.xml文件采用JTA作为事务处理:
默认配置如下,在<session-factory>中
1 <property name="hibernate.transaction.factory_class">
2 org.hibernate.transaction.JDBCTransactionFactory
3 </property>
JTA配置则改为org.hibernate.transaction.JTATransactionFactory
如果你在EJB中使用Hibernate,或者准备用JTA来管理跨Session的长事务,那么就需要使用JTATransaction.
Hibernate中基于JDBC的事务管理只是使用org.hibernate.Transaction封装JDBC的事务管理,使用方法如下:
1 //获得session
2 Transaction tx=null;
3 try{
4 tx=session.beginTransaction();//启动事务
5 Query query=session.createQuery("from Product p");
6
7 //操作数据
8 tx.commit();//提交事务
9 }catch (Exception e) {
10 e.printStackTrace();
11 if(tx.isActive()){
12 try{
13 tx.rollback(); //回滚事务
14 }catch (Exception e2) {
15 e2.printStackTrace();
16 }
17 }
18 }
二.锁
1.数据库中概念-事务隔离级别
(1).Read Uncommitted
一个事务可以读取另一个事务已更新且尚未提交的数据,但在另一个事务提交前不允许其他事务写入。这种隔离级别不可能出现更新丢失,但可能出现脏读。
(2).Read Committed
比(1)严格,某个事务仅可读取(不可修改或者删除)另一个事务已经提交的更新数据,不可用读取尚未提交的更新数据。这种隔离级别不可能出现脏读,但可能出现不可重复读和幻读。这是Oracle的默认隔离级别,其他事务可以修改这个事务中被读取的记录(未被更新),而不必等待事务结束。
(3).Repeatable Read
更严格,一个事务已读取的数据不允许其他事务写入,不可能出现不可重复读,但可能出现幻读。
(4).Serializable 所有事务序列化执行,不能并发。
目前大多数数据库的默认级别是Read Committed,可以在应用程序中解决可能出现的不可重复读等问题,如Hibernate使用悲观锁和乐观锁。Oracle中只支持Read Committed ,Serializable.SQL92中标准的设置事务隔离级别的语句如下:
set transaction isolation level read committed;
2.Hibernate中的锁定模式
- LockMode.NONE:不加锁(默认),读取数据时先从缓存读,没有则到数据库
- LockMde.READ:共享读锁,读数据越过缓存到数据库中。当底层数据库的事务隔离级别是Repeatable Read或者Serialzable时,Hibernate读取数据采用此。
- LockMode.WRITE:插入或更新数据时锁定,Hibernate会在写入数据时自动使用此。
- LockMode.UPGRADE:使用悲观锁,在Hibernate底层利用SQL中的for update加锁,加锁后无论读入还是写入的其他事务都无法访问。
- LockMode.UPGRADE_NOWAIT:类似(4),使用for update nowait子句,Oracle特有
- LockMode.FORCE 使用版本号方式实现乐观锁时可以强迫指定的持久化对象递增版本号。
如果数据库不支持某个锁定模式,则Hibernate选择一个合适的锁定模式替换而不抛出违例。除了Hibernate自动设置,还可在Session,Query,Criteria中显示设置。
3.悲观锁
“悲观”地认为每次读取或修改数据时,其他事务也在并发访问相同的数据,悲观锁对读取的数据锁定,其他事务必须等待释放锁才能访问。LockMode.UPGRADE和UPGRADE_NOWAIT表示使用悲观锁,这种锁完全基于数据库的锁机制实现(for update子句等提交给数据库,让数据库负责)。
1 Query query=session.createQuery("from Product p");
2 query.setLockMode("p", LockMode.UPGRADE);//必须在获取数据前
3 List<Product> list=query.list();
使用Session对象的方法
1 tx=session.beginTransaction();//启动事务
2 Product product=(Product)session.get(Product.class, new Integer(5));
3 session.lock(product, LockMode.UPGRADE);
4 tx.commit();//提交事务
或者
1 tx=session.beginTransaction();//启动事务
2 Product product=(Product)session.get(
3 Product.class, new Integer(5),LockMode.UPGRADE);
4 //操作数据
5 tx.commit();//提交事务
Hibernate输出SQL:
1 select
2 product0_.ID as ID4_0_,
3 product0_.CATEGORY_ID as CATEGORY2_4_0_,
4 product0_.NAME as NAME4_0_,
5 product0_.PRICE as PRICE4_0_,
6 product0_.DESCRIPTION as DESCRIPT5_4_0_
7 from
8 hib.pro product0_
9 where
10 product0_.ID=? for update
4.乐观锁
由应用程序实现的,乐观地认为数据库中的数据很少发生同时操作的问题。事务隔离级别设置为Read Commited下可能出现第二类丢失更新(即一方的更新被另一方的更新覆盖)。解决方法如下三种:
(1).先更新为主。事务A先提交,B在提交时会得到错误信息提交失败,必须重新获取数据后更新。(Hibernate推荐,使用版本号机制)
(2).后更新为主:事务A和B都可提交,B覆盖A的数据
(3).合并冲突的更新。A先提交,B更新时会得到错误信息提示检查所有字段,有选择性地更新没有冲突的字段。
5.版本号机制
数据库中有一个名为version的字段表示版本号,整形,长整型或Timestamp类型。读数据时读入版本号,更新时将提交数据的版本号与数据库中版本号比较,如果小于说明已经有别的事务更新过了(更新后版本号会加1),那么不予更新并抛出违例。
(1)增加version字段: alter table pro add column version int not null;
update pro set version=1;
(2)POJO类中增加字段
private Integer version;//不需要getter setter
(3).hbm.xml文件中
optimistic-lock="version"和 <version>标签
1 <class name="com.tazi.domin.Product" table="pro" catalog="hib" optimistic-lock="version">
2 <id name="id" type="java.lang.Integer">
3 <column name="ID" />
4 <generator class="identity" />
5 </id>
6 <version name="version" column="VERSION" access="field"/>
7 <property name="name" type="java.lang.String">
8 <column name="NAME" length="200" not-null="true" />
9 </property>
10 <property name="price" type="java.lang.Float">
11 <column name="PRICE" precision="12" scale="0" />
12 </property>
13 </class>
(4).程序
1 Transaction tx1=session1.beginTransaction();
2 Transaction tx2=session2.beginTransaction();//另一个session
3 Product product1=(Product)session1.get(Product.class, 1);
4 Product product2=(Product)session2.get(Product.class, 1);
5 product1.setName("新名字");
6 product2.setPrice(2.552f); //新价格
7 tx1.commit();
8 //版本号加一
9 tx2.commit();//提交失败
Hibernate输出:
1 Hibernate:
2 select
3 product0_.ID as ID4_0_,
4 product0_.VERSION as VERSION4_0_,
5 product0_.CATEGORY_ID as CATEGORY3_4_0_,
6 product0_.NAME as NAME4_0_,
7 product0_.PRICE as PRICE4_0_,
8 product0_.DESCRIPTION as DESCRIPT6_4_0_
9 from
10 hib.pro product0_
11 where
12 product0_.ID=?
13 Hibernate:
14 update
15 hib.pro
16 set
17 VERSION=?,
18 CATEGORY_ID=?,
19 NAME=?,
20 PRICE=?,
21 DESCRIPTION=?
22 where
23 ID=?
24 and VERSION=?
25 Exception in thread "main" org.hibernate.TransactionException: Transaction not successfully started
26 at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:131)
27 at com.tazi.dao.ProductDao.main(ProductDao.java:35)
但查看数据库中数据时,发现两个更新语句都生效了。就算session2的更新语句为setName(),也会发现session2的事务执行成功了,只是仍然报错。注:我实验的Hibernate3.
(5).使用时间戳
数据库字段类型和POJO属性类型改为Timestamp相应类型,映射文件中使用<timestamp>