자바 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
버전 명시는 따로 하지 않았음. 스프링 부트가 자동으로 의존성 버전 관리를 해주기 때문에
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 |
- Memeber 클래스 생성 후, @Entity 어노테이션을 붙여준다
- @Entity를 붙여줌으로써 JPA에게 테이블과 매핑할 클래스라는 걸 알려준다.
- Primary Key와 매핑할 필드에 @Id와 @GeneratedValue 어노테이션을 붙여준다.
- @Id: JPA에게 해당 필드가 PK와 매핑하는 것을 알려줌
- @GeneratedValue: PK의 생성 전략을 지정해준다.
- 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
'Study > spring' 카테고리의 다른 글
자바 ORM 표준 JPA 프로그래밍(4) - 플러시와 준영속 (0) | 2020.12.23 |
---|---|
자바 ORM 표준 JPA 프로그래밍(3) - 영속성 컨텍스트 (0) | 2020.12.22 |
자바 ORM 표준 JPA 프로그래밍(2) - JPA 시작 (0) | 2020.12.04 |
자바 ORM 표준 JPA 프로그래밍(1) - JPA란 무엇인가? (0) | 2020.11.18 |
자바 ORM 표준 JPA 프로그래밍(1) - SQL을 직접 다룰 때 발생하는 문제점, 패러다임 불일치 (0) | 2020.11.16 |