이 글은 김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편 강의를 듣고 정리한 글입니다.
→ 객체를 자바 컬렉션에 저장하듯이 DB에 저장할 수 있는 방법 : JPA(Java Persistence API)
자바 진영의 ORM 기술 표준, 패러다임 불일치 해결
ORM(Object-relational mapping 객체 관계 매핑)
객체는 객체대로 설계, 관계형 데이터베이스는 관계형 데이터베이스대로 설계
ORM 프레임워크가 중간에서 매핑
→ ORM은 객체와 RDB 두 기둥 위에 있는 기술
저장: jpa.persist(member)
조회: Member member = jpa.find(memberId)
수정: member.setName("변경할 이름")
삭제: jpa.remove(member)
기존 -> 필드 변경시 모든 SQL 수정
JPA: 필드만 추가하면 됨, SQL은 JPA가 처리
상속
연관관계, 객체 그래프 탐색 → 신뢰할 수 있는 엔티티, 계층
비교 → 동일 트랜잭션에서 조회한 엔티티는 같음을 보장
- 1차 캐시와 동일성(identity) 보장
: 같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상- 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
ex) 트랜잭션을 커밋할 때까지 INSERT SQL을 모았다가,
JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송- 지연 로딩 (Lazy Loading)
- 지연 로딩: 객체가 실제 사용될 때 로딩
- 즉시 로딩: JOIN SQL로 한번에 연관된 객체까지 미리 조회
IDE: IntelliJ
Build tool: Maven
DB: H2
Java Version: 11
프로젝트를 생성한 후, Maven 소스를 다운로드해줍니다.
<persistence-unit name="hello"> <!-- DB -->
<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>
프로젝트 폴더 - src - main - java - hellopjpa - JpaMain.class
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory enf = Persistence.createEntityManagerFactory("hello");
// pom.xml의 name 값이 매개변수로 들어가, 설정 파일을 읽어와 만들어진다.
EntityManager entityManager = enf.createEntityManager();
//code
entityManager.close();
enf.close();
}
}
EntityManagerFactory
는 pom.xml의 name 값이 매개변수로 들어가, 설정 파일을 읽어와 만들어진다. (pom.xml의 <persistence-unit name="hello">
의 name 값이 Persistence.createEntityManagerFactory()
의 매개변수로 들어간다.)EntityManagerFactory
는 애플리케이션이 로딩될 때 한 번만 만들어 전체에서 공유한다.EntityManager
는 Transaction 단위마다 만들어야 한다.EntityManager
는 Thread 간에 공유하면 절대로 안된다. (사용하고 버려야 한다.)import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
// @Table(name = "USER") // Member 테이블이 아닌 USER 테이블에 저장
public class Member {
@Id
private Long id;
// @Column(name = "username") // username 속성에 저장
private String name;
public Member() {} // 기본 생성자
// 가급적이면 setter의 사용을 최소한하고, 생성자에서 값을 세팅하는 것이 좋다. (유지보수성)
public Member(Long id, String name) {
this.id = id;
this.name = 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;
}
}
JPA의 @Entity에는 기본 생성자가 필수로 있어야 한다!
내부적으로 Reflection을 통해 객체를 생성하기 때문이다.
Java Reflection은 구체적인 클래스타입을 알지 못할 때, 해당 클래스의 메소드, 멤버 변수 등을 알게 해주는 기능이다. 하지만, 생성자의 인자 정보들을 알 수 없기 때문에, 기본 생성자가 없다면 Java Reflection 객체를 생성할 수 없다. 따라서,@Entity
는 반드시 파라미터가 없는 생성자가 public 혹은 protected로 있어야 한다.(하이버네이트같은 구현체들은 라이브러리 등을 통해 이런 문제를 회피할 수 있어 당장의 코드가 실행될 수는 있겠지만, JPA 스펙에서 기본 생성자를 필수로 강제했기 때문에 이런 부분은 따르는 것이 좋다고 한다.)
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory enf = Persistence.createEntityManagerFactory("hello");
// pom.xml의 name 값이 매개변수로 들어감
EntityManager em = enf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
// ** code
// 생성
Member member = new Member();
member.setId(1L);
member.setName("HelloA");
Member member = new Member();
member.setId(2L);
member.setName("HelloB");
// 조회 & 출력
Member findMember = em.find(Member.class, 2L);
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.name = " + findMember.getName());
// 수정 & 출력
findMember.setName("HelloJPA");
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.name = " + findMember.getName());
// **
// 영속화
em.persist(member);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
enf.close();
}
}
commit()
을 통해 반영, 문제가 생기면 rollback()
EntityManager
가 데이터베이스 커넥션과 같이 동작하기 때문에 close()
해주는 것이 중요하다.EntityManager
를 Java의 Collection처럼 이해하면 됨. // code
List<Member> result = em.createQuery("select m from Member as m", Member.class)
.setFirstResult(5)
.setMaxResults(8)
.getResultList();
for (Member member : result) {
System.out.println("member = " + member.getName());
}
.setFirstResult(5).setMaxResults(8)
를 통해 페이징 가능(persistence.xml
의 hibernate.dialect
의 값을 수정)
H2 DB (value="org.hibernate.dialect.H2Dialect"
)
Oracle DB (value="org.hibernate.dialect.Oracle12cDialect"
)
→ 쿼리가 바뀐 것을 알 수 있다
⇒ 즉, JPQL은 객체 지향 SQL이며, 특정 데이터베이스 SQL에 의존 하지 않는다.