2장 JPA 시작
*.gradle 파일
plugins {
id 'java'
}
group = 'org.example'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
testImplementation platform('org.junit:junit-bom:5.9.1')
testImplementation 'org.junit.jupiter:junit-jupiter'
// JPA, 하이버네이트
// hibernate-core.jar, hibernate-jpa-2.1-api.jar 라이브러리도 함께 내려받는다.
implementation 'org.hibernate:hibernate-entitymanager:4.3.10.Final'
// H2 데이터베이스
// H2 데이터베이스에 접속해야 하므로 h2 라이브러리도 지정한다.
implementation 'com.h2database:h2:1.4.187'
}
test {
useJUnitPlatform()
}
2.4 객체 매핑 시작
회원 테이블
CREATE TABLE MEMBER (
ID VARCHAR(255) NOT NULL, --아이디(기본 키)
NAME VARCHAR(255), --이름
AGE INTEGER NOT NULL, --나이
PRIMARY KEY (ID)
)
회원 클래스에 JPA가 제공하는 매핑 에노테이션 추가
매핑 정보 | 회원 객체 | 회원 테이블 |
---|---|---|
클래스와 테이블 | Member | MEMBER |
기본키 | id | ID |
필드와 컬럼 | username | NAME |
필드와 컬럼 | age | AGE |
package ch02.jpa.start1;
import javax.persistence.*; //**
/**
* 애플리케이션에서 사용할 회원 클래스 생성
*/
@Entity
@Table(name="MEMBER")
public class Member {
// 기본 키(PK)에 매핑한다.
// id 필드를 테이블의 ID 기본키 컬럼에 매핑한다.
// 식별자 필드
@Id
@Column(name = "ID")
private String id;
// 필드를 컴럼에 매핑한다.
@Column(name = "NAME")
private String username;
// 매핑 정보가 없는 필드
// 필드 명이 age이르로 age컬럼으로 매핑
private Integer age;
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
}
@Entity
- 이 클래스를 테이블과 매핑한다고 JPA에게 알려준다.
- 이렇게 @Entity가 사용된 클래스를 엔티티 클래스라 한다.
@Table
- 엔티티 클래스에 매핑할 테이블 정보를 알려준다.
- 이 어노테이션을 생략하면 클래스 이름을 테이블 이름으로 매핑한다(더 정황히는 에니팉 이름을 사용한다. 엔티티 이름은 4.1절에서 설명)
@Id
- 엔티티 클래스의 필드를 테이블의 기본 키 (Primary key)dㅔ 매핑한다.
- 엔티티의 id 필드를 테이블의 ID 기본키 컬럼에 매핑했다.
- 이렇게 @Id가 사용된 필드를 식별자 필드라 한다.
@Colum
- 필드를 컬럼에 매핑한다.
- name 속성을 사용해서 Member 엔티티의 username 필드를 MEMBER 테이블의 컴럼에 매핑했다.
매핑 정보가 없는 필드
- age 필드에는 매핑 어노테이션이 없다. 이렇게 매핑 어노테이션을 생략하면 필드명을 사용해서 컬럼명으로 매핑한다.
- 대소문자를 구분하는 데이터베이스를 사용하면
- @Column(name="AGE")처럼 명시적으로 매핑
2.5 Persistence.xml 설정
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.1">
<persistence-unit name="jpabook">
<!-- p78
영속성 유닛에서 JPA설정이 시작된다.
일반적으로 연결할 데이터베이스당 하나의 영속성 유닛을 등록
영속성 유닛에서는 고유한 이름을 부여
사용한 속성은 <properties>으로 속성 정의
-->
<!-- 엔티티 클래스 지정 -->
<class>ch02.jpa.start1.Member</class>
<properties>
<!-- 필수 속성 -->
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<!-- <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>-->
<property name="javax.persistence.jdbc.user" value="sa"/>
<!-- <property name="javax.persistence.jdbc.user" value="root"/>-->
<property name="javax.persistence.jdbc.password" value=""/>
<!-- <property name="javax.persistence.jdbc.password" value="qkrtmdcks1!"/>-->
<property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:testdb"/>
<!-- <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost/study_jpa?characterEncoding=UTF-8"/>-->
<!-- p79 데이터 베이스 방언(Dialect) 설정 -->
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
<!-- 옵션 -->
<!-- p81 하이버네이트가 실행한 SQL을 출력한다. -->
<property name="hibernate.show_sql" value="true" />
<!-- p81 하이버네이트가 실행한 SQL을 출력할 때 보기 쉽게 정렬한다. -->
<property name="hibernate.format_sql" value="true" />
<!-- p81 쿼리를 출력할 때 주석도 함께 출력한다. -->
<property name="hibernate.use_sql_comments" value="true" />
<!-- p81 JPA 표준에 맞춘 새로운 키생성 전략을 사용한다. -->
<property name="hibernate.id.new_generator_mappings" value="true" />
<!-- Hibernate가 엔티티 클래스를 기반으로 데이터베이스 테이블을 자동으로 생성하려고 시도합니다.
다음과 같이 설정할 수 있습니다: -->
<!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
</properties>
</persistence-unit>
</persistence>
- JPA 설정은 영속성 유닛(persistence-unit)이라는 것부터 시작하는데
- 일반적으로 연결할 데이터베이스당 하나의 영속성 유닛을 등록한다.
- 영속성 유닛에는 고유한 이름을 부여해야하는데 여기서는 jpabook이라는 이름을 사용
-
<persistence-unit name="jpabook">
속성 분석
하이버 네이트 속성 (가장 중요하다.)
-
이름이 javax.persistence로 시작하는 속성은 JPA 표준 속성으로 특정 구현체에 종속 되지 않는다.하이버 네이트에서만 사용할 수 있다.<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
- 반명에 hiberate로 시작하는 속성은 하이버네이트 전용 속성이므로
2.5.1 데이터베이스 방언 <DB 고유기능, 메서드 차이>
- JPA는 특정 데이터베이스에 종속적이지 않은 기술이다.
- 따라서 다른 데이터베이스로 손쉽게 교체할 수 있다.
방언의 예
- 데이터 타입
- 다른 함수명
- 페이징 처리 : MySQL은 LIMIT을 사용하지만 오라클은 ROWNUM을 사용한다.
- 이처럼 SQL 표준을 지키지 않거나 특정 데이터 베이스만의 고유한 기능을 JPA에는 방언(Dialect)라고 한다.
- 하이버네이트를 포함한 대부분의 JPA 구현체들은 이런 문제를 해결하려고 다양한 데이터베이스 방언 클래스를 제공한다.
- 개발자는 JPA가 제공하는 표준 문법에 맞추어 JPA를 사용하면 되고, 특정 데이터베이스에 의존적인 SQL은 데이터베이스 방언이 처리해준다.
- 따라서 데이터베이스가 변경되어도 애플리케이션 코드를 변경할 필요 없이 데이터베이스 방언만 교체하면 된다.
- H2 : org.hibernate.dialect.H2Dialect
- 오라클 10g : org.hibernate.dialect.Oracle10gDialect
- MySQL : org.hibernate.dialect.MySQL5InnoDBDialect
참고
사용된 하이버네이트 전용 속성은 다음과 같다.
<!-- p81 하이버네이트가 실행한 SQL을 출력한다. -->
<property name="hibernate.show_sql" value="true" />
<!-- p81 하이버네이트가 실행한 SQL을 출력할 때 보기 쉽게 정렬한다. -->
<property name="hibernate.format_sql" value="true" />
<!-- p81 쿼리를 출력할 때 주석도 함께 출력한다. -->
<property name="hibernate.use_sql_comments" value="true" />
<!-- p81 JPA 표준에 맞춘 새로운 키생성 전략을 사용한다. -->
<property name="hibernate.id.new_generator_mappings" value="true" />
<!-- Hibernate가 엔티티 클래스를 기반으로 데이터베이스 테이블을 자동으로 생성하려고 시도합니다.
다음과 같이 설정할 수 있습니다: -->
<!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
JPA 구현체들은 보통 에니팉 클래스를 자동으로 인식하지만, 환경에 따라 인식하지 못할 때도 있다.
그때는 persistence.xml에 <class>을 사용해 엔티티클래스를 지정한다.
<persistence-unit name="jpabook">
<!-- 엔티티 클래스 지정 -->
<class>ch02.jpa.start1.Member</class>
2.6 애플리케이션 개발
package ch02.jpa.start1;
import javax.persistence.*;
import java.util.List;
/**
* @author holyeye
*/
public class JpaMain {
public static void main(String[] args) {
//엔티티 매니저 팩토리 생성
// p83. JPA를 시작하려면 우선 persistence.xml의 설정 정보를 사용해서 엔티티 매니저 팩토리 생성
// 생성 비용이 크기 때문에 => 프로젝트 전반에 한번만 생성하고 공유해서 사용한다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
// p83. 엔티티 매니저는 등록/수정/삭제/조회를 할수 있다.
// 엔티티 매니저는 내부에 데이터소스(데이터베이스 커넥션)를 유지 하면서 데이터베이스와 통신
EntityManager em = emf.createEntityManager(); //엔티티 매니저 생성
EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득
try {
tx.begin(); //트랜잭션 시작
logic(em); //비즈니스 로직
tx.commit();//트랜잭션 커밋
} catch (Exception e) {
e.printStackTrace();
tx.rollback(); //트랜잭션 롤백
} finally {
em.close(); //엔티티 매니저 종료
}
emf.close(); //엔티티 매니저 팩토리 종료
}
코드는 3부분으로 나뉘어 있다.
- 엔티티 매니저 설정
- 트랜잭션 관리
- 비즈니스 로직
2.6.1 엔티티 매니저 설정
그림 2.12 엔티티 매니저 생성 과정
엔티티 매니저 팩토리 생성
- JPA를 시작하려면 우선 persitence.xml의 설정 정보를 이용해
- 엔티티 매니저 팩토리를 생성해야 한다.
- Persistence 클래스를 사용하는데
- 이클래스는 엔티티 매니저 팩토리를 생성해서 JPA를 사용 할 수 있게 준비한다.
-
//엔티티 매니저 팩토리 생성 // p83. JPA를 시작하려면 우선 persistence.xml의 설정 정보를 사용해서 엔티티 매니저 팩토리 생성 // 생성 비용이 크기 때문에 => 프로젝트 전반에 한번만 생성하고 공유해서 사용한다. EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
- 미름이 jpabook인 영속성 유닛(persistence-unit)을 찾아서 엔티티 매니저 팩토리를 생성한다.
- persistence.xml의 설정 정보를 읽어서 JPA를 동작시키기 위한 기반 객체를 만들고 JPA 구현체에 따라서는 데이터베이스 커넥션 풀도 생성하므로
- 엔티티 매니저 팩토리를 생성하는 비용은 아주 크다.
- 엔티티 매니저 팩토리는 애플리케이션 전체에서 딱 한번만 생성하고 공유해서 사용해야 한다.
엔티티 매니저 생성
// p83. 엔티티 매니저는 등록/수정/삭제/조회를 할수 있다.
// 엔티티 매니저는 내부에 데이터소스(데이터베이스 커넥션)를 유지 하면서 데이터베이스와 통신
EntityManager em = emf.createEntityManager(); //엔티티 매니저 생성
- 엔티티 매니저 팩토리에서 엔티티 매니저를 생성한다.
- JPA의 기능 대부분은 이 엔티티 매니저가 제공한다.
- 엔티티 매니저를 사용해서 엔티티를 데이터베이스에 등록/수정/삭제/조회할 수 있다.
- 엔티티 매니저는 내부에 데이터소스(데이터베이스 커넥션)을 유지하면서 데이터베이스와 통신한다.
- 참고로 엠티티 매니저는 데이터베이스 커넥션과 밀접한 관계가 있으므로 스레드간에 공유하거나 재사용하면 안 된다.
종료
- 마지막으로 사용이 끝난 엔티티 매니저는 다음처럼 반드시 종료해야한다.
-
em.close(); // 엔티티 매니저 종료
- 애플리케이션을 종료할 때 엔티티 매니저 팩토리도 다음처럼 종료해야 한다.
-
emf.close(); // 엔티티 매니저 팩토리 종료
2.6.2 트랜잭션 관리
- JPA을 사용하면 항상 트랜잭션 안에서 데이터를 변경해야 한다.
- 트랜젝션 없이 데이터를 변경하면 예외가 발생한다.
- 트랜잭션을 시작하려면 엔티티 매니저(em)에서 트랜잭션 API를 받아와야 한다.
-
... EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득 try {
tx.begin(); //트랜잭션 시작
logic(em); //비즈니스 로직
tx.commit();//트랜잭션 커밋
} catch (Exception e) {
e.printStackTrace();
tx.rollback(); //트랜잭션 롤백
}
...
## 2.6.3 비즈니스 로직 <emd을 통해 CRUD 관리>
- 회원 엔티티를 하나 생성한 다음 엔티티 매니저를 통해 데이터베이스에 등록, 수정, 삭제, 조회한다.
```java
public static void logic(EntityManager em) {
String id = "id1";
Member member = new Member();
member.setId(id);
member.setUsername("지한");
member.setAge(2);
//등록
// p85. 저장을 하는 메서드, 매개 변수에 저장할 엔티티를 넘겨준다.
em.persist(member);
//수정
// p85. 수정자 메소드(set)메소드 호출
member.setAge(20);
//한 건 조회
// p86. @Id로 데이터베이스 테이블의 기본키와 매핑한 식별자 값으로 엔티티 하나를 조회
Member findMember = em.find(Member.class, id);
System.out.println("findMember=" + findMember.getUsername() + ", age=" + findMember.getAge());
//목록 조회
// p87. JPQL
// 하나 이상의 회원 목록을 조회하는 다음 코드를 자세히 살펴보자.
// JPQL을 사용하기 위해서는 em.createQuery(JPQL, 반환_타입.class) 메소드를 실행하서
// query.getResultList()을 실행한다.
TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();
System.out.println("members.size=" + members.size());
//삭제
em.remove(member);
}
- 비즈니스 로직을 보면 등록, 수정, 삭제, 조회 작업이 엔티티 매니저(em)를 통해서 수행되는 것을 알수 있다.
- 엔티티 매니저는 객체를 저장하는 가상의 데이터베이스 처럼 보인다.
- 먼저 등록, 수정, 삭제 코드를 분석해보다.
등록
String id = "id1";
Member member = new Member();
member.setId(id);
member.setUsername("지한");
member.setAge(2);
//등록
// p85. 저장을 하는 메서드, 매개 변수에 저장할 엔티티를 넘겨준다.
em.persist(member);
- Persist() 메소드에 저장할 엔티티를 넘겨주면 된다.
- em.persist(member)를 실행해서 엔티티를 저장했다.
수정
//수정
// p85. 수정자 메소드(set)메소드 호출
member.setAge(20);
- 엔티티를 수정한 후에 수정 내용을 반영하려면 em.update() 같은 메소드를 호출해야 할 것 같은데 단순히 엔티티의 값만 변경 했다.
- member.setAge(20)처럼 엔티티의 값만 변경하면 다음과 같은 UPDATE SQL을 생성해서 데이터베이스에 값을 변경한다.
삭제
//삭제
em.remove(member);
- 엔티티를 삭제하려면 엔티티 매니저의 remove() 메소드에 삭제하려는 엔티티를 넘겨준다.
한 건 조회
//한 건 조회
// p86. @Id로 데이터베이스 테이블의 기본키와 매핑한 식별자 값으로 엔티티 하나를 조회
Member findMember = em.find(Member.class, id);
System.out.println("findMember=" + findMember.getUsername() + ", age=" + findMember.getAge());
- find() 메소드는 조회할 엔티티 타입과
- @Id로 데이터베이스 테이블의 기본키와 매핑한 식별자 값으로 엔티티 하나를 조회하는 가장 단순한 조회 메소드다.
JPQL
//목록 조회
// p87. JPQL
// 하나 이상의 회원 목록을 조회하는 다음 코드를 자세히 살펴보자.
// JPQL을 사용하기 위해서는 em.createQuery(JPQL, 반환_타입.class) 메소드를 실행하서
// query.getResultList()을 실행한다.
TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();
System.out.println("members.size=" + members.size());
- 엔티티 객체를 중심으로 개발하고 데이터베이스에 대한 처리는 JPA에 맡겨야 한다.
- JPA는 엔티티 객체를 중심으로 개발하므로 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색해야한다.
- 테이블이 아닌 엔티티 객체를 대상으로 검색하려면 데이터베이스의 모든 데이터를 애플리케이션으로 불러와서 엔티티 객체로 변경항 다음 검색해야하는데
- 이는 불가능하다.
- JPA는 JPQL(java Persistence Query Language)이라는 쿼리 언어로 이런 문제를 해결한다.
- JPA는 SQL을 추상화한 JPQL이라는 객체지향 쿼리 언어를 제공한다.
- JPQL은 엔티티 객체를 대상으로 쿼리한다. 쉽게 이야기해서 클래스와 필드를 대상으로 쿼리한다.
- SQL은 데이터베이스 테이블을 대항으로 쿼리 한다.
select m from Member m
이 바로 JPQL이다. 여기서from Member
는 회원 엔티티 객체를 말하는 것이다.- JPQL은 데이터베이스 테이블을 전혀 알지 못한다.
- JPQL을 사용하려면 먼저 em.createQuery(JPQL, 반환 타입) 메소드를 실행해서 쿼리 객체를 생성한 후 쿼리 객체의 getResultList() 메소드를 호출하면 된다.
'개인공부 > JPA' 카테고리의 다른 글
6장 다양한 연관관계 매핑 (1) | 2024.02.05 |
---|---|
5장 연관관계 매핑 기초 (1) | 2024.02.05 |
4장 엔티티 매핑 (1) | 2024.01.30 |
3장 영속성 관리 (1) | 2024.01.23 |
1장 JPA 소개 (0) | 2024.01.20 |