Hibernate 5 详解!

Hibernate

1、Hibernate框架概述

1.1、什么是Hibernate?

1、Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。

2、Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在Servlet/JSP的Web应用中使用,最具革命意义的是,Hibernate可以在应用EJB的JaveEE架构中取代CMP,完成数据持久化的重任。

3、总结下来

  • 一个框架
  • 一个Java领域的持久层框架
  • 一个ORM框架

1.2、对象的持久化

1、狭义的理解,“持久化”仅仅指把对象永久保存到数据库中

2、广义的理解,“持久化"包括和数据库相关的各种操作:

  • 保存:把对象永久保存到数据库中。
  • 更新:更新数据库中对象(记录)的状态。一删除:从数据库中删除一个对象。
  • 查询:根据特定的查询条件,把符合查询条件的一个或多个对象从数据库加载到内存中。
  • 加载:根据特定的OID,把一个对象从数据库加载到内存中。

3、什么是OID?

为了在系统中能够找到所需对象,需要为每一个对象分配一个唯一的标识号。在关系数据库中称之为主键,而在对象术语中,则叫做对象标识(Object identifier—OID)

1.3、对ORM的理解

1、ORM(Object/Relation Mapping):对象关系映射。ORM主要解决对象-关系的映射。
在这里插入图片描述
2、ORM的思想:将关系数据库中表中的记录映射成为对象,以对象的形式展现,程序员可以把对数据库的操作转化为对对象的操作。

3、ORM采用元数据来描述对象-关系映射细节,元数据通常采用XML格式,并且存放在专门的对象-关系映射文件中。

1.4、Java里流行的ORM框架

1、Hibernate:

  • 非常优秀、成熟的ORM框架。-完成对象的持久化操作
  • Hibernate 允许开发者采用面向对象的方式来操作关系数据库
  • 消除那些针对特定数据库厂商的SQL代码

2、MyBatis:

  • 相比 Hibernate灵活高,运行速度快
  • 开发速度慢,不支持纯粹的面向对象操作,需熟悉sql语句,并且熟练使用sql语句优化功能

3、TopLink

4、OJB 等

2、Hibernate第一个入门程序

2.1、环境准备

使用maven新建一个普通Java项目,并导入Hibernate需要的依赖(hibernate-core),junit以及MySQL驱动的依赖即可,并解决资源导出问题,注意这里关于Hibernate只需导入hibernate-core的依赖即可,maven会自动帮助我们下载其他需要的依赖。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.howie</groupId>
    <artifactId>day01</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--相关依赖-->
    <dependencies>
        <!--mysql-->
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.21</version>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>provided</scope>
        </dependency>
        <!--hibernate-core-->
        <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.4.10.Final</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>ssm-build</finalName>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>
</project>

2.2、编写实体类(pojo)与映射文件(xxx.hbm.xml)

1、编写实体类,注意这里我们以一个User实体类为例,我们这里使用Hibernate暂时不需要创建与实体类对应数据库表,只要建好数据库即可。

User.class

/**
 * @description: User实体类
 * @author: laizhenghua
 * @date: 2020/12/4 13:18
 */
public class User {
    private Integer id;
    private String username;
    private String password;

    public User(){} // 注意必须要有空参构造器
    ...
    // getter and setter
    // toString
}

2、编写映射文件,在Hibernate中,映射文件的命名规范为类名.hbm.xml。编写完的映射配置文件应与实体类在同一个包下。

User.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<!--Mapper映射文件-->
<hibernate-mapping>
    <!-- 建立类与表的映射 -->
    <class name="com.howie.pojo.User" table="user">
        <!-- 建立类中的属性与表中的主键相对应 -->
        <id name="id" type="java.lang.Integer">
            <column name="id"/>
            <!-- 主键的生成策略,后面会讲,native:使用数据库本地生成策略 -->
            <generator class="native"/>
        </id>

        <!-- 建立类中的普通属性和表中的字段相对应 -->
        <property name="username" type="java.lang.String">
            <column name="username"/>
        </property>
        <property name="password" type="java.lang.String">
            <column name="password"/>
        </property>
    </class>
</hibernate-mapping>

2.3、编写Hibernate的核心配置文件

核心配置文件主要是Hibernate框架所使用的,它主要包含了连接数据库的相关信息和Hibernate的相关配置等,至于核心配置文件的命名规范为hibernate.cfg.xml,为什么要这样命名呢?

源码分析:

// 当我们通过Configuration类的对象加载核心配置文件时,读取的文件名为 hibernate.cfg.xml
public Configuration configure() throws HibernateException {
	return this.configure("hibernate.cfg.xml");
}

hibernate.cfg.xml

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

<hibernate-configuration>
    <session-factory>
        <!-- Database connection settings -->
        <property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/test?useSSL=FALSE&amp;serverTimezone=UTC&amp;allowPublicKeyRetrieval=true&amp;characterEncoding=utf8</property>
        <property name="connection.username">root</property>
        <property name="connection.password">101323</property>

        <!-- SQL dialect(hibernate所是的数据库方言) -->
        <property name="dialect">org.hibernate.dialect.MySQL8Dialect</property>
        <!-- 是否在控制台打印SQL语句 -->
        <property name="show_sql">true</property>
        <!-- 是否对SQL进行格式化 -->
        <property name="format_sql">true</property>
        <!-- 指定自动生成数据表的策略 -->
        <property name="hbm2ddl.auto">create</property>
        <!-- 告诉Hibernate的核心配置文件加载哪个映射文件 -->
        <mapping resource="com/howie/pojo/User.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

2.4、通过Hibernate API 编写入门程序

1、新建一个单元测试类,并编写相应测试代码!

@Test
public void test(){
    // 1、加载 Hibernate 的核心配置文件
    /*
    Configuration类用来管理我们的配置文件的信息的,通过它我们可以通过创建一个configuration实例来管理相应的配置文档,
    但是通常我们只创建一个configuration实例。
    */
    Configuration configuration = new Configuration().configure();
    //如果在Hibernate的核心配置文件没有设置加载哪个映射文件,则可手动加载映射文件
    //configuration.addResource("com/howie/pojo/User.hbm.xml");

    // 2、创建SessionFactory对象,类似JDBC中的链接池
    SessionFactory sessionFactory = configuration.buildSessionFactory();

    // 3. 通过SessionFactory获取到Session对象,类似于JDBC中的Connection
    Session session = sessionFactory.openSession();
    // 4、手动开启事务,(最好是手动开启事务)
    Transaction transaction = session.beginTransaction();
    // 5、执行保存操作
    User user = new User();
    user.setUsername("alex");
    user.setPassword("123");
    
    session.save(user);
    // 6、提交事务
    transaction.commit();
    // 7、关闭 Session
    session.close();
    // 8、关闭 SessionFactory
    sessionFactory.close();

}

2、执行,查看结果!
在这里插入图片描述
ok,数据库里已经添加成功,到这里我们已正式入门Hibernate !

2.5、第一个入门程序总结

1、Hibernate工作原理是Configuration读取Hibernate的配置文件和映射文bai件中的信息,即加载配置文件和映射文件,并通过Hibernate配置文件生成一个多线程的SessionFactory对象。

2、然后,多线程SessionFactory对象生成一个线程Session 对象;Session对象生成Query对象或者Transaction对象;可通过Session对象的get(),load(),save(),update(),delete()和saveOrUpdate( )等方法对PO进行加载、保存、更新、删除等操作。

3、在查询的情况下,可通过Session 对象生成一个Query对象,然后利用Query对象执行查询操作,如果没有异常,Transaction对象将提交这些操作结果到数据库中。

4、关于Session接口:

  • Session是应用程序与数据库之间交互操作的一个单线程对象,是Hibernate运作的中心,所有持久化对象必须在session的管理下才可以进行持久化操作。此对象的生命周期很短。
  • Session对象有一个一级缓存,显式执行flush之前,所有的持久层操作的数据都缓存在session对象处。相当于JDBC中的Connection。

5、Transaction(事务)

  • 代表一次原子操作,它具有数据库事务的概念。所有持久层都应该在事务管理下进行,即使是只读操作。
  • 常用方法:
  • commit() 提交相关联的session实例
  • rollback() 撤销事务操作
  • wasCommitted() 检查事务是否提交

6、Hibernate配置文件的两个配置项

hbm2ddl.auto:该属性可帮助程序员实现正向工程,即由java代码生成数据库脚本,进而生成具体的表结构。取值create、update、create-drop、validate等

  • create:会根据xx.hbm.xml文件来生成数据表,但是每次运行都会删除上一次的表,重新生成表,哪怕二次没有任何改变
  • create-drop:会根据xx.hbm.xml文件生成表,但是SessionFactory一关闭,表就自动删除
  • update:最常用的属性值,也会根据xx.hbm.xml文件生成表,但若xx.hbm.xml文件和数据库中对应的数据表的表结构不同,Hiberante将更新数据表结构,但不会删除已有的行和列
  • validate :会和数据库中的表进行比较,若xx.hbm.xml文件中的列在数据表中不存在,则抛出异常

format_sql:是否将SQL转化为格式良好的SQL,取值true | false

3、Hibernate里的Session接口

3.1、搭建实验环境

1、新建一个数据库,并添加两条数据

USE test;
# 建表
CREATE TABLE `user`(
    id int(20) PRIMARY KEY NOT NULL,
    username varchar(30) DEFAULT NULL,
    password varchar(30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;

# 添加两条数据
INSERT INTO test.USER(id,username,password) VALUE(1,"alex","123");
INSERT INTO test.USER(id,username,password) VALUE(2,"howie","123");

2、使用maven新建一个Java项目,并导入相关依赖,解决资源导出等问题。

添加依赖

<!--mysql-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.21</version>
</dependency>
<!--junit-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>provided</scope>
</dependency>
<!--hibernate-core-->
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.4.10.Final</version>
</dependency>

解决资源导出问题

 <build>
    <finalName>ssm-build</finalName>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
    </resources>
</build>

3、编写实体类与实体类对应的映射文件

User.java

/**
 * @description: User实体类
 * @author: laizhenghua
 * @date: 2020/12/4 13:18
 */
public class User {
    private Integer id;
    private String username;
    private String password;

    public User(){} // 提供一个无参的构造器:使用Hibernate可以使用newlnstance()来实例化实体类
    ...
    // getter and setter
    // toString 方便测试
}

为了包结构简洁我们把映射文件放在src/main/resources/mapper/User.hbm.xml

User.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<!--Mapper映射文件-->
<hibernate-mapping>
    <!-- 建立类与表的映射 -->
    <class name="com.howie.pojo.User" table="user">
        <!-- 建立类中的属性与表中的主键相对应 -->
        <id name="id" type="java.lang.Integer">
            <column name="id"/>
            <!-- 主键的生成策略,后面会讲,native:使用数据库本地生成策略 -->
            <generator class="native"/>
        </id>

        <!-- 建立类中的普通属性和表中的字段相对应 -->
        <property name="username" type="java.lang.String">
            <column name="username"/>
        </property>
        <property name="password" type="java.lang.String">
            <column name="password"/>
        </property>
    </class>
</hibernate-mapping>

4、编写Hibernate的核心配置文件

hibernate.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<!-- 核心配置文件 -->
<hibernate-configuration>
    <session-factory>
        <!-- Database connection settings -->
        <property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/test?useSSL=FALSE&amp;serverTimezone=UTC&amp;allowPublicKeyRetrieval=true&amp;characterEncoding=utf8</property>
        <property name="connection.username">root</property>
        <property name="connection.password">101323</property>

        <!-- SQL dialect(hibernate所是的数据库方言) -->
        <property name="dialect">org.hibernate.dialect.MySQL8Dialect</property>
        <!-- 是否在控制台打印SQL语句 -->
        <property name="show_sql">true</property>
        <!-- 是否对SQL进行格式化 -->
        <property name="format_sql">true</property>
        <!-- 指定自动生成数据表的策略,update最常用 -->
        <property name="hbm2ddl.auto">update</property>
        <!-- 告诉Hibernate的核心配置文件加载哪个映射文件 -->
        <mapping resource="mapper/User.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

5、为了方便测试,我们把加载配置文件,获取Session对象、Transaction对象等的代码,封装为一个工具类。

HibernateUtil.java

package com.howie.utils;

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

/**
 * @description: Hibernate的工具类,用于获取Session对象与Transaction对象
 * @author: laizhenghua
 * @date: 2020/12/4 17:08
 */
public class HibernateUtil {
	/* 实际开发中,为了代码安全Session对象一般不能作为成员变量 */
    private static SessionFactory sessionFactory = null;
    private static Session session = null;
    /**
     * @description: 用于获取Session对象
     * @author: laizhenghua
     * @date: 2020/12/4 17:13
     * @param: null
     * @return: org.hibernate.Session
     */
    public static Session getSession(){
        // 1、加载核心配置文件
        Configuration configuration = new Configuration().configure();
        // 2、创建SessionFactory对象,类似JDBC中的链接池
        sessionFactory = configuration.buildSessionFactory();
        // 3、返回Session对象
        session = sessionFactory.openSession();
        return session;
    }
    /**
     * @description: 获取Transaction对象(操作事务的对象)
     * @author: laizhenghua
     * @date: 2020/12/4 17:26
     * @param: session
     * @return: org.hibernate.Transaction
     */
    public static Transaction getTransaction(Session session){
        return session.beginTransaction();
    }
    /**
     * @description: 关闭Session对象
     * @author: laizhenghua
     * @date: 2020/12/4 17:28
     * @param:
     * @return: void
     */
    public static void closeSession(){
        if(session != null){
            session.close();
        }
        if(sessionFactory != null){
            sessionFactory.close();
        }
    }
}

6、新建一个测试类HibernateTest.java,进行添加测试

@Test
public void test(){
    // 1、获取Session对象
    Session session = HibernateUtil.getSession();
    // 2、获取Transaction对象
    Transaction transaction = HibernateUtil.getTransaction(session);
    // 3、添加一条数据
    User user = new User();
    user.setId(3);
    user.setUsername("无情哈拉少");
    user.setPassword("123");
    String sql = "insert into test.user(id,username,password) value(?,?,?)";
    session.createSQLQuery(sql).setParameter(1,user.getId()).
            setParameter(2,user.getUsername()).setParameter(3,user.getPassword()).executeUpdate();
    // 4、提交事务
    transaction.commit();
    // 5、关闭资源
    HibernateUtil.closeSession();
}

查看结果!
在这里插入图片描述
OK,我们的环境已搭建完毕!

3.2、Session接口概述

1、Session接口是Hibernate向应用程序提供的操纵数据库的最主要的接口,它提供了基本的保存,更新,删除和加载Java对象的方法。

2、Session具有一个缓存(自带一级缓存默认开启),位于缓存中的对象称为持久化对象,它和数据库中的相关记录对应。Session能够在某些时间点,按照缓存中对象的变化来执行相关的SQL语句,来同步更新数据库,这一过程被称为刷新缓存(flush)。

3、站在持久化的角度,Hibernate 把对象分为4种状态:持久化状态,临时状态,游离状态,删除状态。Session的特定方法能使对象从一个状态转换到另一个状态。

3.3、Session缓存

1、在 Session 接口的实现中包含一系列的Java集合,这些Java集合构成了Session 缓存,只要Session 实例没有结束生命周期,且没有清理缓存,则存放在它缓存中的对象也不会结束生命周期。

2、Session缓存可减少Hibernate应用程序访问数据库的频率。

3、一级缓存也叫本地缓存(Session级别的缓存),与数据库同一次会话期间查询到的数据会放在本地缓存中,以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库

/* 测试一级缓存 */
@Test
public void test1(){
    // 1、获取Session对象
    Session session = HibernateUtil.getSession();
    // 2、获取数据
    User user1 = session.get(User.class, 2);
    System.out.println(user1);
    // 这里 user2 不在从从数据里读取的,而是从缓存中读取,SQL也只执行了一次
    User user2 = session.get(User.class,2);
    System.out.println(user2);
    System.out.println(user1 == user2);
    // 3、关闭资源
    HibernateUtil.closeSession();
}

查看输出的日志!
在这里插入图片描述
小结:一级缓存默认是开启的,只在一次Session中有效,也就是拿到连接到关闭连接这个区间段 !

4、缓存失效情况:

  1. 查询不同的东西

  2. 增删改操作,可能会改变原来的数据,所以必定会刷新缓存!

  3. 查询不同的Mapper.xml

  4. 手动清理缓存!

@Test
public void test1(){
    // 1、获取Session对象
    Session session = HibernateUtil.getSession();
    // 2、获取数据
    User user1 = session.get(User.class, 2);
    System.out.println(user1);
    session.clear(); // 手动清理缓存
    /* 此时SQL语句会执行两次 */
    User user2 = session.get(User.class,2);
    System.out.println(user2);
    System.out.println(user1 == user2); // false
    // 3、关闭资源
    HibernateUtil.closeSession();
}

3.4、flush缓存

1、操作Session中的缓存
在这里插入图片描述
2、flush:Session按照缓存中对象的属性变化来同步更新数据库。默认情况下Session在以下时间点刷新缓存:

  • 显式调用Session的flush()方法
  • 当应用程序调用Transaction的commit()方法的时,该方法先flush,然后在向数据库提交事务
  • 当应用程序执行一些查询(HQL,Criteria)操作时,如果缓存中持久化对象的属性已经发生了变化,会先flush缓存。以保证查询结果能够反映持久化对象的最新状态。

flush缓存的例外情况:

  • 如果对象使用native生成器生成OID,那么当调用Session的save()方法保存对象时,会立即执行向数据库插入该实体的insert语句。

3、commit()flush()方法的区别:flush 执行一系列sql语句,但不提交事务,commit方法先调用flush()方法,然后提交事务。意味着提交事务意味着对数据库操作永久保存下来。

/**
flush: 使数据表中的记录和 Session 缓存中的对象的状态保持一致. 为了保持一致, 则可能会发送对应的 SQL 语句.
1. 在 Transaction 的 commit() 方法中: 先调用 session 的 flush 方法, 再提交事务
2. flush() 方法会可能会发送 SQL 语句, 但不会提交事务. 
3. 注意: 在未提交事务或显式的调用 session.flush() 方法之前, 也有可能会进行 flush() 操作.
	1) 执行 HQL 或 QBC 查询, 会先进行 flush() 操作, 以得到数据表的最新的记录
	2) 若记录的 ID 是由底层数据库使用自增的方式生成的, 则在调用 save() 方法时, 就会立即发送 INSERT 语句. 因为 save 方法后, 必须保证对象的 ID 是存在的!
 */
@Test
public void testSessionFlush2(){
	News news = new News("Java", "SUN", new Date());
	session.save(news);
}

@Test
public void testSessionFlush(){
	News news = (News) session.get(News.class, 1);
	news.setAuthor("Oracle");
	
// session.flush();
// System.out.println("flush");
	
	News news2 = (News) session.createCriteria(News.class).uniqueResult();
	System.out.println(news2);
}

4、reresh()方法

@Test
public void rereshTest(){
    /*User user = new User();
    user.setId(4);
    user.setUsername("关羽");
    user.setPassword("123");*/
    Session session = HibernateUtil.getSession();
    User user = session.get(User.class, 3);
    System.out.println(user);

    session.refresh(user);
    /*
    refresh(): 会强制发送 SELECT 语句, 以使 Session 缓存中对象的状态和数据表中对应的记录保持一致!
     */
    System.out.println(user);
    HibernateUtil.closeSession();
}

3.5、Session接口的关系图

在这里插入图片描述

3.6、Hibernate中设置隔离级别

1、MySQL支持4中事务隔离级别,MySQL默认的事务隔离级别为:REPEATABLE READ

2、REPEATABLE READ(可重复读):确保事务可以多次从一个字段中读取相同的值。在这个事务持续期间,禁止其他事物对这个字段进行更新。可以避免脏读和不可重复读,但幻读的问题仍然存在。

3、回顾MySQL中设置隔离级别

# 1、每启动一个mysql程序,就会获得一个单独的数据库连接。
# 2、每个数据库连接都有一个全局变量@@txisolation,表示当前的事务隔离级别。
# 3、MySQL默认的隔离级别为RepeatableRead

# 查看当前的隔离级别: 
SELECT @@tx...isalation;

# 设置当前mySQL连接的隔离级别:
set transaction isolation level read committed;

# 设置数据库系统的全局的隔离级别:
set global transaction isolation level read committed;

4、在Hibernate中设置隔离级别

JDBC数据库连接使用数据库系统默认的隔离级别。在Hibernate的配置文件中可以显式的设置隔离级别。每一个隔离级别都对应一个整数:

READ UNCOMMITED == 1
READ COMMITED == 2
REPEATABLE READ == 3
SERIALIZEABLE == 4

而我们使用Hibernate设置数据库隔离级别时,只需在配置文件中添加一个property标签即可。

<!--设置Hibernate的事务隔离级别-->
<property name="connection.isolation">2</property>

hibernate.connection.isolation 属性来设置事务的隔离级别。

4、持久化对象的状态

4.1、持久化对象的4种状态

1、站在持久化的角度,Hibernate 把对象分为4种状态

  • 持久化状态
  • 临时状态
  • 游离状态
  • 删除状态

Session的特定方法能使对象从一个状态转换到另一个状态。

2、临时对象(Transient)

  • 在使用代理主键的情况下,OID通常为null
  • 不处于 Session 的缓存中。
  • 在数据库中没有对应的记录。

3、持久化对象(也叫"托管",Persist)

  • OID不为null
  • 位于Session缓存中。
  • 若在数据库中已经有和其对应的记录,持久化对象和数据库中的相关记录对应
  • Session在 flush缓存时,会根据持久化对象的属性变化,来同步更新数据库。
  • 在同一个Session实例的缓存中,数据库表中的每条记录只对应唯一的持久化对象。

4、删除对象(Removed)

  • 在数据库中没有其OID对应的记录
  • 不在属于 Session 缓存中
  • 一般情况下应用程序不该在使用被删除的对象。

5、游离对象(也叫"脱管") (Detached)

  • OID不为null
  • 不再处于Session缓存中。
  • 一般情况需下,游离对象是由持久化对象转变过来的,因此在数据库中可能还存在与它对应的记录。
  • 也可理解为,请假,虽然暂时脱离了,公司的管理,但是你还是公司的人。

4.2、持久化对象状态转换图

在这里插入图片描述

4.3、Session的save()方法

首先我们知道 Session里的save()方法可以把持久化对象从临时状态变为持久化状态!并把对象保存到数据库里。

@Test
public void testSave(){
    /*
        save()方法
        1、使临时对象转变为持久化对象
        2、在flush缓存时,会发送一条insert语句
 
    */
    Session session = HibernateUtil.getSession();

    User user = new User();
    user.setUsername("无情哈拉少");
    user.setPassword("123");

    System.out.println("临时状态:" + user);
    session.save(user); // 保存,注意我们在数据库里设置了 id 字段自增
    System.out.println("持久化状态:" + user);

    HibernateUtil.closeSession();
}

我们再来看输出的日志!

INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
临时状态:User{id=null, username='无情哈拉少', password='123'}
Hibernate: 
    insert 
    into
        user
        (username, password) 
    values
        (?, ?)
持久化状态:User{id=6, username='无情哈拉少', password='123'}
十二月 09, 2020 2:13:23 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PoolState stop

我们发现在Hibernate5版本里save()方法自动提交了事务,并且发送了一条insert语句!!把数据保存到数据里,把对象从临时对象转变为持久化状态(再次打印user时,id已不为null)。

4.4、Session的persist()方法

persist()save()方法类似,也会执行INSERT语句,也是把持久化对象从临时状态转变为持久化状态,但是也有一些区别,在Hibernate5里persist()方法不会自动提交事务! 在调用 persist 方法之前, 若对象已经有 id 了, 则不会执行 INSERT, 而抛出异常!!!

@Test
public void persistTest(){
    Session session = HibernateUtil.getSession();
    Transaction transaction = HibernateUtil.getTransaction(session); // 获取操作事务的对象
    User user = new User();
    user.setUsername("persist");
    user.setPassword("123");

    System.out.println("临时状态:" + user);
    session.persist(user); // 保存对象
    System.out.println("持久化状态:" + user);
    
    transaction.commit(); // 提交事务
    HibernateUtil.closeSession();
}

/* 输出的部分日志
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
临时状态:User{id=null, username='persist', password='123'}
Hibernate: 
    insert 
    into
        user
        (username, password) 
    values
        (?, ?)
持久化状态:User{id=7, username='persist', password='123'}
十二月 09, 2020 2:35:13 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PoolState stop
*/

4.5、Session的get()&load()方法

Session中load()方法和get()方法的功能类似,也是查询出指定的一条记录,都会发送查询语句,但是get()load()也有一定的区别!!

@Test
public void loadTest(){
    Session session = HibernateUtil.getSession();
    User user1 = session.load(User.class, 1); // load延迟加载,节省内存(使用就加载,不使用则不加载)
    System.out.println(user1.getClass().getName()); // 代理对象:com.howie.pojo.User$HibernateProxy$3aJET1Gx

    session.close();
    // 若打印对象前,把Session关闭,会抛出一个懒加载异常
    System.out.println(user1);

    /*User user2 = session.get(User.class, 1);
    System.out.println(user1 == user2); // true*/
    /*
    get() vs load()
        1、执行get方法:会立即加载对象,而执行load方法若不使用对象的属性,则不会立即执行查询操作,而返回一个代理对象。
        2、get是立即检索,而load是延迟检索
        3、若数据库表中没有对应的记录,且 Session 也没有被关闭,同时需要使用对象时。get在返回null。load抛出异常
            load方法若不使用该对象也不会抛出异常。
        4、load方法可能会抛出懒加载异常:org.hibernate.LazyInitializationException。
            在需要初始化代理对象之前,已经关闭Session,此时就会抛出懒加载异常
    */
}
@Test
public void testGet(){
    Session session = HibernateUtil.getSession();
    User user = session.get(User.class, 1);
    session.close();
    // 即使Session已经关闭了,但是执行get方法时,对象已经加载了,所以可以打印出对象
    System.out.println(user);
}

4.6、Session的update()方法

1、Session的update()方法使一个游离对象转变为持久化对象,并且计划执行一条update语句。

2、若希望 Session仅当修改了User对象的属性时,才执行update()语句,可以把映射文件中元素的select-before-update设为true,该属性的默认值为false。

3、update()方法关联一个游离对象时,如果在Session的缓存中已经存在相同OID的持久化对象,会抛出异常。

4、当update()方法关联一个游离对象时,如果在数据库中不存在相应的记录,也会抛出异常。

/*
update()方法:
1、前面我们知道,若持久化对象与查询出来的数据表的记录不一样,提交事务后,Hibernate会自动发送一条更新语句,更新最新的记录
2、若更新一个持久化对象,不需要显示调用 update方法,因为提交事务时,会先执行 Session 的flush() 方法。
3、更新一个游离对象,需要显示调用 Session 的 update()方法,可以把一个游离对象变为持久化对象

注意点:
1、无论要更新的游离对象和数据表的记录是否一致,都会发送update语句
    如何才能让 update 方法不盲目的发送update语句?
    在 xxx.hbm.xml 映射文件中的 class 节点设置一个 select-before-update=true (默认为false,通常不用设置此属性)
2、若数据表中没有对应的记录,但还调用了 update 方法会抛出异常。
3、update()方法关联一个游离对象时,如果在Session的缓存中已经存在相同OID的持久化对象,会抛出异常。
    Session缓存中不能有两个 OID 相同的对象
*/
@Test
public void updateTest(){
    Session session = HibernateUtil.getSession();
    Transaction transaction = HibernateUtil.getTransaction(session);
    User user = session.get(User.class, 1);

    transaction.commit();
    HibernateUtil.closeSession();

    session = HibernateUtil.getSession();
    transaction = HibernateUtil.getTransaction(session);
    user.setUsername("alex"); // 此时user对象就变为游离状态,需要显示调用 update 方法
    session.update(user);
    transaction.commit();
    System.out.println(user);
}

4.7、Session的saveOrUpate()方法

1、Session的saveQrUpdate()方法同时包含了save()与update()方法的功能。
在这里插入图片描述
2、判断对象为临时对象的标准

  • Java对象的 OID 为 null
  • 映射文件中为设置了unsaved-value属性,并且Java对象的OID取值与这个unsaved-value属性值匹配
/*
注意点:
1、若 OID 不为null,但数据表中还没有和其对应的记录,会抛出异常
2、了解:0ID 值等于id的unsaved-value属性值的对象,也被认为是一个游离对象
 */
@Test
public void saveOrUpdateTest(){
    User user = new User();
    user.setUsername("哈士奇");
    user.setPassword("123");

    Session session = HibernateUtil.getSession();
    Transaction transaction = HibernateUtil.getTransaction(session);
    session.saveOrUpdate(user); // 执行save()方法,发送 insert 语句
    // user.setId(1); 执行update()方法,发送update语句
    transaction.commit();
    HibernateUtil.closeSession();
}

4.8、Session的delete()方法

1、Session的delete()方法既可以删除一个游离对象,也可以删除一个持久化对象。

2、Session的delete()方法处理过程

  • 计划执行一条delete语句。
  • 把对象从Session缓存中删除,该对象进入删除状态。
/*
delete()方法:
1、执行删除操作,只要OID和数据表中有一条记录对应,就会准备执行delete操作!
2、若OID咋数据表中没有对应的记录则抛出异常
 */
@Test
public void deleteTest(){
    User user = new User();
    user.setId(8);
    Session session = HibernateUtil.getSession();
    Transaction transaction = HibernateUtil.getTransaction(session);
    session.delete(user); // 删除游离对象
    transaction.commit();
    HibernateUtil.closeSession();
}

/* 输出的日志
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
Hibernate: 
    delete 
    from
        user 
    where
        id=?
十二月 11, 2020 11:52:20 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PoolState stop
*/

3、Hibernate 的cfg.xml配置文件中有一个hibernate.use_identifier_rollback属性,其默认值为false,若把它设为true,将改变delete()方法的运行行为:delete()方法会把持久化对象或游离对象的OID设置为null,使它们变为临时对象。

修改hibernate.cfg.xml配置文件

<!--删除对象后,使其OID置为null-->
<property name="hibernate.use_identifier_rollback">true</property>

测试

@Test
public void deleteTest(){
    Session session = HibernateUtil.getSession();
    Transaction transaction = HibernateUtil.getTransaction(session);

    User user = session.get(User.class, 6);
    session.delete(user);

    System.out.println(user);
    transaction.commit();
    HibernateUtil.closeSession();
}
/* 输出的日志
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.username as username2_0_0_,
        user0_.password as password3_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
User{id=null, username='无情哈拉少', password='123'}
Hibernate: 
    delete 
    from
        user 
    where
        id=?
十二月 11, 2020 11:59:07 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PoolState stop
*/

4.9、Session的evict()方法

1、Session中的evict()方法,从session缓存中把指定的持久化对象移除。

2、测试

@Test
public void evictTest(){
    Session session = HibernateUtil.getSession();
    Transaction transaction = HibernateUtil.getTransaction(session);

    User user = session.get(User.class, 2);
    user.setUsername("哈士奇");

    session.evict(user); // 从session缓存中移除user对象,数据表中并没有得到更新

    transaction.commit();
    HibernateUtil.closeSession();
}

5、Hibernate中的存储过程

5.1、通过Hibernate调用存储过程

1、Hibernate并没有提供操作存储过程的API,在Hibernate里我们只能操作原生jdbc的Connection来操作存储过程等。

2、Session的doWork(Work)方法用于执行Work 对象指定的操作,即调用Work 对象的execute()方法。Session会把当前使用的数据库连接传递给execute()方法。

@Test
public void doWorkTest(){
    Session session = HibernateUtil.getSession();
    session.doWork(new Work() {
        @Override
        public void execute(Connection connection) throws SQLException {
            System.out.println(connection); // com.mysql.cj.jdbc.ConnectionImpl@5327a06e
            
            // 在此我们可以操作原生 jdbc的Connection,从而操作存储过程
        }
    });
}

5.2、Hibernate与触发器协同工作

1、Hibernate 与数据库中的触发器协同工作时,会造成两类问题:

  • 触发器使Session的缓存中的持久化对象与数据库中对应的数据不一致:触发
    器运行在数据库中,它执行的操作对Session是透明的。
  • Session 的update()方法盲目地激发触发器:无论游离对象的属性是否发生变
    化,都会执行update语句,而update语句会激发数据库中相应的触发器。

2、解决方案

  • 在执行完Session的相关操作后,立即调用Session 的flush()和refresh()方法,迫使Session的缓存与数据库同步(refresh()方法重新从数据库中加载对象)
  • 在映射文件的的元素中设置select-before-update属性:当Session的update()saveOrUpdate()方法更新一个游离对象时,会先执行Select语句,获得当前游离对象在数据库中的最新数据,只有在不一致的情况下才会执行update语句。

6、Hibernate配置文件详解

1、Hibernate配置文件主要用于配置数据库连接和Hibernate运行时所需的各种属性。每个Hibernate配置文件对应一个Configuration对象。

2、Hibernate配置文件可以有两种格式:

  • hibernate.properties
  • hibernate.cfg.xml 推荐使用

3、下面就介绍hibernate.cfg.xml常用的属性。

6.1、JDBC连接属性

1、常用属性名

connection.url:数据库URL

connection.username:数据库用户名

connection.password:数据库用户密码

connection.driver_class:数据库JDBC驱动

dialect:配置数据库的方言,根据底层的数据库不同产生不同的sgl语句,Hibernate 会针对数据库的特性在访问时进行优化。

2、配置实例

<!-- Database connection settings -->
<property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/test?useSSL=FALSE&amp;serverTimezone=UTC&amp;allowPublicKeyRetrieval=true&amp;characterEncoding=utf8</property>
<property name="connection.username">root</property>
<property name="connection.password">xxx</property>

<!-- SQL dialect(hibernate所是的数据库方言) -->
<property name="dialect">org.hibernate.dialect.MySQL8Dialect</property>
<!-- 是否在控制台打印SQL语句 -->

6.2、切换数据库连接池

1、Hibernate自带的连接池算法相当不成熟.。它只是为了让你快些上手,并不适合用于产品系统或性能测试中。 出于最佳性能和稳定性考虑你应该使用第三方的连接池。只需要用特定连接池的设置替hibernate.connection.pool_size即可。这将关闭Hibernate自带的连接池。例如,我们可以使用C3P0/Druid等,这里我们以Druid为例!

2、关于更多数据库连接池的学习,可在网上自行学习!

3、使用步骤:

  1. 引入maven依赖
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.21</version>
</dependency>
  1. 加入配置(这里只列出常用配置)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <!-- SQL dialect(hibernate所是的数据库方言) -->
        <property name="dialect">org.hibernate.dialect.MySQL8Dialect</property>
        <!-- 是否在控制台打印SQL语句 -->
        <property name="show_sql">true</property>
        <!-- 是否对SQL进行格式化 -->
        <property name="format_sql">true</property>
        <!-- 指定自动生成数据表的策略 -->
        <property name="hbm2ddl.auto">update</property>
        <!--删除对象后,使其OID置为null-->
        <property name="hibernate.use_identifier_rollback">true</property>
        <!-- 告诉Hibernate的核心配置文件加载哪个映射文件 -->

        <!-- 配合Druid数据源 -->
        <property name="connection.provider_class">com.alibaba.druid.support.hibernate.DruidConnectionProvider</property>
        <property name="url">jdbc:mysql://localhost:3306/test?useSSL=FALSE&amp;serverTimezone=UTC&amp;allowPublicKeyRetrieval=true&amp;characterEncoding=utf8</property>
        <property name="username">root</property>
        <property name="password">101323</property>
        <property name="driverClassName">com.mysql.cj.jdbc.Driver</property>
        <property name="filters">stat</property>
        <property name="maxActive">20</property>
        <property name="initialSize">1</property>
        <property name="maxWait">60000</property>
        <property name="minIdle">1</property>
        <property name="timeBetweenEvictionRunsMillis">60000</property>
        <property name="minEvictableIdleTimeMillis">300000</property>
        <property name="testWhileIdle">true</property>
        <property name="testOnBorrow">false</property>
        <property name="testOnReturn">false</property>
        <property name="poolPreparedStatements">true</property>
        <property name="maxOpenPreparedStatements">20</property>
        <property name="asyncInit">true</property>

        <mapping resource="mapper/User.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

更多配置可自行查询!

  1. 我们来编写测试方法,来查看数据源!
@Test
public void druidTest(){
    Session session = HibernateUtil.getSession();
    session.doWork(new Work() {
        @Override
        public void execute(Connection connection) throws SQLException {
            System.out.println("数据源:" + connection);
        }
    });
}
  1. 输出结果
    在这里插入图片描述

6.3、其他配置

1、show_sql:是否将运行期生成的SQL输出到日志以供调试。取值true | false

2、format_sql:是否将SQL转化为格式良好的SQL。取值true | false

3、hbm2ddl.auto:在启动和停止时自动地创建,更新或删除数据库模式。取值create | update | create-drop l validate

<!-- SQL dialect(hibernate所是的数据库方言) -->
<property name="dialect">org.hibernate.dialect.MySQL8Dialect</property>
<!-- 是否在控制台打印SQL语句 -->
<property name="show_sql">true</property>
<!-- 是否对SQL进行格式化 -->
<property name="format_sql">true</property>
<!-- 指定自动生成数据表的策略 -->
<property name="hbm2ddl.auto">update</property>
<!--删除对象后,使其OID置为null-->
<property name="hibernate.use_identifier_rollback">true</property>
<!-- 告诉Hibernate的核心配置文件加载哪个映射文件 -->
<mapping resource="mapper/User.hbm.xml"/>

4、hibernate.jdbc.fetch_size:实质是调用 Statement.setFetchSize() 方法设定JDBC的Statement读取数据的时候每次从数据库中取出的记录条数。

/*
例如一次查询1万条记录,对于Oracle的JDBC驱动来说,是不会1次性把1万条取出来的,而只会取出fetchSize, 条
数,当结果集遍历完了这些记录以后,再去数据库取 fetchSize 条数据。因此大大节省了无谓的内存消耗。

FetchSize设的越大,读数据库的次数越少,速度越快;FetchSize越小,读数据库的次数越多,速度越慢。Oracle
数据库的JDBC驱动默认的FetchSize = 10,是一个保守的设定,根据测试,当Fetch Size=50时,性能会提升1倍
之多,当fetch Size=100,性能还能继续提升20%,Fetch Size继续增大,性能提升的就不显著了。并不是所有的
数据库都支持Fetch Size特性,例如MlySQL就不支持.
*/

5、hibernate.jdbc.batch_size:设定对数据库进行批量删除,批量更新和批量插入的时候的批次大小,类似于设置缓冲区大小的意思。batchSize越大,批量操作时向数据库发送sql的次数越少,速度就越快。

/*
测试结果是当Batch Size = 0 的时候,使用Hibernate对Oracle数据库删除1万条记录需要25秒,
Batch Size = 50的时候,删除仅仅需要5秒! Oracle数据库batchSize=30的时候比较合适。
*/

7、对象关系映射文件

7.1、映射文件说明

1、我们知道,在使用Hibernate作为持久层框架时,都需要在hibernate.cfg.xml配置文件里指明要加载的映射文件,有了映射文件,hibernate才能完成对象与数据表的关系映射。

2、POJO类和关系数据库之间的映射可以用一个XML文档来定义。

3、通过POJO类的数据库映射文件,Hibernate可以理解持久化类和数据表之间的对应关系,也可以理解持久化类属性与数据库表列之间的对应关系。

4、在运行时Hibernate将根据这个映射文件来生成各种SQL语句。映射文件的扩展名为实体类名.hbm.xml

5、官方中文文档:https://hibernate.net.cn/column/5.html

7.2、hibernate-mapping

1、类层次(class标签)

主键:id

  • 被映射的类必须定义对应数据库表主键字段。大多数类有一个JavaBeans风格的属性,为每一个实例包含唯一的标识。<id>元素定义了该属性到数据库表主键字段的映射。

基本类型:property

实体引用类:many-to-one | one-to-one

集合:set | list | map | array

  • one-to-many
  • many-to-many

子类:subclass | joined-subclass

其它:component | any 等

2、查询语句:query (用来放置查询语句,便于对数据库查询的统一管理和优化)

3、每个Hibernate-mapping中可以同时定义多个类。但更推荐为每个类都创建一个单独的映射文件。

4、hibernate-mapping可配置属性
在这里插入图片描述
例如package属性的配置:

我们指明包路径后,建立类与表的映射关系时,只需写明类名即可。

<!--Mapper映射文件-->
<hibernate-mapping package="com.howie.pojo">
    <!-- 建立类与表的映射 -->
    <class name="User" table="user" select-before-update="true">
    ....

7.3、映射对象标识符

1、Hibernate使用对象标识符(OID)来建立内存中的对象和数据库表中记录的对应关系.对象的OID和数据表的主键对应,Hibernate通过标识符生成器来为主键赋值。

2、Hibernate 推荐在数据表中使用代理主键,即不具备业务含义的字段,代理主键通常为整数类型,因为整数类型比字符串类型要节省更多的数据库空间。

3、在对象-关系映射文件中,<id>元素用来设置对象标识符。<generator>子元素用来设定标识符生成器。

4、Hibernate提供了标识符生成器接口:LdentifierGenerator,并提供了各种内置实现

end

所有有关Hibernate有关的内容都可以在官网上查询得到!!!!

官网地址:http://hibernate.org/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lambda.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值