Backend boot camp/Session3
[Spring MVC] JPA 데이터 액세스 계층
by orioncsy
2022. 11. 7.
JPA 개요
JPA 개념
JPA(Java Persistence API)
- Java에서 사용하는 ORM 기술의 표준 사양
- Java의 인터페이스로 정의, 구현체 따로 존재
Hibernate ORM
- JPA 구현체로 Hibernate ORM, EclipseLink, DataNucleus 존재
- Hibernate ORM는 JPA 기능 이외 자체 사용 API 지원
Data Access layer에서 JPA
- 데이터 액세스 계층의 상단에 위치
- JPA를 거쳐 구현체인 Hibernate ORM에서 데이터 처리 작업 작동
- Hibernate ORM은 내부적으로 JDBC API 사용하여 DB 접근
JPA 의미
Persistence Context
- ORM은 객체와 DB table을 매핑하여 엔티티 객체 안에 포한된 정보를 테이블에 저장하는 기술
- JPA는 테이블과 매핑되는 엔티티 객체 정보를 Persistence Context라는 곳에 보관
- 보관된 엔티티 정보는 DB 테이블에서 데이터 가공하는 데 사용
- 1차 캐시와 쓰기 지연 SQL 저장소라는 영역으로 구분
- 엔티티 정보를 저장하는 API를 사용하면 1차 캐시에 엔티티 정보 저장
JPA API로 Persistence Context 이해
- build.gradle dependencies에 의존 라이브러리 추가
- implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 추가
- Application.yml 설정
spring:
h2:
console:
enabled: true
path: /h2
datasource:
url: jdbc:h2:mem:test
jpa:
hibernate:
ddl-auto: create
show-sql: true
- ddl-auto: create는 jpa에서 사용하는 엔티티 클래스 정의하고 엔티티와 매핑되는 테이블을 DB에 자동으로 생성
- Spring data JDBC에서는 schema.sql 파일에서 직접 테이블 생성을 지정해줘야 했지만 JPA가 자동으로 DB에 테이블을 생성
- show-sql:true는 SQL 쿼리를 로그로 출력
- JpaBasicConfig
- JpaBasicConfig를 클래스로 @Configuration 붙여서 선언
- 필드 맴버로 EntityManager, EntityTransaction 선언
- @Bean을 붙여서 testJpaBasicRunner 메서드 선언 EntityManaberFactory를 매개변수로 받고 반환형으로 CommandLineRunner로 설정
- 매개로 받은 EntityManagerFactory 객체에 .createEntityManager()를 사용해서 EntityManager 필드에 할당
- EntityManger 필드 변수에 .getTransaction()을 사용해서 EntityTransaction에 할당
- 마지막에 CommandLineRunner 객체를 람다 표현으로 정의해서 반환
- Persistence context에 entity 저장
- entity 클래스에 @Entity를 붙여 선언
- 해당 클래스의 기본키에 해당하는 필드 맴버에 @Id를 선언
- @GeneratedValue를 선언하여 DB 테이블에서 키본키가 되는 식별자를 자동 생성
- JpaBasicConfig에서 EntityManager 객체에 .persist([엔티티명])을 통해 persistence context에 저장
- 엔티티가 저장되었는지 확인하기 위해 EntityManager 객체에 .find([엔티티명.class], [해당 id 식별자 값])로 조회 가능
- 실행
- .persist(); [INSERT]
- 1차 캐시에 엔티티 객체가 저장되고 쓰기 지연 SQL 저장소에 INSERT 쿼리가 등록
- 쓰기 지연 SQL 저장소의 쿼리를 실행하기 위해서는 EntityTransaction 사용
- EntityTransaction 객체를 EntityManager 객체에서 .getTransaction()을 통해 가져온다.
- EntityTransaction 객체에서 .begin() 메서드를 통해 Transacation 시작
- EntityTransaction 객체에서 .commit() 메서드를 통해 persistence context에 저장되어 있는 엔티티 객체를 DB 테이블에 저장
- .commit() 메서드를 사용하면 쓰기 지연 SQL 저장소에 실행된 쿼리가 제거된다.
- EntityManager 객체에서 .fin()를 호출하면 1차 캐시에서 객체를 찾고 없으면 테이블에 SELECT 쿼리를 전송하여 조회한다.
- setter [UPDATE]
- persist()를 사용하여 엔티티 객체를 1차 캐시에 저장한다.
- find를 통해 해당 엔티티 객체를 찾아서 setter 메서드를 사용하여 값을 변경한다.
- setter 메서드를 사용하는 것만으로도 업데이트 로직은 완성
- commit()을 사용하면 쓰기 지연 SQL 저장소에 등록된 UPDATE 쿼리 실행
- remove [DELETE]
- persist()를 사용하여 엔티티 객체를 1차 캐시에 저장
- find를 통해 엔티티 객체를 찾아서 EntityManager 객체에서 remove()를 사용해서 제거
- EntityManager에서 flush() API
- EntityTransaction 객체에서 commit() 메서드가 실행되면 JPA 내부적으로 EntityManager 에서 .flush()가 호출되어 영속성 콘텍스트 변경 내용을 DB에 반영
JPA 엔티티 매핑과 연관 관계 매핑
JPA 엔티티 매핑
- 엔티티 객체에 @Entity를 사용해 엔티티 클래스와 테이블 매핑
- @Entity(name=””) name 속성을 사용하여 entity 이름 설정
- @Table(name=””) name 속성을 사용하여 table 이름을 변경(주로 클래스 이름과 테이블 이름 다를 때 사용)
- 둘 다 설정을 안할 경우 기본적으로 클래스 이름을 사용
- @Table은 옵션이고 @Entity, @Id는 필수
- 파라미터가 없는 기본 생성자(@NoArgsConstructor) 필수
기본 키 매핑
- 기본적으로 @Id를 추가한 필드가 기본 키 칼럼으로 설정
- 기본 키 생성 전략
- IDENTITY
- 기본키 생성을 DB에 위임하는 전략
- MySQL의 AUTO_INCREMENT 기능을 통해 자동 증가 숫자를 기본키로 사용하는 방식 존재
- SEQUENCE
- DB에서 제공하는 시퀀스를 사용해 기본키 생성
- TABLE
- 예시
- 엔티티 객체에서 해당 필드에 @Id만 붙이면 기본키를 직접 할당해서 엔티티를 저장한다
- 실제 저장할 때 em.persist(new Member(1L));
- IDENTITY 전략
- 객체 엔티티에 해당 필드에 @GeneratedValue(strategy=GenerationType.IDENTITY)를 붙인다.
- 데이터베이스에서 기본키를 대신 생성
- SEQUENCE 전략
- 객체 엔티티에 해당 필드에 @GeneratedValue(strategy=GenerationType.SEQUENCE)를 붙인다.
- 데이터베이스 시퀀스 사용
- 영속성 컨텍스트에 저장되기 전에 DB에 시퀀스에서 기본키에 해당하는 값 제공
- @GeneratedValue(strategy = GenerationType.AUTO)
- JPA가 자동으로 Dialect에 맞게 적절한 전략 선택
- Dialect
- 표준SQL이 아닌 특정 DB에 특화된 고유 기능
필드(멤버 변수)와 칼럼 간의 매핑
- @Column 애너테이션으로 필드와 칼럼을 매핑
- @Column 애너테이션이 존재하지 않는다고 한다면 기본적으로 필드와 테이블 칼럼과 매핑되는 필드로 간주하고 attribute는 defualt로 적용
- attribute
- nullable
- 컬럼에 null 허용할지 여부 지정
- 디폴트는 true
- updatable
- 칼럼 데이터를 수정할 수 있는 지정
- 디폴트 true
- unique
- 하나의 칼럼에 unique 제약 조건 설정
- 디폴트 false
- 원시 타입은 null값을 가질 수 없어 nullable은 false를 두는 것이 좋다
- length
- java.util.Date, java.util.Calendar 타입으로 매핑하려면 @Temporal이 붙어야 하지만 LocalDateTime은 TIMESTAMP 타입과 자동 매핑
- @Transient
- 테이블 칼럼과 매핑하지 않는다고 JPA 인식
- 임시 데이터를 메모리에서 사용하기 위한 용도
- @Enumerated
- enum 타입과 매핑할 때 사용하는 애너테이션
- EnumType.ORDINAL은 순서를 나타내는 숫자를 테이블에 저장
- EnumType.STRING은 enum 이름을 테이블에 저장
- 순서가 바뀔 수 있으므로 STRING 권장
- 엔티티와 테이블 간 매핑
JPA 엔티티 간의 연관 관계 매핑
연관 관계 매핑
- 단방향 연관관계
- 한쪽 클래스가 다른 쪽 클래스의 참조 정보를 단방향으로만 가지고 있는 경우
- 양방향 연관관계
- 서로 참조하고 있는 클래스 정보를 가지고 있어서 양방향으로 상대 정보를 알 수 있는 경우
- JPA는 단방향, 양방향 모두 지원, Spring Data JDBC는 단방향 연관관계 지원
일대다 단방향 연관 관계
- 일에 해당하는 클래스가 다에 해당하는 객체를 참조하는 관계
- List 객체를 참조하여 사용
- 주로 사용하지 않는다
- DB에서는 다에 해당하는 테이블이 일에 해당하는 기본키를 외래 키로 가진다
- 그러나 일대다 단방향 연관관계는 일에 해당하는 클래스가 참조하는 구조라 정상적으로 표현 불가
다대일 단방향 연관 관계
- 다에 해당하는 클래스가 일에 해당하는 객체를 참조하는 관계
- 외래 키를 가지는 방식과 유사하여 기본적인 매핑 관계로 사용
- 구현
- @ManyToOne 애너테이션과 @JoinColumn(name=”MEMBER_ID”)를 붙인 필드를 다에 해당하는 클래스에 작성
- @JoinColumn(name=”MEMBER_ID”) name에는 외래 키에 해당하는 칼럼명
다대일 단방향 연관 관계에 일대다 단방향 연관관계 추가
- 다대일 매핑에서 일대다 매핑을 추가하여 양방향 매핑 구현
- 구현
- 일에 해당하는 객체에서 @OneToMany(mappedBy=”member”)를 추가한 다에 해당하는 객체 리스트를 선언
- mappedBy에는 다에 해당하는 객체에서 필드 멤버 중에 외래 키 역할을 하는 필드 이름을 적는다.
다대다 연관 관계 매핑
- 다대다 중간에 하나의 테이블을 만들어 1대 다 관계, 다대 1 관계를 만든다
- 다대일 매핑을 두 번 사용하여 매핑
- 다대일 매핑을 통해 객체 그래프 탐색으로 원하는 객체를 조회할 수 없다면 양방향 매핑을 추가
일대일 연관 관계 매핑
- 다대일 연관관계 매핑과 동일
- @OneToOne 애너테이션으로 바꾸고 다른 것은 동일
- 다대일에 일대다 매핑을 추가하는 방식도 동일
- @OneToOne 애너테이션으로 바꾸고 다른 것은 동일
Spring Data JPA를 활용한 데이터 액세스 계층 구현
Spring Data JPA
JPA, Hibernate ORM, Spring Data JPA
- JPA는 java에서 DB를 사용하기 위해 정해 놓은 표준 스펙
- Hibernate ORM은 JPA의 구현체 중 하나(실제 사용하는 API)
- Spring Data JPA는 JPA 구현체를 더 쉽게 사용할 수 있게 해주는 모듈
Spring JPA 적용 순서
- 엔티티 클래스를 Spring Data JPA에 맞게 수정
- repository 인터페이스 구현
- 서비스 클래스 구현
- 기타 수정 코드
엔티티 클래스를 Spring Data JPA에 맞게 수정
- @Entity를 선언하고 @id를 지정하고 @Column을 설정한다
Repository 구현
- JpaRepository <Member, Long>을 상속받는 인터페이스 선언
- findByXXX 형태로 바디 구성
- JPQL을 통한 객체 지향 쿼리 사용
- JPQL이라는 객체 지향 쿼리를 통해 DB 테이블 조회 가능
- 테이블 대상이 아니라 엔티티 클래스의 객체를 대상으로 조회하는 방법
- “SELECT c FROM Coffee c WHERE c.coffeeId = :coffeeId"
- COFFEE 테이블이 아니라 Coffee 엔티티를 참고하고, * 대신 c를 사용하는 등 SQL과 다르다
- SELECT c는 생략 가능하다
- 네이티브 SQL을 통한 조회
- @Query(value = "SELECT * FROM COFFEE WHERE coffee_Id = :coffeeId", nativeQuery=true)
- nativeQuery를 true로 설정하면 SQL문 사용 가능
- Spring Data JDBC의 @Query : org.springframework.data.jdbc.repository.query.Query
- Spring Data JPA의 @Query : org.springframework.data.jpa.repository.Query