三颗纽扣

世界上最宽广的是海洋,比海洋更宽广的是天空,比天空更宽广的是人的胸怀

导航

Hibernate乐观锁真的会抛出异常吗?

关于Hibernate 悲观锁和乐观的论述,G一下大把,来源大多一致,示例代码好像差不多,据说乐观锁不使用数据库锁机制,并且都贴出一段乐观锁代码,启动两个事务修改同一条记录,据说将抛出异常,然而我突然就觉得很不可思议,如果不使用数据库锁机制,也没有内部锁机制,两个事务如果先后提交的话,数据库貌似不一定会提交失败吧。

下面是抄来的代码,据说会抛出异常:

public void update(){ 
    //开启事务tx1 
    Session session1 = HibernateUtil.getSession();           
    Transaction tx1 = session1.beginTransaction(); 
    Users users1 = (Users) session1.get(Users.class, 1);           //获取id为1的用户 
     
    //开启事务tx2 
    Session session2 = HibernateUtil.getSession(); 
    Transaction tx2 = session2.beginTransaction(); 
    Users users2 = (Users) session2.get(Users.class, 1);         //获取id为1的用户 
     
    users1.getName().setFirstName("first name1");   
    users2.getName().setFirstName("first name2"); 
     
    tx1.commit();             //..........1 
    tx2.commit();             //..........2 
 
    session1.close(); 
    session2.clear(); 
     
} 

        执行以上代码,代码将在.....2处抛出StaleObjectStateException异 常,并指出版本检查失败。

 我也自己写了一个:

    @Test
    public void test03 () throws Exception {
        init();
        ChrTask task = new ChrTask();
        task.inputFile = "test";
        task.x1Output = "x1";
        task.doOutput = "do";
        task.dfOutput = "df";
        task.id = "id";
        HibernateSessionFactory.getSession().insert(ChrTaskManager.ACTIVE_QUEUE, task);
        HibernateSessionFactory.closeSession();
        
        StatelessSession s1 = HibernateSessionFactory.getStatelessSession();
        
        Transaction t1 = s1.beginTransaction();
        ChrTask tt1 = (ChrTask) s1.get(ChrTaskManager.ACTIVE_QUEUE, task.id);
        tt1.state = 1;
        s1.update(ChrTaskManager.ACTIVE_QUEUE, tt1);
        System.in.read();

        StatelessSession s2 = HibernateSessionFactory.getStatelessSession();
        Transaction t2 = s2.beginTransaction();
        ChrTask tt2 = (ChrTask) s2.get(ChrTaskManager.ACTIVE_QUEUE, "id"); 
        tt2.state = 2;
        s2.update(ChrTaskManager.ACTIVE_QUEUE, tt2);
        System.in.read();
        
        t1.commit();
        s1.close();
        t2.commit(); 
        s2.close();
    }

然而很不幸的是,并没有在 t2.commit() 处抛出异常,而是执行到 ChrTask tt2 = (ChrTask) s2.get(ChrTaskManager.ACTIVE_QUEUE, "id");  这里的时候,日志打印出相应的 sql 语句后,就block住了,只能终止程序。显然,在前一个事务提交前,后面的 select 都不允许了,这一定是使用了某种排他锁机制,说不定是数据库的表级锁定。不过保险起见,我又做了个简单的测试,写了两个测试用例,分到两个进程去跑。

    @Test
    public void test01 () throws Exception {
        init();
        ChrTask task = new ChrTask();
        task.inputFile = "test";
        task.x1Output = "x1";
        task.doOutput = "do";
        task.dfOutput = "df";
        task.id = "id";
        HibernateSessionFactory.getSession().insert(ChrTaskManager.ACTIVE_QUEUE, task);
        HibernateSessionFactory.closeSession();
        
        StatelessSession s1 = HibernateSessionFactory.getStatelessSession();
        
        Transaction t1 = s1.beginTransaction();
        ChrTask tt1 = (ChrTask) s1.get(ChrTaskManager.ACTIVE_QUEUE, task.id);
        tt1.state = 1;
        s1.update(ChrTaskManager.ACTIVE_QUEUE, tt1);
        System.in.read();
        t1.commit();
        
        s1.close();
    }
    
    @Test
    public void test02 () throws Exception {
        HibernateSessionFactory.getSession();
        StatelessSession s2 = HibernateSessionFactory.getStatelessSession();
        Transaction t2 = s2.beginTransaction();
        ChrTask tt2 = (ChrTask) s2.get(ChrTaskManager.ACTIVE_QUEUE, "id");
        tt2.state = 2;
        s2.update(ChrTaskManager.ACTIVE_QUEUE, tt2);
        System.in.read();
        t2.commit();
        s2.close();
    }
    

这个和前面的没什么不同,不过是把两个事务分到两个进程中去,用命令行输入来控制两个进程的同步,保证在一个事务提交前,另一个事务已经启动,然而结果很意外,在eclipse 下直接分别启动两个测试用例,由于 test01 先启动,它暂停在 System.in.read() 处,test02则同样block在 ChrTask tt2 = (ChrTask) s2.get(ChrTaskManager.ACTIVE_QUEUE, "id"); 处,打印出select 语句后就卡住了,在test01按下回车后test02才继续,但是,但是,两个进程都顺利执行完毕了!!!没有抛出任何异常,这是显然的,由于第二个事务的 select 是在第一个事务结束后才开始的,两个事务被串行化了。事实上,数据库中存在的记录也表明,state=2, version=2

如果在第一个事务update之前开始第二个事务的 select 呢?将 System.in.read() 移动到 s1.update 之前,test02 不变,还是先启动 test01,结果两个都挺在了 read() 上了,在 01 中按下回车后,它继续到 update 又被 block 住了,在 02 按下回车后,02 顺利结束,而 01 则抛出了异常,检测到事务冲突。但异常是在 update 处抛出的,并非在 commit 的时候。

这些测试中,之所以总是会在 select 或者 update 的时候被 block 住,应该还是数据库事务串行化起到了左右,当然,即使在没有事务的数据库平台或未启动事务的情况下执行并发修改,由于 update 操作在数据库层面是原子操作,所以后面的 update 一定会检测到数据变更从而引发异常。

posted on 2012-07-29 21:26  三颗纽扣  阅读(1127)  评论(0编辑  收藏  举报