JDBCTransaction
单个数据库(一个SesisonFactory对应一个数据库),由JDBC实现。
Session session = null;
Transaction tx =null;
try {
session = sessionFactory.openSession();
tx = session.beginTransaction();//相当于JDBC的connection.setAutoCommit(false);
//process
tx.commit();//相当于JDBC的connection.commit();
} catch(HibernateException e){
if(tx != null)tx.rollback();//相当于JDBC的connection.rollback();
throw e;
}finally {
if (session != null)session.close();
}
JTATransaction(分布式事务,垮数据库,比如说银行转账,就需要这种跨数据库的事务来操作)
可以简单的理解成跨数据库的事物,由应用JTA 容器实现;使用JTATransaction需要配置hibernate.transaction.factory_class参数,该参数缺省值 是org.hibernate.transaction. JDBCTransactionFactory,当使用JTATransaction时需要将该参数改成org.hibernate.transaction.JTATransactionFactory,并配置jta.UserTransaction参数JNDI名(Hibernate在启动JTATransaction时要用该值到JNDI的上下文Context中去找javax.transaction.UserTransaction)。
javax.transaction.UserTransactin tx = context.lookup(“jndiName”);
try{
tx.begin();
//多个数据库的session操作;
//session1….
//session2….
tx.commit();
}catch(Exception e){
tx.rollback();
throw e;
}
session context和事务边界
事务边界:即事务开启,提交,关闭这些代码的位置。
mvc三层架构的思想上,事务边界的处理应该放在业务逻辑层中,但是Transaction对象是dao层的,所以要采用spring这种框架来实现明确的mvc分层。
Open session in view解决事务边界问题和数据懒加载问题。
Open session in view带来的问题:
session和transaction的时间延长。如果对数据加锁那就更长时间占用对象,session延长对内存的占用时间更长,对内存大小要求更大,且和数据库长时间交互,数据库的并发能力降低。
用current_session_context_class属性来定义context(用sessionFactory.getCurrentSession()来获得session),其值为:
1.thread:ThreadLocal来管理Session实现多个操作共享一个Session,避免反复获取Session,并控制事务边界,此时session不能调用close当commit或rollback的时候session会自动关闭(connection.release_mode:after_transaction)。Open session in view:在生成(渲染)页面时保持 session打开。
2.jta:由JTA事务管理器来管理事务(connection.release_mode:after_statement)。
用图理解:
从图中可以显示:客户端看到viewHTML时session还没有关闭,session要消息4后Filter中关闭。
把session的范围扩大到整个请求的范围内,这样在一方面很好的解决了懒加载session关闭的问题,另一方面一次请求变成了一个事务,那么用户做的更新操作对数据库来说都是一条sql语句了。
但是放大了session范围,如果网速比较慢,那么session就会一直打开,一级缓存的数据得不到释放,还有事务本应当在业务逻辑层开启关闭的,也放大到了整个请求。
code:
HibernateUtil.java
package cn.itcast.hibernate;
import java.io.Serializable;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
//工具类一般不需要更改,所以final
public final class HibernateUtil {
private static SessionFactory sessionFactory;
private static ThreadLocal session = new ThreadLocal();//把ThreadLocal当成一个Map容器
//禁止别的类new
private HibernateUtil() {
}
static {
Configuration cfg = new Configuration();
cfg.configure();
sessionFactory = cfg.buildSessionFactory();
}
public static Session getThreadLocalSession() {
Session s = (Session) session.get();//ThreadLocal的key是默认的,可以把key理解为当前线程
if (s == null) {
s = getSession();
session.set(s);
}
return s;
}
public static void closeSession() {
Session s = (Session) session.get();
if (s != null) {
s.close();
session.set(null);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}
OpenSessionInView.java过滤器。负责从ThreadLocal中获得session和关闭session
package com.taobao.hibernate;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.hibernate.Session;
import org.hibernate.Transaction;
public class OpenSessionInView implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest arg0, ServletResponse arg1,
FilterChain arg2) throws IOException, ServletException {
Session session = null;
Transaction tx = null;
try {
session = HibernateUtil.getThreadLocalSession();
tx = session.beginTransaction();
arg2.doFilter(arg0, arg1);
tx.commit();
} catch (Exception e) {
if (tx != null)
tx.rollback();
throw new RuntimeException(e.getMessage(), e);
} finally {
HibernateUtil.closeSession();
}
}
public void init(FilterConfig arg0) throws ServletException {
}
}
然后在业务层可以处理多个业务数据,在dao层获得过滤器打开的session处理数据和数据库的交互。
悲观锁和乐观锁
悲观锁由数据库来实现,从读取的时候开始加锁,直到修改完成。
缺点:读取完以后,用户修改过程缓慢,造成同步性降低。
乐观锁hibernate用version和timestamp来实现。两人同时编辑时候,假设获取的版本号都为1,编辑完提交的时候,版本号为2,第一个提交完以后,数据库版本号为2,第二个提交的时候对比版本号就会报错。
code:
给Users加上版本号
Users.java
import java.util.Date;
public class Users {
private int id;
private String name;
private Date birthday;
private int ver;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public void setVer(int ver) {
this.ver = ver;
}
public int getVer() {
return ver;
}
}
Users.hbm.xml
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.taobao.hibernate.domain"> <class name="User"> <id name="id"> <generator class="native" /> </id> <version name="ver"/><!--版本号,时间戳必须写在id后面--><!--也可以使用<timestamp name=""/>,但version标签可以铜鼓type指定不同类型--> <!-- <property name="name" /> --> <component name="name"> <property name="firstName" column="first_name"/> <property name="lastName" column="last_name" /> </component> <property name="birthday" /> </class> </hibernate-mapping>
VersionTest.java
package com.taobao.hibernate;
import java.util.Date;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import com.taobao.hibernate.domain.Name;
import com.taobao.hibernate.domain.User;
public class VersionTest {
public static void main(String[] args) {
User user = new User();
user.setBirthday(new Date());
Name n = new Name();
n.setFirstName("firstName");
n.setLastName("lastName");
user.setName(n);
// 111
addUser(user);
// System.out.println("id: " + user.getId());
// 222
System.out.println("1111111");
update(user.getId());
}
static void update(int id) {
Session s1 = null;
s1 = HibernateUtil.getSession();
Transaction tx1 = s1.beginTransaction();
User user1 = (User) s1.get(User.class, id);
Session s2 = HibernateUtil.getSession();
Transaction tx2 = s2.beginTransaction();
User user2 = (User) s2.get(User.class, id);
user1.getName().setFirstName("new1 firstName");
user2.getName().setFirstName("new2 firstName");
tx1.commit();
tx2.commit();
s1.close();
s2.close();
}
static void addUser(User user) {
Session s = null;
Transaction tx = null;
try {
s = HibernateUtil.getSession();
tx = s.beginTransaction();
s.save(user);
// 333
// s.persist(user);
Name n = new Name();
n.setFirstName("firstName");
n.setLastName("lastName");
user.setName(n);
user.setBirthday(new Date());
tx.commit();
user.setBirthday(new Date());
tx = s.beginTransaction();
tx.commit();
// 444
} catch (HibernateException e) {
if (tx != null)
tx.rollback();
throw e;
} finally {
if (s != null)
s.close();
}
}
static void addUser1(User user) {
Session s = null;
Transaction tx = null;
try {
s = HibernateUtil.getSession();
tx = s.beginTransaction();
s.save(user);
tx.commit();
} finally {
if (s != null)
s.close();
}
}
}
拟两个线程同步操作,更新user
运行结果如下
Hibernate: insert into Users (ver, name, birthday) values (?, ?, ?)
Hibernate: select users0_.id as id0_0_, users0_.ver as ver0_0_, users0_.name as name0_0_, users0_.birthday as birthday0_0_ from Users users0_ where users0_.id=?
Hibernate: update Users set ver=?, name=?, birthday=? where id=? and ver=?
Hibernate: update Users set ver=?, name=?, birthday=? where id=? and ver=?
Exception in thread "main" org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [domain.Users#1]
第二个提交的事务内报错,数据库内字段修改为new2。