`
JavaCrazyer
  • 浏览: 3008707 次
  • 性别: Icon_minigender_1
  • 来自: 河南
社区版块
存档分类

Hibernate温习(11)--多事务并发访问控制

阅读更多


   在并发环境,一个数据库系统会同时为各种各样的客户程序提供服务,也就是说,在同一时刻,会有多个客户程序同时访问数据库系统,这多个客户程序中的失误访问数据库中相同的数据时,如果没有采取必要的隔离机制,就会导致各种各样的并发问题的发生,这些并发问题可归纳为以下几类


  多个事务并发引起的问题:


1)第一类丢失更新:撤消一个事务时,把其它事务已提交的更新的数据覆盖了。

2)脏读:一个事务读到另一个事务未提交的更新数据。

3) 幻读:一个事务执行两次查询,但第二次查询比第一次查询多出了一些数据行。

4)不可重复读:一个事务两次读同一行数据,可是这两次读到的数据不一样。

5)第二类丢失更新:这是不可重复读中的特例,一个事务覆盖另一个事务已提交的更新数据。

 事务隔离级别


为了解决多个事务并发会引发的问题。数据库系统提供了四种事务隔离级别供用户选择。

1) Serializable:串行化。隔离级别最高

2) Repeatable Read:可重复读。--MySQL默认是这个

3) Read Committed:读已提交数据。--Oracle默认是这个

4) Read Uncommitted:读未提交数据。隔离级别最差。--sql server默认是这个

数据库系统采用不同的锁类型来实现以上四种隔离级别,具体的实现过程对用户是透明的。用户应该关心的是如何选择合适的隔离级别。

对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed,它能够避免脏读,而且具有较好的并发性能。

每个数据库连接都有一个全局变量@@tx_isolation,表示当前的事务隔离级别。JDBC数据库连接使用数据库系统默认的隔离级别。在Hibernate的配置文件中可以显示地设置隔离级别。每一种隔离级别对应着一个正整数。

Read Uncommitted: 1

Read Committed: 2

Repeatable Read: 4

Serializable: 8

在hibernate.cfg.xml中设置隔离级别如下:

    <session-factory>

<!-- 设置JDBC的隔离级别 -->

<property name="hibernate.connection.isolation">2</property>

</session-factory>

设置之后,在开始一个事务之前,Hibernate将为从连接池中获得的JDBC连接设置级别。需要注意的是,在受管理环境中,如果Hibernate使用的数据库连接来自于应用服务器提供的数据源,Hibernate不会改变这些连接的事务隔离级别。在这种情况下,应该通过修改应用服务器的数据源配置来修改隔离级别。


并发控制

当数据库系统采用Red Committed隔离级别时,会导致不可重复读和第二类丢失更新的并发问题,在可能出现这种问题的场合。可以在应用程序中采用悲观锁或乐观锁来避免这类问题。

     乐观锁(Optimistic Locking):


乐观锁假定当前事务操纵数据资源时,不会有其他事务同时访问该数据资源,因此不作数据库层次上的锁定。为了维护正确的数据,乐观锁使用应用程序上的版本控制(由程序逻辑来实现的)来避免可能出现的并发问题。

唯一能够同时保持高并发和高可伸缩性的方法就是使用带版本化的乐观并发控制。版本检查使用版本号、 或者时间戳来检测更新冲突(并且防止更新丢失)。


三种方式。

1)Version版本号

2)时间戳

3)自动版本控制。

这里不建议在新的应用程序中定义没有版本或者时间戳列的版本控制:它更慢,更复杂,如果你正在使用脱管对象,它则不会生效。



通过在表中及POJO中增加一个version字段来表示记录的版本,来达到多用户同时更改一条数据的冲突

数据库脚本: 


create table studentVersion (id varchar(32),name varchar(32),ver int); 


 POJO


package Version;

public class Student {
  private String id;
  private String name;
  private int version;
public String getId() {
    return id;
}
public void setId(String id) {
    this.id = id;
}
public String getName() {
    return name;
}
public void setName(String name) {
    this.name = name;
}
public int getVersion() {
    return version;
}
public void setVersion(int version) {
    this.version = version;
}


 Student.hbm.xml


<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- 
    Mapping file autogenerated by MyEclipse - Hibernate Tools
-->
<hibernate-mapping>
<class name="Version.Student" table="studentVersion" >
    <id name="id" unsaved-value="null">
      <generator class="uuid.hex"></generator>
    </id>
    <!--version标签必须跟在id标签后面-->
    <version name="version" column="ver" type="int"></version>
    <property name="name" type="string" column="name"></property>  
</class>

</hibernate-mapping>
 


 Hibernate.cfg.xml


<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
          "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<!-- Generated by MyEclipse Hibernate Tools.                   -->
<hibernate-configuration>

<session-factory>
    <property name="connection.username">root</property>
    <property name="connection.url">
        jdbc:mysql://localhost:3306/schoolproject?characterEncoding=gb2312&amp;useUnicode=true
    </property>
    <property name="dialect">
        org.hibernate.dialect.MySQLDialect
    </property>
    <property name="myeclipse.connection.profile">mysql</property>
    <property name="connection.password">1234</property>
    <property name="connection.driver_class">
        com.mysql.jdbc.Driver
    </property>
    <property name="hibernate.dialect">
        org.hibernate.dialect.MySQLDialect
    </property>
    <property name="hibernate.show_sql">true</property>
    <property name="current_session_context_class">thread</property>
    <property name="jdbc.batch_size">15</property>
    <mapping resource="Version/Student.hbm.xml" />
</session-factory>

</hibernate-configuration> 

 



测试代码


package Version;


import java.io.File;
import java.util.Iterator;
import java.util.Set;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class Test {


    public static void main(String[] args) {

        String filePath=System.getProperty("user.dir")+File.separator+"src/Version"+File.separator+"hibernate.cfg.xml";
        File file=new File(filePath);
        System.out.println(filePath);
        SessionFactory sessionFactory=new Configuration().configure(file).buildSessionFactory();
        Session session=sessionFactory.openSession();
        Transaction t=session.beginTransaction();
        
        Student stu=new Student();
        stu.setName("tom11");
        session.save(stu);
        t.commit();
      
        
        /*
         * 模拟多个session操作student数据表
         */
        
        Session session1=sessionFactory.openSession();
        Session session2=sessionFactory.openSession();
        Student stu1=(Student)session1.createQuery("from Student s where s.name='tom11'").uniqueResult();
        Student stu2=(Student)session2.createQuery("from Student s where s.name='tom11'").uniqueResult();
        
        //这时候,两个版本号是相同的
        System.out.println("v1="+stu1.getVersion()+"--v2="+stu2.getVersion());
        
        Transaction tx1=session1.beginTransaction();
        stu1.setName("session1");
        tx1.commit();
        //这时候,两个版本号是不同的,其中一个的版本号递增了
        System.out.println("v1="+stu1.getVersion()+"--v2="+stu2.getVersion());
        
        Transaction tx2=session2.beginTransaction();
        stu2.setName("session2");
        tx2.commit();
        
        
        
    }

}
 


 测试结果


Hibernate: insert into studentVersion (ver, name, id) values (?, ?, ?)
Hibernate: select student0_.id as id0_, student0_.ver as ver0_, student0_.name as name0_ from studentVersion student0_ where student0_.name='tom11'
Hibernate: select student0_.id as id0_, student0_.ver as ver0_, student0_.name as name0_ from studentVersion student0_ where student0_.name='tom11'
v1=0--v2=0
Hibernate: update studentVersion set ver=?, name=? where id=? and ver=?
v1=1--v2=0
Hibernate: update studentVersion set ver=?, name=? 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): [Version.Student#4028818316cd6b460116cd6b50830001]

可以看到,第二个“用户”session2修改数据时候,记录的版本号已经被session1更新过了,所以抛出了红色的异常,我们可以在实际应用中处理这个异常,例如在处理中重新读取数据库中的数据,同时将目前的数据与数据库中的数据展示出来,让使用者有机会比较一下,或者设计程序自动读取新的数据



注意:

    要注意的是,由于乐观锁定是使用系统中的程式来控制,而不是使用资料库中的锁定机制,因而如果有人特意自行更新版本讯息来越过检查,则锁定机制就会无效,例如在上例中自行更改stu的version属性,使之与资料库中的版本号相同的话就不会有错误,像这样版本号被更改,或是由于资料是由外部系统而来,因而版本资讯不受控制时,锁定机制将会有问题,设计时必须注意。

    如果手工设置stu.setVersion()自行更新版本以跳过检查,则这种乐观锁就会失效,应对方法可以将Student.java的setVersion设置成private

 





如果是注解方式的,POJO应为这样


@Entity
@Table(name="student ")
public class Student {
  @Id   @GeneratedValue
  private Integer id;
  private String name;
  private Integer version;
public Integer getId() {
    return id;
}
public void setId(Integer id) {
    this.id = id;
}
public String getName() {
    return name;
}
public void setName(String name) {
    this.name = name;
}
public Integer getVersion() {
    return version;
}
public void setVersion(Integer version) {
    this.version = version;
}

}


 悲观锁控制(Pressimistic Locking)


悲观锁...他依赖于数据库机制,在整个过程中将数据库锁定,其他任何用户都不能读取或者修改..通俗一点说,先读的用户就一直占用这个资源,直到结束.这里的例子,我们说一个账户信息.一共有三个字段,一个id,一个name,还有一个money,表示的是账户余额.很明显,当一个人在操作这个账户的时候,其他人是不能操作这个账户的,否则就会造成数据的不一致.

 悲观锁的一般实现方式是在应用程序中显式采用数据库系统的独占锁来锁定数据库资源。在如下几种方式时可能显示指定锁定模式为LockMode.UPGRADE

1)调用session的get()或load()方法

2)调用session的lock()方法

3)调用Query的setLockMode()方法


实体类

Acount.java


package com.test.model;
public class Acount
{
 private int id;
 private String name;
 private int money;
 
 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 int getMoney()
 {
  return money;
 }
 public void setMoney(int money)
 {
  this.money = money;
 }

}


Account.hbm.xml


<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- 
    Mapping file autogenerated by MyEclipse Persistence Tools
-->
<hibernate-mapping package="com.test.model">
    <class name="Acount" table="Acount" >
     <id name="id">
      <generator class="native"></generator>
     </id>
     <property name="name"></property>
     <property name="money"></property>
    </class>
</hibernate-mapping>

 上面两个都没啥可以说的,算是最简单的hibernate实体类和配置文件了...

我们使用两个测试方法来模拟两个用户.同样,我们使用JUnit4


package com.test.junit;

import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import org.hibernate.LockMode;

import com.test.model.Acount;
import com.test.util.HibernateSessionFactory;

public class extendsTest
{
 @Test
 public void test1()
 {
  Session session = HibernateSessionFactory.getSession();
  Transaction tx = session.beginTransaction();
  
  Acount acount = (Acount)session.load(Acount.class, 1,LockMode.UPGRADE);//注意,这里的最后那个参数..他将锁定这个操作.
  System.out.println(acount.getName());
  System.out.println(acount.getMoney());
  
  acount.setMoney(acount.getMoney() - 20000);
  
  tx.commit();
  session.close();
 }
 
 @Test
 public void test2()
 {
  Session session = HibernateSessionFactory.getSession();
  Transaction tx = session.beginTransaction();
  
  Acount acount = (Acount)session.load(Acount.class, 1,LockMode.UPGRADE);
  System.out.println(acount.getName());
  System.out.println(acount.getMoney());
  
  acount.setMoney(acount.getMoney() - 20000);
  
  tx.commit();
  session.close();
 }
}

 

具体的做法是,我们在test1方法的事务提交前设置一个断点,然后我们用debug模式运行.然后,我们再直接运行test2方法.我们可以看到下面这样




 
 也就是说,后面那个用户就一直在等待,.只要第一个用户没有提交.他就无法继续运行....这就是悲观锁...


悲观锁的缺点显而易见..他是彻底的占用了这个资源....所以,我们一般需要用这个来解决短事务,也就是周期比较短的事务..否则,第一个用户如果一直不操作,后面任何用户都无法进行...


经过测试,还有一个结论就是:使用悲观锁,session的load方法的延迟加载机制失效


总结:尽管悲观锁能够方式丢失更新和不可重复读之类并发问题的发生的,但是它影响并发性能。因此不建议使用悲观锁,尽量使用乐观锁

 

  • 大小: 138.9 KB
分享到:
评论

相关推荐

    hibernate-jpa-2.1-api-1.0.0.final.jar.zip

    - **多版本并发控制**: JPA 2.1引入了乐观锁和悲观锁策略,用于解决多线程环境下的数据一致性问题。 - **级联操作**: 支持在实体之间定义级联操作,如删除一个实体时自动删除与之关联的其他实体。 - **延迟加载**:...

    hibernate-jpa-2.0-api-1.0.1.Final.jar

    hibernate-jpa-2.0-api-1.0.1.Final.jar

    hibernate-jpa-2.0-api-1.0.1.Final-sources.jar

    hibernate-jpa-2.0-api-1.0.1.Final-sources.jar hibernate jpa 源代码

    hibernate-validator-5.0.0.CR2-dist.zip

    使用hibernate-validator 进行校验的jar包,里面包括了基础hibernate-validator-5.0.0.CR2.jar hibernate-validator-annotation-processor-5.0.0.CR2.jar 之外,还包括了el-api-2.2.jar javax.el-2.2.4等项目必不可...

    hibernate-commons-annotations-4.0.1.Final.jar

    hibernate-commons-annotations-4.0.1.Final.jar

    Spring与Hibernate集成---声明式事务

    总结来说,Spring与Hibernate的集成使我们可以轻松地在应用程序中管理数据访问和事务,同时保持代码的整洁和模块化。声明式事务管理让开发者专注于业务逻辑,而无需关心事务控制的细节,提高了开发效率和代码质量。...

    hibernate-mapping-3.0.dtd

    hibernate-mapping-3.0.dtd 配置后,就会在xml中进行提示

    hibernate-core-5.0.11.Final.jar

    本文将重点探讨`hibernate-core-5.0.11.Final.jar`的核心源码,帮助读者深入理解其内部机制,并提供实用的应用指导。 一、Hibernate Core概述 Hibernate Core是Hibernate框架的基础部分,它包含了ORM的核心功能,...

    hibernate-release-5.0.7.Final的所有jar包

    Hibernate是Java领域中一款著名...总之,`hibernate-release-5.0.7.Final`版本的jar包集合是Java开发者进行ORM编程的强大工具,涵盖了从数据持久化到事务处理、验证和缓存等多个方面,极大地提升了开发效率和代码质量。

    Hibernate 离线的配置方法(hibernate-mapping-3.0.dtd)

    因为Hibernate在读出hbm.xml文件时需要通过网络读取到hibernate-mapping-3.0.dtd 文件。 如果没有网络不能正常工作。 所以提供上述文件。 以及hibernate-mapping-3.0.dtd,hibernate-configuration-3.0.dtd提供下载...

    hibernate-release-5.0.0.Final(1).zip

    本次我们将深入探讨的版本是`hibernate-release-5.0.0.Final(1)`,这个版本在Hibernate的生命周期中具有重要的地位,它引入了多项新特性和改进,旨在提升开发效率和性能。 一、Hibernate概述 Hibernate是Java领域中...

    hibernate-cglib-repack-2.1_3.jar

    《深入理解Hibernate-CGLIB-2.1_3在Java开发中的应用》 在Java开发领域,Hibernate作为一款强大的对象关系映射(ORM)框架,极大地简化了数据库操作。而CGLIB则是一个代码生成库,它允许开发者在运行时动态创建子类...

    hibernate-release-4.2.4.Final.zip

    9. **事务管理**:Hibernate支持自动和手动的事务管理,可以很好地处理并发和数据一致性问题。开发者可以通过Session的beginTransaction、commit和rollback方法来控制事务的边界。 10. **缓存机制**:Hibernate提供...

    hibernate-release-5.0.7.Final.zip

    而"hibernate-release-5.0.7.Final"目录则可能包含源码、文档、示例项目等,是深入学习和研究Hibernate的好材料。 总之,Hibernate 5.0.7.Final为Java开发带来了极大的便利,通过传智播客黑马程序员2016版的框架,...

    hibernate-release-5.3.2.Final

    1. Session接口:作为与数据库交互的主要接口,Session负责保存、更新、删除和检索对象,支持事务管理和并发控制。 2. Criteria API:提供了一种比HQL更面向对象的查询方式,可以动态构建查询条件,避免硬编码SQL...

    hibernate源码release-4.1.4.Final版

    在Java EE环境下,推荐使用JTA进行分布式事务控制;而在Java SE环境中,通常采用JDBC事务管理。 六、持久化操作 Hibernate提供了save()、update()、merge()和delete()方法来处理对象的持久化状态。其中,save()适合...

    hibernate-extensions-2.1.3.zip ,middlegen for hibernate

    而`hibernate-extensions-2.1.3.zip`则包含了一系列对Hibernate原生功能的扩展,这些扩展为开发人员提供了更多便利和灵活性。Middlegen,作为`Middlegen-Hibernate-r5.zip`的主要内容,是针对Hibernate的一个实用...

    最新 hibernate-release-4.2.13.Final.tgz

    在“hibernate-release-4.2.13.Final”目录下,你会发现一系列的核心jar包,如hibernate-core.jar,它包含了Hibernate的主要功能,包括实体管理、查询API、事务处理等。此外,还有hibernate-entitymanager.jar,专注...

    hibernate-entitymanager-3.4.0.GA.rar

    hibernate-entitymanager-3.4.0.GA.rar hibernate-entitymanager-3.4.0.GA.rar hibernate-entitymanager-3.4.0.GA.rar hibernate-entitymanager-3.4.0.GA.rar hibernate-entitymanager-3.4.0.GA.rar hibernate-...

Global site tag (gtag.js) - Google Analytics