[스프링 DB 2편] #5. 데이터 접근 기술 - JPA #577
Develop-KIM
started this conversation in
동환
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
JPA 시작
스프링과 JPA는 자바 엔터프라이즈(기업) 시장의 주력 기술이다.
스프링이 DI 컨테이너를 포함한 애플리케이션 전반의 다양한 기능을 제공한다면 JPA는 ORM 데이터 접근 기술을 제공한다.
JdbcTemplate이나 MyBatis 같은 SQL 매퍼 기술은 SQL을 개발자가 직접 작성해야 하지만, JPA를 사용하면 SQL도 JPA가 대신 작성하고 처리해준다.
실무에서는 JPA를 더욱 편리하게 사용하기 위해 스프링 데이터 JPA와 Querydsl이라는 기술을 함께 사용한다.
스프링 데이터 JPA, Querydsl은 JPA를 편리하게 사용하도록 도와주는 도구라 생각하면 된다.
ORM 개념1 - SQL 중심적인 개발의 문제점
객체와 관계형 데이트베이스의 차이
1. 상속

2. 연관관계
문제점
객체를 테이블에 맞춰 모델링해야 한다.
테이블에 맞춘 객체 저장해야 한다.
계층형 아키텍처 진정한 의미의 계층 분할이 어렵다.
객체 답게 모델링 할수록 매핑 작업만 늘어난다.
결론: 객체를 자바 컬렉션에 저장 하듯이 DB에 저장할 수는 없을까?
이렇게 탄생한게 JPA 이다.
ORM 개념2 - JPA 소개
JPA
ORM
JPA는 애플리케이션과 JDBC 사이에서 동작한다.
JPA 동작 - 저장
JPA 동작 - 조회
JPA 소개
EJB - 엔티티 빈(자바 표준) -> 하이버네이트(오픈 소스) -> JPA(자바 표준)
JPA는 표준 명세
JPA 버전
JPA를 왜 사용해야 하는가?
생산성 - JPA와 CRUD
유지보수
JPA와 패러다임의 불일치 해결
JPA와 상속
JPA의 성능 최적화 기능
1차 캐시와 동일성 보장
트랜잭션을 지원하는 쓰기 지연 - INSERT
트랜잭션을 지원하는 쓰기 지연 - UPDATE
지연 로딩과 즉시 로딩
JPA 설정
build.gradle에 다음 의존 관계를 추가한다.다음과 같은 라이브러리가 추가된다.
hibernate-core: JPA 구현체인 하이버네이트 라이브러리jakarta.persistence-api: JPA 인터페이스spring-data-jpa: 스프링 데이터 JPA 라이브러리application.properties
에 다음 설정을 추가하자.main - application.properties`test - application.propertiesorg.hibernate.SQL=DEBUG: 하이버네이트가 생성하고 실행하는 SQL을 확인할 수 있다.org.hibernate.type.descriptor.sql.BasicBinder=TRACE: SQL에 바인딩 되는 파라미터를 확인할 수 있다.spring.jpa.show-sql=true: 참고로 이런 설정도 있다. 이전 설정은logger를 통해서 SQL이 출력된다.이 설정은
System.out콘솔을 통해서 SQL이 출력된다. 따라서 이 설정은 권장하지는 않는다.(둘다 켜면
logger,System.out둘다 로그가 출력되어서 같은 로그가 중복해서 출력된다.)스프링 부트 3.0
스프링 부트 3.0 이상을 사용하면 하이버네이트 6 버전이 사용되는데, 로그 설정 방식이 달려졌다. 다음과 같이 로그를 설정해야 한다.
JPA 적용1 - 개발
JPA에서 가장 중요한 부분은 객체와 테이블을 매핑하는 것이다.
Item - ORM 매핑
@Entity: JPA가 사용하는 객체라는 뜻이다. 이 에노테이션이 있어야 JPA가 인식할 수 있다. 이렇게@Entity가 붙은 객체를 JPA에서는 엔티티라 한다.@Id: 테이블의 PK와 해당 필드를 매핑한다.@GeneratedValue(strategy = GenerationType.IDENTITY): PK 생성 값을 데이터베이스에서 생성하는IDENTITY방식을 사용한다.예) MySQL auto increment
@Column: 객체의 필드를 테이블의 컬럼과 매핑한다.name = "item_name": 객체는itemName이지만 테이블의 컬럼은item_name이므로 이렇게 매핑했다.length = 10: JPA의 매핑 정보로 DDL(create table)도 생성할 수 있는데, 그때 컬럼의 길이 값으로 활용된다. (varchar 10)@Column을 생략할 경우 필드의 이름을 테이블 컬럼 이름으로 사용한다.참고로 지금처럼 스프링 부트와 통합해서 사용하면 필드 이름을 테이블 컬럼 명으로 변경할 때
객체 필드의 카멜 케이스를 테이블 컬럼의 언더스코어로 자동으로 변환해준다.
-
itemNameitem_name, 따라서 위 예제의@Column(name = "item_name")를 생략해도 된다.JPA는
public또는protected의 기본 생성자가 필수이다.JpaItemRepositoryV1
private final EntityManager em: 생성자를 보면 스프링을 통해 엔티티 매니저(EntityManager)라는 것을 주입받은 것을 확인할 수 있다.JPA의 모든 동작은 엔티티 매니저를 통해서 이루어진다. 엔티티 매니저는 내부에 데이터소스를 가지고 있고, 데이터베이스에 접근할 수 있다.
@Transactional: JPA의 모든 데이터 변경(등록, 수정, 삭제)은 트랜잭션 안에서 이루어져야 한다. 조회는 트랜잭션이 없어도 가능하다.변경의 경우 일반적으로 서비스 계층에서 트랜잭션을 시작하기 때문에 문제가 없다.
하지만 이번 예제에서는 복잡한 비즈니스 로직이 없어서 서비스 계층에서 트랜잭션을 걸지 않았다.
JPA에서는 데이터 변경시 트랜잭션이 필수다. 따라서 리포지토리에 트랜잭션을 걸어주었다.
일반적으로 는 비즈니스 로직을 시작하는 서비스 계층에 트랜잭션을 걸어주는 것이 맞다.
JpaConfig
ItemServiceApplication - 변경
JPA 적용2 - 리포지토리 분석
save() - 저장
em.persist(item): JPA에서 객체를 테이블에 저장할 때는 엔티티 매니저가 제공하는persist()메서드를 사용하면 된다.JPA가 만들어서 실행한 SQL
id에 값이 빠져있는 것을 확인할 수 있다. PK 키 생성 전략을IDENTITY로 사용했기 때문에JPA가 이런 쿼리를 만들어서 실행한 것이다. 물론 쿼리 실행 이후에
Item객체의id필드에 데이터베이스가 생성한 PK값이 들어가게 된다.(JPA가 INSERT SQL 실행 이후에 생성된 ID 결과를 받아서 넣어준다)
PK 매핑 참고
update() - 수정
JPA가 만들어서 실행한 SQL
em.update()같은 메서드를 전혀 호출하지 않았다. 그런데 어떻게 UPDATE SQL이 실행되는 것일까?이 부분은 JPA 기본편에서 자세히 다룬다. 지금은 트랜잭션 커밋 시점에 JPA가 변경된 엔티티 객체를 찾아서 UPDATE SQL을 수행한다고 이해하면 된다.
테스트에서 UPDATE SQL을 확인하려면
@Commit을 붙이면 확인할 수 있다.findById() - 단건 조회
find()를 사용하고 조회 타입과, PK 값을 주면 된다.그러면 JPA가 다음과 같은 조회 SQL을 만들어서 실행하고, 결과를 객체로 바로 변환해준다.
JPA가 만들어서 실행한 SQL
findAll() - 목록 조회
JPQL
JPA는 JPQL(Java Persistence Query Language)이라는 객체지향 쿼리 언어를 제공한다.
주로 여러 데이터를 복잡한 조건으로 조회할 때 사용한다. SQL이 테이블을 대상으로 한다면, JPQL은 엔티티 객체를 대상으로 SQL을 실행한다 생각하면 된다.
엔티티 객체를 대상으로 하기 때문에
from다음에Item엔티티 객체 이름이 들어간다. 엔티티 객체와 속성의 대소문자는 구분해야 한다.JPQL은 SQL과 문법이 거의 비슷하기 때문에 개발자들이 쉽게 적응할 수 있다.
실행된 JPQL
JPQL을 통해 실행된 SQL
파라미터
JPQL에서 파라미터는 다음과 같이 입력한다.
where price <= :maxPricequery.setParameter("maxPrice", maxPrice)동적 쿼리 문제
JPA를 사용해도 동적 쿼리 문제가 남아있다. 동적 쿼리는 뒤에서 설명하는 Querydsl이라는 기술을 활용하면 매우 깔끔하게 사용할 수 있다.
실무에서는 동적 쿼리 문제 때문에, JPA 사용할 때 Querydsl도 함께 선택하게 된다.
JPA 적용3 - 예외 변환
EntityManager는 순수한 JPA 기술이고, 스프링과는 관계가 없다. 따라서 엔티티 매니저는 예외가 발생하면 JPA 관련 예외를 발생시킨다.PersistenceException과 그 하위 예외를 발생시킨다.IllegalStateException,IllegalArgumentException을 발생시킬 수 있다.DataAccessException)로 어떻게 변환할 수 있을까?@Repository에 있다.@Repository의 기능@Repository가 붙은 클래스는 컴포넌트 스캔의 대상이 된다.@Repository가 붙은 클래스는 예외 변환 AOP의 적용 대상이 된다.(
PersistenceExceptionTranslator)를 등록한다.예외 변환 후

결과적으로 리포지토리에
@Repository애노테이션만 있으면 스프링이 예외 변환을 처리하는 AOP를 만들어준다.All reactions