1. 總覽
Hibernate 已成為 Java 專案的實際標準 ORM 解決方案。支援所有主要關聯式資料庫,並啟用更強大的 ORM 工具,例如 Spring Data JPA。此外,還有許多與 Hibernate 相容的架構,例如 Spring Boot、Microprofile 和 Quarkus。
適用於 Hibernate ORM 的 Cloud Spanner 方言可讓您搭配使用 Hibernate 和 Cloud Spanner。您可享有 Cloud Spanner 的優點 (可擴充性和關聯語意),同時使用 Hibernate 的慣用持續性。這有助於您將現有應用程式遷移至雲端,或利用以 Hibernate 為基礎的技術提高開發人員工作效率,進而編寫新應用程式。
課程內容
- 如何編寫連線至 Cloud Spanner 的簡單 Hibernate 應用程式
- 如何建立 Cloud Spanner 資料庫
- 如何使用 Hibernate ORM 的 Cloud Spanner Dialect
- 如何使用 Hibernate 實作建立、讀取、更新、刪除 (CRUD) 作業
軟硬體需求
2. 設定和需求
自修實驗室環境設定
請記住專案 ID,這是所有 Google Cloud 專案中不重複的名稱 (上述名稱已遭占用,因此不適用於您,抱歉!)。本程式碼研究室稍後會將其稱為 PROJECT_ID。
- 接著,您必須在 Cloud 控制台中啟用帳單,才能使用 Google Cloud 資源。
完成本程式碼研究室的費用應該不高,甚至完全免費。請務必按照「清除」部分的指示操作,瞭解如何停用資源,避免在本教學課程結束後繼續產生帳單費用。Google Cloud 新使用者可參加價值$300 美元的免費試用計畫。
啟用 Cloud Shell
- 在 Cloud 控制台,點選「啟用 Cloud Shell」 圖示
。
如果您是首次啟動 Cloud Shell,系統會顯示中繼畫面 (摺疊式螢幕下方),說明這個指令列環境。點選「繼續」後,這則訊息日後就不會再出現。以下是這個初次畫面的樣子:
佈建並連至 Cloud Shell 預計只需要幾分鐘。
這部虛擬機器搭載各種您需要的開發工具,並提供永久的 5GB 主目錄,而且可在 Google Cloud 運作,大幅提升網路效能並強化驗證功能。本程式碼研究室幾乎所有工作都可在瀏覽器或 Chromebook 上完成。
連線至 Cloud Shell 後,您應會發現自己通過驗證,且專案已設為您的專案 ID。
- 在 Cloud Shell 中執行下列指令,確認您已通過驗證:
gcloud auth list
指令輸出
Credentialed Accounts
ACTIVE ACCOUNT
* <my_account>@<my_domain.com>
To set the active account, run:
$ gcloud config set account `ACCOUNT`
gcloud config list project
指令輸出
[core] project = <PROJECT_ID>
如未設定,請輸入下列指令手動設定專案:
gcloud config set project <PROJECT_ID>
指令輸出
Updated property [core/project].
3. 建立資料庫
啟動 Cloud Shell 後,您就可以開始使用 gcloud 與 GCP 專案互動。
首先,請啟用 Cloud Spanner API。
gcloud services enable spanner.googleapis.com
現在,讓我們建立名為 codelab-instance 的 Cloud Spanner 執行個體。
gcloud spanner instances create codelab-instance \ --config=regional-us-central1 \ --description="Codelab Instance" --nodes=1
現在,我們需要為這個執行個體新增資料庫。我們將其命名為 codelab-db。
gcloud spanner databases create codelab-db --instance=codelab-instance
4. 建立空白應用程式
我們將使用 Maven 快速入門原型建立簡單的 Java 控制台應用程式。
mvn archetype:generate \ -DgroupId=codelab \ -DartifactId=spanner-hibernate-codelab \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DarchetypeVersion=1.4 \ -DinteractiveMode=false
切換至應用程式目錄。
cd spanner-hibernate-codelab
使用 Maven 編譯並執行應用程式。
mvn compile exec:java -Dexec.mainClass=codelab.App
控制台中應該會顯示 Hello World!。
5. 新增依附元件
開啟 Cloud Shell 編輯器,瀏覽 spanner-hibernate-codelab 目錄,即可查看原始碼。

到目前為止,我們只有一個基本的 Java 控制台應用程式,會列印 "Hello World!"。不過,我們真正想編寫的 Java 應用程式,是使用 Hibernate 與 Cloud Spanner 通訊。為此,我們需要 Hibernate 適用的 Cloud Spanner 方言、Cloud Spanner JDBC 驅動程式和 Hibernate 核心。因此,請在 pom.xml 檔案的 <dependencies> 區塊中新增下列依附元件。
pom.xml
<!-- Spanner Dialect -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-spanner-hibernate-dialect</artifactId>
<version>1.5.0</version>
</dependency>
<!-- JDBC Driver -->
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-spanner-jdbc</artifactId>
<version>2.0.0</version>
</dependency>
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.29.Final</version>
</dependency>
6. 設定 Hibernate ORM
接下來,我們將建立 Hibernate 設定檔 hibernate.cfg.xml 和 hibernate.properties。執行下列指令建立空白檔案,然後使用 Cloud Shell 編輯器編輯檔案。
mkdir src/main/resources \ && touch src/main/resources/hibernate.cfg.xml \ && touch src/main/resources/hibernate.properties
因此,請填入 hibernate.cfg.xml,向 Hibernate 說明要對應至資料庫的註解實體類別。(我們稍後會建立實體類別)。
src/main/resources/hibernate.cfg.xml
<hibernate-configuration>
<session-factory>
<!-- Annotated entity classes -->
<mapping class="codelab.Album"/>
<mapping class="codelab.Singer"/>
</session-factory>
</hibernate-configuration>
Hibernate 也需要知道如何連線至 Cloud Spanner 執行個體,以及要使用的方言。因此,我們會指示使用 SpannerDialect 做為 SQL 語法、Spanner JDBC 驅動程式,以及包含資料庫座標的 JDBC 連線字串。這會寫入 hibernate.properties 檔案。
src/main/resources/hibernate.properties
hibernate.dialect=com.google.cloud.spanner.hibernate.SpannerDialect
hibernate.connection.driver_class=com.google.cloud.spanner.jdbc.JdbcDriver
hibernate.connection.url=jdbc:cloudspanner:/projects/{PROJECT_ID}/instances/codelab-instance/databases/codelab-db
# auto-create or update DB schema
hibernate.hbm2ddl.auto=update
hibernate.show_sql=true
請記得將 {PROJECT_ID} 替換為您的專案 ID,您可以執行下列指令取得專案 ID:
gcloud config get-value project
由於我們沒有現有的資料庫結構定義,因此新增了 hibernate.hbm2ddl.auto=update 屬性,讓 Hibernate 在我們首次執行應用程式時,在 Cloud Spanner 中建立這兩個資料表。
通常您也會確保已設定驗證憑證,方法是使用 GOOGLE_APPLICATION_CREDENTIALS 環境變數中的服務帳戶 JSON 檔案,或是使用 gcloud auth application-default login 指令設定的應用程式預設憑證。不過,由於我們是在 Cloud Shell 中執行,因此預設專案憑證已設定完成。
7. 建立註解實體類別
現在可以開始編寫程式碼了。
我們會定義兩個簡單傳統的 Java 物件 (POJO),分別對應至 Cloud Spanner 中的資料表:Singer 和 Album。Album 會與 Singer 建立 @ManyToOne 關係。我們也可以將 Singer 對應至 Album 的清單,並加上 @OneToMany 註解,但以這個範例來說,我們並不想在每次需要從資料庫擷取歌手時,都載入所有專輯。
新增 Singer 和 Album 實體類別。
建立類別檔案。
touch src/main/java/codelab/Singer.java \ && touch src/main/java/codelab/Album.java
貼上檔案內容。
src/main/java/codelab/Singer.java
package codelab;
import java.util.Date;
import java.util.UUID;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.annotations.Type;
@Entity
public class Singer {
@Id
@GeneratedValue
@Type(type = "uuid-char")
private UUID singerId;
private String firstName;
private String lastName;
@Temporal(TemporalType.DATE)
private Date birthDate;
public Singer() {
}
public Singer(String firstName, String lastName, Date birthDate) {
this.firstName = firstName;
this.lastName = lastName;
this.birthDate = birthDate;
}
public UUID getSingerId() {
return singerId;
}
public void setSingerId(UUID singerId) {
this.singerId = singerId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Date getBirthDate() {
return birthDate;
}
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Singer)) {
return false;
}
Singer singer = (Singer) o;
if (!firstName.equals(singer.firstName)) {
return false;
}
if (!lastName.equals(singer.lastName)) {
return false;
}
return birthDate.equals(singer.birthDate);
}
@Override
public int hashCode() {
int result = firstName.hashCode();
result = 31 * result + lastName.hashCode();
result = 31 * result + birthDate.hashCode();
return result;
}
}
src/main/java/codelab/Album.java
package codelab;
import java.util.UUID;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import org.hibernate.annotations.Type;
@Entity
public class Album {
@Id
@GeneratedValue
@Type(type = "uuid-char")
UUID albumId;
@ManyToOne
Singer singer;
String albumTitle;
public Album() {
}
public Album(Singer singer, String albumTitle) {
this.singer = singer;
this.albumTitle = albumTitle;
}
public UUID getAlbumId() {
return albumId;
}
public void setAlbumId(UUID albumId) {
this.albumId = albumId;
}
public Singer getSinger() {
return singer;
}
public void setSinger(Singer singer) {
this.singer = singer;
}
public String getAlbumTitle() {
return albumTitle;
}
public void setAlbumTitle(String albumTitle) {
this.albumTitle = albumTitle;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Album)) {
return false;
}
Album album = (Album) o;
if (!singer.equals(album.singer)) {
return false;
}
return albumTitle.equals(album.albumTitle);
}
@Override
public int hashCode() {
int result = singer.hashCode();
result = 31 * result + albumTitle.hashCode();
return result;
}
}
請注意,在這個範例中,我們使用自動產生的 UUID 做為主鍵。這是 Cloud Spanner 中偏好的 ID 類型,因為系統會依據索引鍵範圍將資料分配到各伺服器,因此可避免資源使用率不均。單調遞增的整數鍵也適用,但效能可能較差。
8. 儲存及查詢實體
設定完所有項目並定義實體物件後,我們就可以開始寫入及查詢資料庫。我們將開啟 Hibernate Session,然後使用它先刪除 clearData() 方法中的所有資料表列,在 writeData() 方法中儲存一些實體,並在 readData() 方法中使用 Hibernate 查詢語言 (HQL) 執行一些查詢。
將 App.java 改成以下內容:
src/main/java/codelab/App.java
package codelab;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
public class App {
public final static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
public static void main(String[] args) {
// create a Hibernate sessionFactory and session
StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();
SessionFactory sessionFactory = new MetadataSources(registry).buildMetadata()
.buildSessionFactory();
Session session = sessionFactory.openSession();
clearData(session);
writeData(session);
readData(session);
// close Hibernate session and sessionFactory
session.close();
sessionFactory.close();
}
private static void clearData(Session session) {
session.beginTransaction();
session.createQuery("delete from Album where 1=1").executeUpdate();
session.createQuery("delete from Singer where 1=1").executeUpdate();
session.getTransaction().commit();
}
private static void writeData(Session session) {
session.beginTransaction();
Singer singerMelissa = new Singer("Melissa", "Garcia", makeDate("1981-03-19"));
Album albumGoGoGo = new Album(singerMelissa, "Go, Go, Go");
session.save(singerMelissa);
session.save(albumGoGoGo);
session.save(new Singer("Russell", "Morales", makeDate("1978-12-02")));
session.save(new Singer("Jacqueline", "Long", makeDate("1990-07-29")));
session.save(new Singer("Dylan", "Shaw", makeDate("1998-05-02")));
session.getTransaction().commit();
}
private static void readData(Session session) {
List<Singer> singers = session.createQuery("from Singer where birthDate >= '1990-01-01' order by lastName")
.list();
List<Album> albums = session.createQuery("from Album").list();
System.out.println("Singers who were born in 1990 or later:");
for (Singer singer : singers) {
System.out.println(singer.getFirstName() + " " + singer.getLastName() + " born on "
+ DATE_FORMAT.format(singer.getBirthDate()));
}
System.out.println("Albums: ");
for (Album album : albums) {
System.out
.println("\"" + album.getAlbumTitle() + "\" by " + album.getSinger().getFirstName() + " "
+ album.getSinger().getLastName());
}
}
private static Date makeDate(String dateString) {
try {
return DATE_FORMAT.parse(dateString);
} catch (ParseException e) {
e.printStackTrace();
return null;
}
}
}
現在,請編譯並執行程式碼。我們會新增 -Dexec.cleanupDaemonThreads=false 選項,禁止 Maven 嘗試清理精靈執行緒時發出警告。
mvn compile exec:java -Dexec.mainClass=codelab.App -Dexec.cleanupDaemonThreads=false
輸出內容應如下所示:
Singers who were born in 1990 or later: Jacqueline Long born on 1990-07-29 Dylan Shaw born on 1998-05-02 Albums: "Go, Go, Go" by Melissa Garcia
此時,如果您前往 Cloud Spanner 控制台,查看資料庫中 Singer 和 Album 資料表的資料,會看到類似下列內容:


9. 清除所用資源
請刪除一開始建立的 Cloud Spanner 執行個體,確保不會不必要地耗用資源。
gcloud spanner instances delete codelab-instance
10. 恭喜
恭喜!您已成功建構 Java 應用程式,使用 Hibernate 將資料保存到 Cloud Spanner。
- 您已建立 Cloud Spanner 執行個體和資料庫
- 您已設定應用程式使用 Hibernate
- 您建立了兩個實體:藝人和專輯
- 您已為應用程式自動產生資料庫結構定義
- 您已成功將實體儲存至 Cloud Spanner 並查詢這些實體
您現已瞭解使用 Cloud Spanner 撰寫 Hibernate 應用程式的重要步驟。