[Spring] JPA 정리
개발을 배우면서 처음 Java에서 DB 와 관련된 작업을 할 때는 JDBC API를 배웠습니다. JDBC API는 항상 모든 로직 안에서 Connection 객체를 받아오고, SQL 문을 작성하고, 끝나면 close 시켜야 했습니다. 그리고 이러한 중복되고 불필요한 코드를 작성하는 것을 보완해주는 MyBatis, Spring JdbcTemplate 를 배워 사용하였습니다.
최근에는 ORM 기술을 통해 SQL 작성없이 객체를 DB에 직접 저장/관리를 할 수 있게 도와주는 JPA 를 사용하여 DB 작업을 대부분 사용하고 있습니다.
이번 글에서는 이 JPA에 대해서 정리를 해보고자 합니다.
0. ORM(Ojbect-Relational Mapping) 이란?
- JPA에 대해서 알기전에 기본이 되는 ORM에 대해서 알아야 합니다.
- ORM : 객체는 객체대로 설계하고, RDB(관계형 데이터베이스)는 RDB 대로 설계한다.
- 프레임워크로 객체와 RDB 를 중간에서 서로 매핑해주는 역할을 담당한다.
- 대중적인 언어에는 대부분 ORM 기술이 존재한다.
- ORM vs SQL Mapper(Mybatis, Spring JdbcTemplate)
- ORM
- DB 테이블을 Java 객체로 Mapping(매핑)함으로써 객체 간의 관계를 바탕으로 SQL문을 자동으로 생성(객체를 통해 간접적으로 디비 데이터를 조작)
- DB 데이터 ← Mapping → Object 필드
- SQL 쿼리가 아니라 메서드로 데이터를 조작
- 객체간 관계를 바탕으로 SQL 을 자동으로 생성 - SQL Mapper
- SQL 을 직접 명시(SQL 문으로 직접 디비를 조작)
- SQL ← Mapping → Object 필드 - ORM은 RDB의 관계를 Object 에 반영하는 것이 목적이라면, SQL Mapper 는 단순히 필드를 매핑시키는 것이 목적이라는 차이점이 있습니다.
- ORM
- 과거 EJB 시절에 만들어놓은 ORM 개념이 있었으나, 사용하기도 까다롭고 성능상 좋지 않아 사용되지 않았습니다.
1. JPA(Java-Persistence API) 란?
- Java 진영에서의 ORM 기술 표준으로, Java Application 에서 RDB를 사용하는 방식을 정의한 인터페이스의 모음이다.
- 즉, 실제로 동작하는 것이 아닌 인터페이스를 구현하여 사용해야 한다.(대표적으로 Hibernate)
- JPA 2.1 표준 명세를 구현한 3가지 구현체가 있다.
- Hibernate
- EclipseLink
- DataNucleus
- SQL 작성없이 객체를 데이터베이스에 직접 저장할 수 있게 도와주는 기술로 Application 과 JDBC 사이에서 동작한다.
2. SQL을 직접 다룰 때의 문제점(ex: MyBatis)
- 반복적인 코드의 작성
- 모든 DB 작업에 대한 SQL 문을 작성해야 한다.
- 반복적인 CURD SQL 작성과 객체를 SQL 에 매핑하는 코드를 작성하는데 시간이 오래 걸린다. - SQL 의존적 개발
- 테이블에 Column 이 추가된다면 모든 DAO의 SQL 문 변경이 필요하다.
- 만약 동작하지 않으면 Java 에서의 로직과 SQL 문 둘 다 확인이 필요하다. - 패러다임의 불일치
- Java 는 객체지향 언어이지만, RDB(관계형 데이터베이스)는 객체지향이 다루는 개념이 존재하지 않고 서로 지향하는 목적이 다르기 때문에 패러다임의 불일치가 발생한다.
- 이러한 서로 다른 목적 때문에 개발자가 중간에서 문제를 해결하기 위해 직접 코드를 작성해서 매핑해야 한다.
3. JPA 장단점
- 장점
- 생산성
- JPA 에 객체를 전달만 하면 되므로 SQL 을 작성하고 JDBC API 를 사용하는 반복적인 일을 JPA 가 대신 처리해주어 생산성이 향상된다.
- DDL 도 JPA가 자동으로 생성해주기 때문에 DB 설계 중심을 객체 설계 중심으로 변경할 수 있다. - 유지보수
- SQL 을 직접 다룰 때는 필드를 하나를 추가/삭제하여도 관련된 SQL 과 JDBC 코드를 전부 수정해야 했지만, JPA 는 이를 대신 처리해주어 유지보수가 줄어든다. - 패러다임의 불일치 해결
- JPA는 연관된 객체를 사용하는 시점에 SQL을 전달할 수 있고, 같은 트랜잭션 내에서 조회할 때 동일성도 보장하기 때문에 다양한 패러다임의 불일치를 해결한다. - 성능
- 어플리케이션과 데이터베이스 사이에서 성능 최적화 기능을 제공한다.
- 같은 트랜잭션 안에서는 같은 엔티티를 반환하기 때문에 통신 횟수를 줄일 수 있다. - 데이터 접근 추상화와 벤더 독립성
- RDB는 같은 기능이라도 벤더마다 사용법이 다르기 때문에 처음 선택한 DB에 종속되고 변경이 어렵다.
- JPA는 어플리케이션과 데이터베이스 사이에서 추상화된 데이터 접근을 제공하여 종속되지 않도록 도와준다.
- 단점
- 학습 곡선이 높다.
- JPA 를 사용하려면 객체와 관계형 데이터베이스를 어떻게 매핑해야 하는지 학습한 후에 JPA 의 핵심 개념들을 이해해야 한다.
- JPA 의 핵심 개념인 영속성 컨텍스트에 대한 이해가 부족하면 SQL 을 직접 사용해서 개발하는 것보다 못한 상황이 발생할 수 있다. - 속도 저하 가능성이 있다.
- 프로젝트의 규모가 크고 복잡하여 설계가 잘못된 경우, 속도 저하 및 일관성을 무너뜨릴 수 있다.
- 복잡하고 무거운 Query 는 속도를 위해 별도의 튜닝이 필요하기 때문에 결국 SQL 문을 써야 할 수 있다.
4. 동작 과정
- JPA 는 어플리케이션과 JDBC API 사이에서 동작한다.
- 개발자가 JPA 를 사용하면, JPA 내부에서는 JDBC API 를 사용하여 SQL 을 생성하여 DB와 통신한다.
- JPA 에서의 CRUD
- 간단하게 JPA 에서의 CRUD 메소드는 아래와 같다.
jpaEm.persist(member); // 저장
jpaEm.find(memberId); // 조회
jpaEm.remove(member); // 삭제
// 수정(조회 이후 해당 객체에 변경하면 DB에도 변경된다.)
// 영속성 컨텍스트
Member member = jpaEm.find(memberId);
member.setName("변경할 이름");
- JPA 에서는 수정 메소드를 제공하지 않는다.
- 하지만 수정은 필요한 동작이기 때문에 JPA 에서는 데이터 수정 시 , 매핑된 객체(테이블 데이터)를 조회해서 값을 변경 후 커밋하면 DB 서버에서 UPDATE 문으로 바꾸어 UPDATE 를 실행한다.
JPA에서의 수정은 Java 컬렉션에 수정하는 것처럼 컬렉션(ex: List)에서 조회해온 데이터를 .setName() 으로 수정한 다음 다시 컬렉션에 .add() 하지 않는 것처럼 생각하면 된다.
4-1. 저장(persist = insert)
- Member 를 저장할 때, JPA는 아래와 같이 동작한다.
- Member 객체를 JPA 에 넘긴다.
- JPA는 객체의 엔티티(Entity)를 분석한다.
- JPA에서 INSERT 쿼리(SQL)를 생성한다.
- JPA가 JDBC API 를 사용하여 생성한 SQL를 DB에 요청한다.
4-2. 조회(find = select)
- Member 를 조회할 때, JPA는 아래와 같이 동작한다.
- Member 객체 필드에서 PK(Primary Key) 값을 JPA 에 넘긴다.
- JPA에서 Member 엔티티(Entity)의 매핑 정보를 바탕으로 쿼리를 생성한다.
- JPA가 JDBC API 를 사용하여 생성한 SQL를 DB에 요청한다.
- JPA가 JDBC API 를 통해 DB로 부터 결과를 받아온다.
- JPA는 결과(ResultSet)를 객체(Member)의 Entity에 맞게 모두 매핑한다.
- 쿼리를 JPA가 만들어 주기 때문에 Object와 RDB 간의 패러다임 불일치를 해결할 수 있다.
5. JPA 어노테이션 종류
- JPA 는 어노테이션을 분석하여 어떤 객체가 어떤 테이블과 관계가 있는지를 알아낸다.
어노테이션 | 설명 |
@Entity | 데이터베이스의 테이블과 일대일로 매칭되는 객체 단위이며, 클래스를 테이블과 매핑한다고 JPA 에게 알린다. |
@Table | @Entity 선언된 클래스에 매핑할 테이블정보(테이블이름)을 알려준다. 이 어노테이션이 생략되면 클래스이름을 테이블이름으로 매핑한다. |
@Column | 데이터베이스의 테이블에 있는 컬럼에 필드(변수)를 매핑한다. 별다른 옵션을 설정하지 않는다면 기본적으로는 생략 가능하다. |
@Id | @Entity 선언된 클래스의 필드를 테이블의 기본키(Primary Key)에 매핑한다. |
@GeneratedValue | 새로운 레코드가 생성될 때마다 마지막 PK 값에서 자동으로 +1 을 해줘야 하는 auto increment 컬럼인 것을 알려준다. |
@EmbeddedId | 복합키로서 정의된 값을 정의하고자 할 때 사용한다. |
@Enumerated | Java 의 Enum 형태로 되어 있는 미리 정의되어 있는 코드 값이나 구분값을 데이터 타입으로 사용할 때 사용한다. |
@Transient | Entity 객체에 속성으로 지정되어 있지만, 데이터베이스 에서는 필요없는 속성일 때 사용한다. 해당 속성을 데이터베이스에서 이용하지 않겠다라는 정의이다. Entity 객체에 임시로 값을 담는 용도로 사용한다. |
6. 번외
- 아래 사진은 위의 내용을 요약하며 JPA, Hibernate, Spring Data JPA 의 전반적인 개념을 그림으로 표현한 것입니다.
- JPA와 Spring Data JPA는 똑같이 JPA가 들어가서 처음 접할 때 JPA라고 말하면 Spring Data JPA라고 생각할 수 있는데, 다른 개념이기 때문에 헷갈리면 안 된다.
- JPA는 ORM을 위한 자바 EE 표준이며, Spring Data JPA 는 JPA를 쉽게 사용하기 위해 스프링에서 제공하는 프레임워크이다.
- 추상화 정도는 Spring Data JPA > Hibernate > JPA 이다.
- Hibernate를 쓰는 것과 Spring Data JPA를 쓰는 것 사이에는 큰 차이가 없지만 아래와 같은 이유로 Spring Data JPA를 사용하는 것이 더 좋을 수 있다.
- 구현체 교체의 용이성
- 저장소 교체의 용이성
참고
- https://gmlwjd9405.github.io/2019/08/04/what-is-jpa.html
- https://suhwan.dev/2019/02/24/jpa-vs-hibernate-vs-spring-data-jpa/