Study/spring

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

유경호 2020. 12. 21. 22:39
반응형

자바 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

 

반응형