Hibernate之事务与并发控制

时间:2022-09-21 09:26:35

一,数据库事务简介

1.概念(我百度的)

数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。

事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。

通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。

一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。

事务是数据库运行中的一个逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。

2.事务的属性

原子性(Atomic):

事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。

通常,与某个事务关联的操作具有共同的目标,并且是相互依赖的。

如果系统只执行这些操作的一个子集,则可能会破坏事务的总体目标。原子性消除了系统处理操作子集的可能性。

一致性(Consistency):

事务在完成时,必须使所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。

事务结束时,所有的内部数据结构(如 B 树索引或双向链表)都必须是正确的。

某些维护一致性的责任由应用程序开发人员承担,他们必须确保应用程序已强制所有已知的完整性约束。

例如,当开发用于转帐的应用程序时,应避免在转帐过程中任意移动小数点。

隔离性(Isolation):

由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。

事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。

这称为隔离性,因为它能够重新装载起始数据,并且重播一系列事务,以使数据结束时的状态与原始事务执行的状态相同。

当事务可序列化时将获得最高的隔离级别。在此级别上,从一组可并行执行的事务获得的结果与通过连续运行每个事务所获得的结果相同。

由于高度隔离会限制可并行执行的事务数,所以一些应用程序降低隔离级别以换取更大的吞吐量。

持久性(Duration):

事务完成之后,它对于系统的影响是永久性的。该修改即使出现致命的系统故障也将一直保持。

二,数据库并发问题归类

1.第一类丢失更新

撤销一个事务时,把其他事务提交的更新数据给覆盖了

2.第二类丢失更新

一个事务覆盖另一个事务提交的更新

3.脏读

一个事务读到另一个事务未提交的数据

4.虚读

一个事务读到另一个事务插入提交的数据

5.不可重复读

一个事务读到另一个事务已提交更新的数据

三,数据库系统锁的原理

当一个事务访问数据库资源时,比如执行select必须获得共享锁,执行insert,update,delete必须获得独占锁,

所有的这些所都是用于安全的操纵数据库资源。

当第二个事务访问数据库资源时,通过锁来判断是否获取资源,是否等待锁的释放,获取数据资源。

锁的多粒度性和自动升级:

数据库锁能够锁定的范围很广,比如:数据库,表,行等。

按照锁定资源的粒度分为以下类型的锁:

.数据库级别锁:锁定整个数据库

.表级别锁:锁定一张表

.行级锁:锁定一行数据

等等。

按照*程度,分为共享锁,独占锁,更新锁:

共享锁:

共享锁用于读取数据的操作,允许其他事务同时读取数据库资源,不允许其他事务更新,只读不写。

独占锁(排他锁):

用于更新数据,其他事务不能读,也不能修改,不允许别人读写。

更新锁:

更新操作的初始化阶段用于锁定资源,避免这个时候使用共享锁造成死锁。

四,数据库事务的隔离级别

串行化(Serializable):

隔离级别最高,一个事务在执行过程中完全看不到其他事务对数据库做的操作。一心只读圣贤书,两耳不闻窗外事。

可重复读(Repeatable Read):

一个事务在运行过程中可以看到另外一个事务插入的新数据,但是看不到别人对已有数据的更新。

读已提交数据(Read Committed):

一个事务在运行过程中可以看到另外一个事务插入的新数据,而且能看到别人对已有数据的更新。

读未提交数据(Read Uncommitted):

隔离级别最差,一个事务在运行过程中可以看到另外一个事务插入的新数据,而且能看到别人对已有数据的更新,但是未提交的数据。


在Hibernate的配置文件中可以显示地设置隔离级别。每一种隔离级别对应着一个正整数。

Read Uncommitted: 1 Read Committed: 2 Repeatable Read: 4 Serializable: 8在hibernate中hibernate.cfg.xml文件对隔离的配置:

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

五,悲观锁和乐观锁

悲观锁:

应用程序显示的加载数据库资源。悲观锁假定当前事务操作数据时,会有别的事务来抢,干脆就把这条数据锁了,别人没戏。性能差,慎用。

sql中可以用select...for update锁定数据,而Hibernate中用代码控制转换:

package com.lanhuigu.hibernate.test;

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

import com.lanhuigu.hibernate.entity.Customer;

public class TestHibernate {
public static void main(String[] args) {
Configuration configuration = new Configuration().configure();
SessionFactory sessionFactory = configuration.buildSessionFactory();
Session session = sessionFactory.openSession();
Transaction tr = session.beginTransaction();
//悲观锁使用
Customer customer = (Customer) session.get(Customer.class, new Long(1),LockMode.UPGRADE);
System.out.println(customer.getName());
tr.commit();
session.close();
}
}

控制台sql后多一个for update:

Hibernate: select customer0_.ID as ID1_0_0_, customer0_.NAME as NAME2_0_0_, customer0_.EMAIL as EMAIL3_0_0_, customer0_.PASSWORD as PASSWORD4_0_0_, customer0_.PHONE as PHONE5_0_0_, customer0_.ADDRESS as ADDRESS6_0_0_, customer0_.SEX as SEX7_0_0_, customer0_.IS_MARRIED as IS8_0_0_, customer0_.DESCRIPTION as DESCRIPT9_0_0_, customer0_.IMAGE as IMAGE10_0_0_, customer0_.BIRTHDAY as BIRTHDA11_0_0_, customer0_.REGISTERED_TIME as REGISTE12_0_0_, customer0_.HOME_PROVINCE as HOME13_0_0_, customer0_.HOME_CITY as HOME14_0_0_, customer0_.HOME_STREET as HOME15_0_0_, customer0_.HOME_ZIPCODE as HOME16_0_0_, customer0_.COMP_PROVINCE as COMP17_0_0_, customer0_.COMP_CITY as COMP18_0_0_, customer0_.COMP_STREET as COMP19_0_0_, customer0_.COMP_ZIPCODE as COMP20_0_0_ from CUSTOMERS customer0_ where customer0_.ID=? for update
test

从网上找了一个锁定模式表格:

Hibernate之事务与并发控制

乐观锁:

乐观锁假定当前事务操作数据时,别人不会来的,通过版本号控制。性能好。

在xxx.hbm.xml配置版本号:

 <!-- 设置主键 -->
 <id name="id" column="ID" type="long">
           <!-- 主键生成方式-->
           <generator class="increment"/>
 </id>

<version name="version" column = "VERSION"/>

必须在id配置下方,实体中创建一个version的版本号,类型可以用时间,或者叠加数据。

每次更新数据时增加,把版本号作为条件

update.....where ....and version = (你的上一个版本的值,表示在上一个版本基础上更新数据)