1. 개요
Hibernate는 Java 프로젝트의 사실상 표준 ORM 솔루션이 되었습니다. 모든 주요 관계형 데이터베이스를 지원하며 Spring Data JPA와 같은 훨씬 더 강력한 ORM 도구를 지원합니다. 또한, Spring Boot, Microprofile, Quarkus 등 많은 Hibernate 호환 프레임워크가 있습니다.
Hibernate ORM을 위한 Cloud Spanner Dialect를 사용하면 Cloud Spanner에서 Hibernate를 사용할 수 있습니다. Hibernate의 관용적 지속성을 통해 Cloud Spanner의 확장성 및 관계형 시맨틱스의 이점을 누릴 수 있습니다. 이를 통해 Hibernate 기반 기술에서 제공되는 향상된 개발자 생산성을 활용하여 기존 애플리케이션을 클라우드로 마이그레이션하거나 새 애플리케이션을 작성할 수 있습니다.
학습할 내용
- Cloud Spanner에 연결되는 간단한 Hibernate 애플리케이션을 작성하는 방법
- Cloud Spanner 데이터베이스를 만드는 방법
- Hibernate ORM에 Cloud Spanner Dialect를 사용하는 방법
- Hibernate를 사용하여 CRUD (create-read-update-delete) 작업을 구현하는 방법
필요한 항목
2. 설정 및 요구사항
자습형 환경 설정
- Cloud Console에 로그인하고 새 프로젝트를 만들거나 기존 프로젝트를 다시 사용합니다. (Gmail 또는 G Suite 계정이 없으면 만들어야 합니다.)
모든 Google Cloud 프로젝트에서 고유한 이름인 프로젝트 ID를 기억하세요(위의 이름은 이미 사용되었으므로 사용할 수 없습니다). 이 ID는 나중에 이 Codelab에서 PROJECT_ID
라고 부릅니다.
- 그런 후 Google Cloud 리소스를 사용할 수 있도록 Cloud Console에서 결제를 사용 설정해야 합니다.
이 Codelab 실행에는 많은 비용이 들지 않습니다. 이 가이드를 마친 후 비용이 결제되지 않도록 리소스 종료 방법을 알려주는 '삭제' 섹션의 안내를 따르세요. Google Cloud 새 사용자에게는 미화 $300 상당의 무료 체험판 프로그램에 참여할 수 있는 자격이 부여됩니다.
Cloud Shell 활성화
- Cloud Console에서 Cloud Shell 활성화를 클릭합니다.
이전에 Cloud Shell을 시작하지 않았으면 설명이 포함된 중간 화면(스크롤해야 볼 수 있는 부분)이 제공됩니다. 이 경우 계속을 클릭합니다(이후 다시 표시되지 않음). 이 일회성 화면은 다음과 같습니다.
Cloud Shell을 프로비저닝하고 연결하는 데 몇 분 정도만 걸립니다.
가상 머신에는 필요한 개발 도구가 모두 들어있습니다. 영구적인 5GB 홈 디렉터리를 제공하고 Google Cloud에서 실행되므로 네트워크 성능과 인증이 크게 개선됩니다. 이 Codelab에서 대부분의 작업은 브라우저나 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 빠른 시작 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 편집기를 열고 spanner-hibernate-codelab
디렉터리 내부를 탐색하여 소스 코드를 살펴보겠습니다.
지금까지는 "Hello World!"
를 출력하는 기본 Java 콘솔 앱만 있습니다. 하지만 Hibernate를 사용하여 Cloud Spanner와 통신하는 Java 애플리케이션을 작성하려고 합니다. 이를 위해서는 Hibernate용 Cloud Spanner Dialect, 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 인스턴스에 연결하는 방법과 사용할 언어를 알아야 합니다. 따라서 SQL 문법에 SpannerDialect
를 사용하고, 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가 Cloud Spanner에 두 개의 테이블을 만들 수 있도록 hibernate.hbm2ddl.auto=update
속성을 추가했습니다.
일반적으로 GOOGLE_APPLICATION_CREDENTIALS
환경 변수의 서비스 계정 JSON 파일 또는 gcloud auth application-default login
명령어를 사용하여 구성된 애플리케이션 기본 사용자 인증 정보를 사용하여 사용자 인증 정보가 설정되어 있는지도 확인합니다. 하지만 Cloud Shell에서 실행 중이므로 기본 프로젝트 사용자 인증 정보가 이미 설정되어 있습니다.
7. 주석 지정된 항목 클래스 만들기
이제 코드를 작성할 준비가 되었습니다.
Cloud Spanner의 테이블에 매핑될 두 가지 POJO (Plain Old Java Object)를 Singer
와 Album
로 정의합니다. Album
는 Singer
와 @ManyToOne
관계를 갖습니다. @OneToMany
주석을 사용하여 Singer
를 Album
목록에 매핑할 수도 있지만 이 예에서는 데이터베이스에서 가수를 가져와야 할 때마다 모든 앨범을 로드하고 싶지는 않습니다.
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 및 앨범 테이블의 데이터를 보면 다음과 같이 표시됩니다.
9. 삭제
리소스를 불필요하게 사용하지 않도록 처음에 만든 Cloud Spanner 인스턴스를 삭제해 보겠습니다.
gcloud spanner instances delete codelab-instance
10. 축하합니다
축하합니다. Hibernate를 사용하여 Cloud Spanner에 데이터를 유지하는 Java 애플리케이션을 빌드했습니다.
- Cloud Spanner 인스턴스와 데이터베이스를 만들었습니다.
- 최대 절전 모드를 사용하도록 애플리케이션을 구성함
- 아티스트와 앨범이라는 두 개의 항목을 만들었습니다.
- 애플리케이션의 데이터베이스 스키마가 자동으로 생성되었습니다.
- 항목을 Cloud Spanner에 저장하고 쿼리했습니다.
지금까지 Cloud Spanner로 Hibernate 애플리케이션을 작성하는 데 필요한 주요 단계를 알아봤습니다.