ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링 DB 데이터접근 기술 - JPA
    Spring-Boot/스프링 DB 2편 - 데이터 접근 기술 2022. 9. 22. 16:54

    - 대표적으로 JdbcTemplate이나 MyBatis 같은 SQL 매퍼 기술은 SQL을 개발자가 직접 작성해야 하지만, JPA를 사용하면 SQL도 JPA가 대신 작성하고 처리해준다.

    - 실무에서는 JPA를 더욱 편리하게 사용하기 위해 스프링 데이터 JPA와 Querydsl이라는 기술을 함께 사용한다

     

    ORM 개념1 - SQL 중심적인 개발 문제점

    - SQL에 의존적인 개발을 피하기 어렵다.

    - 객체와 관계형 데이터베이스의 차이

        ㆍ1. 상속    ㆍ2. 연관관계    ㆍ3. 데이터타입    ㆍ4. 데이터 식별 방법

    - 처음 실행하는 SQL에 따라 탐색 범위 결정

        ㆍ계층형 아키텍처 - 진정한 의미의 계층 분할이 어렵다.

    - 객체답게 모델링 할수록 매핑 작업만 늘어난다.

    - 객체를 자바 컬렉션에 저장 하듯이 DB에 저장할 수는 없을까?

     

    ORM 개념2 - JPA 소개

    - JPA - Java Persistence API

    - 자바 진영의 ORM 기술 표준

     

    ORM

    - Object-relational mapping(객체 관계 매핑)

    - 객체는 객체대로 설계

    - 관계형 데이터베이스는 관계형 데이터베이스대로 설계

    - ORM 프레임워크가 중간에서 매핑

    - 대중적인 언어에는 대부분 ORM 기술이 존재

     

    - JPA는 애플리케이션과 JDBC 사이에서 동작

     

    JPA를 왜 사용해야 하는가?

    - SQL 중심적인 개발에서 객체 중심으로 개발

    - 생산성

    - 유지보수

        ㆍJPA : 필드만 추가하면됨, SQL은 JPA가 처리

    - 패러다임의 불일치 해결

        ㆍ1. JPA와 상속

        ㆍ2. JPA와 연관관계

        ㆍ3. JPA와 객체 그래프 탐색

                 - 신뢰할 수 있는 엔티티, 계층

        ㆍ4. JPA와 비교하기

                 - 동일한 트랜잭션에서 조회한 엔티티는 같음을 보장

    - 성능

    - 데이터 접근 추상화와 벤더 독립성

    - 표준

     

    생산성 - JPA와 CRUD
    - 저장 : jpa.persist(member)

    - 조회 : Membe membe = jpa.find(memberId)

    - 수정 : member.setName("변경할 이름")

    - 삭제 : jpa.remove(member)

     

    JPA의 성능 최적화 기능

    1. 1차 캐시와 동일성(identity) 보장

        ㆍ1. 같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상

        ㆍ2. DB Isolation Level이 Read Commit이어도 애플리케이션에서 Repeatable Read 보장

    2. 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)

        ㆍ1. 트랜잭션을 커밋할 때까지 INSERT SQL을 모음

        ㆍ2. JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송

        ㆍ3. UPDATE, DELETE로 인한 로우(ROW)락 시간 최소화

        ㆍ4. 트랜잭션 커밋 시 UPDATE, DELETE SQL 실행하고, 바로 커밋

    3. 지연 로딩(Lazy Loading)

    - 지연로딩 : 객체가 실제 사용될 때 로딩

    - 즉시로딩 : JOIN SQL로 한번에 연관된 객체까지 미리 조회


    JPA 설정

    #JPA log
    logging.level.org.hibernate.SQL=DEBUG
    logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

    - `org.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` 둘다 로그가 출력되어서 같은 로그가 중복해서 출력 )

     

    JPA 적용1 - 개발

    - `@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`을 생략할 경우 필드의 이름을 테이블 컬럼 이름으로 사용한다.

              참고로 지금처럼 스프링 부트와 통합해서 사용하면 필드 이름을 테이블 컬럼 명으로 변경할 때

              객체 필드의 카멜 케이스를 테이블 컬럼의 언더스코어로 자동 변환

             ㆍ`itemName` → `item_name`

     

    @Transactional
    public class JpaItemRepository implements ItemRepository {
    
        private final EntityManager em;
    
        public JpaItemRepository(EntityManager em) {
            this.em = em;
        }
        ...
    }

    - `private final EntityManager em` : 생성자를 보면 스프링을 통해 엔티티 매니저(`EntityManager`)라는 것을 주입받은 것을 확인할 수 있다. JPA의 모든 동작은 엔티티 매니저를 통해서 이루어진다. 엔티티 매니저는 내부에 데이터소스를 가지고 있고, 데이터베이스에 접근할 수 있다.

    - `@Transactional` : JPA의 모든 데이터 변경(등록, 수정, 삭제)은 트랜잭션 안에서 이루어져야 한다. 조회는 트랜잭션이 없어도 가능하다. 변경의 경우 일반적으로 서비스 계층에서 트랜잭션을 시작하기 때문에 문제가 없다. 하지만 이번 예제에서는 복잡한 비즈니스 로직이 없어서 서비스 계층에서 트랜잭션을 걸지 않았다. JPA에서는 데이터 변경시 트랜잭션이 필수 다. 따라서 리포지토리에 트랜잭션을 걸어주었다. 다시한번 강조하지만 일반적으로 비즈 니스 로직을 시작하는 서비스 계층에 트랜잭션을 걸어주는 것이 맞다

     

    * 참고 *

    - JPA를 설정하려면 `EntityManagerFactory`, JPA 트랜잭션 매니저(`JpaTransactionManager`), 데이터소스 등등 다양한 설정을 해야한다.

    - 스프링 부트는 이 과정을 모두 자동화 해준다

     


    JPA 적용2 - 리포지토리 분석

    - em.persist(item) : JPA에서 객체를 테이블에 저장할 때는 엔티티 매니저가 제공하는 persist() 사용

     

    -  트랜잭션 커밋 시점에 JPA가 변경된 엔티티 객체를 찾아서 UPDATE SQL을 실행

     

    - JPA에서 엔티티 객체를 PK를 기준으로 조회할 때는 `find()`를 사용하고 조회 타입과 PK값을 주면 된다.

     

    * JPQL *

    - JPA는 JPQL(Java Persistence Query Language)이라는 객체지향 쿼리 언어를 제공

    - 주로 여러 데이터를 복잡한 조건으로 조회할 때 사용

    - SQL이 테이블을 대상으로 한다면, JPQL은 엔티티 객체를 대상으로 SQL을 실행한다 생각하면 된다.

    - 엔티티 객체를 대상으로 하기 때문에 `from` 다음에 `Item` 엔티티 객체 이름이 들어간다.

       엔티티 객체와 속성의 대소문자는 구분해야 한다.

    - JPQL은 SQL과 문법이 거의 비슷

    - 결과적으로 JPQL을 실행하면 그 안에 포함된 엔티티 객체의 매핑 정보를 활용해서 SQL 생성

     

    * 파라미터 *

    - JPQL에서 파라미터는 다음과 같다.

        ㆍ`where price <= :maxPrice`

    - 파라미터 바인딩

        ㆍ`query.setParameter("maxPrice",maxPrice)`

     

    * 동적 쿼리 문제 *

    - JPA를 사용해도 동적쿼리문제가 남아있다.

    - 동적 쿼리는 Querydsl이라는 기술을 활용하면 매우 깔끔하게 사용


    JPA 적용3 - 예외 변환

    - `EntityManager`는 순수한 JPA기술이고, 스프링과는 관계가 없다. 따라서 엔티티 매니저는 예외가 발생하면 JPA관련 예외를 발생

    - JPA는 `PersistenceException`과 그 하위 예외를 발생 시킨다.

        ㆍ추가로 JPA는 `IllegalStateException`, `IllegalArgumentException`을 발생시킬 수 있다.

    - 그렇다면 어떻게 JPA 예외를 스프링 예외 추상화(`DataAccessException`)로 변환할 수 있을까?

    - 비밀은 바로 @Repository에 있다.

     

    * @Repository의 기능 *

    - `@Repository` 가 붙은 클래스는 컴포넌트 스캔의 대상이 된다

    - `@Repositroy` 가 붙은 클래스는 예외 변환 AOP의 적용 대상이된다

        ㆍ스프링과 JPA를 함께 사용하는 경우 스프링은 JPA 예외 변환기(`PersistenceExceptionTranslator`)를 등록

        ㆍ예외 변환 AOP 프록시는 JPA 관련 예외가 발생하면 JPA 예외 변환기를 통해 발생된 예외를

              스프링 데이터 접근 예외로 변환

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

    댓글

Designed by Tistory.