- 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的類有如下要求:
- 所有屬性採用
private
修飾、get()/set()
方法採用public
修飾。 - 具有一個無參
public
構造方法。 - 具有一個映射數據庫主鍵字段的OID。
- 不可用
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 創建時是否根據映射文件自動驗證表結構或 自動創建、自動更新數據庫表結構。該參數的取值爲 validate 、 update 、 create 和 create-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配置映射關係(推薦)
- 對
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>
- 在
hibernate.cfg.xml
中標註該映射關係文件:
<mapping resource="pers/model/domain/Student.hbm.xml"/> <!-- 類的地址請自行調整 -->
2.2 使用註解配置映射關係
- 修改
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;
}
- 在
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 的二級緩存中,可以設定以下四種類型的併發訪問策略,以解決這些問題。每一種訪問策略對應一種事務隔離級別,具體介紹如下:
- 只讀型(Read-Only):提供 Serializable 事務隔離級別,對於從來不會被修改的數據,可以採用這種訪問策略。
- 讀寫型(Read-write):提供 Read Committed 事務隔離級別,對於經常讀但是很少被修改的數據,可以採用這種隔離類型,因爲它可以防止髒讀。
- 非嚴格讀寫(Nonstrict-read-write):不保證緩存與數據庫中數據的一致性,提供 Read Uncommitted 事務隔離級別,對於極少被修改,而且允許髒讀的數據,可以採用這種策略。
- 事務型(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)
字段,用於樂觀鎖的前後比對。