JPA(Java Persistence API)는 자바에서 제공하는 관계형 데이터베이스 처리용 API다. JPA는 라이브러리가 아니라 인터페이스다. 즉, JPA 자체가 특정 기능을 하지 못한다. JPA는 인터페이스로 단지 RDBMS(관계형 데이터베이스)를 어떻게 사용해야 하는지(Java ORM 기술)에 대한 명세(프로그램의 구조와 기능을 상세하게 기술한 문서)로 볼 수 있다. 이런 JPA인터페이스를 상속받은 구현체들은 Hibernate, EclipseLink, Apache OpenJPA 등이 있다. 이 구현체들을 ORM 프레임워크라고 한다.
자바 JPA는 javax.persistence 패키지로 제공된다.
JPA는 ORM이고, ORM은 Persistence Framework의 한 종류다.
DB마다 다른 sql문을 몰라도 된다 ( 특정 DB에 종속되지 않음 ) :
데이터베이스마다 sql문법이 조금씩 다르다. 그럼 사용하는 데이터베이스가 달라질 때마다 그에 맞춰서 sql문법을 공부해야 한다. 예를 들어 mysql에서는 varchar, oracle에서는 varchar2라는 타입을 문자열 타입으로 쓴다. 자바에서 mysql을 이용하려고 하는데 JDBC를 이용해 쿼리문을 작성할 때 varchar2타입을 사용하면 문제가 생긴다. 하지만 JPA는 DB별 방언 처리기(Hibernate,EclipseLink 등등) 설정만 해주면 알아서 sql문법에 맞게 sql쿼리문을 생성해준다. 이를 통해 sql문법에 대한 부담을 줄일 수 있다.
반복적인 SQL문 작성이 필요없다 ( 생산성 향상 ) :
JPA는 간단한 sql 쿼리문에 대한 메서드를 제공한다. 간단한 예를 들어본다.
Member oneFindMember = em.find(Member.class, "id1");
위 코드에서 find메서드가 찾고자 하는 엔티티에서 primary key를 이용해 검색하는 기능을 제공한다. 즉, 자주 쓰이는 select쿼리문을 미리 메서드로 만들어 놓은 것이다. JDBC처럼 직접 select쿼리문을 작성할 필요가 없다.( 복잡한 sql문은 JPQL을 이용해 따로 생성해야됨 )
전체적인 흐름은 다음과 같다. DB의 테이블과 같은 구조를 지닌 Entity라는 객체를 만든다. 이 Entity와 DB의 테이블을 매핑시키면 직접 DB테이블을 건들지 않고 Entity를 활용해 DB를 조작하는 것과 같은 효과를 낼 수 있다.
JPA는 미리 sql문을 메서드로 만들어 편하게 사용하지만, 모든 상황에 관한 메서드를 만들어놓지는 않는다. 복잡한 sql문은 직접 sql문을 작성할 수 밖에 없다. JPA에서 사용하는 객체지향 쿼리 언어를 JPQL이라고 하고, JPA에서 JPQL을 이용해 SQL문을 작성한다. JPQL실행을 위해서 Query객체가 필요하고, EntityManager의 createQuery메서드를 이용해 Query객체를 만들 수 있다. 또한 이 문장은 Entity를 이용해 작성하지 DB에 있는 테이블을 이용하는 식이 아니다. 즉, select문을 작성한다고 했을 때 문장이 'select m from Member m'라고 한다면 여기서
JPQL을 실행하기 위한 쿼리 객체
TypeQuery : 반환할 타입을 명확하게 지정할 수 없을 때 사용
Query : 반환할 타입을 명확하게 지정할 수 있을 때 사용
@NamedQuery : Entity에서 미리 작성
사전에 미리 파싱되므로 실행 속도가 좋다
Application 로딩 시에 미리 문법체크가 이뤄진다
사전에 DB에 접근했던 SQL문장들은 캐싱을 해놓는다
@NamedQuery(query = "쿼리문", name = "호출할때이름")
쿼리 메서드
createQuery() : JPQL문을 이용한다.
// Member타입을 리턴해 List형식으로 저장하는 JPQL쿼리문 작성 예시
List<Member> 객체명 = EntityManager객체명.createQuery("JPQL쿼리문", Member.class).getResultList();
createNamedQuery() : @NamedQuery를 이용한다.
// Employee엔티티에 작성된 NamedQuery가 있고
// @NamedQuery(query = "select e from Employee e where e.eid = :eid", name = "Employee.findbyEmpno") 내용이 다음과 같을 때
// Employee타입으로 한개 결과값을 받아 e1객체에 넣기
Employee e1 = (Employee)EntityManager객체명.createNamedQuery("Employee.findbyEmpno").setParameter("eid", 7369).getSingleResult();
문법
JPQL에는 엔티티명을 사용해야 한다. 만약 엔티티명이 따로 지정되지 않았다면 테이블명을 사용한다.
별칭(Alias)이 필수다.( 예를 들어 테이블 전체내용이 필요해 sql문의 all('*')을 쓰고 싶다면 all대신 테이블 별칭을 사용한다. ex)select m from Member m )
':' : 이름 기준 파라미터
'?' : 위치 기준 파라미터
조인 : foreign key설정된 클래스의 원래 엔티티명을 작성하는 것이 아니라, '별칭.클래스에서참조된멤버변수명 별칭' 이런식으로 작성해야 한다.
// sql문법에서는 "select * from Member m left join Team t on m.teamId=t.teamId where m.teamId=1"로 작성할 내용을 다음과 같이 작성한다.
// ( Member테이블이 Team테이블의 teamId를 foregin key로 참조하고 있음 )
List<Member> datas2 = em.createQuery("select m from Member m left join m.teamId t on m.teamId=t.teamId where m.teamId=1", Member.class).getResultList();
페이징API
EntityManagerFactory emf = Persistence.createEntityManagerFactory("dbinfo");
EntityManager em = emf.createEntityManager();
EntityTransaction tx =em.getTransaction();
tx.begin();
트랜잭션에 대한 내용을 작성한 뒤 EntityManager의 메서드들을 사용하는데 용도에 대해 알아본다.
Team t1 = Team.builder()
.teamName("농구팀")
.members(new ArrayList<>()) // 빌더패턴에서는 이렇게 초기화를 안하면 그냥 null로 인식해버림
.build();
em.persist(t1);
em.flush();
em.clear();
EntityTransaction의 메서드로 DB에 영구저장하는 단계다.
tx.commit();
더 이상 사용하지 않는 EntityManger객체와 EntityManagerFactory객체를 반환한다.
em.close();
emf.close();
fetch설정을 통해 데이터가 필요하지 않은 부분에 대해서는 데이터를 불러오지 않다가, 데이터가 직접적으로 필요할 때 뒤늦게 데이터를 불러오게 할 수 있다. 이를 통해 필요없는 데이터는 불러오지 않게 효율적인 설정이 가능하다.
1:N 관계를 의미한다
N:1 관계를 의미한다