hibernate 설정 + 간단 예제

Hibernate 란?

정의

관계형 데이터베이스(RDBMS)와 객체지향 언어(Java)를 연결해주는 프레임워크이다. 주로 이런 애들을 ORM (Object/Relational Mapping) 프레임워크 라고 부른다. Node.js에서는 Sequelize 라는 놈이 있다.

특징

  • Object/Relational Mapping

    • 말 그대로 객체/관계의 매핑을 제공한다.
  • JPA Provider

    • Java Persistance API를 구현하여 제공한다. 이를 통해 JPA를 지원하는 모든 환경에서 쉽게 사용할 수 있다.
  • Idiomatic persistence

    • 객체지향 패러다임에서 자연스럽게 Persistance class를 개발할 수 있게 도와준다.
  • High Performance

    • 개발자 생산성 및 런타임 측면에서 그냥 JDBC 코드를 쓴것보다 우수한 성능을 제공함.
  • Scalability

    • 어플리케이션이 서버 클러스터에서 작동하고 확장성이 뛰어난 아키텍처를 제공하도록 설계됨.
  • Reliable

    • 수많은 Java 개발자들이 사용하여 안전성 및 품질이 입증됨.
  • Extensibility

    • 설정과 확장이 쉬움

이정도 되겠다.

자 설정을 해보자

우리는 Gradle 을 이용해서 의존성을 설정해 줄꺼다. 안그럼 넘넘넘넘넘나 번거롭다. 그리고 IDE는 Eclipse를 사용할 것이다. (무료니깐!)

설마 Eclipse를 모를 사람은 없을테니 자세한 설명은 생략한다. 다운은 여기에서 받도록..

Gradle은 모르는 사람이 있을 수도 있다.

하지만 나도 잘은 모른다. 그냥 라이브러리 갖다 쓸 수 있는 정도만 안다. 앞으로 더 알게되면 포스팅 하도록 하겠다.

Gradle 다운은 여기에서 편한 방법으로 받으면 된다.

참고로 JDK 9 를 이용해서 했었는데 호환이 잘 안되는지 Eclipse와 Gradle 연동이 안됬었다. 그래서 JDK를 다시 8 버전대로 바꾸니깐 잘 됬었다. 참고하시길…


자 그럼 우린 JDK와 Eclipse, Gradle이 모두 준비 되었다. (벌써?)

나는 블로그에 그림 넣는걸 별로 안좋아한다. (절대 귀찮아서가 아니다 :( )

따라서 커맨드 형식으로 보여주기 위해 프로젝트를 커맨드 형식으로 생성 후 Eclipse에서 import 하는 방식으로 진행할 것이다.


Eclipse 프로젝트 생성

먼저 프로젝트를 만들 적당한 곳으로 이동한다. 나는 ~/workspace/hibernate 이곳을 프로젝트 홈으로 만들 것이다.

1
2
$ mkdir -p ~/workspace/hibernate
$ cd ~/workspace/hibernate

그런 다음 gradle init --type java-library 이라고 명령어를 날려보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ gradle init --type java-library

BUILD SUCCESSFUL in 1s
2 actionable tasks: 2 executed
$ ls -al
total 40
drwxr-xr-x 9 hun staff 306 Nov 17 13:45 .
drwxr-xr-x 3 hun staff 102 Nov 17 13:43 ..
drwxr-xr-x 4 hun staff 136 Nov 17 13:43 .gradle
-rw-r--r-- 1 hun staff 1031 Nov 17 13:43 build.gradle
drwxr-xr-x 3 hun staff 102 Nov 17 13:43 gradle
-rwxr-xr-x 1 hun staff 5296 Nov 17 13:43 gradlew
-rw-r--r-- 1 hun staff 2260 Nov 17 13:43 gradlew.bat
-rw-r--r-- 1 hun staff 584 Nov 17 13:43 settings.gradle
drwxr-xr-x 4 hun staff 136 Nov 17 13:43 src

자 이제 build.gradle 파일을 열어서 java와 eclipse를 사용하겠다고 알려주자.

apply plugin: 'java-library' 부분 아래에 apply pulgin: 'eclipse'를 추가하자.

1
2
3
// Apply the java-library plugin to add support for Java Library
apply plugin: 'java-library'
apply pulgin: 'eclipse' // 여기!

이제 저장하고 나서 다시 프로젝트의 홈 디렉토리로 돌아가서 다음 명령어를 날려보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ gradle eclipse

BUILD SUCCESSFUL in 1s
3 actionable tasks: 3 executed
$ ls -al
total 56
drwxr-xr-x 12 hun staff 408 Nov 17 13:51 .
drwxr-xr-x 3 hun staff 102 Nov 17 13:50 ..
-rw-r--r-- 1 hun staff 3026 Nov 17 13:51 .classpath
drwxr-xr-x 4 hun staff 136 Nov 17 13:50 .gradle
-rw-r--r-- 1 hun staff 383 Nov 17 13:51 .project
drwxr-xr-x 3 hun staff 102 Nov 17 13:51 .settings
-rw-r--r-- 1 hun staff 1055 Nov 17 13:51 build.gradle
drwxr-xr-x 3 hun staff 102 Nov 17 13:50 gradle
-rwxr-xr-x 1 hun staff 5296 Nov 17 13:50 gradlew
-rw-r--r-- 1 hun staff 2260 Nov 17 13:50 gradlew.bat
-rw-r--r-- 1 hun staff 584 Nov 17 13:50 settings.gradle
drwxr-xr-x 4 hun staff 136 Nov 17 13:50 src

자, 이제 Eclipse 프로젝트가 생성이 되었다. ~(참 쉽죠?)~

Hibernate 추가

이제, Eclipse로 돌아가서 File > Import 를 클릭한 뒤, gradle > Existing Gradle Project 를 선택하고 Next!

How to experience the best blah blah blah~ 나오면 Next!

Project root directory 부분에 우리가 만들었던 ~/workspace/hibernate 를 절대경로로 바꿔주고 난 뒤 Finish!

이제 Eclipse 프로젝트가 완성되었다. (왜케 힘들지? …)

하지만 우린 아직 갈길이 멀다. Eclipse에서 build.gradle 파일을 열어 몇가지 내용을 추가해주자.

현재 build.gradle 파일을 보면 3가지 섹션으로 나눠져 있는것을 알 수 있을 것이다.

첫 번째는 apply 가 적힌 부분! 내가 이런애들 쓸꺼야~ 라고 선언해주는 부분이다.

두 번째는 repositories 이 부분!

이 부분에서는 우리가 가져올 라이브러리들이 존재하는 경로를 알려주면 된다. Hibernate의 경우 Maven 리파지토리를 쓰면 된다.

마지막 세 번째는 dependencies 이 부분!

우리가 실제로 사용할 라이브러리들을 알려주는 부분이다. 여기에 적힌 라이브러리를 gradle이 보고, 자동으로 의존성을 확인해서 관련된 라이브러리들을 모~두 가져다 준다. (슈퍼 쿨!)

자 그럼 Hibernate를 추가해보자! 아래에 같이 build.gradle 파일을 수정한 뒤 저장해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*
* This build file was generated by the Gradle 'init' task.
*
* This generated file contains a sample Java Library project to get you started.
* For more details take a look at the Java Libraries chapter in the Gradle
* user guide available at https://docs.gradle.org/4.3.1/userguide/java_library_plugin.html
*/

// Apply the java-library plugin to add support for Java Library
apply plugin: 'java-library'
apply plugin: 'eclipse'

// In this section you declare where to find the dependencies of your project
repositories {
// Use jcenter for resolving your dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
mavenCentral() // maven 리파지토리를 추가해준다
}

dependencies {
// This dependency is exported to consumers, that is to say found on their compile classpath.
api 'org.apache.commons:commons-math3:3.6.1'

// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation 'com.google.guava:guava:23.0'

// Use JUnit test framework
testImplementation 'junit:junit:4.12'

// hibernate 5.2.12 버전과 slf4j 라이브러리를 추가해준다.
compile 'org.hibernate:hibernate-core:5.2.12.Final'
compile 'org.slf4j:slf4j-api:1.7.21'
}

이제 hibernate를 사용할 준비가 완료되었다.

커맨드창으로 가서 gradle build 라고 했을때 오류 없이 잘 되면 성공한 것이다. (당연한 소릴 하네…)

1
2
3
4
$ gradle build

BUILD SUCCESSFUL in 4s
4 actionable tasks: 4 executed

CUBRID 추가

나는 데이터베이스를 국산 오픈소스 DB인 CUBRID를 사용할 것이다. MySQL이나 다른 유명한 DB들은 검색하면 워낙에 많은 정보들이 나오니 다른거 쓰실 분들은 다른거 쓰셔도 된다.

이를 위해 우리는 repositories와 dependencies에 CUBRID 설정을 추가해야 한다.

다음처럼 추가해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
repositories {
// Use jcenter for resolving your dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
mavenCentral()

// CUBRID를 위한 리파지토리 추가
maven {
url 'http://maven.cubrid.org/'
}
}

dependencies {
// This dependency is exported to consumers, that is to say found on their compile classpath.
api 'org.apache.commons:commons-math3:3.6.1'

// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation 'com.google.guava:guava:23.0'

// Use JUnit test framework
testImplementation 'junit:junit:4.12'
testImplementation 'org.hamcrest:hamcrest-all:1.3'

compile 'org.hibernate:hibernate-core:5.2.12.Final'
compile 'org.slf4j:slf4j-api:1.7.21'

// CUBRID를 사용하기 위한 추가!
compile group: 'cubrid', name: 'cubrid-jdbc', version: '10.1.0.7663'
}

hibernate의 경우 compile 'org.hibernate:hibernate-core:5.2.12.Final' 이렇게 되 있는데 CUBRID는 왜 compile group: 'cubrid', name: 'cubrid-jdbc', version: '10.1.0.7663' 이렇게 되 있는지 궁금해 할 수 있겠다.

근데 별 차이 없다. hibernate 선언한 부분은 CUBRID 선언한 부분을 그저 짧게 작성한 것 뿐이다. :)

여기까지 하고 다시 커맨드로 가서 gradle build 를 입력해서 오류가 안난다면 성공이다.

Hibernate 세팅

이제 본격적인 Hibernate를 사용할 시간이다. 두근두근 하지 않는가?

자 먼저 Eclipse 프로젝트의 구조를 보면 다음과 같이 생겼을 것이다. (이미지를 넣을껄 그랬나?)

1
2
3
4
5
6
7
8
9
10
11
hibernate
`- src/main/java
`- src/test/java
`- JRE System Library [JavaSE-1.8]
`- Project and External Dependencies
`- gradle
`- src
`- build.gradle
`- gradlew
`- gradlew.bat
`- settings.gradle

여기에 hibernate 설정 파일을 추가하기 위해 프로젝트명을 우클릭하여 new > source folder 를 클릭한 뒤 src/main/resources 라고 생성해준다.

이제 방금 만든 src/main/resources 를 우클릭하여 new > file 을 클릭한 뒤 hibernate.cfg.xml 이라는 파일을 생성해준다. 이 파일이 hibernate의 DB 접속 정보와 설정들, 그리고 매핑 클래스들 등 온갖 정보가 들어가게 된다.

우리는 아주 간단하게 만들 예정이니 참고하기 바란다.

hibernate.cfg.xml 파일에 아래와 동일하게 입력한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection properties - Driver, URL, user, password -->
<property name="hibernate.connection.driver_class">cubrid.jdbc.driver.CUBRIDDriver</property>
<property name="hibernate.connection.url">jdbc:cubrid:localhost:33000:demodb:public::</property>
<property name="hibernate.connection.username">public</property>
<property name="hibernate.connection.password"></property>
<!-- Connection Pool Size -->
<property name="hibernate.connection.pool_size">1</property>

<!-- Outputs the SQL queries, should be disabled in Production -->
<property name="hibernate.show_sql">true</property>

<!-- Dialect is required to let Hibernate know the Database Type, MySQL, Oracle etc
Hibernate 4 automatically figure out Dialect from Database Connection Metadata -->
<property name="hibernate.dialect">org.hibernate.dialect.CUBRIDDialect</property>

<mapping class="dev.huna.model.Employee"/>
<mapping class="dev.huna.model.Address"/>
</session-factory>
</hibernate-configuration>

<session-factory>에 있는 프로퍼티들 중 중요한 애들은 다음과 같다.

  • hibernate.connection.driver_class: JDBC 드라이버가 로드될 때 필요한 클래스명을 알려주는 놈이다.
  • hibernate.connection.url: JDBC Connection을 맺기 위한 url 접속 정보다.
  • hibernate.connection.username: DB 사용자 이름이다.
  • hibernate.connection.password: DB 사용자의 비밀번호이다.
  • hibernate.connection.pool_size: Connection pool을 몇 개 사용할 것인지 설정이다.
  • hibernate.show_sql: 이 값을 true로 설정하면 HQL이라는 Hibernate sql이 일반 SQL로 변환되어 로그로 출력된다.
  • hibernate.dialect: 특정 DBMS에 맞게 설정된 클래스를 지정하는 부분인데 사실 4.대부터는 자동으로 잡아줘서 상관이 없긴 하다.

마지막으로 <mapping> 태그는 RDBMS의 테이블과 매핑시킬 클래스를 지정해 주는 것이다. 곧 Employee와 Address 클래스를 만들 것이다.

다 됬는가? 그럼 테이블을 먼저 준비해보자.

Database 세팅

먼저 table을 만들어보자. 간단한 Employee와 Address 이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
CREATE TABLE `Employee` (
`emp_id` int NOT NULL AUTO_INCREMENT,
`emp_name` varchar(20) NOT NULL,
`emp_salary` numeric(10,0) NOT NULL DEFAULT 0,
PRIMARY KEY (`emp_id`)
);

CREATE TABLE `Address` (
`emp_id` int NOT NULL,
`address_line1` varchar(50) NOT NULL DEFAULT '',
`zipcode` varchar(10) DEFAULT NULL,
`city` varchar(20) DEFAULT NULL,
PRIMARY KEY (`emp_id`),
CONSTRAINT `emp_fk_1` FOREIGN KEY (`emp_id`) REFERENCES `Employee` (`emp_id`)
);

INSERT INTO `Employee` (`emp_id`, `emp_name`, `emp_salary`)
VALUES
(1, 'Mr. Huna', 1000),
(2, 'Miss A', 200),
(3, 'Dr. strange', 300),
(4, 'Jack and kong tree water', 400);


INSERT INTO `Address` (`emp_id`, `address_line1`, `zipcode`, `city`)
VALUES
(1, 'devro 1-2', '95129', 'Developer Town'),
(2, 'room 9', '95051', 'JYP'),
(3, 'undefined', '560100', 'Marvel Universe'),
(4, 'first street', '100100', 'Above the cloud');

Employee 와 Address는 foreign key로 연결되어 있고, 1:1 대응관계에 있다는 사실을 염두해두기 바란다. join을 구현하려면 이게 중요하다.

Mapping class 작성

방금 만든 두 개의 테이블에 대응하는 클래스를 각각 만들어야 한다. 매핑하는 방법은 xml을 이용하는 방법이 있고, annotation을 이용하는 방법이 있는데 여기서는 annotation을 이용하도록 하겠다.

클래스 이름은 알아보기 쉽게 테이블과 똑같이 만들어주자.

dev.huna.model.Employee

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package dev.huna.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity
@Table(name="EMPLOYEE",
uniqueConstraints={@UniqueConstraint(columnNames={"EMP_ID"})})
public class Employee {
private int empId;
private String empName;
private int empSalary;
private Address address;

@Id
@Column(name="EMP_ID", nullable=false)
@GeneratedValue(strategy=GenerationType.IDENTITY)
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
@Column(name="EMP_NAME", length=20, nullable=false)
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
@Column(name="EMP_SALARY", nullable=false)
public int getEmpSalary() {
return empSalary;
}
public void setEmpSalary(int empSalary) {
this.empSalary = empSalary;
}
@OneToOne(mappedBy="employee")
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public String toString() {
return "Employee [empId=" + empId + ", empName="
+ empName + ", empSalary=" + empSalary + "]";
}
}

dev.huna.model.Address

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package dev.huna.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity
@Table(name="ADDRESS",
uniqueConstraints={@UniqueConstraint(columnNames={"EMP_ID"})})
public class Address {
private int empId;
private String addressLine1;
private String zipCode;
private String city;
private Employee employee;

@Id
@Column(name="EMP_ID")
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
@Column(name="ADDRESS_LINE1")
public String getAddressLine1() {
return addressLine1;
}
public void setAddressLine1(String addressLine1) {
this.addressLine1 = addressLine1;
}
@Column(name="ZIPCODE")
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
@Column(name="CITY")
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@OneToOne
@PrimaryKeyJoinColumn
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
@Override
public String toString() {
return "Address [empId=" + empId + ", addressLine1="
+ addressLine1 + ", zipCode=" + zipCode + ", city=" + city
+ "]";
}
}

다른 annotation들은 비교적 간단하나 개인적으로 JOIN에 사용되는 annotation이 난해했었다.

앞서 이야기한대로 1:1 대응관계이기 때문에 @OneToOne annotation을 사용하였다.

참고로 Employee 클래스의 @OneToOnemappedBy에는 Address 클래스의 employee를 멤버변수명을 써주면 되고

Address 클래스에서 Employee 클래스를 참조하는 경우에는 @PrimaryKeyJoinColumn annotation은 말 그대로 Primary key 이면서 join 컬럼으로 사용될 때 사용하면 되므로 요걸 사요하면 된다. (참 쉽죠?)

Hibernate 연결 클래스

Hibernate를 이용해서 DBMS에 접속하기 위해서는 먼저 Configuration 클래스를 만들어서 hibernate.cfg.xml 설정파일을 읽어들인 다음 SessionFactory 를 생성해내면 된다.

소스코드는 다음과 같다.

dev.huna.util.HibernateUtil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package dev.huna.util;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateUtil {

private static SessionFactory sessionFactory;

private static SessionFactory buildSessionFactory() {
try {
Configuration configuration = new Configuration();
configuration.configure("hibernate.cfg.xml");
return configuration.buildSessionFactory();
}
catch (Throwable ex) {
ex.printStackTrace();
throw new ExceptionInInitializerError(ex);
}
}

public static SessionFactory getSessionFactory() {
if(sessionFactory == null) sessionFactory = buildSessionFactory();
return sessionFactory;
}
}

Main 클래스

이제 고지가 눈앞이다. 쫌만 힘내길 바란다.

Main 클래스는 그냥 Hibernate를 이용해서 질의를 수행한다. 여기서는 간단히 HQL이라고 불리는 문법을 통해 질의를 수행해 볼 것이다.

dev.huna.main.Main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package dev.huna.main;

import java.util.Arrays;
import java.util.List;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.query.Query;

import dev.huna.model.Employee;
import dev.huna.util.HibernateUtil;

public class Main {
public static void main(String[] args) {
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
Session session = sessionFactory.getCurrentSession();

session.beginTransaction();

// Select all records at table of Employee
Query query = session.createQuery("FROM Employee");

List<Employee> empList = query.list();

for (Employee emp: empList) {
System.out.println(emp);
}

// Select specific record at table of Employee
query = session.createQuery("FROM Employee WHERE empId = :id");
query.setParameter("id", 1);
Employee employee = (Employee) query.uniqueResult();
System.out.println(employee);

// Inner join between Employee and Address
query = session.createQuery("select e.empName, a.city, a.addressLine1 from Employee e INNER JOIN e.address a");
List<Object[]> addrList = query.list();
for (Object[] arr: addrList) {
System.out.println(Arrays.toString(arr));
}

session.getTransaction().commit();

HibernateUtil.getSessionFactory().close();
}
}

처음 질의 FROM Employee 는 Employee 테이블의 모든 데이터를 조회한다. 주의할 점은 Employee 는 Java 클래스를 의미하므로 첫 글짜가 Employee 클래스처럼 대문자로 적혀 있다. 궁금하면 FROM employee 로 수정해서 돌려보길 바란다. Exception이 후두두두둑 떨어질 것이다.

두 번째 질의는 파라미터를 바인딩 하는 예제이다. JDBC의 prepare, setXXX 이런 애들과 동일하다. 여기서 또 주의해야 할 점은 WHERE empId 에서 empIdEmployee 클래스의 empId 멤버변수를 의미한다. 대소문자 주의하기 바란다.

아! 바인딩 할때 :id 와 같이 임의로 이름을 정해준 뒤 query.setParameter(변수명, 값) 형태로 바인딩하면 된다. JDBC 드라이버에 비해 굉장히 추상적이라서 맘에 든다. (setObject() 도 비슷하긴 하지만… 그래도…)

마지막 예제는 Employee 클래스와 Address 클래스의 INNER JOIN 예제이다. Employee 클래스에서 address 라는 멤버변수를 생성했던걸 기억하는가? HQL 내부의 e.address 는 바로 그 멤버변수를 가리키는 것이다. Hibernate 는 질의도 객체지향적이다! (슈퍼 쿨!)

join의 경우 결과값이 Object[]로 생성됨을 유의하자.




휴, 드디어 끝났다.

실행은 각자 해보길 바란다.