JPA (Java Persistance API)
과거에는 객체를 DB에 저장하기 위하여 Jdbc를 일일히 입력해야 했다. 그 후에 JdpcTemplate이 나오면서 보다 번거로움은 줄었지만 sql은 직접 개발자가 String으로 입력해주어야 하고, 동적쿼리 문제를 해결하는데 어려움을 겪었다. 나 또한 올해 말에 진행했던 앱 프로젝트에서 DB연동을 위해 sql을 일일히 함수에 추가해주고, 계획에 변경이 생길 때마다 관련 sql문과 함수들을 찾아 직접 변경해주며 번거로움을 느낀 경험이 있었다.
하지만 번거롭더라도 관계형 데이터베이스(RDB)를 다루기 위해서는 sql은 필수적이기 때문에 생략할 수 없다. 그래서 CRUD 등의 기능을 구현하기 위해서는 객체를 생성하고 나서 sql문에 의존적인 개발이 진행될 수 밖에 없었고, 객체를 SQL로 변환하여 DB에 연동하는데 개발자가 객체지향 프로그래밍(OOP)이 가지는 추상화, 캡슐화, 상속, 다형성 등의 장점을 살리며 개발을 진행하는 과정에서 자바 컬렉션과 괴리(패러다임의 불일치)가 존재하여 다양한 번거로움과 어려움이 존재했다.
그래서 SQL중심적인 개발에서 객체와 RDB 사이의 패러다임 불일치(상속, 연관관계 등)를 해결하고, 쿼리문 작성을 보다 편리하게 해주기 위해 나온 것이 ORM기술이며, 자바의 ORM 표준이 바로 JPA다. JPA의 등장으로 개발자들은 쿼리문 작성의 압박에서 벗어나 보다 체계적으로 객체와 DB를 연결하고 유지보수 할 수 있게 되었다.
//기존 db조회
String memberId = "100";
Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);
member1 == member2; //호출시마다 새로운 객체를 만들어서 다르다.
class MemberDAO {
public Member getMember(String memberId) {
String sql = "SELECT * FROM MEMBER WHERE MEMBER_ID = ?";
...
//JDBC API, SQL 실행
return new Member(...);
}
}
앞서 말했듯이 JPA는 자바 ORM(java 객체-관계형DB 매핑)의 표준이다. 이게 무슨 말이냐면, JPA를 이용하면 객체와 관계형DB를 각각 설계한 뒤 JPA라는 프레임워크(정확히 말하면 JPA는 인터페이스이고, 구현체인 Hibernate가 프레임워크)가 중간에서 둘을 매핑해준다는 것이다.
JPA는 객체를 분석한뒤 적절한 쿼리문을 생성하고 Jdbc api를 사용하여 DB에 송/수신한다. 즉 개발자가 객체지향 언어와 DB의 연동을 위해 했던 번거로운 작업들을 대신 해준다. 기존에는 필드 변경시 모든 SQL을 수정했다면, JPA를 사용하면 그냥 필드만 변경해주면 된다!
사용 예시
- 저장 : 개발자는 jpa.persist만 해주면 상속관계에 맞추어서 jpa가 insert해준다
- 조회 : 개발자는 jpa.find를 이용하면 jpa가 상속관계에 맞게 부모클래스까지 조회해준다
- 수정 : setter를 통해서 바로 데이터베이스까지 수정이 이루어진다. (
- 삭제 : jpa.remove를 호출하면 삭제가 이루어진다
마치 자바 컬렉션을 호출하듯이 호출하면 jpa가 알아서 DB와의 통신작업을 진행해준다.
//jpa를 이용한 db조회
String memberId = "100";
Member member1 = jpa.find(Member.class, memberId); //sql
Member member2 = jpa.find(Member.class, memberId); //동일 트랜잭션에서 호출시 재사용(caching)
member1 == member2; //같다.
동일한 트랜잭션에서 조회한 엔티티는 같다 (트랜잭션:작업의 단위. 예를들어 고객 한명의 조회)
그밖에도 jpa를 사용하면 성능상에도 약간의 이득을 얻을 수도 있다.
1. 1차 캐시와 동일성 보장 : 같은 트랜잭션 안에서는 같은 엔티티를 반환하여 조회 성능 향상
2. 트랜잭션을 지원하는 쓰기지연 : 같은 트랜잭션 내의 쓰기sql문을 한번에 전송
transaction.begin();
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
transaction.commit(); //메모리에 저장된 A,B,C의 쿼리를 한번에 전송
3. 지연 로딩과 즉시 로딩 :
지연 로딩 : 객체가 실제 사용될 때 로딩 / 즉시 로딩 : JOIN SQL로 한번에 연관된 객체까지 미리 조회
> member객체가 team에 관한 필요가 있다면 그때 추가적인 데이터쿼리를 날려서 조회
> 미리 JOIN하여 조회하도록 설정. sql를 한번만 보내서 쿼리가 네트워크를 여러번 통하지 않게 만듬 (위 예제에서는 Member를 조회하면 TEAM을 JOIN하여 조회하도록 설정)
필요에 따라서 지연로딩과 즉시로딩을 옵션을 통해 간단하게 조작할 수 있다.
JPA, Spring Data JPA, Hibernate, JDBC
먼저 JPA는 자바 어플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스이다. JPA는 말 그대로 인터페이스이기 때문에 기능의 명세만을 정의할 뿐, 동작을 위해서는 별도의 구현체를 이용해야 한다. 마치 백엔드 API가 클라이언트가 어떻게 서버를 사용해야 하는지를 정의한 것처럼, JPA 역시 자바 어플리케이션에서 RDB를 어떻게 사용하면 좋을지를 정의하는 여러 방법 중 하나인 것이다. 또한 JPA는 Hibernate를 통해 JDBC API를 사용하며, 추상화를 통해 JDBC를 이용하면서도 코드의 복잡성을 최소화한다.
(참고로 JDBC란, 자바에서 데이터베이스에 접근하여 데이터를 조작하기 위한 API이다.)
따라서 JPA 인터페이스를 사용하기 위해서는 구현체가 필요한데, 대표적으로는 EclipseLink, OpenJPA, Hibernate와 같이 다양한 종류의 구현체가 존재한다. 우리가 알고 가장 많이 사용하는 Hibernate 이외에도 여러가지 구현체가 존재하며, 해당 구현체들을 이용하여 JPA를 사용할 수 있다. Hibernate는 내부적으로 JDBC를 사용하여 db와의 통신을 수행하게 된다.
javax.persistence 패키지를 살펴보면, (해당 패키지는 JPA 인터페이스들이 존재하는 패키지이다. JPA가 java persistence api의 약자잖아) 해당 패키지의 대부분은 interface, enum, annotation과 exception들로 이루어져 있음을 알 수 있다. (https://docs.oracle.com/javaee/7/api/javax/persistence/package-summary.html - EntityManager도 객체가 아니라 인터페이스이며, 우리가 EntityManager 객체를 사용하기 위해서는 Persistence라는 클래스에서 static메소드(createEntityManagerFactory)를 사용하여 구현체를 얻어오는 것을 볼 수 있다.)
마지막으로 Spring Data JPA는 Spring에서 제공하는 모듈 중 하나로, 개발자가 JPA를 더욱 편하게 사용할 수 있도록 돕는다. (JPA가 아니라 JDBC, Redis등 다른 기술들에 대한 모듈도 당연히 존재한다) Repository 인터페이스를 사용하여 명명규칙에 맞게 추상메소드를 입력하면 Spring이 알아서 동적 프록시 기술을 이용하여 해당 메소드 이름에 적합한 jpql쿼리를 날리는 구현체(EntityManager의 구현체)를 사용하여 빈으로 등록하는데, 이러한 기능이 Spring Data JPA의 기능이다. Spring Data JPA는 JPA를 추상화시킨 것으로, 내부적으로 JPA의 EntityMager를 사용하여 기능을 구현함을 알 수 있다.
JPA와 Hibernate, Spring Data JPA의 관계를 그림으로 나타내면 아래와 같다. 개발자는 JPA를 처음 배웠을때 EntityManager를 직접 사용했던 것처럼 직접 JPA의 추상메소드를 이용할 수도 있고, Spring Data JPA를 사용하여 JPA를 사용할 수도 있다.
- Spring Data JPA는 동적 프록시 기술을 사용하여 Repository 인터페이스의 구현 클래스를 생성해주고, 인터페이스 내의 추상 메소드를 분석하여 jpql을 작성해주는 쿼리 메소드 기능을 제공한다(Spring Data JPA가 메소드 이름을 분석하고 jqpl을 작성하여 JPA로 보내주면 JPA가 sql로 번역하여 RDB로 요청한다). 만약 쿼리 메소드 기능을 사용하기 싫으면 @Query 어노테이션을 사용하면 된다.
'[ Backend ] > Spring DB, JPA' 카테고리의 다른 글
[JPA] 기본 키 매핑-SEQUENCE 전략, 에러 해결 (0) | 2022.01.02 |
---|---|
[JPA] 엔티티 매핑 (0) | 2021.12.31 |
[JPA] JPA 구조, 특성 (0) | 2021.12.29 |
[JPA] JPQL 예시 (0) | 2021.12.28 |
[JPA] 설정, 기본 실습 (0) | 2021.12.28 |