zoukankan      html  css  js  c++  java
  • 数据库乐观锁、悲观锁的初步认识,以及hibernate对它们的支持实现

    从“锁”说起

      学过数据库理论的都知道,关系型数据库有ACID特征。其中I是Isolation的简写,表示数据库并发进行事务时,只有顺序,串联执行的事务才会最终反映到数据库中,即是每个成功执行的事务都是孤立分离的,它们之间不会相互影响。这个Isolation就是由数据库的锁(lock)机制来实现的。所谓的锁,也叫读锁或者写锁(分别对应于读事务及写事务的情景),锁的作用就是防止数据库在并发进行读、写事务时出现数据过时或者无效的情况。对于这些并发的用户而言,同一时间内某个用户只能更新(读事务不会影响数据状态)被加上锁的数据记录(records),加上锁后能够确保数据不能被访问,只有释放了锁后才能访问。

    锁机制:悲观锁和乐观锁

      锁的类型有两种:悲观锁和乐观锁。悲观锁是当事务一开始就进行locking,被locked的table或者records都对外不可见,直到事务结束;乐观锁则是只有数据的更改变动向数据库提交时才进行的locking。悲观锁能够确保所有更改变动都正确提交到数据库,而乐观锁则有可能出现事务无法提交的情况(例如前一个用户已提交更改,后一个用户会提交失败,因为这时数据状态已经不是后一个用户原来读出来的那个了,已经被前一个用户更改了)。悲观锁能最大程度保证事务的隔离,但是占用数据库的开销太大(事务时间越长,开销就越大),在大并发是场景下是不可接受的。乐观锁机制在一定程度上解决了这个问题,但代价时更改不一定能够成功提交。

    Hibernate的实现

      注意,这里的Hibernate是指4.3.5.Final版本。无论是乐观锁还是悲观锁,Hibernate都是使用数据库的锁机制实现,不会对内存的对象进行锁;

    • 悲观锁:需要定义隔离级别(isolation level),然后依赖数据库的锁机制。LockMode定义了Hibernate请求的锁机制,如下:
    LockMode.WRITE

    当Hibernate进行updates or inserts a row时自动请求锁;

    LockMode.UPGRADE

    在数据库层面使用 SELECT ... FOR UPDATE 语句(语法视数据库不同而不同)

    LockMode.UPGRADE_NOWAIT

    类似UPGRAGE级别,不同的是此模式在事务不能提交时,立刻返回异常,而不是等待当前事务结束再进行等待的事务,适用于Oracle数据库

    LockMode.UPGRADE_SKIPLOCKED

    acquired upon explicit user request using aSELECT ... FOR UPDATE SKIP LOCKED in Oracle, orSELECT ... with (rowlock,updlock,readpast) in SQL Server.

    LockMode.READ

    acquired automatically when Hibernate reads data under Repeatable Read or Serializable isolation level. It can be re-acquired by explicit user request.

    LockMode.NONE

    The absence of a lock. All objects switch to this lock mode at the end of a Transaction. Objects associated with the session via a call to update()or saveOrUpdate() also start out in this lock mode.

      具体显式编码可通过以下三种方式请求锁级别:

      1.Session.load();//eg session.load(User.class, new Long(1), LockMode.UPGRADE);

      2.Session.lock();//eg session.lock(user, LockMode.UPGRADE);

      3.Query.setLockMode();//eg query.setLockMode("user", LockMode.UPGRADE);

    • 乐观锁

      在那种读操作多、写操作少的情况下,Hibernate的乐观锁能够提供一定程度的事务隔离。在遇到同样的数据先后被更改时,后更改提交的事务将会被告知冲突。

      Hibernate实现乐观锁的方法有两种:1.检测版本号;2.时间戳。

      检测版本号

    • 通过在要作为版本号列的字段或者对应的getter上面加上@Version注释。通过实现UserVersionType可以实现任意类型的版本号。默认情况下用户不能认为修改version字段的值,但是可以通过更改LockModeType.OPTIMISTIC_FORCE_INCREMENT或者LockModeType.PESSIMISTIC_FORCE_INCREMENT的值来实现手动增加版本号;

       

    //数据表:
    表名:
    person_table
    
    
    personId name age version
    1 Peter 20 4

    //Entity:
    @Entity
    @Table(name="person_table")
    public class Person {

    private Integer personId;

    private String name;

    private Integer age;

    private Integer version;

    @Id
    @GeneratedValue()
    public Integer getPersonId() {
    return personId;
    }

    public void setPersonId(Integer personId) {
    this.personId = personId;
    }

    @Column
    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    @Column
    public Integer getAge() {
    return age;
    }

    public void setAge(Integer age) {
    this.age = age;
    }

    @Version //这里是有version字段作为version列,并且让Hibernate自动增长值
    public Integer getVersion() {
    return version;
    }

    public void setVersion(Integer version) {
    this.version = version;
    }
    }


    //测试代码
    public
    class OptimisticLockTest { public static void main(String[] args) { SessionFactory sessionFactory = HibernateUtils.getSessionFactory(); Session session1 = sessionFactory.openSession(); Session session2 = sessionFactory.openSession(); Person person1 = (Person)session1.get(Person.class, new Integer(3)); Person person2 = (Person)session2.get(Person.class, new Integer(3)); System.out.println("before transation person1.getVersion = "+person1.getVersion()); System.out.println("before transation person2.getVersion = "+person2.getVersion()); session1.beginTransaction(); person1.setAge(25); session1.getTransaction().commit(); session2.beginTransaction(); person2.setAge(28); session2.getTransaction().commit(); } }
    输出:

    before transation person1.getVersion = 4
    before transation person2.getVersion = 4

    session2提交事务时,出现异常:

    Exception in thread "main" org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.entity.Person#3]
    at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:2541)
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3285)
    at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3183)
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3525)
    at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:159)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:463)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:349)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1222)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:425)
    at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
    at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:177)
    at com.test.OptimisticLockTest.main(OptimisticLockTest.java:28)

       查看session1更新的行,version由4变为5了,session2没有更新成功,因为它提交事务时原来的数据版本发生了变化,需要或者最新版本数据再更新。

    1. 配置文件的方式:使用<version />标签配置version字段,主要有以下几个相关属性:
    column

    The name of the column holding the version number. Optional, defaults to the property name.

    name

    The name of a property of the persistent class.

    type

    The type of the version number. Optional, defaults to integer.

    access

    Hibernate's strategy for accessing the property value. Optional, defaults to property.

    unsaved-value

    Indicates that an instance is newly instantiated and thus unsaved. This distinguishes it from detached instances that were saved or loaded in a previous session. The default value,undefined, indicates that the identifier property value should be used. Optional.

    generated

    Indicates that the version property value is generated by the database. Optional, defaults tonever.

    insert

    Whether or not to include the version column in SQL insert statements. Defaults to true, but you can set it to false if the database column is defined with a default value of 0.

      时间戳(Timestamp)

      当选择Date或者Calendar类型的字段作为version列时,Hibernate将会自动使用Timestamp作为version信息。例如

    @Entity
    public class Flight implements Serializable {
    ...
        @Version
        public Date getLastUpdate() { ... }
    }

      默认情况下,Hibernate读取数据库的时间作为version的时间戳,不过可以通过@org.hibernate.annotations.Source注释来控制读取的时间源:org.hibernate.annotations.SourceType.DB ;org.hibernate.annotations.SourceType.VM(来源JVM)。

      此外,也可以通过配置文件方式进行配置。例如:

    <timestamp
            column="timestamp_column"
            name="propertyName"
            access="field|property|ClassName"
            unsaved-value="null|undefined"
            source="vm|db"
            generated="never|always"
            node="element-name|@attribute-name|element/@attribute|."
    />
    相关的属性说明:
    column

    The name of the column which holds the timestamp. Optional, defaults to the property namel

    name

    The name of a JavaBeans style property of Java type Date or Timestamp of the persistent class.

    access

    The strategy Hibernate uses to access the property value. Optional, defaults to property.

    unsaved-value

    A version property which indicates than instance is newly instantiated, and unsaved. This distinguishes it from detached instances that were saved or loaded in a previous session. The default value of undefined indicates that Hibernate uses the identifier property value.

    source

    Whether Hibernate retrieves the timestamp from the database or the current JVM. Database-based timestamps incur an overhead because Hibernate needs to query the database each time to determine the incremental next value. However, database-derived timestamps are safer to use in a clustered environment. Not all database dialects are known to support the retrieval of the database's current timestamp. Others may also be unsafe for locking, because of lack of precision.

    generated

    Whether the timestamp property value is generated by the database. Optional, defaults tonever.

    不闻不若闻之,闻之不若见之,见之不若知之,知之不若行之
  • 相关阅读:
    创造别人想要的东西(2)----创业15问
    创造别人想要的东西(1)----创业的本质
    有点迷茫
    普林斯顿大学算法公开课(1)----介绍
    工业控制系统信息安全与信息安全、功能安全的区别【转自工控工程网】
    十条概念区分信息安全与隐私保护【转自安全牛】
    NBT(NetBIOS Over TCP)名称解析概述
    SMB协议概述
    PCI DSS合规建设ASV扫描介绍
    信息安全等级测评师(初级 技术)
  • 原文地址:https://www.cnblogs.com/lauyu/p/5042154.html
Copyright © 2011-2022 走看看