3. JPA와 하이버네이트
◉ DBMS에도 여러 종류가 있는 것처럼, ORM에도 여러 종류가 있음
▪︎ 자바에서는 JPA( Java Persistence API )를 표준으로 사용함
▪︎ JPA : 자바에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스
→ 실제 사용을 위해서는 ORM 프레임 워크를 추가로 선택해야 함
⇒ 대표적으로 Hibernate를 많이 사용함
▪︎ 하이버네이트
- JPA 인터페이스를 구현한 구현체이자 자바용 ORM 프레임워크
- 내부적으로 JDBC API 사용
→ 하이버네이트의 목표 : 자바 객체를 통해 데이터베이스 종류에 상관없이 데이터베이스를 자유자재로 사용할 수 있게 해줌
🗣️ JPA와 하이버네이트의 역할 ★★★
- JPA : 자바 객체와 데이터베이스를 연결해 데이터를 관리
객체 지향 도메인 모델과 데이터베이스의 다리 역할
- 하이버네이트 : JPA의 인터페이스를 구현, 내부적으로 JDBC API 사용
◉ JPA의 중요한 컨셉 - 엔티티 메니저 / 영속성 컨텍스트 ★★★
1) 엔티티 매니저란?
▪︎ 엔티티 (entity)
• 데이터베이스의 테이블과 매핑되는 객체를 의미
• 본질적으로 자바 객체이므로 일반 객체와 다르지 않으나, 데이터베이스의 테이블과 직접 연결된다는 특징으로 구분됨
→ 객체이긴 하지만, 데이터베이스에 영향을 미치는 쿼리를 실행하는 객체
▪︎ 엔티티 매니저
• 엔티티를 관리해 데이터베이스와 애플리케이션 사이에서 객체를 생성, 수정, 삭제하는 등의 역할
• 엔티티 매니저 팩토리(entity manager factory)에서 생성됨
• ( 예시 )
회원 2명이 동시에 회원가입을 하려는 경우,
회원 1의 요청에 대해서 가입 처리할 엔티티 메니저를 엔티티 매니저 팩토리에서 생성하면,
이를 통해 가입 처리해 데이터베이스에 회원 정보를 저장 / 회원 2도 동일하게 진행
회원 1, 2를 위해 생성된 엔티티 매니저는 필요한 시점에 데이터베이스와 연결한 뒤에 쿼리함
◉ 스프링부트에서도 직접 엔티티 매니저 팩토리를 만들어서 관리할까? 사실 그렇지 않음!
▪︎ 스프링 부트에서 엔티티 매니저 팩토리를 하나만 생성해서 관리
→ @PersistenceContext 또는 @Autowired 애너테이션을 사용해서 앤티티 매니저를 사용함
// * 스프링 부트가 엔티티 매니저를 사용하는 방법 예
@PersistenceContext
EntityManager em; // 프록시 엔티티 매니저. 필요할 때 진짜 엔티티 매니저 호출
▪︎ 스프링 부트에서 빈은 하나만 생성해서 공유 → 동시성 문제 발생할 수 있음
- 실제 엔티티 매니저와 연결하는 프록시(가짜) 엔티티 매니저를 사용함
- 필요할 때 데이터베이스 트랜잭션과 관련된 실제 엔티티 매니저를 호출함
※ 엔티티 매니저는 Spring Data JPA에서 관리하므로, 직접 생성하거나 관리할 필요 없음
2) 영속성 컨텍스트란?
◉ 영속성 컨텍스트
- 엔티티 매니저는 엔티티를 영속성 컨텍스트에 저장함
- 영속성 컨텍스트
• 엔티티를 관리하는 가상의 공간
• 데이터를 생성한 프로그램이 종료되더라도 사라지지 않는 데이터의 특성을 의미
( 영속성을 갖지 않는 데이터와 비교 : 단지 메모리에서만 존재 → 프로그램 종료 시 모두 잃어버리게 됨 )
→ 영속성 컨텍스트로 인해 데이터베이스에서 효과적으로 데이터를 가져올 수 있고, 엔티티를 편하게 사용할 수 있음
◉ 영속성 컨텍스트의 기본적인 특징
① 1차 캐시
• 영속성 컨텍스트 : 내부에 1차 캐시를 가지고 있음
• 캐시의 키 : 엔티티의 @Id 애너테이션에 달린 기본키 역할을 하는 식별자
캐시의 값 : 엔티티
• 엔티티 조회 시, 1차 캐시에서 조회 → (값이 없으면) 데이터베이스에서 조회
( 데이터베이스에서 조회 후 1차 캐시에 저장한 다음 반환 )
• 캐시된 데이터를 조회할 때에는 데이터베이스를 거치지 않음 → 매우 빠르게 데이터 조회 가능
② 쓰기 지연 (tranactional write-behind)
• 트랜잭션을 커밋하기 전까지는 데이터베이스에 실제로 질의문을 보내지 않고 모음
→ 트랜잭션 커밋하는 시점에 모았던 쿼리를 한꺼번에 전송
• 적당한 묶음으로 쿼리 요청 가능 → 데이터베이스 시스템의 부담을 줄일 수 있음
③ 변경 감지
• 트랜잭션 커밋 → 1차 캐시에 저장되어 있는 엔티티의 값과 현재 엔티티의 값을 비교해서 변경된 값이 있다면 변경사항을 감지, 변경된 값을 데이터베이스에 자동으로 반영함
• 쓰기 지연과 동일하게 적당한 묶음으로 쿼리 요청 가능 → 데이터베이스 시스템의 부담을 줄일 수 있음
④ 지연 로딩 (lazy loading)
• 쿼리로 요청한 데이터를 애플리케이션에 바로 로딩하는 것이 아니라 필요할 때 쿼리를 날려 데이터 조회
↔︎ 즉시 로딩 : 조회할 때 쿼리를 보내 연관된 모든 데이터를 즉시 조회
⇒ 이러한 특징으로 인해 데이터베이스의 접근을 최소화시켜 성능 향상이 가능함
3) 엔티티의 상태
◉ 엔티티의 상태 - 4가지
- 분리(detached) 상태 : 영속성 컨텍스트가 관리하고 있지 않는 상태
- 관리(managed) 상태 : 영속성 컨텍스트가 관리하는 상태
- 비영속(transient) 상태 : 영속성 컨텍스트와 전혀 관계가 없는 상태
- 삭제된(removed) 상태
※ 엔티티의 상태는 특정 메서드를 호출해 변경 가능 → 필요에 따라 엔티티의 상태를 조절해 데이터를 올바르게 유지하고 관리 가능
// * 엔티티 상태 변경 예
public class EntityManagerTest {
@Autowired
EntityManager em;
public void example(){
// ① 엔티티 매니저가 엔티티를 관리하지 않는 상태 (비영속 상태)
Member member = new Member(1L, "홍길동");
// ② 엔티티가 관리 상태가 됨 (관리 상태)
em.persist(member);
// ③ 엔티티 객체가 분리된 상태가 됨 (분리 상태)
em.detach(member);
// ④ 엔티티 객체가 삭제된 상태가 됨 (삭제 상태)
em.remove(member);
}
}
① 엔티티를 처음 만들면, 엔티티는 비영속 상태가 됨
② persist( ) 메서드를 이용해 엔티티를 관리 상태로 만들 수 있음 → Member 객체는 영속성 컨텍스트에서 상태가 관리됨
③ 엔티티를 영속성 컨텍스트에서 관리하고 싶지 않을 경우, detach( ) 메서드를 사용해 분리 상태로 만들 수 있음
④ 더 이상 객체가 필요 없다면, remove( ) 메서드를 사용해 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제 가능
4. 스프링 데이터와 스프링 데이터 JPA
▪︎ 스프링 데이터
- 엔티티의 상태 직접 관리 / 필요한 시점에 커밋 실행 등 개발자가 신경써야 할 부분이 많음
→ 스프링 데이터 이용하여 비즈니스 로직에 더 집중 가능
- 스프링 데이터 : 데이터베이스 사용 기능을 클래스 레벨에서 추상화 → 인터페이스를 통해서 스프링 데이터 사용 가능
- 이 인터페이스의 경우,
• CRUD를 포함한 여러 메서드가 포함되어 있음
• 알아서 쿼리를 만들어줌
• 페이징 처리 기능
• 메서드 이름으로 자동으로 쿼리 빌딩하는 기능 제공
1) 스프링 데이터 JPA란?
- 스프링 데이터의 공통적인 기능에서 JPA의 유용한 기술이 추가된 기술
- 스프링 데이터의 인터페이스인 PagingAndSortingRepository를 상속받아 JpaRepository 인터페이스를 만들었으며, JPA를 더 편리하게 사용하는 메서드를 제공함
▪︎ 기존에 엔티티 상태를 바꾸는 방법
→ 메서드 호출로 엔티티 상태를 바꿨음
// * 메서드 호출로 엔티티 상태 변경 예시
@PersistenceContext
EntityManager em;
public void join(){
// 기존에 엔티티 상태를 바꾸는 방법 (메서드 호출을 해서 상태 변경)
Member member = new Member(1L, "홍길동");
em.persist(member);
}
⇒ 스프링 데이터 JPA 사용하면,
리포지토리 역할을 하는 인터페이스를 만들어 데이터베이스의 테이블 조회, 수정, 생성, 삭제 같은 작업을 간단히 할 수 있음
// * 기본 CRUD 메서드를 사용하기 위한 JpaRepository 상속 예
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
}
▪︎ JpaRepository 인터페이스를 우리가 만든 인터페이스에서 상속받고,
제네릭에는 관리할 <엔티티 이름, 엔티티 기본키의 타입>을 입력하면, 기본 CRUD를 위해 만든 메서드 사용 가능
2) 스프링 데이터 JPA에서 제공하는 메서드 사용해보기
▪︎ MemberService.java
package org.choongang.service;
import java.util.List;
import java.util.Optional;
import org.choongang.domain.Member;
import org.choongang.domain.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MemberService {
// 데이터베이스와 작업은 리포지토리 또는 모델
@Autowired
MemberRepository memberRepository;
public void test() {
// ❶ 생성(Create) - JPA에서 데이터 추가 : save()
memberRepository.save(new Member(1L, "A"));
// ❷ 조회(Read) - JPA에서 데이터 검색 2가지
// - 컬럼별 레코드 검색 : findById()
// → 기본키 Id로 검색
// - java.util.Optional<T> 클래스 ( 아래 설명 참고 )
Optional<Member> member = memberRepository.findById(1L);
// - 전체 레코드 검색 : findAll()
List<Member> allMembers = memberRepository.findAll();
// ❸ 삭제(Delete) : deleteById()
memberRepository.deleteById(1L);
}
}
❶ save( ) 메서드 호출해 데이터 객체 저장 가능
전달 인수로 엔티티 Member를 넘기면 반환값으로 저장한 엔티티를 반환받을 수 있음
❷ 단건 조회 : findById( ) 메서드에 id를 지정해 엔티티 하나를 조회
전체 조회 : findAll( ) 메서드를 통해 전체 엔티티 조회
❸ deleteById( ) 메서드에 id를 지정해 엔티티 삭제 가능
▪︎ java.util.Optional<T> 클래스
- 'T타입의 객체'를 감싸는 래퍼 클래스로, 모든 타입의 객체를 담을 수 있음
→ 존재할 수도 있지만, 안할 수도 있는 객체
즉, null이 될 수도 있는 객체를 감싸고 있는 일종의 Wrapper 클래스
public final class Optional<T> {
private final T value; // T 타입의 참조변수
...
}
- 최종 연산의 결과를 그냥 반환하는 게 아니라 Optional 객체에 담아서 반환하면,
반환된 결과가 null인지 매번 if문으로 체크하는 대신 Optional에 정의된 메서드를 통해서 간단히 처리 가능
- Optional<T> 이용 시, 널 체크를 위한 if문 없이도 NullPointerException이 발생하지 않는, 보다 간결하고 안전한 코드 작성 가능
+ 추가적인 개념
[ 참고 ] https://www.daleseo.com/java8-optional-after/
▪︎ Optional이란?
- 존재할 수도 있지만, 안할 수도 있는 객체
즉, null이 될 수도 있는 객체를 감싸고 있는 일종의 wrapper 클래스
- 직접 다루기에 위험하고 까다로운 null을 담을 수 있는 특수한 그릇으로 생각하자
▪︎ Optional의 효과
- NPE를 유발할 수 있는 null을 직접 다루지 않아도 됨
- null 체크를 직접 하지 않아도 됨
- 명시적으로 해당 변수가 null일 수도 있다는 가능성을 표현할 수 있음
▪︎ Optional 기본 사용법
- java.util.Optinal<T> 클래스 이용
- Optional 변수 선언
• 제네릭 제공 → 변수 선언 시 명기한 타입 파라미터에 따라서 감쌀 수 있는 객체의 타입이 결정됨
Optional<Member> member ; // Member 타입의 객체를 감쌀 수 있는 Optional 타입의 변수
• 변수명의 경우, 보통 클래스 이름을 사용하기도 하지만, ‘opt’와 같은 접두어를 붙여 Optional 타입의 변수라는 것을 좀 더 명확히 나타내기도 함
5. 예제 코드 살펴보기
◉ 지금까지 배운 애너테이션이 어떤 역할을 하는지 확인해보자
▪︎ Member.java
package org.choongang.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access=AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter // 데이터가 한번 만들어지면 고칠 수 없도록 설정 (VO 형식)
@Entity // 모델 클래스와 데이터 클래스를 1:1로 동기화함
// 데이터베이스 테이블의 구조와 동등 -> JPA가 만듦
public class Member {
@Id
// 일련번호로 Primary Key를 사용함
@GeneratedValue(strategy=GenerationType.IDENTITY)
// GeneratedValue : 자동생성된다는 의미
// strategy : 만드는 방식, 전략 -> 자동으로 일련번호를 만들어서 사용하라
@Column(name="id", updatable=false)
private Long id;
// 테이블의 'id' 컬럼과 매핑
@Column(name="name", nullable=false)
private String name;
}
❶ @Entity
- 엔티티 : 데이터베이스의 테이블과 매핑되는 객체
- Member 객체를 JPA가 관리하는 엔티티로 지정
→ Member 클래스와 실제 데이터베이스의 테이블을 매핑시키는 역할
- @Entity의 name 속성 이용 시, name의 값을 가진 테이블 이름과 매핑
name을 지정하지 않으면, 클래스 이름과 같은 이름의 테이블과 매핑됨 (현재는 후자에 해당)
❷ @NoArgsConstructor(access=AccessLevel.PROTECTED)
- 엔티티는 반드시 기본 생성자가 있어야 함
- 접근 제어자의 경우, public 또는 protected로 지정 ( public보다는 protected가 더 안전 )
❸ @Id
- JPA 엔티티 객체의 식별자로 사용할 필드에 적용
- Long 타입의 id 필드를 테이블의 기본키로 지정
❹ @GeneratedValue
- 기본키의 생성 방식을 결정
• AUTO : 선택한 데이터베이스 방언(dialect)에 따라 방식을 자동으로 선택(기본값)
• IDENTITY : 기본 키 생성을 데이터베이스에 위임
→ id값을 null로 하면 DB가 알아서 AUTO_INCREMENT 해줌
• SEQUENCE : 데이터베이스 시퀀스를 사용해서 기본 키를 할당하는 방법 → 오라클에서 주로 사용
• TABLE : 키 생성 테이블 사용
❺ @Column
- 데이터베이스의 컬럼과 필드를 매핑해주는 역할
- 속성
속성 | 기능 | 기본값 |
name | 필드와 매핑할 컬럼 이름 설정하지 않으면, 필드 이름으로 지정함 * Java - 카멜표기법 권장 / DB - 스네이크표기법 권장 ⇒ name 속성으로 매핑 |
객체의 필드 이름 |
nullable | DDL 생성 시 null 값의 허용 여부를 설정함 false로 설정하면 Not null 제약조건이 붙음 |
true |
updatable | 엔티티 수정 시 이 필드도 같이 수정함 false로 설정하면 데이터베이스에 수정하지 않음 false 옵션은 읽기 전용일 때 사용함 |
true |
unique | 컬럼의 유일한 값 여부 설정하지 않으면 false |
false |
columnDefinition | 컬럼 정보 설정 default값을 줄 수 있음 |
자바 필드의 타입과 데이터베이스 방언 설정 정보를 사용해 적절히 생성 |
▪︎ MemberRepository.java
package org.choongang.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface MemberRepository extends JpaRepository<Member, Long>{
}
- 리포지토리 : 엔티티에 있는 데이터들을 조회하거나 저장, 변경, 삭제를 할 때 사용하는 인터페이스
스프링 데이터 JPA에서 제공하는 인터페이스인 JpaRepository 클래스를 상속받아 간단하게 구현 가능
- 단순히 인터페이스를 생성 후, JpaRepository< Entity 클래스, PK 타입 > 를 상속하면 기본적인 CRUD 메서드가 자동 생성됨
→ 이 레포지토리 사용 시, JpaRepository에서 제공하는 여러 메서드 사용 가능
- JpaRepository에 대한 추가 설명
• JpaRepository : PagingAndSortingRepository, QueryByExampleExecutor 인터페이스를 상속받고 있음
• PagingAndSortingRepository : CrudRepository 인터페이스를 상속받고 있음
• CrudRepository : 기본적인 CRUD 메서드 제공
→ save( ), findById( ), existsById( ), count( ), deleteById( ), delete( ), deleteAll( )
• QueryByExampleExecutor : 더 다양한 CRUD 메서드 제공
→ findOne( ), findAll( ), count( ), exists( )
1. ORM : 객체와 데이터베이스를 연결하는 프로그래밍 기법
2. JPA : 자바에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스
- 엔티티 : 영속성을 가진 객체 ( 데이터베이스의 테이블과 매핑되는 객체 )
- 엔티티 매니저 : 엔티티를 관리하며 조회, 삭제, 수정, 생성하는 역할을 함
- 엔티티 매니저를 만드는 곳이 엔티티 매니저 팩토리
- 엔티티 매니저는 엔티티를 영속성 컨텍스트에 저장함
- 영속성 컨텍스트의 특징 : 1차 캐시 / 쓰기 지연 / 변경 감지 / 지연 로딩
- 엔티티의 상태 : 분리 / 관리 / 비영속 / 삭제 상태
3. 하이버네이트 : JPA의 구현체 중 대표적인 구현체로, 자바 언어를 위한 ORM 프레임워크
4. 스프링 데이터 JPA : JPA를 쓰기 편하게 만들어 놓은 모듈
→ 데이터베이스에 접근해 CRUD 작업을 수행하는 메서드를 제공하는 인터페이스 : JpaRepository
'백엔드 > Spring Boot' 카테고리의 다른 글
JSP에 CSS 및 Javascript 연결 (0) | 2023.07.25 |
---|---|
6. 블로그 기획하고 API 만들기 (1) - API와 REST API (0) | 2023.07.24 |
5. 데이터베이스 조작이 편해지는 ORM (1) - 데이터베이스, ORM (0) | 2023.07.17 |
4. 스프링 부트 3와 테스트 (0) | 2023.07.14 |
3. 스프링 부트 3 구조 이해하기 (0) | 2023.07.12 |