一文快速入門體驗 Hibernate

god23bin發表於2023-05-10

前言

Hibernate 是一個優秀的持久層的框架,當然,雖然現在說用得比較多的是 MyBaits,但是我工作中也不得不接觸 Hibernate,特別是一些老專案需要你維護的時候。所以,在此寫下這篇文章,方便自己回顧,也方便新手入門體驗 Hibernate。

注:使用的版本是 Hibernate 5.x 的

什麼是 ORM?

ORM(Object Relational Mapping,物件關係對映)可以說是一種理論,或者說是一種設計思想,主要是讓「關係型資料庫」和「物件導向程式語言」之間建立對映關係。目的是將資料庫中的資料轉換為物件,以便開發人員更方便地進行資料庫操作。

那為什麼會出現 ORM 呢?

在我們學習 JDBC 的時候,我們需要編寫大量的 SQL 語句來執行資料庫的 CRUD 操作,而且還需要手動將查詢結果轉換為物件。這樣的操作是比較繁瑣的,還容易出錯,而且對於大型專案來說,資料庫操作的程式碼量通常很大,維護起來也是非常困難的。所以,ORM 出現了,幫助我們簡化資料庫操作,提高開發效率。

市面上有很多不同的 ORM 框架可供選擇,在 Java 後端開發的學習路線上,我們需要知道的就有 Hibernate 和 MyBatis。

簡單來說,ORM 就是將資料庫操作封裝成物件操作,透過物件的方式來進行資料庫的增刪改查。

理解 JPA 和 ORM 的關係

JPA(Java Persistence API)是 Java EE(現在稱為 Jakarta EE)規範中定義的一套 API,用於實現物件和關聯式資料庫之間的對映。JPA 提供了一種標準的方式來進行物件持久化操作。而 ORM 是一個通用的概念,不侷限於特定的程式語言或框架。比如 Python 也有對應的實現 ORM 的框架。

換句話說,JPA 它定義了一系列的介面和註解,開發者可以使用這些介面和註解來描述物件和資料庫表之間的對映關係,並進行資料庫操作。而 ORM 是一個更廣泛的概念,它可以適用於其他程式語言和框架,並不侷限於 Java 和 JPA。

注:JPA 就只定義,沒有具體實現,就是所謂的規範、標準,我 JPA 規定了這些 API 能進行相關操作,具體的實現是交給軟體廠商去實現的,比如 Hibernate 就是 JPA 標準的一種實現。

理解 JPA 和 Hibernate 的關係

Hibernate 是 JPA 的一種具體實現,它使用了 JPA 規範定義的介面和註解,提供了 ORM 功能。

從時間線上來看:JPA(Java Persistence API)是在 Hibernate 之後出現的。

最早由 Gavin King 在2001年建立 Hibernate,目標是簡化開發人員進行資料庫的操作,以物件導向的方式去運算元據庫。

JPA 的第一個版本是在2006年釋出的,其中包含了一系列的介面和註解,用於描述物件和資料庫表之間的對映關係,以及進行資料庫操作。Hibernate 的創始人 Gavin King 是 JPA 規範的主要參與者之一。

JPA 規範的出現是為了標準化 ORM 框架的行為和功能,使開發人員可以在不同的 ORM 實現之間進行切換,而不需要修改大量的程式碼。

總結來說,Hibernate 是在 JPA 規範之前出現的 ORM 框架,而 JPA 是在 Hibernate 的基礎上產生的一套標準化的 ORM API。Hibernate 作為 JPA 的一種實現,為開發人員提供了強大的 ORM 功能,併成為了 JPA 規範的主要影響者之一。

正題:Hibernate 簡介

Hibernate 是全自動的物件關係對映的持久層框架,主要透過持久化類(.Java,當然,也習慣說的實體類)、對映檔案(.hbm.xml)和配置檔案(.cfg.xml)來操作關係型資料庫。

Hibernate 封裝了資料庫的訪問細節,透過配置的屬性檔案,來關聯上關係型資料庫和實體類的。

Hibernate 中有 3 個我們需要知道的類,分別是配置類(Configuration)、會話工廠類(SessionFactory)和會話類(Session),注意,此處的 Session 可不是 HttpSession 啊!

  • 配置類

主要負責管理 Hibernate 的配置資訊以及 Hibernate 的啟動,在執行的時候,配置類會讀取一些底層的基本資訊,比如資料庫的 URL、資料庫的使用者名稱、密碼、驅動類、方言(介面卡,Dialect)等資訊。

  • 會話工廠類

主要負責生成 Session,這個工廠類會儲存當前資料庫中所有的對映關係。

  • 會話類

持久化操作的核心,透過它實現 CRUD,它不是執行緒安全的,需要注意不要多個執行緒共享一個 Session 物件。

理解會話二字:顧名思義,實際上就是交流,通訊。在網路中,一次會話可以是一次 HTTP 請求到 HTTP 響應的過程,在資料庫操作中,一次會話,可以是一次新增操作的請求到資料庫中,然後資料庫做出響應。

使用 Maven 構建 Hibernate 專案

最原始引入 Jar 包的方式來建立 Hibernate 專案,可以參考這裡:

http://m.biancheng.net/hibernate/first-example.html

由於我比較懶,所以使用 Maven 來構建一個具有 Hibernate 的 Web 專案。

引入依賴項

分別引入 Hibernate、MySQL 資料庫驅動、單元測試 Junit4(建立 Maven 時自帶的)。

<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.6.14.Final</version>
</dependency>

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
</dependency>

Hibernate 配置檔案

在 resource 目錄下建立一個 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>
        <!-- 配置資料來源、連線池等相關資訊 -->
        <property name="connection.url">jdbc:mysql://localhost:3306/demo_hibernate</property>
        <property name="connection.username">root</property>
        <property name="connection.password">123456</property>
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <!-- Hibernate 方言 -->
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
        <!-- 列印 SQL 語句-->
        <property name="show_sql">true</property>
        <!-- 格式化 SQL 語句-->
        <property name="format_sql">true</property>
        <!-- 對映檔案所在位置 -->
        <mapping resource="cn/god23bin/demo/domain/mapping/User.hbm.xml" />
    </session-factory>
</hibernate-configuration>

以上的配置只是一小部分,還可以配置資料庫連線池、是否自動生成資料庫表等等。

持久化類(實體類)

我們是透過持久化類來運算元據庫表的,也就是 ORM 的體現,即物件對映到資料庫表,資料庫表也對映物件,操作物件就相當於運算元據庫表。所以我們需要編寫持久化類來描述資料庫表,類中的屬性需要與資料庫表中的欄位相匹配。

建立一個 User 類,作為一個 JavaBean(只有 getter 和 setter 方法,沒有其他業務方法的物件)

package cn.god23bin.demo.domain.entity;

/**
 * @author god23bin
 */
public class User {
    private Integer id;
    private String name;
    private String password;

    // 省略 getter 和 setter 方法
}

這種類(JavaBean)在日常開發中是無處不在的,百分之百會用到,也稱它為 POJO(Plain Old Java Object),我們知道這種概念就行,反正這種類就只有屬性和對應的 getter 和 setter 方法。

需要注意的幾點:

  • 必須有無參構造方法,便於 Hibernate 透過 Constructor.newInstance() 例項化持久類。
  • 提供一個標識屬性,一般這個標識屬性對映的是資料庫表中的主鍵欄位,就上面 User 中的 id 屬性。
  • 設計實體類,屬性都是宣告為 private 的。

Hibernate 對映

我們單獨寫了一個持久化類,目前是還沒有做對映的,也就是說還不能透過這個類去運算元據庫,那如何去做對映呢?

這就涉及到一個對映檔案了,對映檔案是 xml 檔案,命名規則一般是 持久化類名.hbm.xml,以 User 為例,它的對映檔案就是 User.hbm.xml

我們可以在專案某個包下建立對映檔案,我選擇在 cn.god23bin.demo.domain.mapping 包下建立:

<?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">
<hibernate-mapping>
    <!-- name 屬性:持久化類的全路徑 -->
    <!-- table 屬性:表的名稱 -->
    <class name="cn.god23bin.demo.domain.entity.User" table="user">
        <!-- 主鍵 -->
        <id name="id" column="id" type="java.lang.Integer">
            <!-- 主鍵生成策略 -->
            <generator class="native"/>
        </id>
        <!-- type 屬性 的三種寫法 -->
        <!-- 1. Java型別 :java.lang.String -->
        <!-- 2. Hibernate型別:string -->
        <!-- 3. SQL型別 :不能直接使用type屬性,需要子標籤<column> -->
        <!--    <column name="name" sql-type="varchar(20)"/> -->
        <property name="name" column="name" type="string" not-null="true" length="50"/>
        <property name="password" column="password" not-null="true" length="50"/>
    </class>
</hibernate-mapping>

注意:對映檔案的編寫需要按照持久化類來編寫,而不是資料庫表。

cn.god23bin.demo.domain.entity.User 稱為全路徑 | 全限定類名 | 全限定名 | 全包名,反正我是見過多種叫法的,指的都是同個東西。

如果對映檔案中沒有配置 column 和 type 屬性,那麼 Hibernate 會預設使用持久化類中的屬性名和屬性型別去匹配資料庫表中的欄位。

建立完這個對映檔案後,我們需要在配置檔案中 <session-factory> 裡指定該對映檔案所在的位置,這樣 Hibernate 才知道對映檔案在哪裡。

<mapping resource="cn/god23bin/demo/domain/mapping/User.hbm.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>
        <property name="connection.url">jdbc:mysql://localhost:3306/demo_hibernate</property>
        <property name="connection.username">root</property>
        <property name="connection.password">123456</property>
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="show_sql">true</property>
        <property name="format_sql">true</property>
        <!-- 對映檔案所在位置 -->
        <mapping resource="cn/god23bin/demo/domain/mapping/User.hbm.xml" />
    </session-factory>
</hibernate-configuration>

配置 pom.xml

由於我是 Maven 來構建專案的,所以需要新增一個配置,便於讓 Hibernate 能夠找到 Maven 工程編譯後的 *.hbm.xml 對映檔案。

在 pom.xml 中,找到 build 標籤,在裡面加上如下的配置:

<build>
    ...
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.xml</include>
                <include>**/*.properties</include>
            </includes>
        </resource>
    </resources>
</build>

Hibernate 工具類

Hibernate 有 3 個需要知道的類,不知道現在你還記不記得,不記得就翻到上面簡介那裡看看。

其中,Session 是持久化操作的核心類,透過它可以實現 CRUD 操作。那麼如何獲取 Session 物件呢?顯而易見,就是透過 Session 工廠,即 SessionFactory 物件,來獲取 Session 物件,那問題又來了,Session 工廠如何獲取?

這裡就得說到 Configuration 配置類了,透過它建立 SessionFactory 物件,進而獲取 Session 物件。

核心程式碼是這樣的:

// 讀取 hibernate.cfg.xml 配置檔案並建立 SessionFactory
Configuration configure = new Configuration().configure(); // 載入配置檔案,configure() 方法可以指定配置檔案所在位置,沒有指定的話,預設為專案的 classpath 根目錄下的 hibernate.cfg.xml
SessionFactory sessionFactory = configure.buildSessionFactory();  // 建立 SessionFactory 物件

一般情況下,我們會寫一個工具類來獲取 Session 物件,如下:

package cn.god23bin.demo.util;

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

/**
 * @author god23bin
 */
public class HibernateUtil {

    /**
     * 一個 ThreadLocal 變數,用於儲存執行緒區域性變數 Session。
     * ThreadLocal 提供了執行緒區域性變數的機制,保證每個執行緒都有自己的 Session 例項。
     */
    private static final ThreadLocal<Session> THREAD_LOCAL = new ThreadLocal<>();

    private static SessionFactory sessionFactory;

    static {
        try{
            // 讀取 hibernate.cfg.xml 配置檔案並建立 SessionFactory
            Configuration configure = new Configuration().configure();
            sessionFactory = configure.buildSessionFactory();
        } catch (Exception e) {
            System.err.println("Hibernate 建立會話工廠失敗!");
            e.printStackTrace();
        }
    }

    /**
     * 獲取 Session 物件
     */
    public static Session getSession() {
        Session session = THREAD_LOCAL.get();
        if (session == null || session.isOpen()) {
            if (sessionFactory == null) {
                rebuildSessionFactory();
            }
            session = (sessionFactory != null) ? sessionFactory.openSession() : null;
            THREAD_LOCAL.set(session);
        }
        return session;
    }

    /**
     * 重新建立會話工廠
     */
    private static void rebuildSessionFactory() {
        try{
            // 讀取 hibernate.cfg.xml 配置檔案並建立 SessionFactory
            Configuration configure = new Configuration().configure();
            sessionFactory = configure.buildSessionFactory();
        } catch (Exception e) {
            System.err.println("Hibernate 建立會話工廠失敗!");
            e.printStackTrace();
        }
    }

    /**
     * 返回唯一的會話工廠物件
     */
    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    /**
     * 關閉 Session 物件
     */
    public static void closeSession() {
        Session session = THREAD_LOCAL.get();
        THREAD_LOCAL.remove();
        if (session != null) {
            session.close();
        }
    }

}

測試

建立一張 user 表:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(50) NOT NULL COMMENT '名稱',
  `password` varchar(50) NOT NULL COMMENT '密碼',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

編寫一個測試類,由於我這裡是 Maven 專案,按照約定,測試類放在 /src/test/java/ 目錄下,我這裡就把這個測試類 HibernateTest 放在 java 目錄下的 cn.god23bin.demo 包中:

package cn.god23bin.demo;

import cn.god23bin.demo.domain.entity.User;
import cn.god23bin.demo.util.HibernateUtil;
import org.hibernate.Session;
import org.junit.Test;

/**
 * @author god23bin
 */
public class HibernateTest {

    @Test
    public void test() {
        // 獲取 Session 物件
        Session session = HibernateUtil.getSession();
        User user = new User();
        user.setName("god23bin");
        user.setPassword("123456");
        try {
            // 開啟事務,即使是執行一次資料庫操作,也是事務
            session.beginTransaction();
            // 執行插入操作
            session.save(user);
            // 提交事務
            session.getTransaction().commit();
        } catch (Exception e) {
            // 發生異常,則回滾事務
            session.getTransaction().rollback();
            System.out.println("插入User資料失敗!");
            e.printStackTrace();
        } finally{
            // 關閉 Session 物件
            HibernateUtil.closeSession();
        }
    }
}

控制檯輸出:

五月 07, 2023 11:52:13 下午 org.hibernate.Version logVersion
INFO: HHH000412: Hibernate ORM core version 5.6.14.Final
五月 07, 2023 11:52:15 下午 org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
五月 07, 2023 11:52:17 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
五月 07, 2023 11:52:17 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost:3306/demo_hibernate]
五月 07, 2023 11:52:17 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
五月 07, 2023 11:52:17 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
五月 07, 2023 11:52:17 下午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections <init>
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
Sun May 07 23:52:17 CST 2023 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
五月 07, 2023 11:52:18 下午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
五月 07, 2023 11:52:20 下午 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
Hibernate: 
    insert 
    into
        user
        (name, password) 
    values
        (?, ?)

Process finished with exit code 0

我們可以檢視資料庫中 User 表中是否存在我們剛剛插入的資料,可以發現是存在的:

image-20230508001245394

總結

我們依次說明了什麼是 ORM,並且梳理了 JPA 和 ORM的關係以及 JPA 和 Hibernate 的關係。

我相信還是有很多人沒有搞清楚它們之間的聯絡的,就只是學了而已,或者說學過而已,當然,也有的人說,知道了它們的關係又能怎樣呢?我不知道我也能用 Hibernate 去運算元據庫。話雖如此,但是我認為明白它們之間的聯絡,是有利於我們後續其他知識的學習的,也能跟其他知識建立起聯絡,而不是單獨的一個知識孤島。

接著介紹了 Hibernate,以及如何使用 Maven 專案去構建一個具有 Hibernate 的 Web 應用,畢竟我們們開發,基本都是 Web 應用程式。

使用 Maven 去構建,就需要引入相關的依賴,Hibernate 的核心依賴以及資料庫驅動的依賴,接著需要編寫配置檔案、持久化類、持久化類的對映檔案,最後寫一個獲取 Session 物件的工具類,便於我們獲取 Session 物件執行資料庫操作。

以上,就是本篇文章的內容,現在恭喜你已經入門 Hibernate 了!是不是很快上手了!哈哈哈

最後的最後

希望各位螢幕前的靚仔靚女們給個三連!你輕輕地點了個贊,那將在我的心裡世界增添一顆明亮而耀眼的星!

我們們下期再見!

相關文章