1. סקירה כללית
Hibernate הפך לפתרון ה-ORM הסטנדרטי בפועל לפרויקטים ב-Java. הוא תומך בכל מסדי הנתונים הרלציוניים העיקריים ומאפשר כלי ORM חזקים עוד יותר כמו Spring Data JPA. בנוסף, יש הרבה frameworks רבות שתואמות למצב הירדמות, כמו Spring Boot, Microprofile ו-Quarkus.
בעזרת Cloud Spanner Dialect for Hibernate ORM אפשר להשתמש ב-Hibernate עם Cloud Spanner. נהנים מהיתרונות של Cloud Spanner – מדרגיות וסמנטיקה יחסית – עם ההתמדה האידיומטית של Hibernate. כך ניתן להעביר אפליקציות קיימות לענן או לכתוב אפליקציות חדשות, תוך ניצול פרודוקטיביות מוגברת של המפתחים הודות לטכנולוגיות המבוססות על Hibernate.
מה תלמדו
- איך לכתוב אפליקציה פשוטה למצב 'שינה' שמתחברת ל-Cloud Spanner
- איך יוצרים מסד נתונים ב-Cloud Spanner
- איך משתמשים בדיאלקט Cloud Spanner ל-Hibernate ORM
- איך ליישם פעולות יצירה-קריאה-עדכון-מחיקה (CRUD) באמצעות מצב שינה
מה צריך להכין
2. הגדרה ודרישות
הגדרת סביבה בקצב עצמאי
- נכנסים למסוף Cloud ויוצרים פרויקט חדש או עושים שימוש חוזר בפרויקט קיים. (אם עדיין אין לכם חשבון Gmail או G Suite, עליכם ליצור חשבון).
חשוב לזכור את מזהה הפרויקט, שם ייחודי לכל הפרויקטים ב-Google Cloud (השם שלמעלה כבר תפוס ולא מתאים לכם, סליחה). בהמשך ב-Codelab הזה, היא תיקרא PROJECT_ID
.
- בשלב הבא צריך להפעיל את החיוב במסוף Cloud כדי להשתמש במשאבים של Google Cloud.
מעבר ב-Codelab הזה לא אמור לעלות הרבה, אם בכלל. חשוב לבצע את כל ההוראות בקטע 'ניקוי' שמסביר איך להשבית משאבים כדי שלא תצברו חיובים מעבר למדריך הזה. משתמשים חדשים ב-Google Cloud זכאים להשתתף בתוכנית תקופת ניסיון בחינם בשווי 1,200 ש"ח.
הפעלת Cloud Shell
- במסוף Cloud, לוחצים על Activate Cloud Shell .
אם זו הפעם הראשונה שאתם מפעילים את Cloud Shell, יוצג לכם מסך ביניים (בחלק הנגלל) שמתאר מהו. במקרה כזה, לוחצים על המשך (וזה לא יקרה שוב). כך נראה המסך החד-פעמי:
ההקצאה וההתחברות ל-Cloud Shell נמשכת כמה דקות.
למכונה הווירטואלית הזו נטען כל כלי הפיתוח הדרושים. יש בה ספריית בית בנפח מתמיד של 5GB והיא פועלת ב-Google Cloud, מה שמשפר משמעותית את ביצועי הרשת והאימות. אם לא את כולן, ניתן לבצע חלק גדול מהעבודה ב-Codelab הזה באמצעות דפדפן או Chromebook.
אחרי ההתחברות ל-Cloud Shell, אתם אמורים לראות שכבר בוצע אימות ושהפרויקט כבר מוגדר למזהה הפרויקט שלכם.
- מריצים את הפקודה הבאה ב-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
עכשיו ניצור מכונה של Cloud Spanner בשם codelab-instance
.
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 Quickstart Archetype כדי ליצור אפליקציה פשוטה במסוף 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 Editor ומעיינים בספרייה spanner-hibernate-codelab
.
עד עכשיו, יש לנו רק אפליקציה בסיסית של מסוף Java שמדפיסה את "Hello World!"
. עם זאת, אנחנו באמת רוצים לכתוב אפליקציית Java שמשתמשת ב-Hibernate כדי לדבר עם Cloud Spanner. לשם כך נזדקק לדיאלקט של Cloud Spanner for Hibernate, למנהל התקן ה-JDBC של Cloud Spanner ולליבה של Hibernate. עכשיו נוסיף את יחסי התלות הבאים לבלוק <dependencies>
שבתוך הקובץ pom.xml
.
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. הגדרת ORM למצב שינה
בשלב הבא ניצור קובצי תצורה של מצב 'שינה' hibernate.cfg.xml
ו-hibernate.properties
. מריצים את הפקודה הבאה כדי ליצור את הקבצים הריקים ואז עורכים אותם באמצעות Cloud Shell Editor.
mkdir src/main/resources \ && touch src/main/resources/hibernate.cfg.xml \ && touch src/main/resources/hibernate.properties
בואו נודיע ל-Hibernate על מחלקות הישויות עם הערות שנמפה למסד הנתונים על ידי מילוי של השדה hibernate.cfg.xml
. (אנחנו ניצור את סיווגי הישויות מאוחר יותר).
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, במנהל התקן JDBC Spanner ובמחרוזת החיבור 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}
במזהה הפרויקט. כדי לקבל אותו, מריצים את הפקודה הבאה:
gcloud config get-value project
אין לנו סכימה קיימת של מסד נתונים, לכן הוספנו את המאפיין hibernate.hbm2ddl.auto=update
כדי לאפשר ל-Hibernate ליצור את שתי הטבלאות ב-Cloud Spanner כשאנחנו מפעילים את האפליקציה בפעם הראשונה.
בדרך כלל צריך גם לוודא שהוגדרו פרטי הכניסה לאימות באמצעות קובץ JSON של חשבון שירות במשתנה הסביבה GOOGLE_APPLICATION_CREDENTIALS
או באמצעות פרטי הכניסה שמוגדרים כברירת מחדל של האפליקציה באמצעות הפקודה gcloud auth application-default login
. עם זאת, מכיוון שאנחנו מריצים את Cloud Shell, כבר הוגדרו פרטי כניסה לפרויקט כברירת מחדל.
7. יצירת מחלקות של ישויות עם הערות
עכשיו אנחנו מוכנים לכתוב קוד.
נגדיר שני אובייקטים ישנים רגילים של Java (POJO) שימפו לטבלאות ב-Cloud Spanner – Singer
ו-Album
. לחשבון Album
יהיה קשר @ManyToOne
עם Singer
. אפשר גם למפות 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, כי הוא נמנע מנקודות לשיתוף אינטרנט, כי המערכת מחלקת את הנתונים בין שרתים לפי טווחי מפתחות. אפשר גם מפתח מספר שלם מונוטוני מוגדל, אבל הביצועים שלו עשויים להיות נמוכים יותר.
8. שמירת ישויות ושליחת שאילתות
לאחר שכל האובייקטים מוגדרים והאובייקטים של הישויות, אנחנו יכולים להתחיל לכתוב למסד הנתונים ולשלוח שאילתות לגביו. אנחנו נפתח Session
של שנת יםclearData()
writeData()
readData()
מחליפים את התוכן של 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
כדי לבטל אזהרות לגבי ניקוי של שרשורי דימון (daemon) ש-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 ומסד נתונים
- הגדרת את האפליקציה לשימוש במצב 'שינה'
- יצרתם שתי ישויות: 'אומן' ו'אלבום'
- יצרתם באופן אוטומטי את סכימת מסד הנתונים לאפליקציה שלכם
- שמרת בהצלחה ישויות ב-Cloud Spanner ושלחת להן שאילתות
עכשיו אתם יודעים מהם השלבים העיקריים שנדרשים כדי לכתוב אפליקציית Hibernate באמצעות Cloud Spanner.