Hibernate中的事务

一.什么是事物?

数据库事务是指由一个或多个SQL语句组成的工作单元,这个工作单元中的SQL语句相互依赖,如果有一个SQL语句执行失败,就必须撤销整个工作单元,在并发环境中,多个事务同时访问相同的数据资源时,可能会造成各种并发问题。
通俗的说的话,事务就是一件事情,要么成功执行到底,要么回到起点,什么都不做。

二.事物的特性

原子性:正如原子时自然界最小颗粒,具有不可再分的特征一样。意思就是说,咱的事务是一个逻辑单元,不能再拆分了,比如整体的执行。

一致性:事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态。比如说银行之间的转账,从A账户向B账户转入1000元。系统先减少A账户1000元,然后再为B账户增加1000元。如果全部执行成功的话,则数据库就处于一致性状态。如果仅仅A账户金额修改,B账户没有增加的话,那么数据库就处于不一致的状态。因此,一致性必须通过原子性来保证。

隔离性:各个事务执行互不干扰,任意一个事务的内部操作对其他并发的事务都是隔离的。也就是说,并发执行的事务之间不能看到对方的中间状态。并发事务之间是不能互相影响的。

持久性:事务一旦提交,对数据所做的改变都要记录到存储器中,通常就是保存进物理数据库。

三.由于事物的隔离性带来的问题

由于各个事务之间可能出现并发访问的问题,因此隔离性将会带来脏读、不可重复读、幻读等的问题。

脏读:一个事务读取了另一个事务未提交的数据。
例如,小明去银行取钱,她老婆呢此时正在通过支付宝转账。她老婆查出来的钱为2000,于是,转走1000,但是还没有提交事务,小明此时过来查出来的钱为2000,于是这2000就为脏数据。整个过程中,她老婆的事务过程包裹这小明的事务过程,但是并未执行完全,因此导致了脏数据的产生。

不可重复读:一个事务中连续读取两次,第二次读取另一个事务已经提交Update修改的数据(数据改变),两次读取到的数据不一致。
还是紧接着上述的例子,小明首先查出来的钱为2000,此时她老婆转走1000,并且她老婆的事务提交,完成。当小明取完钱后,再看了一下自己的账户余额,发现为何不是1000,而是0呢?因此两次读取的数据不一致。导致了不可重复读的产生。

幻读:一个事务读取另一个事务已经提交插入的数据,因此记录条数已经改变。
比如有一张学生信息的表,统计这全班男女的学生信息,此时我正在通过SQL语句,查询男学生的个数,有一个同学向库里面又插入了一条男同学的信息,当我第二次再次查询的时候,多出一条,导致了幻读的产生。

四.事物隔离级别

数据库事务的隔离级别有4种,由低到高分别为Read uncommitted 为1、Read committed 为2、Repeatable read为4 、Serializable为8 。

1.Read uncommitted:读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。
比如老板发工资,他转账给你,老板不小心多打了1千块,但事物还没提交,你在这时查看了这个月工资,还以为涨工资了,老板发现不对,马上回滚提交了事物,改了数字后并提交了。你看到的是老板还没提交事务时的数据。这就是脏读。

2.Read committed :就是一个事务要等另一个事务提交后才能读取数据,能解决脏读问题。若有事务对数据进行更新操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。

3.Repeatable read :重复读,就是在开始读取数据(事务开启)时,不再允许修改操作,但是可能还会有幻读问题。因为幻读问题对应的是插入操作,而不是操作。

4.Serializable :Serializable 是最高的事务隔离级别,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。

大多数数据库默认的事务隔离级别是Read committed,Mysql的默认隔离级别是Repeatable read。

这里写图片描述

这个是要在hibernate配置文件中设置

<property name="hibernate.connection.isolation">4</property>
//设置级别为4,也就是Repeatable read

五.乐观锁和悲观锁

为什么不直接使用数据库的事务隔离级别来解决问题呢?如果直接设置数据库的事务隔离级别,就不能动态调整事务隔离级别了,在一个项目中不同的地方可能使用不同的隔离级别,如果都使用串行化的隔离级别自然能解决所有并发问题,但是这会带来性能上的下降。

一般我们都是,先设置hibernate.connection.isolation 的值为2 即:READ_COMMITTED,它就会使用JDBC中的COMMITTED隔离机制。但是这样还是解决不了不可重复读 ,解决的办法就是再加把锁,一种加乐观锁,一种加悲观锁。

1.悲观锁
他感觉每一个对他操作的程序都有可能产生并发。它指的是对数据被(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。
Hibernate的加锁模式有:
LockMode.NONE :无锁机制。
LockMode.WRITE :Hibernate在 Insert和 Update记录的时候会自动获取。
LockMode.READ :Hibernate在读取记录的时候会自动获取。
LockMode.UPGRADE :利用数据库的 for update 子句加锁。

String hqlStr ="from TUser as user where user.name=‘张三‘";  
Query query = session.createQuery(hqlStr);  
query.setLockMode("user",LockMode.UPGRADE); // 加锁  
List userList = query.list();// 执行查询,获取数据  

在Hibernate使用悲观锁十分容易,但实际应用中悲观锁是很少被使用的,因为它大大限制了并发性,并且利用数据库底层来维护锁,这样大大降低了应用程序的效率。一般都会使用乐观锁。

2.乐观锁
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。乐观锁原理,读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一,一般是通过为数据库表增加一个”version”字段来实现。。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
在hibernate配置文件中配置

<?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">  

<hibernate-mapping>  
    <classname classname="com.xxx.People"table="people">  
        <idname idname="id"type="string">  
            <columnname columnname="id"></column>  
            <generatorclass generatorclass="uuid"></generator>  
        </id>  
        <!--version标签用于指定表示版本号的字段信息-->  
        <versionname versionname="version"column="version"type="integer"></version>        <propertyname propertyname="name"column="name"type="string"></property>       
   </class>  
</hibernate-mapping>  

猜你喜欢

转载自blog.csdn.net/qq_38682952/article/details/78649219