ballqs 님의 블로그
[Spring] JPA(Java Persistence API)란? 본문
JPA란?
Java ORM(Object-Relational Mapping)기술의 대표적인 표준 명세이다.
- JPA는 애플리케이션과 JDBC 사이에서 동작된다
- JPA를 사용하면 DB 연결 과정을 직접 개발하지 않아도 자동으로 처리해준다.
- 또한 객체를 통해 간접적으로 DB 데이터를 다룰 수 있기 때문에 매우 쉽게 DB 작업을 처리가 가능하다.
하이버네이트(Hibernate)란?
- JPA 는 표준 명세이고, 이를 실제 구현한 프레임워크 중 사실상 표준이 하이버네이트이다.
- 스프링 부트에서는 기본적으로 ‘하이버네이트’ 구현체를 사용 중이다.
영속성 컨텍스트란?
- Persistence를 객체의 관점으로 해석해 보자면 ‘객체가 생명(객체가 유지되는 시간)이나 공간(객체의 위치)을 자유롭게 유지하고 이동할수 있는 객체의 성질’을 의미한다.
- 영속성 컨텍스트를 좀 더 쉽게 표현해 보자면 Entity 객체를 효율적으로 쉽게 관리하기 위해 만들어진 공간이다.
영속성 컨텍스트의 기능
- 캐시 기능
- 영속성 컨텍스트는 내부적으로 캐시 저장소를 가지고 있다.
- 캐시 저장소는 Map 자료구조이다.
- key에는 @Id로 매핑한 기본키로 저장
- value는 해당 Entity 클래스의 객체를 저장
캐시 저장소에 조회하는 Id가 존재하지 않은 경우
@Test
@DisplayName("Entity 조회 : 캐시 저장소에 해당하는 Id가 존재하지 않은 경우")
void test2() {
try {
Memo memo = em.find(Memo.class, 1);
System.out.println("memo.getId() = " + memo.getId());
System.out.println("memo.getUsername() = " + memo.getUsername());
System.out.println("memo.getContents() = " + memo.getContents());
} catch (Exception ex) {
ex.printStackTrace();
} finally {
em.close();
}
emf.close();
}
DB에서 데이터를 조회만 하는 경우에 데이터의 변경이 발생하는 것이 아니기 때문에 트랜잭션이 없어도 조회가 가능하다.
Memo memo = em.find(Memo.class, 1); 호출 시 캐시 저장소에 해당 값이 존재하지 않기 때문에 DB에 SELECT 조회하여 캐시 저장소에 저장한 후 반환한다.
- 쓰기 지연 저장소(ActionQueue)
- JPA는 DB의 트랜잭션의 기능을 활용해 SQL를 모아서 한번에 DB에 반영한다.
@Test
@DisplayName("쓰기 지연 저장소 확인")
void test6() {
EntityTransaction et = em.getTransaction();
et.begin();
try {
Memo memo = new Memo();
memo.setId(2L);
memo.setUsername("Robbert");
memo.setContents("쓰기 지연 저장소");
em.persist(memo);
Memo memo2 = new Memo();
memo2.setId(3L);
memo2.setUsername("Bob");
memo2.setContents("과연 저장을 잘 하고 있을까?");
em.persist(memo2);
System.out.println("트랜잭션 commit 전");
et.commit();
System.out.println("트랜잭션 commit 후");
} catch (Exception ex) {
ex.printStackTrace();
et.rollback();
} finally {
em.close();
}
emf.close();
}
em > actionQueue를 확인해보면 insertions > executables에 Insert할 memo#2, memo#3 Entity 객체 2개가 들어가 있는 것을 확인할수 있다.
트랜잭션 commit 후
- 변경 감지(Dirty Checking)
- JPA는 영속성 컨텍스트에 Entity를 저장할 때 최초 상태(LoadedState)를 저장한다.
- 트랜잭션이 commit되고 em.flush(); 가 호출되면 Entity의 현재 상태와 저장한 최초 상태를 비교한다.
- 변경 내용이 있다면 Update SQL을 생성하여 쓰기 지연 저장소에 저장하고 모든 쓰기지연 저장소의 SQL을 DB에 요청한다.
- 마지막으로 DB의 트랜잭션이 commit 되면서 반영한다.
- 따라서 변경하고 싶은 데이터가 있다면 먼저 데이터를 조회하고 해당 Entity 객체의 데이터를 변경하면 자동으로 Update SQL이 생성되고 DB에 반영한다.
- JPA는 영속성 컨텍스트에 Entity를 저장할 때 최초 상태(LoadedState)를 저장한다.
@Test
@DisplayName("변경 감지 확인")
void test8() {
EntityTransaction et = em.getTransaction();
et.begin();
try {
System.out.println("변경할 데이터를 조회합니다.");
Memo memo = em.find(Memo.class, 4);
System.out.println("memo.getId() = " + memo.getId());
System.out.println("memo.getUsername() = " + memo.getUsername());
System.out.println("memo.getContents() = " + memo.getContents());
System.out.println("\n수정을 진행합니다.");
memo.setUsername("Update");
memo.setContents("변경 감지 확인");
System.out.println("트랜잭션 commit 전");
et.commit();
System.out.println("트랜잭션 commit 후");
} catch (Exception ex) {
ex.printStackTrace();
et.rollback();
} finally {
em.close();
}
emf.close();
}
- ntityInstance는 Entity 객체의 현재 상태이다.
- entityEntry > loadedState는 조회했을 때 즉, 해당 Entity의 최초 상태이다
- 트랜잭션 commit 후 em.flush(); 메서드가 호출되면 현재 상태와 최초 상태를 비교하고 변경이 있다면 Update SQL을 생성하여 쓰기 지연 저장소에 저장한 후 DB에 요청한다.
JPA의 트랜잭션
JPA는 DB의 트랜잭션 개념을 사용하여 효율적으로 Entity를 관리하고 있다.
@Test
@DisplayName("EntityTransaction 성공 테스트")
void test1() {
EntityTransaction et = em.getTransaction(); // EntityManager 에서 EntityTransaction 을 가져옵니다.
et.begin(); // 트랜잭션을 시작합니다.
try { // DB 작업을 수행합니다.
Memo memo = new Memo(); // 저장할 Entity 객체를 생성합니다.
memo.setId(1L); // 식별자 값을 넣어줍니다.
memo.setUsername("Robbie");
memo.setContents("영속성 컨텍스트와 트랜잭션 이해하기");
em.persist(memo); // EntityManager 사용하여 memo 객체를 영속성 컨텍스트에 저장합니다.
et.commit(); // 오류가 발생하지 않고 정상적으로 수행되었다면 commit 을 호출합니다.
// commit 이 호출되면서 DB 에 수행한 DB 작업들이 반영됩니다.
} catch (Exception ex) {
ex.printStackTrace();
et.rollback(); // DB 작업 중 오류 발생 시 rollback 을 호출합니다.
} finally {
em.close(); // 사용한 EntityManager 를 종료합니다.
}
emf.close(); // 사용한 EntityManagerFactory 를 종료합니다.
}
- et.begin();
- 트랜잭션을 시작하는 명령어이다
- et.commit();
- 트랜잭션의 작업들을 영구적으로 DB에 반영하는 명령어이다.
- et.rollback();
- 오류가 발생했을 때 트랜잭션의 작업을 모두 취소하고, 이전 상태로 되돌리는 명령어이다.
적용하면서 공부
build.gradle 적용
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
application.properties 적용
#create 테이블부터 다시 만들어서 진행하는 방법
#create-drop create와 대부분 같으며 끝날시 테이블 drop까지 하는 것
#update 검색시 기존에 없으면 생성 있으면 그대로 이용하는 갱신방법
#validate
#non 아무것도 하지 않는 것
spring.jpa.hibernate.ddl-auto=update
#sql 보여주기
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
#database 버전이 달라서 생기는 오류! 밑에 줄을 추가해줘야 에러 해결
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQLDialect
MySQLDialect 에러 해결 -> 링크
Repository 적용
import com.sparta.memo.entity.Memo;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface MemoRepository extends JpaRepository<Memo, Long> {
}
interface로 만들어서 JpaRepository를 상속받아야 하나보다...
메서드
save
public MemoResponseDto createMemo(MemoRequestDto requestDto) {
// RequestDto -> Entity
Memo memo = new Memo(requestDto);
// DB 저장
Memo saveMemo = memoRepository.save(memo);
// Entity -> ResponseDto
MemoResponseDto memoResponseDto = new MemoResponseDto(saveMemo);
return memoResponseDto;
}
findAll
public List<MemoResponseDto> getMemos() {
// DB 조회
return memoRepository.findAll().stream().map(MemoResponseDto::new).toList();
}
findById
private Memo findMemo(Long id) {
return memoRepository.findById(id).orElseThrow(() ->
new IllegalArgumentException("선택한 메모는 존재하지 않습니다.")
);
}
update
@Transactional
public Long updateMemo(Long id, MemoRequestDto requestDto) {
// 해당 메모가 DB에 존재하는지 확인
Memo memo = findMemo(id);
// memo 내용 수정
memo.update(requestDto);
return id;
}
- impleJpaRepository에 update라는 메서드는 존재하지 않는다.
- 따라서 영속성 컨텍스트 기능중 하나인 변경감지가 적용되기 위해 해당 메서드에 @Transactional을 추가했다.
delete
public Long deleteMemo(Long id) {
// 해당 메모가 DB에 존재하는지 확인
Memo memo = findMemo(id);
// memo 삭제
memoRepository.delete(memo);
return id;
}
마무리
모든 내용을 다 적기에는 무리가 있다....
강의에서 들은 내용을 일단 까먹지 않기 위해 이해한 부분만 작성했다...
다만 어떻게 말로 풀어야할지 몰라서 설명을 그대로 가져오긴 했다. ㅠㅠㅠ
'코딩 공부 > Spring' 카테고리의 다른 글
[Spring] PasswordEncoder 암호화 및 bcrypt 암호화 (0) | 2024.08.23 |
---|---|
[Spring] Naver Open API 사용방법 (0) | 2024.08.20 |
[Spring] JWT(Json Web Tokens)란? (0) | 2024.08.19 |
[Spring] @Valid 유효성 검사 , @ExceptionHandler , MethodArgumentNotValidException (0) | 2024.08.12 |
[Spring] Lombok이란? (0) | 2024.08.09 |