Hibernate ORM ile Cloud Spanner

1. Genel Bakış

Hibernate, Java projeleri için fiili standart ORM çözümü haline geldi. Tüm büyük ilişkisel veritabanlarını destekler ve Spring Data JPA gibi daha da güçlü ORM araçlarını etkinleştirir. Ayrıca Spring Boot, Microprofile ve Quarkus gibi Hibernate ile uyumlu birçok çerçeve vardır.

Hibernate ORM için Cloud Spanner Dialect, Hibernate'in Cloud Spanner ile kullanılmasını sağlar. Hibernate'in deyimsel kalıcılığıyla Cloud Spanner'ın ölçeklenebilirlik ve ilişkisel semantik gibi avantajlarından yararlanabilirsiniz. Bu, mevcut uygulamaları buluta taşımanıza veya Hibernate tabanlı teknolojilerin sağladığı artan 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 Dialect'i kullanma
  • Hibernate ile oluşturma, okuma, güncelleme ve silme (CRUD) işlemlerini uygulama

Gerekenler

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

2. Kurulum ve Gereksinimler

Yönlendirmesiz ortam kurulumu

  1. Cloud Console'da oturum açın ve 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-CAQC2t3CH0kuQRxdtP0ws43t5-O2d4d0WXDUfaw

KoK3nfWQ73s_x4QI69xqzqdDR4tUuNmrv4FC9Yq8vtK5IVm49h_8h6x9X281hAcJcOFDtX7g2BXPvP5O7SOR2V4UI6W8gN6cTJCVAdtWHRrS89zH-qWE0IQdjFpOs_8T-s4vQCXA6w

Proje kimliğini unutmayın. Bu kimlik, tüm Google Cloud projelerinde benzersiz bir addır (Yukarıdaki ad zaten alınmış olduğundan sizin için çalışmayacaktır). Bu codelab'in ilerleyen kısımlarında PROJECT_ID olarak adlandırılacaktır.

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

Bu codelab'i tamamlamak neredeyse hiç maliyetli değildir. Bu eğitimin ötesinde faturalandırma ücreti alınmaması için kaynakları nasıl kapatacağınız konusunda size tavsiyelerde bulunan "Temizleme" bölümündeki talimatları uyguladığınızdan emin olun. Google Cloud'un yeni kullanıcıları 300 ABD doları değerinde ücretsiz deneme programından yararlanabilir.

Cloud Shell'i etkinleştirme

  1. Cloud Console'da Cloud Shell'i etkinleştir 'i R47NpBm6yyzso5vnxnRBikeDAXxl3LsM6tip3rJxnKuS2EZdCI0h-eIOGm9aECq8JXbMFlJkd68uTutXU8gGmQUVa5iI1OdZczXP2tzqZ_mj0pR4sZ8eSwOwUlWADR7ARCqrMTQPQA tıklayın.

STsYbcAtkIQyN6nL9BJhld3Fv5KxedYynpUVcRWwvIR-sYMMc4kfK-unIYgtsD4P6T0P8z-A12388tPmAh-Trsx80qobaW4KQXHJ7qJI6rwm762LrxurYbxwiDG-v_HiUYsWnXMciw

Cloud Shell'i daha önce hiç başlatmadıysanız ne olduğunu açıklayan bir ara ekran (ekranın alt kısmı) gösterilir. Bu durumda Devam'ı tıkladığınızda bu ekranı bir daha görmezsiniz. Bu tek seferlik ekran aşağıdaki gibi görünür:

LnGMTn1ObgwWFtWpjSdzlA9TDvSbcY76GiLQLc_f7RP1QBK1Tl4H6kLCHzsi89Lkc-serOpqNH-F2XKmV5AnBqTbPon4HvCwSSrY_ERFHzeYmK1lnTfr-6x5eVoaHpRSrCUrolXUPQ

Cloud Shell'in temel hazırlığı ve bağlanması yalnızca birkaç dakikanızı alır.

hfu9bVHmrWw01Hnrlf4MBNja6yvssDnZzN9oupcG12PU88Vvo30tTluX9IySwnu5_TG3U2UXAasX9eCwqwZtc6Yhwxri95zG82DLUcKxrFYaXnVd7OqVoU6zanoZa0PtvubjLLHxnA

Bu sanal makine, ihtiyaç duyacağınız tüm geliştirme araçlarını içerir. 5 GB boyutunda kalıcı bir ana dizin bulunur ve Google Cloud'da çalışır. Bu sayede ağ performansı ve kimlik doğrulama önemli ölçüde güçlenir. Bu codelab'deki çalışmalarınızın neredeyse tamamını yalnızca bir tarayıcı veya Chromebook'unuzla yapabilirsiniz.

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. Kimliğinizin doğrulandığını onaylamak için Cloud Shell'de şu 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>

Değilse şu 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şim kurmak için gcloud kullanmaya başlayabilirsiniz.

Öncelikle 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 bir veritabanı eklememiz gerekiyor. Buna codelab-db adını vereceğiz.

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

4. Boş bir uygulama oluşturma

Basit bir Java konsol uygulaması oluşturmak için Maven Quickstart Archetype'ı 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ı derleyin ve çalıştırın.

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

Konsolda Hello World! yazısını görmeniz gerekir.

5. Bağımlılık ekleme

Cloud Shell Düzenleyici'yi açıp spanner-hibernate-codelab dizininde gezinerek kaynak kodu inceleyelim.

b5cb37d043d4d2b0.png

Şu ana kadar yalnızca "Hello World!" yazdıran temel bir Java konsol uygulamamız var. Ancak, Cloud Spanner ile iletişim kurmak için Hibernate kullanan bir Java uygulaması yazmak istiyoruz. Bunun için Hibernate için Cloud Spanner Dialect, Cloud Spanner JDBC sürücüsü ve Hibernate çekirdeği gerekir. Bu nedenle, pom.xml dosyasındaki <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

Ardından, Hibernate yapılandırma dosyaları hibernate.cfg.xml ve hibernate.properties 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

Bu nedenle, hibernate.cfg.xml alanını doldurarak Hibernate'e veritabanıyla eşleyeceğimiz ek açıklamalı öğe sınıfları hakkında bilgi verelim. (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 diyalekti kullanacağını da bilmesi gerekir. Bu nedenle, SQL söz dizimi için SpannerDialect, Spanner JDBC sürücüsü ve veritabanı koordinatlarını içeren JDBC bağlantı dizesini kullanmasını söyleyeceğiz. Bu, hibernate.properties dosyasına girilir.

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} yerine proje kimliğinizi yazmayı unutmayın. Proje kimliğinizi almak için aşağıdaki komutu çalıştırabilirsiniz:

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 tablo oluşturmasına olanak tanımak 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 varsayılan uygulama kimlik bilgileri kullanılarak ayarlandığından da emin olursunuz. Ancak Cloud Shell'de çalıştığımız için varsayılan proje kimlik bilgileri zaten ayarlanmıştır.

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

Artık kod yazmaya hazırız.

Cloud Spanner'daki tablolarla eşlenecek iki düz eski Java nesnesi (POJO) tanımlayacağız: Singer ve Album. Album, Singer ile @ManyToOne ilişkisine sahip olur. Singer öğelerini @OneToMany ek açıklamasıyla Album listeleriyle de eşleyebilirdik ancak bu örnekte, veritabanından bir şarkıcı getirmemiz gerektiğinde her seferinde tüm albümleri yüklemek istemiyoruz.

Singer ve Album öğe 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ızı unutmayın. Bu, Cloud Spanner'da tercih edilen bir kimlik türüdür. Sistem, verileri anahtar aralıklarına göre sunucular arasında böldüğü için bu türde etkin noktalar oluşmaz. Tekdüze olarak artan bir tam sayı anahtarı da işe yarayabilir ancak daha düşük performans gösterebilir.

8. Varlıkları kaydetme ve sorgulama

Her şey yapılandırılıp varlık nesneleri tanımlandıktan sonra veritabanına yazmaya ve veritabanını sorgulamaya başlayabiliriz. Bir Hibernate Session açıp 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 dosyasının içeriğini aşağıdakiyle 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ğı daemon iş parçacıkları temizliğiyle ilgili uyarıları bastırmak için -Dexec.cleanupDaemonThreads=false seçeneğini ekleyeceğiz.

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

Çıkışta aşağıdakine benzer bir şey görmelisiniz:

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 Console'a gidip veritabanındaki Singer ve Album tablolarının verilerini görüntülerseniz şuna benzer bir şey görürsünüz:

f18276ea54cc266f.png

952d9450dd659e75.png

9. Temizleme

Gereksiz yere kaynak tüketmemesi için başlangıçta oluşturduğumuz Cloud Spanner örneğini silelim.

gcloud spanner instances delete codelab-instance

10. Tebrikler

Tebrikler. Verileri Cloud Spanner'da kalıcı hale getirmek için Hibernate'i kullanan bir Java uygulamasını başarıyla oluşturdunuz.

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

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

Yapabilecekleriniz