본문 바로가기
Study/spring

SpringBoot를 활용한 Hibernate 연동 JPA Basic 실습

by 유경호 2020. 12. 21.
반응형

자바 ORM 표준 JPA 프로그래밍 도서를 통해 학습하였습니다. 책의 예제는 Java Maven 프로젝트를 생성하여 진행하나 필자는 스프링부트 프로젝트를 생성하여 실습하였습니다.


pom.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
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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>me.kyeongho</groupId>
    <artifactId>jpa-getting-started</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>jpagettingstartd</name>
    <description>Study JPA</description>
 
    <properties>
        <java.version>11</java.version>
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!-- H2 Database -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- JPA 하이버네이트 -->
        <dependency>
               <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
        </dependency>    
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
 
</project>
cs

 

h2 인메모리 디비를 실습에 사용하고, 스프링 부트 자동설정과 h2-console을 사용하기 위해 web과 jdbc 의존성을 받는다. 그리고 실습에 사용할 JPA 구현체인 hibernate 의존성을 추가한다.

  • spring-boot-starter-web
  • spring-boot-starter-jdbc
  • h2
  • hibernate-entitymanager

버전 명시는 따로 하지 않았음. 스프링 부트가 자동으로 의존성 버전 관리를 해주기 때문에

ref: https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-dependency-versions.html#dependency-versions


application.properties

1
2
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:test
cs

→ 스프링 부트의 외부설정을 추가하거나 수정하는 파일이다. http://localhost:8080/h2-console을 통해 h2-console에 접근하기 위해 spring.h2.console.enabled 값을 true로 줬고, 몇 번 테스트 해보니 h2 database의 datasource.url이 스프링 부트에 의해 자동으로 매번 바뀌어서 하나로 고정해줬다. 이에 관한 정확한 내용은 추후에 조사해보겠다.

 

ApplicationRunner를 구현하여 빈 등록해 h2 데이터베이스와 컨넥션이 잘 되는지 테스트한다.

JpaRunner.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class JpaRunner implements ApplicationRunner {
 
    @Autowired
    DataSource dataSource;
 
    @Override
    public void run(ApplicationArguments args) throws Exception {
        try (Connection connection = dataSource.getConnection()) {
            // 컨넥션 테스트
            System.out.println(connection.getMetaData().getURL());
            System.out.println(connection.getMetaData().getUserName());
        }
    }
}
cs

 

실행결과

jdbc:h2:mem:test
SA

JPA hibernate 영속성 설정을 위해 persistence.xml 생성

META-INF/persistence.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
26
27
28
29
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
    xmlns="http://xmlns.jcp.org/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
 
    <persistence-unit name="hello">
        <properties>
 
            <!-- 필수 속성 -->
            <property name="javax.persistence.jdbc.driver"
                value="org.h2.Driver" />
            <property name="javax.persistence.jdbc.user" value="sa" />
            <property name="javax.persistence.jdbc.password" value="" />
            <property name="javax.persistence.jdbc.url"
                value="jdbc:h2:mem:test" />
            <property name="hibernate.dialect"
                value="org.hibernate.dialect.H2Dialect" />
 
            <!-- 옵션 -->
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.format_sql" value="true" />
            <property name="hibernate.use_sql_comments" value="true" />
            <!-- <property name="hibernate.show_sql" value="true" /> -->
 
        </properties>
    </persistence-unit>
 
</persistence>
cs

 

영속성 설정 파일을 생성했다면, EntityManager가 잘 만들어지는지 테스트한다.

JpaRunner.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Component
public class JpaRunner implements ApplicationRunner {
 
    @Autowired
    DataSource dataSource;
 
    @Override
    public void run(ApplicationArguments args) throws Exception {
        try (Connection connection = dataSource.getConnection()) {
            // 컨넥션 테스트
            System.out.println(connection.getMetaData().getURL());
            System.out.println(connection.getMetaData().getUserName());
        }
 
        // Unit Name을 넣어서 생성 EntityManagerFactory 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        em.close();
 
        emf.close();
    }
}
cs

 

실행 결과

2020-12-21 20:35:12.978  INFO 32472 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: hello]
2020-12-21 20:35:13.123  INFO 32472 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.4.25.Final
2020-12-21 20:35:13.403  INFO 32472 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2020-12-21 20:35:13.584  WARN 32472 --- [           main] org.hibernate.orm.connections.pooling    : HHH10001002: Using Hibernate built-in connection pool (not for production use!)
2020-12-21 20:35:13.585  INFO 32472 --- [           main] org.hibernate.orm.connections.pooling    : HHH10001005: using driver [org.h2.Driver] at URL [jdbc:h2:mem:test]
2020-12-21 20:35:13.585  INFO 32472 --- [           main] org.hibernate.orm.connections.pooling    : HHH10001001: Connection properties: {password=****, user=sa}
2020-12-21 20:35:13.585  INFO 32472 --- [           main] org.hibernate.orm.connections.pooling    : HHH10001003: Autocommit mode: false
2020-12-21 20:35:13.588  INFO 32472 --- [           main] .c.i.DriverManagerConnectionProviderImpl : HHH000115: Hibernate connection pool size: 20 (min=1)
2020-12-21 20:35:13.606  INFO 32472 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2020-12-21 20:35:14.063  INFO 32472 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-12-21 20:35:14.197  INFO 32472 --- [           main] org.hibernate.orm.connections.pooling    : HHH10001008: Cleaning up connection pool [jdbc:h2:mem:test]

h2 데이터베이스에 직접 JPA로 데이터를 관리해보기 위해 http://localhost:8080/h2-console에 접속하여 Memeber 테이블을 생성해주었다.

CREATE TABLE Member (
    id BIGINT NOT NULL,
    name VARCHAR(255),
    PRIMARY KEY (id)
);

실제 h2 데이버테이스의 Member 테이블과 매핑하기위한 Entity 생성

Member.java

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
@Entity
public class Member {
 
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
 
    private String name;
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
}
cs
  1. Memeber 클래스 생성 후, @Entity 어노테이션을 붙여준다
    • @Entity를 붙여줌으로써 JPA에게 테이블과 매핑할 클래스라는 걸 알려준다.
  2. Primary Key와 매핑할 필드에 @Id와 @GeneratedValue 어노테이션을 붙여준다.
    • @Id: JPA에게 해당 필드가 PK와 매핑하는 것을 알려줌
    • @GeneratedValue: PK의 생성 전략을 지정해준다.
  3. Getter, Setter 메소드를 생성해준다.

Class 이름과 Table이름이 다르다면 @Table어노테이션을 통해 매핑해줄 수 있다.

DB의 칼럼 이름과 필드명이 다르다면 @Column 어노테이션을 통해 매핑해줄 수 있다.

1
2
3
4
5
6
7
8
9
10
@Entity
@Table(name = "USER")
public class Member {
 
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
 
    @Column(name = "username")
    private String name;
}
cs

 

Member 테이블과 매핑한 Member 엔티티를 실제로 EntityManager를 통해 저장해본다.

JpaRunner.java

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
@Component
public class JpaRunner implements ApplicationRunner {
 
    @Autowired
    DataSource dataSource;
 
    @Override
    public void run(ApplicationArguments args) throws Exception {        
        // Unit Name을 넣어서 생성 EntityManagerFactory 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        // 한 트랜잭션에서 실행되는 작업을 시작할 때마다 EntityManager를 반드시 만들어서 사용해줘야함
        EntityManager em = emf.createEntityManager();    
        //EntityManager로 부터 트랜잭션을 받아와 사용할 수 있다.
        EntityTransaction tx = em.getTransaction();
 
        tx.begin();
 
        Member member = new Member();
        member.setName("Kyeongho");
 
        em.persist(member);
 
        tx.commit();
 
        em.close();
        emf.close();
    }
}
cs

→ Memeber 엔티티 저장 테스트

실행결과

2020-12-21 21:20:00.852  INFO 13728 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: hello]
2020-12-21 21:20:00.967  INFO 13728 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.4.25.Final
2020-12-21 21:20:01.221  INFO 13728 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2020-12-21 21:20:01.434  WARN 13728 --- [           main] org.hibernate.orm.connections.pooling    : HHH10001002: Using Hibernate built-in connection pool (not for production use!)
2020-12-21 21:20:01.435  INFO 13728 --- [           main] org.hibernate.orm.connections.pooling    : HHH10001005: using driver [org.h2.Driver] at URL [jdbc:h2:mem:test]
2020-12-21 21:20:01.436  INFO 13728 --- [           main] org.hibernate.orm.connections.pooling    : HHH10001001: Connection properties: {password=****, user=sa}
2020-12-21 21:20:01.436  INFO 13728 --- [           main] org.hibernate.orm.connections.pooling    : HHH10001003: Autocommit mode: false
2020-12-21 21:20:01.439  INFO 13728 --- [           main] .c.i.DriverManagerConnectionProviderImpl : HHH000115: Hibernate connection pool size: 20 (min=1)
2020-12-21 21:20:01.453  INFO 13728 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
Hibernate: 

    drop table if exists Member CASCADE 
2020-12-21 21:20:02.286  INFO 13728 --- [           main] org.hibernate.orm.connections.access     : HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@1a3c4b3e] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
Hibernate: 

    create table Member (
       id bigint generated by default as identity,
        name varchar(255),
        primary key (id)
    )
2020-12-21 21:20:02.290  INFO 13728 --- [           main] org.hibernate.orm.connections.access     : HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@58b5f7d2] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
2020-12-21 21:20:02.308  INFO 13728 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
Hibernate: 
    /* insert me.kyeongho.Member
        */ insert 
        into
            Member
            (id, name) 
        values
            (null, ?)
2020-12-21 21:20:02.410  INFO 13728 --- [           main] org.hibernate.orm.connections.pooling    : HHH10001008: Cleaning up connection pool [jdbc:h2:mem:test]

이와 같이 하이버네이트가 동작하는 것을 Console에 보기 좋게 찍어준다.

아래의 실무 정석 코드를 보면 엔티티 매니저는 데이터베이스를 물고 있기 때문에 사용 후에는 꼭 닫아줘야한다.

 

저장 예시

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
@Component
public class JpaRunner implements ApplicationRunner {
 
    @Autowired
    DataSource dataSource;
 
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // Unit Name을 넣어서 생성 EntityManagerFactory 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        // 한 트랜잭션에서 실행되는 작업을 시작할 때마다 EntityManager를 반드시 만들어서 사용해줘야함
        EntityManager em = emf.createEntityManager();
        // EntityManager로 부터 트랜잭션을 받아와 사용할 수 있다.
        EntityTransaction tx = em.getTransaction();
        tx.begin();
 
        try {
            Member member = new Member();
            member.setName("Kyeongho");
 
            // 저장
            em.persist(member);
 
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
        emf.close();
    }
}
cs

 

실행결과

Hibernate: 
    /* insert me.kyeongho.Member
        */ insert 
        into
            Member
            (id, name) 
        values
            (null, ?)

조회, 수정, 삭제 예시

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
@Component
public class JpaRunner implements ApplicationRunner {
 
    @Autowired
    DataSource dataSource;
 
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // Unit Name을 넣어서 생성 EntityManagerFactory 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        // 한 트랜잭션에서 실행되는 작업을 시작할 때마다 EntityManager를 반드시 만들어서 사용해줘야함
        EntityManager em = emf.createEntityManager();
        // EntityManager로 부터 트랜잭션을 받아와 사용할 수 있다.
        EntityTransaction tx = em.getTransaction();
        tx.begin();
 
        try {
                // 조회
                Member member = em.find(Member.class, id);
                System.out.println("memeber.id:" + member2.getId());
                System.out.println("member.name: " + member2.getName());
 
                // 수정
                member.setName("Kyeongs");
 
                //삭제
                em.remove(member);
                tx.commit();
        } catch (Exception e) {
                tx.rollback();
        } finally {
                em.close();
        }
 
        emf.close();
    }
}
cs

 

실행 결과

Hibernate: 
    select
        member0_.id as id1_0_0_,
        member0_.name as name2_0_0_ 
    from
        Member member0_ 
    where
        member0_.id=?
memeber.id:1
member.name: Kyeongho
Hibernate: 
    /* update
        me.kyeongho.Member */ update
            Member 
        set
            name=? 
        where
            id=?
Hibernate: 
    /* delete me.kyeongho.Member */ delete 
        from
            Member 
        where
            id=?

JPQL

  • JPQL은 SQL을 추상화한 객체 지향 쿼리 언어이다.
  • SQL을 추상화하였으므로 특정 데이터베이스 SQL에 의존적이지 않다.
  • SQL 문법과 유사하여, SQL 문법에 익숙한 자라면 금방 배워서 쓸 수 있다.
  • JPQL은 엔티티 객체를 대상으로 쿼리, SQL은 데이터베이스 테이블을 대상으로 쿼리

 

JPQL에 대한 깊은 내용은 추후에 학습을 통해 배울것이므로 지금은 간단한 실습으로 느낌 정도만 깨우치자.

JpqlRunner.java

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
@Component
public class JpqlRunner implements ApplicationRunner {
 
    @Override
    public void run(ApplicationArguments args) throws Exception {
 
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
 
        try {
            Member member = new Member();
            member.setName("Hello");
            Member member2 = new Member();
            member2.setName("Hi");
            // 저장
            em.persist(member);
            em.persist(member2);
 
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        }
 
        tx.begin();
        try {
            List<Member> result = em.createQuery("select m from Member as m", Member.class)
                    .getResultList();
 
            for (Member e: result) {
                System.out.println("memeber.name: " + e.getName());
            }
 
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}
cs

 

실행결과

Hibernate: 
    /* insert me.kyeongho.Member
        */ insert 
        into
            Member
            (id, name) 
        values
            (null, ?)
Hibernate: 
    /* insert me.kyeongho.Member
        */ insert 
        into
            Member
            (id, name) 
        values
            (null, ?)
Hibernate: 
    /* select
        m 
    from
        Member as m */ select
            member0_.id as id1_0_,
            member0_.name as name2_0_ 
        from
            Member member0_
memeber.name: Hello
memeber.name: Hi

 

반응형