[Java]Hibernate學習筆記及實用速查手冊

  • Hibernate是一款開源持久層ORM框架。
  • ORM:Object-Relational Mapping(對象關係映射),描述Java對象和關係型數據庫之間的映射關係,能夠自動將Java程序中的對象持久化到關係型數據庫中。開發人員在配置好映射關係之後,只需要關注Java對象即可,無需編寫SQL。
  • 官方文檔

0. pom.xml

<!-- ORM -->
<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-core</artifactId>
  <version>5.4.17.Final</version>
</dependency>
<!-- mysql依賴 -->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.20</version>
</dependency>

1. 數據庫表、Java對象與Hibernate配置文件

數據庫的表結構和預準備數據:

CREATE TABLE student(
    id BIGINT(20) NOT NULL COMMENT '主鍵ID',
    name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
    age INT(11) NULL DEFAULT NULL COMMENT '年齡',
    email VARCHAR(50) NULL DEFAULT NULL COMMENT '郵箱',
    PRIMARY KEY (id)
);

INSERT INTO student (id, name, age, email) VALUES
(1, 'Tom', 28, '[email protected]'),
(2, 'Jack', 20, '[email protected]');

Java中的對象:

@Data // 使用了 lombok 自動完成set()/get()方法
public class Student {
    private Integer id;
    private String name;
    private Integer age;
    private String email;
}

1.1 持久化類的編寫規範

其中,需要完成ORM的類有如下要求:

  1. 所有屬性採用 private 修飾、 get()/set() 方法採用 public 修飾。
  2. 具有一個無參 public 構造方法。
  3. 具有一個映射數據庫主鍵字段的OID。
  4. 不可用 final 修飾持久化類。

1.2 Higernate配置文件編寫示例

編寫 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.driver_class">com.mysql.cj.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai</property>
        <property name="connection.username">root</property>
        <property name="connection.password">$password</property>

        <!-- JDBC 連接池大小:不推薦使用,生產環境中請使用c3p0 -->
        <property name="connection.pool_size">10</property>

        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>

        <!-- 在控制檯顯示執行的sql語句 -->
        <property name="show_sql">true</property>

        <!-- 格式化SQL語言 -->
        <property name="format_sql">true</property>

      
        <!-- 注意 <mapping>標籤要寫在所有<property/>之後 -->
        <!-- 映射文件配置 -->
        <!-- 如:<mapping resource="pers/model/domain/Student.hbm.xml"/> -->
        <!-- 註解映射 -->
        <!-- 如:<mapping class="pers.model.domain.Student"/> -->

    </session-factory>

</hibernate-configuration>
  • 注意 <mapping> 標籤要寫在所有 <property> 之後。

1.3 配置文件:hibernate-configuration

<session-factory><property name=?> 常用值如下:(其它配置項)

名 稱 描 述
dialect 操作數據庫語言版本,如 org.hibernate.dialect.MySQL5Dialect ;正常情況下會自動指定。
show_sql 在控制檯輸出 SQL 語句,默認爲 false
format_sql 格式化控制檯輸出的 SQL 語句,當 show_sql 爲真時配置起效
hbm2ddl.auto 當 SessionFactory 創建時是否根據映射文件自動驗證表結構或 自動創建、自動更新數據庫表結構。該參數的取值爲 validateupdatecreatecreate-drop
connection.driver_class 連接數據庫驅動程序
connection.url 連接數據庫 URL
connection.username 數據庫用戶名
connection.password 數據庫密碼
connection.autocommit 事務是否自動提交
connection.pool_size 連接池大小【不推薦使用】
connection.isolation 事務的隔離等級,MySQL默認是REPEATABLE_READ。
connection.provider_class 向Hibernate提供JDBC的類
c3p0.timeout 獲得連接的超時時間,如果超過這個時間,會拋出異常,單位毫秒
c3p0.max_statements 最大的PreparedStatement的數量
c3p0.idle_test_period 每隔120秒(默認)檢查連接池裏的空閒連接 ,單位是秒
c3p0.acquire_increment 當連接池裏面的連接用完的時候,C3P0一次性獲取的新的連接數
c3p0.validate 每次都驗證連接是否可用

如果要使用C3P0作爲連接池,還需要引入 hibernate-c3p0

<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-c3p0</artifactId>
  <version>${hibernate.version}</version>
</dependency>

然後配置連接池:

<!-- 配置C3P0連接池(這個是 Hibernate 5.x 之後的新類) -->
<property name="connection.provider_class">org.hibernate.c3p0.internal.C3P0ConnectionProvider</property>
<!--在連接池中可用的數據庫連接的最少數目 -->
<property name="c3p0.min_size">5</property>
<!--在連接池中所有數據庫連接的最大數目 -->
<property name="c3p0.max_size">20</property>
<!--設定數據庫連接的過期時間,以ms爲單位,如果連接池中的某個數據庫連接空閒狀態的時間,超過timeout時間,則會從連接池中清除 -->
<property name="c3p0.timeout">120</property>
<!--每3000s檢查所有連接池中的空閒連接以s爲單位 -->
<property name="c3p0.idle_test_period">3000</property>

2. ORM映射文件

2.1 使用XML配置映射關係(推薦)

  1. Student 類編寫映射關係 Student.hbm.xml
<?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>
    <!-- name代表的是類名,table代表的是表名 --> <!-- 類的地址請自行調整 -->
    <class name="pers.model.domain.Student" table="student">
        <!-- name代表的是User類中的id屬性,column代表的是user表中的主鍵id -->
        <id name="id" type="java.lang.Integer" column="id" >
            <!-- 主鍵生成策略 -->
            <generator class="increment" />
        </id>
        <!-- 其他屬性使用property標籤映射 -->
        <property name="name" type="java.lang.String" column="name" />
        <property name="age" type="java.lang.Integer" column="age" />
        <property name="email" type="java.lang.String" column="email" />
    </class>
</hibernate-mapping>
  1. hibernate.cfg.xml 中標註該映射關係文件:
<mapping resource="pers/model/domain/Student.hbm.xml"/> <!-- 類的地址請自行調整 -->

2.2 使用註解配置映射關係

  1. 修改 Student 類:
@Data
@Entity
// @Table(name = "student")
public class Student {
    @Id
    @GeneratedValue(generator = "increment")
    private Integer id;
    // @Column(name = "name") // 默認映射的表名取字段名
    private String name;
    private Integer age;
    private String email;
}
  1. hibernate.cfg.xml 中標註該實體映射:
<mapping class="pers.model.domain.Student"/> <!-- 類的地址請自行調整 -->

2.2.1 註解詳解

  • @Entity :【必填】聲明一個實體,,默認情況下 name 映射的表名與實體類同名。
  • @Table :【可選】映射到一個表,有三個值 name (表名)、 catalog (目錄)、 schema (模式/數據庫)。
  • @Id :【必填】聲明一個主鍵。
  • @Column :【可選】聲明一個列,會在Entity中默認生成。
  • @Temporal :特殊的, java.util.Date 需要使用該標記來進行映射,如: @Temporal(TemporalType.TIMESTAMP) (年月日時分秒)、@Temporal(TemporalType.DATA) (年月日)、@Temporal(TemporalType.TIME) (時分秒)。
  • @GenericGenerator :聲明一種主鍵生成策略。
  • @GenericValue :引用某種主鍵生成策略,需要搭配@GenericGenerator 使用(參考文獻)。如:
@GeneratedValue(generator="system-uuid")
@GenericGenerator(name="system-uuid", strategy = "uuid.hex")

3. Hibernate運行流程

在這裏插入圖片描述

4. 核心接口

上文中經過映射的ORM可以編寫以下測試類:

public class Main {
    public static void main(String[] args){
        // 1.創建Configuration對象並加載hibernate.cfg.xml配置文件
        Configuration config = new Configuration().configure();
        // 2.獲取SessionFactory
        @Cleanup SessionFactory sessionFactory = config.buildSessionFactory();
        // 3.得到一個Session
        @Cleanup Session session = sessionFactory.openSession();
        // 4.開啓事務
        Transaction transaction = session.beginTransaction();
        // 5.執行持久化操作(全表查詢)
        session.createQuery("FROM Student").list().forEach(System.out::println);
        // 6.提交事務
        transaction.commit();
        // 7.關閉資源
        // session.close(); // 已經由lombok的@Cleanup處理
        // sessionFactory.close();
    }
}

4.1 Configuration

配置項,默認狀態下會從 src 目錄下讀取 hibernatecfg.xml 的配置文件:

Configuration config = new Configuration().configure();
// Configuration config = new Configuration().configure("文件的位置"); // 手動指定
  • 僅在初始化階段存在,待到 SessionFactory 實例化後便銷燬。

4.2 SessionFactory

該接口負責解析ORM文件、構建Session對象、二級緩存,且線程安全。

  • 該對象較爲重量級,一般以單實例啓動,除非應用中有多個數據源。
static final SessionFactory sessionFactory = config.buildSessionFactory();

4.3 Session

持久化操作的核心API,由 SessionFactory.openSession() 創建。

  • 該對象是輕量級的,可以很方便的創建和銷燬,但是其線程不安全,應確保一個 Session 僅被一個線程使用。
  • 創建方法除了SessionFactory.openSession() ,還可以使用 sessionFactory.getCurrentSession() 。區別在於前者需要手動調用 close() 關閉,後者會直接跟當前線程綁定、在事務提交或回滾時自動關閉。

其提供的API有:

名稱 描述
save() 用於執行添加對象操作
update() 用於執行修改對象操作
saveOrUpdate() 用於執行添加或修改對象操作
delete() 用於執行刪除對象操作
get() 根據主鍵查詢數據
load() 根據主鍵查詢數據
createQuery() 用於數據庫操作對象
createSQLQuery() 用於數據庫操作對象
createCriteria() 面向對象的條件查詢

4.4 Transaction

事務管理接口,由 session.beginTransaction() 創建並啓動事務,共有三個方法:

  • commit() 方法:提交相關聯的 session 實例。
  • rollback() 方法:撤銷事務操作。
  • wasCommitted() 方法:檢查事務是否提交。

4.5 Query(推薦使用HQL)

查詢接口,通過HQL語句進行查詢,如:

@Test
public void testHQLSelectById(){
    Configuration config = new Configuration().configure();
    @Cleanup SessionFactory sessionFactory = config.buildSessionFactory();
    @Cleanup Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();
    
    val hql = "SELECT s FROM Student s WHERE s.id = :id"; // HQL 語句
    Query query = session.createQuery(hql).setParameter("id", 2); // Query對象
    query.list().forEach(System.out::println);
    
    transaction.commit();
}

Query對象通過 list()iterator() 等方法收集結果,常用方法有:

名 稱 描 述
setter Query 接口中提供了一系列的 setter 方法用於設置查詢語句中的參 數,針對不同的數據類型,需要用到不同的 setter 方法
Iterator iterator() 該方法用於查詢語句,返回的結果是一個 Iterator 對象,在讀取時只能按照順序方式讀取,它僅把使用到的數據轉換成 Java 實體對象
Object uniqueResult() 該方法用於返回唯一的結果,在確保只有一條記錄的查詢時可以 使用該方法
int executeUpdate() 該方法是 Hibernate 3 的新特性,它支持 HQL 語句的更新和刪除操作
Query setFirstResult(int firstResult) 該方法可以設置獲取第一個記錄的位置,也就是它表示從第幾條 記錄開始查詢,默認從 0 開始計算
Query setMaxResult(int maxResults) 該方法用於設置結果集的最大記錄數,通常與 setFirstResult() 方法結合使用,用於限制結果集的範圍,以實現分頁功能

4.5.1 HQL語句簡析

HQL語法與SQL類似(查詢時可以省略 select 關鍵字):

[select/update/delete...]from...[where...][group by...][having...][order by...][asc/desc]
  • 不同的是,HQL中沒有表、字段的概念,用類和字段做了替換,如 from Student 而不是 from student (前者是一個類,後者是一個SQL表);再例如 select new Student(s.id, s.name, s.age, s.email) from Student as s
  • 條件查詢將使用以下API設置參數:
方 法 名 說 明
setString(int index, value) 給映射類型爲 String 的參數賦值
setDate() 給映射類型爲 Date 的參數賦值
setDouble() 給映射類型爲 double 的參數賦值
setBoolean() 給映射類型爲 boolean 的參數賦值
setInteger() 給映射類型爲 int 的參數賦值
setTime() 給映射類型爲 Date 的參數賦值
setParameter(String":id", value) 給任意類型的參數賦值
  • 條件查詢的佔位符可用 ?:id,當 使用後者時,需要使用 setParameter ,如 setParameter("age", 15)

4.6 Criteria

一種面向對象的查詢接口,被稱爲QBC查詢,不關心SQL語句如何編寫。(參照開頭給出的用戶手冊或者此處)

現已被Hibernate 5棄用,轉爲JPA標準的CriteriaQuery,舉例如下:。

// QBC查詢
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Student> criteria = builder.createQuery(Student.class);
Root<Student> root = criteria.from(Student.class);
criteria.where(builder.equal(root.get("id"), 1));

System.out.println(session.createQuery(criteria).getSingleResult());

5. 持久化對象的狀態與訪問順序

5.1 狀態轉換圖

在這裏插入圖片描述

1)瞬時態(transient)

瞬時態也稱爲臨時態或者自由態,瞬時態的對象是由 new 關鍵字開闢內存空間的對象,不存在持久化標識 OID(相當於主鍵值),且未與任何的 Session 實例相關聯,在數據庫中也沒有記錄,失去引用後將被 JVM 回收。瞬時對象在內存孤立存在,它是攜帶信息的載體,不和數據庫的數據有任何關聯關係。

2)持久態(persistent)

持久態的對象存在一個持久化標識 OID,當對象加入到 Session 緩存中時,就與 Session 實例相關聯。它在數據庫中存在與之對應的記錄,每條記錄只對應唯一的持久化對象。需要注意的是,持久態對象是在事務還未提交前變成持久態的。

3)脫管態(detached)

脫管態也稱離線態或者遊離態,當持久化對象與 Session 斷開時就變成了脫管態,但是脫管態依然存在持久化標識 OID,只是失去了與當前 Session 的關聯。需要注意的是,脫管態對象發生改變時 Hibernate 是不能檢測到的。

5.2 一級緩存與快照

當對象處於持久態時,也就是在某個一級緩存 Session 中時,則直接從一級緩存取出而不再訪問數據庫。

  • 能夠在某些情況下,按照緩存中對象的變化,執行相關的 SQL 語句同步更新數據庫,這一過程被稱爲“刷出緩存(flush)”,即 session.flush() 會發出 update 指令。
  • Hibernate在往一級緩存中存入數據時還會往快照區存入數據;當調用事務的 commit 方法時會CAS快照區,如果發現數據不一致,則執行 update 方法將一級緩存的數據寫入數據源並更新快照區,如:
@Test
public void test3() {
    Session session = HibernateUtils.getSession(); // 得到session對象
    session.beginTransaction();
    Goods goods = new Goods();
    goods.setName("鋼筆");
    goods.setPrice(5.0);
    session.save(goods);    // 向一級緩存中存入session對象
    goods.setPrice(4.5);    // 提交價格
    session.getTransaction().commit();    // 提交事務,此時比對緩衝區發現價格發生變化,於是更新數據庫
    session.close();    //關閉資源
    // 數據庫中最終存入了一根4.5¥的鋼筆
}

5.3 二級緩存

二級緩存屬於進程級別的緩存,由 SessionFactory 進行管理。

當在配置文件中啓用二級緩存對象時,當一級緩存Miss,就會從二級緩存搜尋,最後纔會訪問數據庫獲取數據。

Hibernate配置文件中的緩存屬性:

  • cache.provider_class :自定義的 CacheProvider 的類名。取值 classname.of.CacheProvider
  • cache.use_minimal_puts :以頻繁的讀操作爲代價, 優化二級緩存來最小化寫操作。在Hibernate中,這個設置對的集羣緩存非常有用, 對集羣緩存的實現而言,默認是開啓的,取值 true|false
  • cache.use_query_cache :允許查詢緩存, 個別查詢仍然需要被設置爲可緩存的,取值 true|false
  • cache.use_second_level_cache :能用來完全禁止使用二級緩存。對那些在類的映射定義中指定 <cache> 的類,會默認開啓二級緩存,取值 true|false
  • cache.query_cache_factory :自定義實現QueryCache接口的類名,默認爲內建的 StandardQueryCache
  • cache.region_prefix :二級緩存區域名的前綴。
  • cache.use_structured_entries :強制Hibernate以更人性化的格式將數據存入二級緩存。取值 true|false

啓用示例:

<property name ="hibernate.cache.use_second_level_cache">true</property>
<property name ="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>

<mapping resource ="example/Student.hbm.xml"/>
<!-- 規定進行二級緩存的對象 -->
<class-cache usage ="read-write" class ="example.Student"/>

由於在二級緩存中也會出現併發問題,因此在 Hibernate 的二級緩存中,可以設定以下四種類型的併發訪問策略,以解決這些問題。每一種訪問策略對應一種事務隔離級別,具體介紹如下:

  1. 只讀型(Read-Only):提供 Serializable 事務隔離級別,對於從來不會被修改的數據,可以採用這種訪問策略。
  2. 讀寫型(Read-write):提供 Read Committed 事務隔離級別,對於經常讀但是很少被修改的數據,可以採用這種隔離類型,因爲它可以防止髒讀。
  3. 非嚴格讀寫(Nonstrict-read-write):不保證緩存與數據庫中數據的一致性,提供 Read Uncommitted 事務隔離級別,對於極少被修改,而且允許髒讀的數據,可以採用這種策略。
  4. 事務型(Transactional):僅在受管理環境下使用,它提供了 Repeatable Read 事務隔離級別。對於經常讀但是很少被修改的數據,可以採用這種隔離類型,因爲它可以防止髒讀和不可重複讀。

二級緩存構建示例參考地址:Hibernate二級緩存的併發訪問策略和常用插件

6. M vs. M映射關係

6.1 一對多映射關係

以一個班級含有多個學生爲例:

@Data
public class Class_ { // One
    private Integer id;
    private String name; // 班級名稱
    private Set<Student> students = new HashSet<Student>();
}
@Data
public class Student { // Many
    private Integer id;
    private String name; // 學生名稱
    private Class_ class_; // 學生從屬於某個班級
}

映射文件:

<hibernate-mapping>
    <class name="example.Class_" table="class">
        <id name="id" column="id">
            <generator class="increment" />
        </id>
        <property name="name" column="name"/>
      
        <!-- 一對多的關係使用set集合映射(對應Set對象) -->
        <set name="students" cascade="save-update"> <!-- 默認爲主控方,在save 或 update操作時同步關聯 -->
            <!-- 確定關聯的外鍵列 -->
            <key column="id" /> <!-- Class.id -->
            <!-- 映射到關聯類屬性 -->
            <one-to-many class="example.Student" />
        </set>
      
    </class>
</hibernate-mapping>
<hibernate-mapping>
    <class name="example.Student" table="student">
        <id name="id" column="id">
            <generator class="increment" />
        </id>
        <property name="name" column="name"/>
      
        <!-- 多對一關係映射 -->
        <many-to-one name="class_" class="example.Class_"></many-to-one>
      
    </class>
</hibernate-mapping>

6.2 多對多映射關係

假設一個學生能選多個班級(比如選課時的試讀):

@Data
public class Student { // Many
    private Integer id;
    private String name; // 學生名稱
    private Set<Class_> class_ new HashSet<Class_>();; // 學生從屬於某個班級
}
<hibernate-mapping>
    <class name="example.Student" table="student">
        <id name="id" column="id">
            <generator class="increment" />
        </id>
        <property name="name" column="name"/>
      
        <set name="class_" table="class">
            <key column="id" /> <!-- Student.id -->
            <many-to-many class="example.Class_" column="id" /> <!-- Class_.id -->
        </set>
      
    </class>
</hibernate-mapping>
<hibernate-mapping>
    <class name="example.Class_" table="class">
        <id name="id" column="id">
            <generator class="increment" />
        </id>
        <property name="name" column="name"/>
      
        <set name="student" table="student" inverse="true"> <!-- 由另一方管理關係 -->
            <key column="id" /> <!-- Class_.id -->
            <many-to-many class="example.Student" column="id" /> <!-- Student.id -->
        </set>
      
    </class>
</hibernate-mapping>

6.3 控制關係

6.3.1 反轉關係

  • 默認情況下,一對多關係中由多方管理關係;多對多關係中,需要對其中一方的 <set>inverse 取值爲 true (默認值爲 false )。
  • inverse 只對 <set><one-to-many><many-to-many> 標籤有效,對 <many-to-one><one-to-one> 標籤無效。

6.3.2 級聯關係

級聯關係表示當主控方執行操作時,關聯對象也將與其保持同步。 <set> 標籤的 cascade 取值有:

屬性值 描 述
save-update 在執行 save、update 或 saveOrUpdate 吋進行關聯操作
delete 在執行 delete 時進行關聯操作
delete-orphan 刪除所有和當前解除關聯關係的對象
all 所有情況下均進行關聯操作,但不包含 delete-orphan 的操作
all-delete-orphan 所有情況下均進行關聯操作
none 所有情況下均不進行關聯操作,這是【默認值】

7. 悲觀鎖與樂觀鎖

  • 悲觀鎖:先鎖再訪問讀寫數據,使用 LockMode.UPGRADE
  • 樂觀鎖:先嚐試訪問讀寫,如果前後數據發生異動再嘗試加鎖讀寫。

悲觀鎖的實現:

Student Student = (Student)session.get(Student.class, 1 , LockMode.UPGRADE); // 示例

樂觀鎖的實現需要對數據庫進行操作,在表中增加一個 VERSION INT(10) 字段,用於樂觀鎖的前後比對。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章