Hibernate ORM ile Cloud Spanner

1. Genel Bakış

Hibernate, Java projeleri için fiilen standart ORM çözümü haline geldi. Büyük ilişkisel veritabanlarının tümünü destekler ve Spring Data JPA gibi daha da güçlü ORM araçları sunar. Buna ek olarak Spring Boot, Microprofile ve Quarkus gibi Hibernate ile uyumlu birçok çerçeve vardır.

Hibernate ORM için Cloud Spanner Diyalect, Hibernate'i Cloud Spanner ile birlikte kullanmayı mümkün kılar. Hibernate'in deyimsel kalıcılığıyla Cloud Spanner'ın (ölçeklenebilirlik ve ilişkisel anlamlar) avantajlarından yararlanırsınız. Bu program, mevcut uygulamaları buluta taşımanıza veya Hibernate tabanlı teknolojilerin sağladığı artırılmış geliştirici üretkenliğinden yararlanarak yeni uygulamalar yazmanıza yardımcı olabilir.

Neler öğreneceksiniz?

  • Cloud Spanner'a bağlanan basit bir Hibernate uygulaması yazma
  • Cloud Spanner veritabanı oluşturma
  • Hibernate ORM için Cloud Spanner Diyalektini kullanma
  • Hibernate ile create-read-update-delete (CRUD) işlemlerini uygulama

Gerekenler

  • Bir Google Cloud Platform Projesi
  • Chrome veya Firefox gibi bir tarayıcı

2. Kurulum ve Gereksinimler

Kendi hızınızda ortam kurulumu

  1. Cloud Console'da oturum açıp yeni bir proje oluşturun veya mevcut bir projeyi yeniden kullanın. (Gmail veya G Suite hesabınız yoksa hesap oluşturmanız gerekir.)

k6ai2NRmxIjV5iMFlVgA_ZyAWE4fhRrkrZZ5mZuCas81YLgk0iwIyvgoDet4s2lMYGC5K3xLSOjIbmC9kjiezvQuxuhdYRolbv1rft1lOmA_P2U3OYcaAzN9JgP-Ncm18i5qgf9LzA

UtcCMcSYtCOrEWuILx3XBwb3GILPqXDd6cJiQQxmylg8GNftqlnE7u8aJLhlr1ZLRkpncKdj8ERnqcH71wab2HlfUnO9CgXKd0-CAQC2tDuf0dH3zd0d3C3Hg03G33G

KoK3nfWQ73s_x4QI69xqzqdDR4tUuNmrv4FC9Yq8vtK5IVm49h_8h6x9X281hAcJcOFDtX7g2BXPvP5O7SOR2V4UI6W8gN6cTJCVAdtWHRrS89zH-qWE0IQdjFpOs_8T-s4vQCXA6w

Tüm Google Cloud projelerinde benzersiz bir ad olan proje kimliğini unutmayın (yukarıdaki ad zaten alınmış ve size uygun olmayacaktır!). Bu kod laboratuvarın ilerleyen bölümlerinde PROJECT_ID olarak adlandırılacaktır.

  1. Sonraki adımda, Google Cloud kaynaklarını kullanmak için Cloud Console'da faturalandırmayı etkinleştirmeniz gerekir.

Bu codelab'i çalıştırmanın maliyeti, yüksek değildir. "Temizleme" bölümündeki talimatları izlediğinizden emin olun. bölümünde, bu eğiticinin dışında faturalandırmayla karşılaşmamanız için kaynakları nasıl kapatacağınız konusunda tavsiyelerde bulunuyoruz. Yeni Google Cloud kullanıcıları 300 ABD doları ücretsiz deneme programından yararlanabilir.

Cloud Shell'i etkinleştirme

  1. Cloud Console'da, Cloud Shell'i etkinleştir R47NpB simgesini tıklayın.

STsYbcAtkIQyN6nL9BJhld3Fv5KxedYynpUVcRWwvIR-sYMMc4kfK-unIYgtsD4P6T0P8z-A12388tPmAh-Trsx80qobaW4KQXHJ7qJI6rwm762LrxurYbxwiDG-v_HiUYsWnXMciw

Cloud Shell'i daha önce hiç çalıştırmadıysanız ne olduğunu açıklayan bir ara ekran (ekranın alt kısmında) gösterilir. Bu durumda Devam'ı tıklayın (bunu bir daha görmezsiniz). Tek seferlik ekran şöyle görünür:

LnGMTn1ObgwWFtWpjSdzlA9TDvSbcY76GiLQLc_f7RP1QBK1Tl4H6kLCHzsi89Lkc-serOpqNH-F2XKmV5AnBqTbPon4HvCwSSrY_ERFHzeYmK1lnTfr-6x5eVoaHpRSrCUrolXUPQ

Temel hazırlık ve Cloud Shell'e bağlanmak yalnızca birkaç dakika sürer.

hfu9bVHmrWw01Hnrlf4MBNja6yvssDnZzN9oupcG12PU88Vvo30tTluX9IySwnu5_TG3U2UXAasX9eCwqwZtc6Yhwxri95zG82DLUcKxrFYaXnVd7OqVoU6zanoZa0PtvubjLLHxnA

İhtiyacınız olan tüm geliştirme araçlarını bu sanal makinede bulabilirsiniz. 5 GB boyutunda kalıcı bir ana dizin sunar ve Google Cloud'da çalışarak ağ performansını ve kimlik doğrulamasını büyük ölçüde iyileştirir. Bu codelab'deki çalışmalarınızın tamamı olmasa bile büyük bir kısmı yalnızca bir tarayıcı veya Chromebook'unuzla yapılabilir.

Cloud Shell'e bağlandıktan sonra kimliğinizin doğrulandığını ve projenin proje kimliğinize ayarlandığını görürsünüz.

  1. Kimlik doğrulamanızın tamamlandığını onaylamak için Cloud Shell'de aşağıdaki komutu çalıştırın:
gcloud auth list

Komut çıkışı

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
gcloud config list project

Komut çıkışı

[core]
project = <PROJECT_ID>

Doğru değilse aşağıdaki komutla ayarlayabilirsiniz:

gcloud config set project <PROJECT_ID>

Komut çıkışı

Updated property [core/project].

3. Veritabanı oluşturun

Cloud Shell başlatıldıktan sonra, GCP projenizle etkileşime geçmek için gcloud kullanmaya başlayabilirsiniz.

İlk olarak Cloud Spanner API'yi etkinleştirin.

gcloud services enable spanner.googleapis.com

Şimdi codelab-instance adlı bir Cloud Spanner örneği oluşturalım.

gcloud spanner instances create codelab-instance \
 --config=regional-us-central1 \
 --description="Codelab Instance" --nodes=1

Şimdi bu örneğe veritabanı eklememiz gerekiyor. Adı codelab-db olarak adlandıracağız.

gcloud spanner databases create codelab-db --instance=codelab-instance

4. Boş bir uygulama oluştur

Basit bir Java konsolu uygulaması oluşturmak için Maven Hızlı Başlangıç Arketip'ini kullanacağız.

mvn archetype:generate \
 -DgroupId=codelab \
 -DartifactId=spanner-hibernate-codelab \
 -DarchetypeArtifactId=maven-archetype-quickstart \
 -DarchetypeVersion=1.4 \
 -DinteractiveMode=false

Uygulama dizinine geçin.

cd spanner-hibernate-codelab

Maven kullanarak uygulamayı derleyip çalıştırın.

mvn compile exec:java -Dexec.mainClass=codelab.App

Konsolda Hello World! yazdırılmış şekilde gösterilir.

5. Bağımlılıkları ekleme

Cloud Shell Düzenleyici'yi açıp spanner-hibernate-codelab dizinine göz atarak kaynak kodu keşfedelim.

b5cb37d043d4d2b0.png

Şimdiye kadar "Hello World!" yazdıran temel bir Java konsolu uygulamamız var. Ancak, Cloud Spanner ile iletişim kurmak için Hibernate'i kullanan bir Java uygulaması yazmak istiyoruz. Bunun için Hibernate için Cloud Spanner Dialect'e, Cloud Spanner JDBC sürücüsüne ve Hibernate çekirdeğine ihtiyacımız olacak. Şimdi pom.xml dosyasının içindeki <dependencies> bloğuna aşağıdaki bağımlılıkları ekleyelim.

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'yi yapılandırma

Sonra, hibernate.cfg.xml ve hibernate.properties Hibernate yapılandırma dosyalarını oluşturacağız. Boş dosyalar oluşturmak için aşağıdaki komutu çalıştırın ve ardından Cloud Shell Düzenleyici'yi kullanarak dosyaları düzenleyin.

mkdir src/main/resources \
 && touch src/main/resources/hibernate.cfg.xml \
 && touch src/main/resources/hibernate.properties

Şimdi, hibernate.cfg.xml doldurarak veritabanına eşleyeceğimiz ek açıklamalı varlık sınıflarını Hibernate'e söyleyelim. (Varlık sınıflarını daha sonra oluşturacağız.)

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'in, Cloud Spanner örneğine nasıl bağlanacağını ve hangi diyalektin kullanılacağını da bilmesi gerekir. Bu nedenle, SQL söz dizimi için SpannerDialect, Spanner JDBC sürücüsü ve veritabanı koordinatlarıyla JDBC bağlantı dizesi kullanmasını söyleyeceğiz. Bu, hibernate.properties dosyasına gider.

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} öğesini, aşağıdaki komutu çalıştırarak alabileceğiniz proje kimliğinizle değiştirmeyi unutmayın:

gcloud config get-value project

Mevcut bir veritabanı şemamız olmadığından, uygulamayı ilk kez çalıştırdığımızda Hibernate'in Cloud Spanner'da iki tabloyu oluşturabilmesi için hibernate.hbm2ddl.auto=update özelliğini ekledik.

Genellikle, kimlik doğrulama kimlik bilgilerinin GOOGLE_APPLICATION_CREDENTIALS ortam değişkenindeki bir hizmet hesabı JSON dosyası veya gcloud auth application-default login komutu kullanılarak yapılandırılan uygulama varsayılan kimlik bilgileri kullanılarak oluşturulduğundan emin olmanız gerekir. Ancak Cloud Shell'de çalıştığımız için varsayılan proje kimlik bilgileri zaten ayarlanmış.

7. Ek açıklamalı varlık sınıfları oluşturma

Şimdi kod yazmaya hazırız.

Cloud Spanner'daki tablolarla eşlenecek iki adet düz eski Java nesnesi (POJO) tanımlayacağız (Singer ve Album). Album, Singer ile @ManyToOne ilişkisine sahip olacak. Ayrıca, Singer öğelerini @OneToMany ek açıklamasıyla Album listelerine eşleyebilirdik ancak bu örnekte veritabanından bir şarkıcı getirmemiz gerektiğinde tüm albümleri yüklemeyi istemeyiz.

Singer ve Album varlık sınıflarını ekleyin.

Sınıf dosyalarını oluşturun.

touch src/main/java/codelab/Singer.java \
&& touch src/main/java/codelab/Album.java

Dosyaların içeriğini yapıştırın.

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;
  }
}

Bu örnekte, birincil anahtar için otomatik olarak oluşturulan bir UUID kullandığımıza dikkat edin. Bu, sistem verileri sunucular arasında anahtar aralıklarına göre bölerken hotspot'lardan kaçındığı için Cloud Spanner'da tercih edilen bir kimlik türüdür. Monoton olarak artan tam sayı anahtarları da işe yarar ancak daha düşük performanslı olabilir.

8. Varlıkları kaydetme ve sorgulama

Her şey yapılandırıldıktan ve varlık nesneleri tanımlandıktan sonra veritabanına yazmaya ve sorgulamaya başlayabiliriz. Bir Hibernate Session açacak ve ardından bunu, ilk olarak clearData() yöntemindeki tüm tablo satırlarını silmek, writeData() yönteminde bazı varlıkları kaydetmek ve readData() yönteminde Hibernate sorgu dilini (HQL) kullanarak bazı sorguları çalıştırmak için kullanacağız.

App.java içeriğini şununla değiştirin:

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;
    }
  }
}

Şimdi kodu derleyip çalıştıralım. Maven'in yapmaya çalışacağı arka plan ileti dizileriyle ilgili uyarıları engellemek için -Dexec.cleanupDaemonThreads=false seçeneğini ekleyeceğiz.

mvn compile exec:java -Dexec.mainClass=codelab.App -Dexec.cleanupDaemonThreads=false

Çıkışta şuna benzer bir şey görürsünüz:

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

Bu noktada, Cloud Spanner konsoluna gidip veritabanındaki Şarkıcı ve Albüm tablolarının verilerini görüntülerseniz aşağıdakine benzer bir tablo görürsünüz:

f18276ea54cc266f.png

952d9450dd659e75.png

9. Temizleme

Başta oluşturduğumuz Cloud Spanner örneğini silerek, kaynakları gereksiz yere kullanmadığından emin olalım.

gcloud spanner instances delete codelab-instance

10. Tebrikler

Tebrikler, verileri Cloud Spanner'da tutmak için Hibernate'i kullanan bir Java uygulamasını başarıyla derlediniz.

  • Cloud Spanner örneği ve veritabanı oluşturdunuz
  • Uygulamayı Hibernate'i kullanacak şekilde yapılandırdınız
  • Sanatçı ve Albüm olmak üzere iki öğe oluşturdunuz.
  • Uygulamanızın veritabanı şemasını otomatik olarak oluşturdunuz
  • Varlıkları başarıyla Cloud Spanner'a kaydettiniz ve sorguladınız

Artık Cloud Spanner ile Hibernate uygulaması yazmak için gereken temel adımları biliyorsunuz.

Sırada ne var?