Hibernate笔记+相关面试题

持久化框架

一、持久化:IO、数据库(内存和磁盘进行交互)

对象的瞬时状态和持久状态相互转换的过程,称之为持久化。

持久化是双向的

狭义的理解: “持久化”仅仅指把域对象永久保存到数据库中;广义的理解,“持久化”包括和数据库相关的各种操作(持久化就是将有用的数据以某种技术保存起来,将来可以再次取出来应用,数据库技术,将内存数据以文件的形式保存在磁盘等都是持久化的例子)。
保存:把对象永久保存到数据库。
更新:更新数据库中对象的状态。
删除:从数据库中删除一个对象。
加载:根据特定的OID,把一个对象从数据库加载到内存。
查询:根据特定的查询条件,把符合查询条件的一个或多个对象从数据库加载内在存中。

二、ORM

ORM : Object Relational Mapping

ORM 即 Object Relation Mapping 对象关系映射。是一种程序技术,解决一类问题的思路方法,不局限于应用在 Java 语言。用于面向对象语言中完成类和表,属性和表字段转换的映射机制。

类名和表名、属性和列名、Java类型和表类型等 之间的映射

三、序列化(属于持久化的一种,io中用到)

​ 把对象状态转为可以**可存储(到磁盘文件)或可传输(到另一个网络节点)**的状态称为序列化。简单来说序列化就是一种用来处理对象流的机制,对象流就是将对象的内容进行流化。

​ 把可存储或可传输状态转为对象瞬时状态 称为反序列化

​ 应用:对象克隆用到clone()或序列化反序列化

四、Hibernate使用步骤

1. 下载 jar 包,并在工程中引入

如果使用的 idea 在创建工程时,注意勾选 Hibernate 即可(自动下载),点击右下角 config 也可以更换版本。

2. 创建配置文件 hibernate.cfg.xml

hibernate框架就是对 JDBC 封装,需要 url 、driver 、 username、 password 四个参数,另外还需要 “方言” 。

hibernate.cfg.xml中需要配置五个基本参数,用来连接数据库

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="connection.url">jdbc:mysql://127.0.0.1:3306/mydb</property>
        <property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
        <property name="connection.username">root</property>
        <property name="connection.password">root</property>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQL8Dialect</property>
		<property name="hibernate.show_sql">true</property>
        <!-- DB schema will be updated if needed -->
        <!-- <property name="hibernate.hbm2ddl.auto">update</property> -->
    </session-factory>
</hibernate-configuration>

3. 创建实体类及对应的映射文件(可以注解代替)

创建实体类:

public class Student {
    private int sid;
    private String sname;
    private int age;
    private String phone;

    public int getSid() {
        return sid;
    }

    public void setSid(int sid) {
        this.sid = sid;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }

    public int getAge() {
        return age;
    }

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

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}

创建映射文件 xxx.hbm.xml(与实体类同一文件夹)

<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class name="com.entity.Student" table="student">
        <!--主键-->
        <id name="sid" column="sid"></id>
        <property name="age" column="sage"></property>
        <property name="sname" column="name"></property>
        <property name="phone" column="phone"></property>
    </class>
</hibernate-mapping>

把映射文件添加到配置文件hibernate.cfg.xml中

<mapping resource="com/entity/Student.hbm.xml"></mapping>

4. 编写代码测试

/**
 * 1.jar
 * 2.配置文件: dirver url username password dialect
 * 3.实体类 & 映射文件       引入配置文件中
 * 4.测试
 */
public class TestHibernate {
    public static void main(String[] args) {
        //加载配置文件
        Configuration cfg = new Configuration().configure("/hibernate.cfg.xml");
        //创建session工厂 sessionFactory
        SessionFactory sessionFactory = cfg.buildSessionFactory();
        //创建session
        Session session = sessionFactory.openSession();
        
        Student student = new Student();
        student.setAge(23);
        student.setSname("曹操");
        student.setPhone("133455555");
        student.setSid(1);
        //开启事务
        Transaction transaction = session.beginTransaction();
        //持久化操作
        session.save(student);
        //提交事务
        transaction.commit();
        //关闭
        session.close();
    }
}

五、关联关系

1.多对一(many-to-one)

​ 在多方配置和一方的关联关系,实体类中和映射文件中。

​ Student类:

public class Student {
    private Integer sid;
    private String sname;
    private Date birthday;
    private School school; //依赖School
    //省略 getter setter
}

对应的映射文件:

<class name="com.entity.Student" table="student">
        <id name="sid" column="sid">
            <generator class="native"></generator>
        </id>
        <property name="sname">
            <column name="sname"></column>
        </property>
        <property name="birthday" column="birthday"/>
        <!--********多对一**********-->
        <many-to-one name="school" class="com.wdzl.entity.School">
            <column name="scid"></column>
        </many-to-one>
    </class>

2. 一对多(one-to-many)

实体类 School 中:声明 Set 结合存放多方 Student 对象:

public class School {
    private Integer scid;
    private String scname;
    private String address;
    // 关联 student
    private Set<Student> students = new HashSet<>();
    //省略 getter setter
}

对应的映射文件:

<class name="com.entity.School" table="school">
        <id name="scid" column="scid">
            <!--主键生成策略:
             assigned:程序给定主键
             native:数据库本地负责主键
            -->
            <generator class="native"></generator>
        </id>
        <property name="scname" column="scname"></property>
        <property name="address" column="address"></property>
        <!--***********一对多**************-->
        <set name="students">
            <key><column name="scid"></column></key>
            <one-to-many class="com.entity.Student"></one-to-many>
        </set>
    </class>
Hibernate的主键生成策略
  • 主键分类
    • 自然主键
    • 代理主键
  • 主键生成策略
    •  increment
    •  identity
    •  sequence
    •  uuid
    •  native
    •  assigned
    •  foreign
查询get
在多方关联查询一方:
public void manyToOne(){
        Student student = session.get(Student.class, 1);

        String sname = student.getSname();
        //关联查询一方学校对象
        School school = student.getSchool();

        System.out.println(sname+"=="+school.getScname());
        System.out.println(school.getScid());
        session.close();
    }
在一方关联查询多方:
    public void oneToMany(){
        School school = session.get(School.class, 1);
        System.out.println("学校名:"+school.getScname());
        System.out.println("学校下的所有学生:");
        Set<Student> students = school.getStudents();
        for (Student student : students) {
            System.out.println(">>>="+student.getSname());
        }
    }
保存save
级联保存:通过多方级联保存一方对象入库,先在多方增加级联属性 cascade=“save-update”
 <many-to-one name="school" cascade="save-update" class="com.wdzl.entity.School">
      <column name="scid"></column>
</many-to-one>

代码: 在多方对象中设置一方对方,进行关联依赖。

 public void saveStudent2(){
        Transaction transaction = session.beginTransaction();
        //先查询学校对象
        School school = new School();
//        school.setScid(3);
        school.setScname("二中");
        school.setAddress("xxx路");

        Student student = new Student();
        student.setSname("诸葛亮");
        student.setBirthday(new Date());
        //给学员分配一个新的学校
        student.setSchool(school);
        //通过学生 级联保存学校
        session.save(student);

        transaction.commit();
    }

3.级联删除

案例:通过一方学校,级联删除学生。

会有两种情况:

a. 先解除关系,然后删除多方,最后删除一方
<set name="students" inverse="false" cascade="delete">

b. 直接删除多方,最后删除一方。
<set name="students" inverse="true" cascade="delete">

4.延时加载 lazy

<many-to-one name="school" fetch="join" lazy="proxy" cascade="save-update" class="com.entity.School">
            <column name="scid"></column>
</many-to-one>
 <set name="students" lazy="true" inverse="true" cascade="delete">
            <key><column name="scid"></column></key>
            <one-to-many class="com.entity.Student"></one-to-many>
 </set>

5.抓取策略fetch=“join|select|subselect”

在多方的节点上:<many-to-one fetch="join|select" </many>,默认select 。

在一方的节点上:<set fetch="join|select|subselect"/>

六、HQL

HQL: Hibernate Query Language

HQL :

a. 支持对象查询、update修改、delete删除

b. 支持分组group by,having ,聚合函数 count(*),avg(),sum(),max(),min()

c. 支持排序 order by ,条件 where, 子查询,distinct

d . 不支持 select * ,insert into

e. 支持分页

1. 返回元素类型

  • 查询全部:
from Student
或
select student from Student student

返回结果:List 中元素为 Student 对象

  • 查询多列:
select sid,sname from Student

返回结果:List 中元素为 Object[]

  • 查询单个属性(列):
select sid from Student

返回结果:List 中元素为所查属性的类型

2.命名参数查询

String hql = "from Student where sname=:sname and sid=:sid";
Query query = session.createQuery(hql);
//给问号占位符赋值
query.setParameter("sname","xxxx");
query.setParameter("sid",8); 

List<Student> list = query.list();
for (Student student : list) {
    System.out.println(student.getSname());
}

3. 聚合函数

String hql = "select avg(sid),count(*),sum(sid),max(sid),min(sid) from Student";
Query query = session.createQuery(hql);
Object[] res = (Object[]) query.uniqueResult(); //唯一结果
System.out.println(res[0]+"=="+res[1]);

4. 修改和删除

Transaction transaction = session.beginTransaction();
//String hql = "update Student set sname='AAA' where sid>9";
String hql = "delete from Student where sid>9";
Query query = session.createQuery(hql);
int i = query.executeUpdate();
System.out.println(i);
transaction.commit();

5. 分页

public void page(){
    int rowsPerPage = 2;//每页记录数
    int pageNo = 2;//页号
    String hql = "from Student";
    Query query = session.createQuery(hql);
    //每页记录数
    query.setMaxResults(rowsPerPage);
    int start = (pageNo-1)*rowsPerPage;
    //设置开始位置
    query.setFirstResult(start);
    List<Student> list = query.list();
    for (Student o : list) {
        System.out.println(o.getSname());
    }
}

6. 子查询

fetch="subselect"时,batch-size失效

查询年龄大于平均年龄的学生

public void subselect(){
    String hql = "from Student where age > (select avg(age) from Student)";
    Query query = session.createQuery(hql);
    List<Student> list = query.list();
    for (Student student : list) {
        System.out.println(student.getSname()+"=="+student.getAge());
    }
}

七、缓存

1. 三态

* hibernate 三态:瞬时态、游离态、持久态
*              缓存          数据库
* 瞬时           没           没
* 游离           没           有
* 持久           有           有

2. Session 常用方法

session.get() : 根据ID获取对象

session.evit(obj): 将对象从缓存中移除

session.contains(obj) : 缓存中是否包含对象

session.clear() : 清空缓存

session.close() : 关闭session

session.refresh(obj): 刷新同步,内存中瞬时对象和数据库同步,session同步和管理对象。

2.get()和load()

load 方法 默认不查询数据库的,只有用到时,才查询(包括基本属性)如果查询的对象不存在时,抛出异常。
但是get()返回查询不到,返回null

    //有返回 
    Object id = session.save(school);
    //没有返回值
    session.persist(school);

3.一级缓存

​ 默认开启的,是session级别的缓存。

3.二级缓存配置

a. 下载jar

​ Hibernate5 需要三个:ehcache-2.10.6.jar、hibernate-ehcache-5.4.3.Final.jar、slf4j-api-1.7.25.jar

​ 在Hibernate官方API中查找,位置: hibernate-release-5.4.3.Final\lib\optional\ehcache

b. 开启二级缓存:

​ 在配置文件hibernate.cfg.xml中 (3与5版本冲突)

<property name="cache.use_second_level_cache">true</property>
<property 		            			    name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
</property>
       

开启二级缓存true

指定提供二级缓存的类:

<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory

</property>

c. 在映射文件中配置缓存策略 隔离级别
<cache usage="read-only"></cache>

4. Query缓存

* query缓存
* 1.在二级缓存基础配置上,修改配置文件
* <property name="cache.use_query_cache">true</property>
* 2.查询时,开启缓存查询
Query query = session.createQuery(hql);
query.setCacheable(true);//开启缓存
List<Student> list = query.list();

5. 本地SQL(NativeQuery)–createSQLQuery

Hibernate 同样支持 SQL 语句查询

String sql = "select * from student";
NativeQuery sqlQuery = session.createSQLQuery(sql);
sqlQuery.addEntity(Student.class);

List<Student> list = sqlQuery.list();
for (Student student : list) {
    System.out.println(student.getSname());
}

6. 命名SQL 或 HQL --createNamedQuery

允许在映射文件xml 中定义 HQL或 SQL,在 class 节点外部:

<query name="myhql" >
    from Student
</query>
<sql-query name="mysql" >
    <return class="com.entity.Student"></return>
    select * from student
</sql-query>
代码调用:
public void query(){
    Query myquery = session.createNamedQuery("mysql");
    List list = myquery.list();
    for (Object o : list) {
        System.out.println(o);
    }
}

7. 定制SQL

在映射xml 文件的 class 节点中,注意不能和 version 同时使用

<sql-update>
     update student set sname=?,age=? where sid=?
</sql-update>

八、乐观锁和悲观锁

悲观锁:修改操作对记录加锁,后来需要操作记录的等待,并行变串行,性能低,安全

悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字和Lock的实现类都是悲观锁。

乐观锁:表中增加一列,赋值初始值为0,修改后修改值,判断两次值,一旦小于回滚,性能高

//映射文件:
<version name="version" column="version"></version>`

乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在**更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。**如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)。

img

img

从上面的代码可以看出,乐观锁在 Java 中是通过使用无锁编程来实现,最常采用的是 CAS 算法,Java 中一些原子类(如:AtomicInteger)的递增操作就通过 CAS 自旋实现的。这也就是为何乐观锁能够做到不锁定同步资源也可以正确的实现线程同步的!

CAS全称 Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent 包中的原子类就是通过 CAS 来实现了乐观锁。

CAS 算法涉及到三个核心操作数:

需要读写的内存值 V。

进行比较的值 A。

要写入的新值 B。

当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。一般情况下,“更新”是一个不断重试的操作。

前面提到的原子类,比如 AtomicInteger。我们可以看一下它的源码:

img

上面截图中有 3 个地方值得我们去关注:

unsafe: 获取并操作内存的数据。

valueOffset: 存储 value 在 AtomicInteger 中的偏移量。

value: 存储 AtomicInteger 的 int 值,该属性需要借助 volatile 关键字保证其在线程间是可见的。

在使用 AtomicInteger 时,我们用的最多的是 incrementAndGet 方法,下面我们看看它的源码:

img

CAS 虽然很高效,但是它也存在三大问题,这里也简单说一下:整个“比较+更新”操作封装在 compareAndSwapInt() 中,在 JNI 里是借助于一个 CPU 指令完成的,属于原子操作,可以保证多个线程都能够看到同一个变量的修改值。

悲观锁与乐观锁

这是一篇介绍悲观锁和乐观锁的入门文章。旨在让那些不了解悲观锁和乐观锁的小白们弄清楚什么是悲观锁,什么是乐观锁。不同于其他文章,本文会配上相应的图解让大家更容易理解。通过该文,你会学习到如下的知识。

锁(Lock)

在介绍悲观锁和乐观锁之前,让我们看一下什么是锁。

锁,在我们生活中随处可见,我们的门上有锁,我们存钱的保险柜上有锁,是用来保护我们财产安全的。

程序中也有锁,当多个线程修改共享变量时,我们可以给修改操作上锁(syncronized)。

当多个用户修改表中同一数据时,我们可以给该行数据上锁(行锁)。因此,锁其实是在并发下控制多个操作的顺序执行,以此来保证数据安全的变动。

并且,锁是一种保证数据安全的机制和手段,而并不是特定于某项技术的。悲观锁和乐观锁亦是如此。本篇介绍的悲观锁和乐观锁是基于数据库层面的。

图片描述

悲观锁

悲观锁**(Pessimistic Concurrency Control)**,第一眼看到它,相信每个人都会想到这是一个悲观的锁。没错,它就是一个悲观的锁。

那这个悲观体现在什么地方呢?悲观是我们人类一种消极的情绪,对应到锁的悲观情绪,悲观锁认为被它保护的数据是极其不安全的,每时每刻都有可能变动,一个事务拿到悲观锁后(可以理解为一个用户),其他任何事务都不能对该数据进行修改,只能等待锁被释放才可以执行。

数据库中的行锁,表锁,读锁,写锁,以及syncronized实现的锁均为悲观锁。

图片描述

这里再介绍一下什么是数据库的表锁和行锁,以免有的同学对后面悲观锁的实现看不明白。

我们经常使用的数据库是mysql,mysql中最常用的引擎是Innodb,Innodb默认使用的是行锁。而行锁是基于索引的,因此要想加上行锁,在加锁时必须命中索引,否则将使用表锁。

图片描述

乐观锁

与悲观相对应,乐观是我们人类一种积极的情绪。乐观锁(Optimistic Concurrency Control)的“乐观情绪”体现在,它认为数据的变动不会太频繁。因此,它允许多个事务同时对数据进行变动。

但是,乐观不代表不负责,那么怎么去负责多个事务顺序对数据进行修改呢?

乐观锁通常是通过在表中增加一个版本(version)或时间戳(timestamp)来实现,其中,版本最为常用。

事务在从数据库中取数据时,会将该数据的版本也取出来(v1),当事务对数据变动完毕想要将其更新到表中时,会将之前取出的版本v1与数据中最新的版本v2相对比,如果v1=v2,那么说明在数据变动期间,没有其他事务对数据进行修改,此时,就允许事务对表中的数据进行修改,并且修改时version会加1,以此来表明数据已被变动。

如果,v1不等于v2,那么说明数据变动期间,数据被其他事务改动了,此时不允许数据更新到表中,一般的处理办法是通知用户让其重新操作。不同于悲观锁,乐观锁是人为控制的。

图片描述

如何实现

经过上面的学习,我们知道悲观锁和乐观锁是用来控制并发下数据的顺序变动问题的。那么我们就模拟一个需要加锁的场景,来看不加锁会出什么问题,并且怎么利用悲观锁和乐观锁去解决。

场景:A和B用户最近都想吃猪肉脯,于是他们打开了购物网站,并且找到了同一家卖猪肉脯的>店铺。下面是这个店铺的商品表goods结构和表中的数据。

idnamenum
1猪肉脯1
2牛肉干1

从表中可以看到猪肉脯目前的数量只有1个了。在不加锁的情况下,如果A,B同时下单,就有可能导致超卖。

悲观锁解决

利用悲观锁的解决思路是,我们认为数据修改产生冲突的概率比较大,所以在更新之前,我们显示的对要修改的记录进行加锁,直到自己修改完再释放锁。加锁期间只有自己可以进行读写,其他事务只能读不能写。

A下单前先给猪肉脯这行数据(id=1)加上悲观锁(行锁)。此时这行数据只能A来操作,也就是只有A能买。B想买就必须一直等待。

当A买好后,B再想去买的时候会发现数量已经为0,那么B看到后就会放弃购买。

那么如何给猪肉脯也就是id=1这条数据加上悲观锁锁呢?我们可以通过以下语句给id=1的这行数据加上悲观锁

select num from goods where id = 1 for update;

下面是悲观锁的加锁图解

图片描述

我们通过开启mysql的两个会话,也就是两个命令行来演示。
1、事务A执行命令给id=1的数据上悲观锁准备更新数据

图片描述

这里之所以要以begin开始,是因为mysql是自提交的,所以要以begin开启事务,否则所有修改将被mysql自动提交。

2、事务B也去给id=1的数据上悲观锁准备更新数据

图片描述

我们可以看到此时事务B再一直等待A释放锁。如果A长期不释放锁,那么最终事务B将会报错,这有兴趣的可以去尝试一下。

3、接着我们让事务A执行命令去修改数据,让猪肉脯的数量减一,然后查看修改后的数据,最后commit,结束事务。

图片描述

我们可以看到,此时最后一个猪肉脯被A买走,只剩0个了。

4、当事务A执行完第3步后,我们看事务B中出现了什么

图片描述

我们看到由于事务A释放了锁,事务B就结束了等待,拿到了锁,但是数据此时变成了0,那么B看到后就知道被买走了,就会放弃购买。

通过悲观锁,我们解决了猪肉脯购买的问题。

乐观锁解决

下面,我们利用乐观锁来解决该问题。上面乐观锁的介绍中,我们提到了,乐观锁是通过版本号version来实现的。所以,我们需要给goods表加上version字段,表变动后的结构如下:

idnamenumversion
1猪肉脯10
1牛肉干10

使用乐观锁的解决思路是,我们认为数据修改产生冲突的概率并不大,多个事务在修改数据的之前先查出版本号,在修改时把当前版本号作为修改条件,只会有一个事务可以修改成功,其他事务则会失败。

A和B同时将猪肉脯(id=1下面都说是id=1)的数据查出来,然后A先买,A将id=1和version=0作为条件进行数据更新,即将数量-1,并且将版本号+1。

此时版本号变为1。A此时就完成了商品的购买。最后B开始买,B也将id=1和version=0作为条件进行数据更新,但是更新完后,发现更新的数据行数为0,此时就说明已经有人改动过数据,此时就应该提示用户重新查看最新数据购买。

下面是乐观锁的加锁图解

图片描述

我们还是通过开启mysql的两个会话,也就是两个命令行来演示。

1、事务A执行查询命令,事务B执行查询命令,因为两者查询的结果相同,所以下面我只列出一个截图。

图片描述

此时A和B均获取到相同的数据

2、事务A进行购买更新数据,然后再查询更新后的数据。

图片描述

我们可以看到事务A成功更新了数据和版本号。

事务B再进行购买更新数据,然后我们看影响行数和更新后的数据

图片描述

可以看到最终修改行数为0,数据没有改变。此时就需要我们告知用户重新处理。

优缺点

下面我们介绍下乐观锁和悲观锁的优缺点以便我们分析他们的应用场景,这里我只分析最重要的优缺点,也是我们要记住的。

悲观锁

  • 优点:悲观锁利用数据库中的锁机制来实现数据变化的顺序执行,这是最有效的办法
  • 缺点:一个事务用悲观锁对数据加锁之后,其他事务将不能对加锁的数据进行除了查询以外的所有操作,如果该事务执行时间很长,那么其他事务将一直等待,那势必影响我们系统的吞吐量。

乐观锁

  • 优点:乐观锁不在数据库上加锁,任何事务都可以对数据进行操作,在更新时才进行校验,这样就避免了悲观锁造成的吞吐量下降的劣势。

  • 缺点:乐观锁因为是通过我们人为实现的,它仅仅适用于我们自己业务中,如果有外来事务插入,那么就可能发生错误。

应用场景

悲观锁:因为悲观锁会影响系统吞吐的性能,所以适合应用在写为居多的场景下。

乐观锁:因为乐观锁就是为了避免悲观锁的弊端出现的,所以适合应用在读为居多的场景下。

参考资料

https://chenzhou123520.iteye.com/blog/1860954

]

我们还是通过开启mysql的两个会话,也就是两个命令行来演示。

1、事务A执行查询命令,事务B执行查询命令,因为两者查询的结果相同,所以下面我只列出一个截图。

[外链图片转存中…(img-mCRUHVsC-1598316124118)]

此时A和B均获取到相同的数据

2、事务A进行购买更新数据,然后再查询更新后的数据。

[外链图片转存中…(img-uyNKXxc9-1598316124122)]

我们可以看到事务A成功更新了数据和版本号。

事务B再进行购买更新数据,然后我们看影响行数和更新后的数据

[外链图片转存中…(img-Aa8ky8Wy-1598316124125)]

可以看到最终修改行数为0,数据没有改变。此时就需要我们告知用户重新处理。

优缺点

下面我们介绍下乐观锁和悲观锁的优缺点以便我们分析他们的应用场景,这里我只分析最重要的优缺点,也是我们要记住的。

悲观锁

  • 优点:悲观锁利用数据库中的锁机制来实现数据变化的顺序执行,这是最有效的办法
  • 缺点:一个事务用悲观锁对数据加锁之后,其他事务将不能对加锁的数据进行除了查询以外的所有操作,如果该事务执行时间很长,那么其他事务将一直等待,那势必影响我们系统的吞吐量。

乐观锁

  • 优点:乐观锁不在数据库上加锁,任何事务都可以对数据进行操作,在更新时才进行校验,这样就避免了悲观锁造成的吞吐量下降的劣势。

  • 缺点:乐观锁因为是通过我们人为实现的,它仅仅适用于我们自己业务中,如果有外来事务插入,那么就可能发生错误。

应用场景

悲观锁:因为悲观锁会影响系统吞吐的性能,所以适合应用在写为居多的场景下。

乐观锁:因为乐观锁就是为了避免悲观锁的弊端出现的,所以适合应用在读为居多的场景下。

参考资料

https://chenzhou123520.iteye.com/blog/1860954

https://baike.baidu.com/item/乐观锁/7146502

面试题

1.解释下ORM及作用?

ORM:Object Relational Mapping(对象关系映射)。指的是将一个Java中的对象与关系型数据库中的表建立一种映射关系,从而操作对象就可以操作数据库中的表。

2.什么是持久化?

对象的瞬时状态和持久状态相互转换的过程
狭义的理解: “持久化”仅仅指把域对象永久保存到数据库中;广义的理解,“持久化”包括和数据库相关的各种操作(持久化就是将有用的数据以某种技术保存起来,将来可以再次取出来应用,数据库技术,将内存数据以文件的形式保存在磁盘等都是持久化的例子)。
保存:把对象永久保存到数据库。
更新:更新数据库中对象的状态。
删除:从数据库中删除一个对象。
加载:根据特定的OID,把一个对象从数据库加载到内存。
查询:根据特定的查询条件,把符合查询条件的一个或多个对象从数据库加载内在存中。
持久化技术封装了数据访问细节,为大部分业务逻辑提供面向对象的API。通过持久化技术可以减少访问数据库数据次数,增加应用程序执行速度;代码重用性高,能够完成大部分数据库操作;松散耦合,使持久化不依赖于底层数据库和上层业务逻辑实现,更换数据库时只需修改配置文件而不用修改代码。

3.什么是序列化?

序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
简单来说序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化,流的概念这里不用多说(就是I/O),我们可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间(注:要想将对象传输于网络必须进行流化)!在对对象流进行读写操作时会引发一些问题,而序列化机制正是用来解决这些问题的!

4.Hibernate的使用步骤?

1,导入jar包,pom.xml 中导入需要使用的jar包
2,添加hibernate.cfg.xml配置文件
3,创建数据库表对应的实体类
4,创建类和表的映射 ***.hbm.xml
5, 测试,创测试类

5.Hibernate执行流程(原理)?

配置文件的作用及加载:连接数据库的信息,包括映射资源的引用、优化、缓存等
根据配置文件构建session工厂,(注意回答配置文件的解析)映射文件通过加载、解析配置文件创建连接,连接数据库,根据映射文件的解析生成sql语句,通过jdbc的封装执行入库

1、通过Configuration().configure(); //读取并解析hibernate.cfg.xml配置文件
2、由hibernate.cfg.xml中的 。 读取并解析映射信息.
3、通过config.buildSessionFactory(); //创建SessionFactory;
4、sessionFactory.openSession(); //打开Session
5、session.beginTransaction(); //创建事务Transaction
6、persistent operate 持久化操作.
7、Session.getTransaction().commit(); //提交事务
8、关闭Session
9、关闭SessionFactory

6.乐观锁和悲观锁区别?

1.悲观锁是当线程拿到资源时,就对资源上锁,并在提交后,才释放锁资源,其他线程才能使用资源。
2.乐观锁是当线程拿到资源时,上乐观锁,在提交之前,其他的锁也可以操作这个资源,当有冲突的时候,并发机制会保留前一个提交,打回后面的一个提交,让后一个线程重新获取资源后,再操作,然后提交。和git上传代码一样,两个线程都不是直接获取资源本身,而是先获取资源的两个copy版本,然后在这两个copy版本上修改。
3.悲观锁和乐观锁在并发量低的时候,性能差不多,但是在并发量高的时候, 乐观锁的性能远远优于悲观锁。
4.我们常用的synchronized是悲观锁,lock是乐观锁 。
7.Hibernate实现乐观锁的方式?
乐观锁的工作原理:读取出数据时,将此版本号一同读出,之后更新时,对此版本号加1。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
Hibernate为乐观锁提供了3中实现:
基于version

<!-- version标签用于指定表示版本号的字段信息 -->
<version name="version" column="version" type="integer"></version>
基于timestamp
<!-- timestamp标签用于指定表示版本号的字段信息 -->
<timestamp name="updateDate" column="updateDate"></timestamp>

为遗留项目添加乐观锁
遗留项目,由于各种原因无法为原有的数据库添加"version"或"timestamp"字段,这时不可以使用上面两种方式配置乐观锁,Hibernate为这种情况提供了一个"optimisitic-lock"属性,它位于标签上:

<hibernate-mapping>
    <class name="com.suxiaolei.hibernate.pojos.People" table="people" optimistic-lock="all">
        <id name="id" type="string">
            <column name="id"></column>
            <generator class="uuid"></generator>
        </id>

        <property name="name" column="name" type="string"></property>
    </class>
</hibernate-mapping>
7.解释下Hibernate三态
  • 瞬时态:没有唯一标识OID,没有被session管理
  • 持久态:有唯一标识OID,已经被session管理
  • 脱管态:有唯一标识OID,没有被session管理
8.谈谈你对Hibernate缓存的了解。

Hibernate缓存
缓存是计算机领域的概念,它介于应用程序和永久性数据存储源(如在硬
盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储
源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的复制,
应用程序在运行时直接读写缓存中的数据,只在某些特定时刻按照缓存中的数据
来同步更新数据存储源。
缓存的物理介质通常是内存,而永久性数据存储源的物理介质通常是硬盘
或磁盘,应用程序读写内存的速度显然比读写硬盘的速度快。如果缓存中存放的数据量非常大,也会用硬盘作为缓存的物理介质。
Hibernate缓存一般分为3类:
(1) 一级缓存:也称为Session缓存。由于Session对象的生命周期通常
对应一个数据库事务,因此它的缓存是事务范围的缓存。
一级缓存是必需的,Session默认带有,而且不能卸载。在一级缓存中,持久
化类的每个实例都具有唯一的OID。
(2) 二级缓存。SessionFactory缓存分为内置缓存和外置缓存。
内置缓存是Hibernate自带的,不可拆卸,是只读缓存,用来存放映射元数据和预定义SQL语句。
外置缓存是一个可配置的缓存插件,默认SessionFactory不会启用这个缓存插件。外置缓存中的数据是数据库数据的复制。SessionFactory对象的生命周期和应用程序的整个进程对应。二级缓存是可选的,可以在每个类或每个集合的粒度上配置二级缓存。
(3)查询缓存。它是Hibernate为查询结果提供的,依赖于二级缓存。

缓存的作用分为3类
(1)事务范围。每个事务都有自己的缓存,缓存内数据不会被多个事务并发访问。例如hibernate的一级缓存,事务是不能跨多个Session的,Session内
数据只能被当前事务访问,因此它属于事务范围的缓存。
(2)进程范围。进程内的所有事物共享缓存,进程结束,缓存结束生命周期,例如hibernate的二级缓存,SessionFactory对象的生命周期对应应用程序的整个进程,因此它属于进程范围的缓存。
(3)集群范围。缓存被一个或多个机器上的多个进程共享。Hibernate的二级缓存也可以作为集群范围的缓存。
一级缓存:
一级缓存的生命周期和session的生命周期一致,当前session一旦关闭,一级缓存就消失了,因此一级缓存也叫session级的缓存或事务级缓存,一级缓存只存实体对象,它不会缓存一般的对象属性(查询缓存可以),即当获得对象后,就将该对象缓存起来,如果在同一session中再去获取这个对象时,它会先判断在缓存中有没有该对象的id,如果有则直接从缓存中获取此对象,反之才去数据库中取,取的同时再将此对象作为一级缓存处理。
二级缓存:
开发中的用途没有面试带来作用大。
二级缓存是进程(N个事务)或集群范围内的缓存,可以被所有的Session共享,在多个事务之间共享
二级缓存是可配置的插件
查询缓存:
1查询是数据库技术中最常用的操作,Hibernate为查询提供了缓存,用来提高查询速度,优化查询性能
相同HQL语句检索结果的缓存!
2查询缓存依赖于二级缓存
查询缓存是针对普通属性结果集的缓存,对实体对象的结果集只缓存id(其id不是对象的真正id,可以看成是HQL或者SQL语句,它与查询的条件相关即where后的条件相关,不同的查询条件,其缓存的id也不一样)。查询缓存的生命周期,当前关联的表发生修改或是查询条件改变时,那么查询缓存生命周期结束,它不受一级缓存和二级缓存生命周期的影响,要想使用查询缓存需要手动配置如下:

 * 在hibernate.cfg.xml文件中启用查询缓存,如: 
    <property name="hibernate.cache.use_query_cache">true</property>
    * 在程序中必须手动启用查询缓存,如: 
    query.setCacheable(true); 

其中 Query 和Criteria的list() 就可利用到查询缓存了。

9.Hibernate和Mybatis的区别。

相同点:
Hibernate与MyBatis都可以通过SessionFactoryBuider由XML配置文件生成SessionFactory,然后由SessionFactory 生成Session,最后由Session来开启执行事务和SQL语句。其中SessionFactoryBuider,SessionFactory,Session的生命周期都是差不多的;Hibernate和MyBatis都支持JDBC和JTA事务处理。

  • Mybatis优势:
    MyBatis可以进行更为细致的SQL优化,可以减少查询字段。
    MyBatis容易掌握,而Hibernate门槛较高。
  • MyBatis缺点
    Mybatis的缺点就是框架还是比较简陋,功能尚有缺失,虽然简化了数据绑定代码,但是整个底层数据库查询实际还是要自己写的,工作量也比较大,而且不太容易适应快速数据库修改。
  • Hibernate优势:
    Hibernate的DAO层开发比MyBatis简单,Mybatis需要维护SQL和结果映射。
    Hibernate对对象的维护和缓存要比MyBatis好,对增删改查的对象的维护要方便。
    Hibernate数据库移植性很好,MyBatis的数据库移植性不好,不同的数据库需要写不同SQL。
    Hibernate有更好的二级缓存机制,可以使用第三方缓存。MyBatis本身提供的缓存机制不佳。
    Hibernate功能强大,数据库无关性好,O/R映射能力强,如果你对Hibernate相当精通,而且对Hibernate进行了适当的封装,那么你的项目整个持久层代码会相当简单,需要写的代码很少,开发速度很快,非常爽。
  • Hibernate缺点:
    Hibernate的缺点就是学习门槛不低,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡取得平衡,以及怎样用好Hibernate方面需要你的经验和能力都很强才行。
10.Hibernate相关的session方法列举多个。

save(Object obj)、delete()、get()、load()、update()、beginTrasaction()、close()、saveOrUpdate()

11.Hibernate中关联关系有哪些?举例说明双向一对多的配置方法

关联关系有一对一、一对多、多对一、多对多
双向一对多:比如学校和学生,一个学生对应一个学校,一个学校有多个学生,学生类下可以定义一个学校对象,学校类下可以定义多个学生对象

12.HQL是什么,可以用来做什么?

Hibernate Query Language,
支持对象查询、update修改、delete删除;支持分组group by,having ,聚合函数 count(*),avg(),sum(),max(),min();支持排序 order by ,条件 where, 子查询,distinct;不支持 select * ,insert into;支持分页

13.本地SQL、命名SQL、SQL定制分别是什么?

本地SQL:支持 SQL 语句查询
命名SQL 或 HQL:允许在映射文件xml 中定义 HQL或 SQL,在 class 节点外部:
定制SQL:在映射xml 文件的 class 节点中,注意不能和 version 同时使用。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值