[자바 ORM 표준 JPA 프로그래밍 - 기본편] 02강. JPA 시작
02강. JPA 시작
1. Hello JPA - 프로젝트 생성
H2 데이터베이스 설치와 실행
- http://www.h2database.com/
- 장점
- 웹용 쿼리툴 제공
- 가벼움 (1.5M)
- MySQL, Oracle 데이터베이스 시뮬레이션 기능
- 시퀀스, AUTO INCREMENT 기능 지원
설치 방법(mac)
🚨 주의
- SpringBoot와 호환되는 버전인 1.4.199 버전 다운로드 받으려면 링크에서 해당 버전 다운받기
- 다운로드 후 bin의 sh 파일 실행
- sh 파일 실행 방법 : 폴더로 이동해서
sh (파일명)
- 화면에 설정 정보 입력
- 서버 모드 / 사용자명 sa / 비밀번호 입력 X -> 연결
🔎 발생한 에러
- Database “dir name” not found, either pre-create it or allow remote database creation (not recommended in secure environments) [90149-214]
- 해결방법 : DB를 미리 만들어줌 (참고)
1) JDBC URL에 ‘
jdbc:h2:~/test
’ 입력 후 연결 (연결 시험 XXX)
2) 다시 돌아와서 ‘jdbc:h2:tcp://localhost/~/test
’로 연결
- File corrupted while reading record: null. Possible solution: use the recovery tool [90030-199] 90030/90030
- 원인 : H2 버전 바꾸고 이전과 동일한 DB에 연결 시도
- 해결방법 : 로컬에서 DB 삭제하고 다시 만듦 (삭제해도 상관 없는 DB 였음)
💡 H2 버전 확인 명령어
SELECT H2VERSION() FROM DUAL;
Maven
- https://maven.apache.org/
- 자바 라이브러리, 빌드 관리
- 라이브러리 자동 다운로드 및 의존성 관리
- 최근에는 그래들(Gradle) 사용이 증가
프로젝트 생성
- 자바 8 이상(8 권장)
- 메이븐 설정
- groupId: jpa-basic
- artifactId: ex1-hello-jpa
- version: 1.0.0
💡 IntelliJ로 Maven 프로젝트 만들기 (강의와 UI가 다름!) (참고)
- New Project > Build system : Maven / Advanced Settings
- Maven Archetype으로 생성 X
라이브러리 추가 - pom.xml
- pom.xml에 사용할 라이브러리를 dependency로 추가
-
pom.xml에 추가하는 라이브러리
<dependencies> <!-- JPA 하이버네이트 --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>5.3.10.Final</version> </dependency> <!-- H2 데이터베이스 --> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.199</version> </dependency> </dependencies>
- hibernate
- SpringBoot와 호환 위해 5.3.10.Final 버전 사용
hibernate-entitymanager
: 필요한 의존성을 땡겨옴 (hibernate core, javax.persistence:javax.persistence-api:2.2 등)javax.persistence
: JPA 인터페이스를 가지고 있음
- h2database : DB에 접근하기 위한 드라이버
- 다운로드 받은 h2 데이터베이스와 같은 버전 사용할 것
JPA 설정하기 - persistence.xml
<?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:tcp://localhost/~/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.hbm2ddl.auto" value="create" />-->
</properties>
</persistence-unit>
</persistence>
- JPA 설정 파일
- 위치는 반드시
/META-INF/persistence.xml
- 설정 사항
persistence version="2.2"
: jpa 2.2 버전을 사용함- 필수 속성 : 데이터베이스 접근 정보 등
- ⭐️
hibernate.dialect
: 데이터베이스 방언
- persistence-unit name으로 이름 지정 (
<persistence-unit name="hello">
)- 데이터베이스당 하나 만듦
💡 javax.persistence vs. hibernate
- javax.persistence : JPA 표준 속성 -> hibernate 외의 JPA 구현 라이브러리 사용 가능
- hibernate : hibernate 전용 속성 -> 다른 라이브러리 사용하면 변경 필요
hibernate.show_sql
: DB에 나가는 쿼리를 볼 것인지hibernate.format_sql
: 쿼리를 포맷팅해서 보여줌hibernate.use_sql_comments
: 쿼리에 대한 주석 제공
데이터베이스 방언 (Dialect)
- 방언 : SQL 표준을 지키지 않는 특정 데이터베이스만의 고유한 기능
- JPA는 특정 데이터베이스에 종속 X -> 다른 데이터베이스로 변경 용이
-
각각의 데이터베이스가 제공하는 SQL 문법과 함수는 조금씩 차이가 있음
ex. 가변 문자: MySQL은 VARCHAR, Oracle은 VARCHAR2
문자열을 자르는 함수: SQL 표준은 SUBSTRING(), Oracle은 SUBSTR()
페이징: MySQL은 LIMIT , Oracle은 ROWNUM
hibernate.dialect
속성에 value로 지정- H2 :
org.hibernate.dialect.H2Dialect
- Oracle 10g :
org.hibernate.dialect.Oracle10gDialect
- MySQL :
org.hibernate.dialect.MySQL5InnoDBDialect
- H2 :
- 하이버네이트는 40가지 이상의 데이터베이스 방언 지원 (현업에서 쓸 수 있는 건 거의 다!)
2. Hello JPA - 애플리케이션 개발
JPA 구동 방식
- persistence.class에서 시작
- META-INF/persistence.xml의 설정 정보를 읽어서 EntityManagerFactory 클래스를 만듦
- EntityManagerFactory에서 필요할 때마다 EntityManager를 생성
실습
- JpaMain 클래스 생성 및 JPA 동작 확인
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
// 여기에 code 작성
em.close();
emf.close();
}
}
🚨 주의
- jdb url 같은거 쓰는지 확인!! (persistence.xml)
- Persistence 불러오지 못하면 persistence.xml 확인 필요
- EntityManagerFactory는 로딩 시점에 하나만 생성 (DB 당 하나)
- 실제 DB에 저장하는 트랜잭션 단위마다 entity manager 꼭 만들어줘야 함
객체와 테이블을 생성하고 매핑하기
1) DB에 Member table 생성
create table Member (
id bigint not null,
name varchar(255),
primary key (id)
);
2) 테이블과 매핑되는 Member class 생성
@Entity
: JPA가 관리할 객체@Id
: 데이터베이스 PK와 매핑
package hellojpa;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity // JPA가 관리할 객체임을 JPA가 인식할 수 있도록
public class Member {
@Id // PK
private long id;
private String name;
// getter, setter
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;
}
}
CRUD 실습
-
회원 등록
public class JpaMain { public static void main(String[] args) { // 1. EntityManageFactory 생성 EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); // 2. EntityManagerFactory 이용하여 EntityManager 생성 EntityManager em = emf.createEntityManager(); // 3. Transaction 생성하여 begin EntityTransaction tx = em.getTransaction(); tx.begin(); // 4. 실행할 코드 // 멤버를 저장해보자 Member member = new Member(); member.setId(1L); member.setName("HelloA"); em.persist(member); // 5. 트랜잭션 commit tx.commit(); // 6. 트랜잭션 끝나면 EntityManager close em.close(); // 7. 모두 종료되면 EntityManagerFactory도 close emf.close(); } }
-
실행 결과 -> JPA가 매핑 정보를 보고 추가해줌
Hibernate: /* insert hellojpa.Member */ insert into Member (name, id) values (?, ?)
-
💡 정석 코드
- commit 후(try) 실패하면 rollback(catch). 트랜잭션 종료되면 EntityManager close(finally)
- SpringBoot에서 해줌
public class JpaMain { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); EntityManager em = emf.createEntityManager(); // 트랜잭션 단위 꼭 !!!! EntityTransaction tx = em.getTransaction(); tx.begin(); try { Member member = new Member(); member.setId(2L); member.setName("HelloB"); em.persist(member); tx.commit(); } catch (Exception e) { tx.rollback(); } finally { em.close(); } emf.close(); } }
-
회원 단 건 조회
public class JpaMain { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); EntityManager em = emf.createEntityManager(); // 트랜잭션 단위 꼭 !!!! EntityTransaction tx = em.getTransaction(); tx.begin(); try { Member findMember = em.find(Member.class, 1L); System.out.println("findMember.id = " + findMember.getId()); System.out.println("findMember.name = " + findMember.getName()); tx.commit(); } catch (Exception e) { tx.rollback(); } finally { em.close(); } emf.close(); } }
-
회원 수정 : 수정할 항목을 찾고, setter로 변경
public class JpaMain { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); EntityManager em = emf.createEntityManager(); // 트랜잭션 단위 꼭 !!!! EntityTransaction tx = em.getTransaction(); tx.begin(); try { // 수정할 멤버를 찾음 Member findMember = em.find(Member.class, 1L); // setter로 바꿔줌 (persistence 안해도 저장됨!) findMember.setName("HelloJPA"); tx.commit(); } catch (Exception e) { tx.rollback(); } finally { em.close(); } emf.close(); } }
-
회원 삭제 : 삭제할 항목 찾고 remove
public class JpaMain { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); EntityManager em = emf.createEntityManager(); // 트랜잭션 단위 꼭 !!!! EntityTransaction tx = em.getTransaction(); tx.begin(); try { // 삭제할 멤버를 찾음 Member findMember = em.find(Member.class, 1L); // 그리고 remove로 삭제 em.remove(findMember); tx.commit(); } catch (Exception e) { tx.rollback(); } finally { em.close(); } emf.close(); } }
💡 DB의 테이블명 <> 객체 이름인 경우? DB 컬럼명 <> 객체 속성명인 경우? : Annotation에 추가
@Entity @Table(name = "USER") // DB의 Member 테이블이 아닌 USER 테이블에 추가 public class Member { @Id // PK private long id; @Column(name = "username") // 만약 DB column은 name이 아니고 username이면 private String name; // getter, setter 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; } }
🚨 주의
- EntityManagerFactory : 하나만 생성해서 애플리케이션 전체에서 공유
- EntityManager : 쓰레드간 공유 X
- JPA의 모든 데이터 변경은 트랜잭션 안에서 실행
3. JPQL
JPQL
- JPA를 사용하면 엔티티 객체를 중심으로 개발
- 문제점 : 검색쿼리 (단 건 이상. 최대한 필터링 필요)
- 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색
- 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능
→ 필요한 데이터만 불러오려면 결국 검색 조건이 포함된 SQL이 필요
- 문제점 : 검색쿼리 (단 건 이상. 최대한 필터링 필요)
- JPQL : SQL을 추상화한 객체 지향 쿼리 언어로 JPA가 제공
- SQL과 문법 유사 : SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 지원
- SQL은 데이터베이스 테이블을 대상으로 쿼리 → 방언을 바꾸어도 JPQL 쿼리를 바꿀 필요 X
- 테이블이 아닌 객체를 대상으로 검색하는 객체 지향 쿼리
- SQL을 추상화 → 특정 데이터베이스 SQL에 의존X
JPQL 예제
-
예제 1. JPQL로 전체 회원 검색
public class JpaMain { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); tx.begin(); try { List<Member> result = em.createQuery("select m from Member as m", Member.class) // Member 객체를 대상으로 쿼리 (쿼리의 대상이 테이블이 아님) .getResultList(); for (Member member : result) { System.out.println("member.name = " + member.getName()); } tx.commit(); } catch (Exception e) { tx.rollback(); } finally { em.close(); } emf.close(); } }
-
결과 : JPQL은 m이라는 Member 엔티티로 조회
Hibernate: /* select m from Member as m */ select member0_.id as id1_0_, member0_.name as name2_0_ from Member member0_ member.name = HelloJPA member.name = HelloB
-
-
예제 2.
public class JpaMain { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); EntityManager em = emf.createEntityManager(); // 트랜잭션 단위 꼭 !!!! EntityTransaction tx = em.getTransaction(); tx.begin(); try { List<Member> result = em.createQuery("select m from Member as m", Member.class) .setFirstResult(1) .setMaxResults(8) .getResultList(); for (Member member : result) { System.out.println("member.name = " + member.getName()); } tx.commit(); } catch (Exception e) { tx.rollback(); } finally { em.close(); } emf.close(); } }
-
결과
Hibernate: /* select m from Member as m */ select member0_.id as id1_0_, member0_.name as name2_0_ from Member member0_ limit ? offset ? member.name = HelloB
- limit, offset 자동으로 쿼리
- oracle로 방언 바꾸면 oracle로 쿼리
-
-
JPQL에 대해 자세한 내용은 이후에 객체지향 쿼리에서 학습
Leave a comment