框架学习之Hibernate 第十节 事务原理与分析

1.事务

两种事务:

① JDBC事务:单个数据库的事务

一个SesisonFactory对应一个数据库,使用 JDBC 实现

常用代码的模板:

    Session session = null;
    Transaction tx =null;
    try {
        session = sessionFactory.openSession();
        tx = session.beginTransaction();
        //process
        tx.commit();
    } catch(HibernateException e){
        if(tx != null)tx.rollback();throw e;
    }finally {
        if (session != null)session.close();
    }
    connection.setAutoCommit(false);
    connection.commit();conn.rollback();

幻灯片41

②JTA 事务:跨数据库的事务

使用JTATransaction需要配置hibernate.transaction.factory_class参数,该参数缺省值是org.hibernate.transaction. JDBCTransactionFactory

当使用JTATransaction时需要将该参数改成org.hibernate.transaction.JTATransactionFactory,并配置jta.UserTransaction参数JNDI名

[JNDI:Java Naming and Directory Interface,Java命名和目录接口,是一组在Java应用中访问命名和目录服务的API。

命名服务将名称和对象联系起来,使得我们可以用名称访问对象。目录服务是一种命名服务,在这种服务里,对象不但有名称,还有属性。]

(Hibernate在启动JTATransaction时要用该值到JNDI的上下文Context中去找javax.transaction.UserTransaction)。

javax.transaction.UserTransactin tx = context.lookup(“jndiName”);

javax.transaction.UserTransactin tx = context.lookup(“jndiName”);
try{
    tx.begin();
    //多个数据库的session操作;
    //session1….
    //session2….
    tx.commit();
}catch(Exception e){
    tx.rollback(); throw e;
}

幻灯片42

2.Open Session in View

优点:实现了数据访问层 和 业务逻辑层 的脱离,更好的实现了三层架构

缺点:延长了Session和事务的时间,容易造成内存不够或者数据锁死的现象

Open session in view:在生成(渲染)页面时保持 session打开。

session context和事务边界[事务边界就是和事务有关的处理,包括事务打开,提交和回滚]

current_session_context_class属性来定义context(用sessionFactory.getCurrentSession()来获得session),其值为:

1.threadThreadLocal来管理Session实现多个操作共享一个Session,避免反复获取Session,并控制事务边界

此时session不能调用close,当commit或rollback的时候session会自动关闭(connection.release_mode:after_transaction)。

2.jta:由JTA事务管理器来管理事务(connection.release_mode:after_statement)。

测试代码:[ 注:一下代码经常是放在 J2EE 项目中,而不是J2SE,故下面代码在没有引入JavaEE包时会报错的 ]

package cn.itcast.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() {
        // TODO Auto-generated method stub

    }

    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); // 控制事务边界,避免反复获取session,这里保证了事务执行过程中session始终是打开的
            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 {
        // TODO Auto-generated method stub

    }

}

注意 HibernateUtils中要添加:

    private static ThreadLocal session = new ThreadLocal();

    public static Session getThreadLocalSession() {
        Session s = (Session) session.get();
        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);
        }
    }

ThreadLocal  就是当前线程,通过它可以得到 当前线程的 session 对象

3.事务中的悲观锁和乐观锁

问题:A和B同时打开了 一个编辑页面,然后A将原来的名称name改为了name1,并提交,保存到数据库中了

接着B也将原来的名称改了,改成了name2,并提交,也保存到数据库中了。经过了两个人的操作,A的操作没有任何作用,他的操作被B覆盖掉了

更有甚者,如果他们修改的是不同的地方,B修改名称,A修改内容,那么A的操作完全是白费的

简单地说就是:两个不同的线程的session同时操作了同一条数据,并进行了修改,而且修改后的结果不同,那么谁后提交谁的操作才是有效的操

悲观锁由数据库来实现;乐观锁hibernate用version和timestamp来实现

悲观锁:当A打开编辑时,就把这条数据加上锁

缺点:锁住了之后其他人就不能操作了,一定要等到A提交了才可以

乐观锁:给数据加上一个版本号,每当数据有了更新,就增加版本号。  Hibernate实现了乐观锁定功能,只需要在映射文件中配置一下version就好了

优点:有效的防止了上面的情况,如果提交时检测到提交的数据的版本号低于数据库中的数据的版本号,那么提交就会失败

这种情况下,谁先提交那么谁的提交就会成功!后提交的只能重新打开,重新修改

还有另外一种乐观锁的实现方式就是时间戳,version 中 type 是 timestamp,但是存在时间精度的问题,不如使用version(integer)的效果好

使用version,测试代码:

首先在People类中添加 version 属性,并提供get和set方法

然后再people映射文件中添加: 以integer方式为例

<version name="version" type="integer"></version>

最后,测试类:VersionTest

package com.yinger.main;

import org.hibernate.Session;
import org.hibernate.Transaction;

import com.yinger.domain.Name;
import com.yinger.domain.People;
import com.yinger.util.HibernateUtils;

public class VersionTest {

    public static void main(String[] args) {
        People p = addPeople();
        System.out.println("---------");
        testTransaction(p.getId());
        System.out.println("---------");
        p = getPeople(p.getId());
        System.out.println(p.getName().getFirstName());
    }

    private static void testTransaction(int id) {
        Session s1 = HibernateUtils.getSession();
        Transaction tx1 = s1.beginTransaction();//一个事务开启了
        People p1 = (People)s1.get(People.class, id);
        
        Session s2 = HibernateUtils.getSession();;
        Transaction tx2 = s2.beginTransaction();//又开启了另一个事务,并且操作的是同一条数据
        People p2 = (People)s2.get(People.class, id);
        
        p2.getName().setFirstName("firstName 2");
        p1.getName().setFirstName("firstName 1");
        
        tx2.commit();
        tx1.commit();
        s1.close();
        s2.close();
    }

    public static People getPeople(int id) {
        People p= (People)HibernateUtils.get(People.class, id);
        return p;
    }

    public static People addPeople() {
        People p = new People();
        Name n = new Name();
        n.setFirstName("firstName");
        n.setLastName("lastName");
        p.setName(n);
        HibernateUtils.add(p);
        return p;
    }

}

测试结果:

log4j:WARN No appenders could be found for logger (org.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: insert into People (version, first_name, last_name, id) values (?, ?, ?, ?)
---------
Hibernate: update People set version=?, first_name=?, last_name=? where id=? and version=?
Hibernate: update People set version=?, first_name=?, last_name=? where id=? and version=?
Exception in thread "main" org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.yinger.domain.People#1]
    at org.hibernate.persister.entity.BasicEntityPersister.check(BasicEntityPersister.java:1456)
    at org.hibernate.persister.entity.BasicEntityPersister.update(BasicEntityPersister.java:1999)
    at org.hibernate.persister.entity.BasicEntityPersister.updateOrInsert(BasicEntityPersister.java:1923)
    at org.hibernate.persister.entity.BasicEntityPersister.update(BasicEntityPersister.java:2163)
    at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:75)
    at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:239)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:223)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:137)
    at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:274)
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:675)
    at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:293)
    at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:86)
    at com.yinger.main.VersionTest.testTransaction(VersionTest.java:34)
    at com.yinger.main.VersionTest.main(VersionTest.java:15)

数据库中数据:

id  version first_name  last_name

1    1    firstName 2    lastName

改变一下两个事务提交的顺序,重新测试

tx1.commit();
tx2.commit();
数据库中的数据:

id version first_name last_name

1 1 firstName 1   lastName

 
从结果中可以看出:谁先提交那么谁的操作就是有效的,后者是无法提交的,因为他所操作的数据的版本号低于数据库中的数据的版本号

这个就是乐观锁的方式解决上述问题

幻灯片43

原文地址:https://www.cnblogs.com/yinger/p/2145413.html