session context和事务边界
用current_session_context_class属性来定义context(用sessionFactory.getCurrentSession()来获得session),其值为:
1,thread:ThreadLocal来管理Session实现多个操作共享一个Session,避免反复获取Session,并控制事务边界,此时session不能调用close。当commit或rollback的时候session会自动关闭。
Open session in view: 在生成(渲染)页面时保持session打开。
2,jta:有JTA事务管理器来管理事务
connection.release_mode:after_statement
悲观锁和乐观锁
悲观锁有数据库来实现:乐观锁hibernate用version和timestamp来实现。
悲观锁定,是在读取记录时将其锁定,对记录更新修改后,解锁释放,其他人才可以进行相关操作。这回导致一些问题,一个人读取了数据后会加锁,但是却并没有继续的操作,导致其他人也想读取相同的记录时就会受限,只能等前一个人操作完后才能进行。
而乐观锁,采取的是版本号和时间戳的方式。比如,两个人都可以读取数据记录,在相应的完成修改提交更新时,先提交的没有问题(当时数据的版本号并没有修改过,提交后会导致版本号增加),后提交的会遇到版本号问题。因为先提交的会导致版本号更新,新的版本号和后提交方的版本号不一致了,会导致后者的回滚,这会提示后者,数据已被更新,要么放弃更改,要么再次读取,然后更新。
示例代码:
User 类源代码如下:与以前的代码的区别是增加了一个版本属性
Public class User{
private int id;
private Name userName;
private Date birthday;
private int version;
}
映射文件的配置也要做相应的修改:
<hibernate-mapping package="hibernatetest">
<class name="User" table="user">
<id name="id">
<generator class="native"/>
</id>
<version name="version"/> //该元素必须位于id标签后
<component name="userName">
<property column="first_name" name="firstName"/>
<property column="last_name" name="lastName"/>
</component>
<property name="birthday"/>
</class>
</hibernate-mapping>
测试代码:
public class Main { public static void main(String[] args) { User user = new User(); user.setBirthday(new Date()); saveUser(user); updateUser(user.getId()); } public static void saveUser(User user) { Session session = HibernateUtil.getSession(); Transaction tx = session.beginTransaction(); Name name = new Name(); name.setFirstName("firstName"); name.setLastName("lastName"); user.setUserName(name); session.save(user); tx.commit(); session.close(); } public static void updateUser(int id) { Session s1 = HibernateUtil.getSession(); Transaction tx1 = s1.beginTransaction(); User user = (User)s1.get(User.class, id); user.getUserName().setFirstName("new1 first name");//修该数据 Session s2 = HibernateUtil.getSession(); Transaction tx2 = s2.beginTransaction(); User user2 = (User)s2.get(User.class, id); user2.getUserName().setFirstName("new2 first name");//修该数据 tx1.commit(); tx2.commit(); s1.close(); s2.close(); } }
打印输出的sql语句为:
Hibernate: insert into user (version, first_name, last_name, birthday) values (?, ?, ?, ?)
Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.first_name as first3_0_0_, user0_.last_name as last4_0_0_, user0_.birthday as birthday0_0_ from user user0_ where user0_.id=?
Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.first_name as first3_0_0_, user0_.last_name as last4_0_0_, user0_.birthday as birthday0_0_ from user user0_ where user0_.id=?
Hibernate: update user set version=?, first_name=?, last_name=?, birthday=? where id=? and version=?
Hibernate: update user set version=?, first_name=?, last_name=?, birthday=? where id=? and version=?
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [hibernatetest.User#1]
Exception in thread "main" org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [hibernatetest.User#1]
在updateUser方法中,提交了两个事务,产生了不一致性。后提交的事务被抛异常回滚。
数据库中表的结构为:
+----+---------+-----------------+-----------+---------------------+
| id | version | first_name | last_name | birthday |
+----+---------+-----------------+-----------+---------------------+
| 1 | 1 | new1 first name | lastName | 2009-02-08 14:32:43 |
+----+---------+-----------------+-----------+---------------------+
第一次保存数据时,版本号为0.更新后版本号升级为1.
Hibernate: update user set version=?, first_name=?, last_name=?, birthday=? where id=? and version=?
这条更新语句说明了hibernate内部是参照了id和version的。version不一致则会出现问题。
除了使用version标签,还可以使用timestamp标签。时间戳标签的类型只能是时间了,通过比较时间的先后来决定这次事务是否执行。但是在比较极端的情况下,可能在并发很高时,时间的差异很小到难以区分时,时间戳会失去作用。所以最稳妥的还是用版本来控制。version标签的type属性可以指定版本属性类型,除了为整型,还可以指定为时间戳,这样就和使用timestamp标签类似了。