[TIL] 스프링 입문 - JPA와 영속성 컨텍스트
Spring IoC 컨테이너와 Bean, 의존성 주입의 기본 개념을 정리한 TIL입니다.
For the English version of this post, see here.
오늘 할 일
공부한 내용
IoC Container와 Bean
- DI를 사용하기 위해 객체 생성이 우선되어야 함
- 하지만 언제, 어디서, 누가 객체를 생성할까? -> Spring 프레임워크가 필요한 객체를 생성하고 관리하는 역할을 대신 해줌
- Spring IoC 컨테이너
- ‘Bean’을 모아둔 컨테이너 Bean
- Spring이 관리하는 객체
- Spring ‘Bean’ 등록 방법
@Component: ‘Bean’으로 등록하고자하는 클래스 위에 설정- Spring 서버가 뜰 때 IoC 컨테이너에 ‘Bean’을 저장 해줌
- Spring ‘Bean’에 저장될 때 이름 -> 클래스의 앞글자만 소문자로 변경 ex)
public class MemoService->memoService로 저장됨
@ComponentScan: Spring 서버가 뜰 때@ComponentScan에 설정해 준 packages 위치와 하위 packages들을 전부 확인하여@Component가 설정된 클래스들을 ‘Bean’으로 등록해줌@SpringBootApplication에 의해 default 설정 되어있음 (~Application.java에 존재)
- Spring ‘Bean’ 사용 방법
@Autowired- 필드 위에 사용
1 2 3 4 5 6 7 8
@Component public class MemoService { @Autowired private MemoRepository memoRepository; // ... }
Spring에서 IoC 컨테이너에 저장된 memoRepository ‘Bean’을 해당 필드에 DI 즉, 의존성 주입 해줌
- ‘Bean’을 주입할 때 사용할 메서드 위에 ``` java @Component public class MemoService {
private final MemoRepository memoRepository;
@Autowired public MemoService(MemoRepository memoRepository) { this.memoRepository = memoRepository; }
1
// ... } ``` 객체의 불변성을 확보할 수 있기 떄문에 일반적으로 생성자를 사용하여 DI하는 것이 좋음
- 필드 위에 사용
@AutoWired적용 조건 Spring IoC 컨테이너에 의해 관리되는 ‘Bean’ 객체만 DI에 사용될 수 있음@AutoWired생략 조건- Spring 4.3 부터, 생성자 선언이 1개 일 때만 생략 가능
Lombok의
@RequiredArgsConstructor를 사용하면 다음과 같이 코딩 가능 ``` java @Component @RequiredArgsConstructor // final로 선언된 멤버 변수를 파라미터로 사용하여 생성자를 자동으로 생성합니다. public class MemoService {private final MemoRepository memoRepository;
// public MemoService(MemoRepository memoRepository) { // this.memoRepository = memoRepository; // }
1
...
} ```
- ApplicationContext
- BeanFactory 등을 상속하여 기능을 확장한 Container
- BeanFactory: ‘Bean’의 생성, 관계설정 등의 제어를 담당하는 IoC 객체
- 스프링 IoC 컨테이너에서 ‘Bean’을 수동으로 가져올 수 있음
- BeanFactory 등을 상속하여 기능을 확장한 Container
3 Layer Annotation
- Spring 3 Layer Annotation은 Controller, Service, Repository의 역할로 구분된 클래스들을 ‘Bean’으로 등록할 때 해당 ‘Bean’ 클래스의 역할을 명시하기위해 사용됨
@Controller,@RestController,@Service,@Repository- Spring 3 Layer Annotaiton은 모두
@Component가 추가돼있기 때문에,@Component가 아닌 3 Layer Annotation을 사용해 ‘Bean’으로 등록
JPA
- 기존처럼 DB를 직접 다룰 때
1 2 3 4 5
public class Memo { private Long id; private String username; private String contents; }
- SQL 의존적이라 변경에 취약
- SQL을 직접 수정해야하고, MemoResponseDto 객체에 값을 넣어주는 부분도 수정해야함
- ORM (Object-Relational Mapping)
- 객체와 DB의 관계를 매핑해주는 도구
- Object: 객체 지향 언어 (자바, 파이썬)
- Relational: 관계형 데이터베이스 (H2, MySQL)
- JPA (Java Persistence API)
- 자바 ORM 기술에 대한 표준 명세
- 애플리케이션과 JDBC 사이에서 동작되고 있음
- JPA를 사용하면 DB 연결 과정을 직접 개발하지 않아도 자동으로 처리해줌
- 객체를 통해 간접적으로 DB 데이터를 다룰 수 있기 때문에 매우 쉽게 DB 작업을 처리할 수 있음
Hibernate
- JPA는 표준 명세이고, 이를 실제 구현한 프레임워크 중 사실상 표준이 Hibernate
- SpringBoot에서는 기본적으로 하이버네이트 구현체를 사용 중
Entity
- Entity
- JPA에서 관리되는 클래스 = 즉, 객체를 의미함
- Entity 클래스는 DB의 테이블과 매핑되어 JPA에 의해 관리됨
- Entity 클래스 만들기
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
@Entity // JPA가 관리할 수 있는 Entity 클래스 지정 @Table(name = "memo") // 매핑할 테이블의 이름을 지정 public class Memo { @Id private Long id; // nullable: null 허용 여부 // unique: 중복 허용 여부 (false 일때 중복 허용) @Column(name = "username", nullable = false, unique = true) private String username; // length: 컬럼 길이 지정 @Column(name = "contents", nullable = false, length = 500) private String contents; }
-
@Entity: JPA가 관리할 수 있는 Entity 클래스로 지정할 수 있음@Entity(name="Memo"): Entity 클래스 이름을 지정할 수 있음 (default: 클래스명)- JPA가 Entity 클래스를 인스턴스화 할 때 기본 생성자를 사용하기 때문에 반드시 현재 Entity 클래스에서 기본 생성자가 생성되고 있는지 확인해야함
@Table: 매핑할 테이블을 지정해줌 -@Table(name="memo"): 매핑할 테이블의 이름을 지정할 수 있음 (default: Entity명)@Column(name="username", nullable=false, unique=true): 필드와 매핑할 테이블의 컬럼을 지정할 수 있음 (default: 객체의 필드명)@Id: 테이블의 기본 키를 지정해줌- 이 기본 키는 영속성 컨텍스트에서 Entity를 구분하고 관리할 때 사용되는 식별자 역할을 수행함 -> 기본 키 즉, 식별자 값을 넣어주지 않고 저장하면 오류 발생
@Id옵션만 설정하면 기본 키 값을 개발자가 직접 확인하고 넣어줘야 하는 불편함이 생김 ->@GeneratedValue옵션을 추가하면 기본 키 생성을 DB에 위임할 수 있음 ex)@GeneratedValue(strategy = GenerationType.IDENTITY)
영속성 컨텍스트
- 객체의 관점으로, ‘객체가 생명(객체가 유지되는 시간)이나 공간(객체의 위치)을 자유롭게 유지하고 이동할 수 있는 객체의 성질’
- Entity 객체를 효율적으로 쉽게 관리하기 위해 만들어진 공간
- JPA는 영속성 컨텍스트에 Entity 객체들을 저장하여 관리하면서 DB와 소통함
- EntityManager
- Entity를 관리하는 관리자
- 영속성 컨텍스트에 접근하여 Entity 객체들을 조작하기 위해서는 EntityManager가 필요함
- 개발자들은 EntityManager를 사용해 Entity를 저장하고 조회하고 수정하고 삭제할 수 있음
- EntityManager는 EntityManagerFactory를 통해 생성하여 사용할 수 있음 EntityManagerFactory
- 일반적으로 DB 하나에 하나만 생성되어 애플리케이션이 동작하는 동안 사용됨
- EntityManagerFactory를 만들기 위해서는 DB에 대한 정보를 전달해야함
EntityManagerFactory emf = Persistence.createEntityManagerFactory("memo");- 해당 코드를 호출하면 JPA는 persistence.xml 의 정보를 토대로 EntityManagerFactory를 생성합니다.
EntityManager em = emf.createEntityManager();- 코드를 호출하면 EntityManagerFactory를 사용하여 EntityManager를 생성할 수 있습니다.
JPA의 트랜잭션
- 트랜잭션
- DB 데이터들의 무결성과 정합성을 유지하기 위한 하나의 논리적 개념
- DB의 데이터들을 안전하게 관리하기 위해 생겨난 개념
- 여러 개의 SQL이 하나의 트랜잭션에 포함될 수 있음
- 이때, 모든 SQL이 성공적으로 수행이 되면 DB에 영구적으로 변경을 반영하지만 SQL 중 단 하나라도 실패한다면 모든 변경을 되돌림
- JPA는 DB의 이러한 트랜잭션 개념을 사용하여 효율적으로 Entity를 관리하고 있음
- 영속성 컨텍스트에 Entity 객체들을 저장했다고 해서 DB에 바로 반영되지는 않음
- DB에서 하나의 트랜잭션에 여러 개의 SQL을 포함하고 있다가 마지막에 영구적으로 변경을 반영하는 것처럼, JPA에서도 영속성 컨텍스트로 관리하고 있는 변경이 발생한 객체들의 정보를 쓰기 지연 저장소에 전부 가지고 있다가 마지막에 SQL을 한번에 DB에 요청해 변경을 반영함
- JPA에서 이러한 트랜잭션의 개념을 적용하기 위해서는 EntityManager에서 EntityTransaction을 가져와 트랜잭션을 적용할 수 있음
EntityTransaction et = em.getTransaction();- 해당 코드를 호출하여 EntityTransaction을 가져와 트랜잭션을 관리할 수 있습니다.
et.begin();: 트랜잭션을 시작하는 명령어입니다.et.commit();: 트랜잭션의 작업들을 영구적으로 DB에 반영하는 명령어입니다.et.rollback();: 오류가 발생했을 때 트랜잭션의 작업을 모두 취소하고, 이전 상태로 되돌리는 명령어입니다.
- 해당 코드를 호출하여 EntityTransaction을 가져와 트랜잭션을 관리할 수 있습니다.
영속성 컨텍스트의 기능
- 영속성 컨텍스트: Entity 객체를 효율적으로 쉽게 관리하기 위해 만들어진 공간
1차 캐시
- 영속성 컨텍스트는 내부적으로 캐시 저장소를 가지고 있음
- 저장하는 Entity 객체들이 1차 캐시 즉, 캐시 저장소에 저장된다는 의미
- 캐시 저장소는 Map 자료구조 형태로 되어있음
- key에는
@Id로 매핑한 기본 키 즉, 식별자 값을 저장 - value에는 해당 Entity 클래스의 객체를 저장
- 영속성 컨텍스트는 캐시 저장소 Key에 저장한 식별자값을 사용하여 Entity 객체를 구분하고 관리함
- key에는
- Entity 저장

em.persist(memo)메서드가 호출되면 memo Entity 객체를 캐시 저장소에 저장함
- Entity 조회
- 캐시 저장소에 조회하는 Id가 존재하지 않는 경우
a. 캐시 저장소 조회 em.find(Memo.class, 1);b. DB SELECT 조회 후 캐시 저장소에 저장em.find(Memo.class, 1);호출 시 캐시 저장소를 확인 한 후 해당 값이 없다면? -> DB SELECT 조회 후 해당 값을 캐시 저장소에 저장하고 반환함- DB에서 데이터를 조회만 하는 경우에는 데이터의 변경이 발생하는 것이 아니기 때문에 트랜잭션이 없어도 조회가 가능함
- 캐시 저장소에 조회하는 Id가 존재하는 경우
em.find(Memo.class, 1);호출 시 캐시 저장소에 식별자 값이 1이면서 Memo Entity 타입인 값이 있는지 조회함 -> 값이 있다면 해당 Entity 객체를 반환
- 캐시 저장소에 조회하는 Id가 존재하지 않는 경우
- 1차 캐시 사용의 장점
- DB 조회 횟수를 줄임
- 1차 캐시를 사용해 DB row 1개 당 객체 1개가 사용되는 것을 보장 (객체 동일성 보장)
- 같은 값을 조회하는 memo1과 memo2는 == 결과 true를 반환함 (Java에서는 == 연산자는 주소를 비교하게 되는데 캐시에 있는 객체 그대로를 반환하여 같은 값을 조회하기 때문에 결과로 true를 반환함)
- Entity 삭제

- 삭제할 Entity를 조회한 후 캐시 저장소에 없다면 DB에 조회해 저장함
em.remove(entity);호출 시 삭제할 Entity를 영속성 컨텍스트가 관리하는 MANAGED 상태에서 DELETED 상태로 만든 후 트랜잭션 commit 후 Delete SQL이 DB에 요청됨
쓰기 지연 저장소 (Action Queue)
- JPA는 트랜잭션처럼 SQL을 모아서 한번에 DB에 반영하는 것을 구현하기 위해 쓰기 지연 저장소를 만들어 SQL을 모아두고 있다가 트랜잭션 commit 후 한번에 DB에 반영함
flush()- 트랜잭션 commit 후 추가적인 동작이 있는데, 바로
em.flush();메서드의 호출임 - flush 메서드는 영속성 컨텍스트의 변경 내용을 DB에 반영하는 역할을 수행함
- 즉, 쓰기 지연 저장소의 SQL들을 DB에 요청하는 역할을 수행함
- flush 후 commit을 하게 되면, 이미 쓰기 지연 저장소의 SQL이 요청 되었기 때문에 더 이상 요청할 SQL이 없어 트랜잭션이 commit 된 후에 SQL 기록이 보이지 않음
- 트랜잭션 commit 후 추가적인 동작이 있는데, 바로
- Insert, Update, Delete 즉, 데이터 변경 SQL을 DB에 요청 및 반영하기 위해서는 트랜잭션이 필요함 (조회 제외)
변경 감지 (Dirty Checking)
- 영속성 컨텍스트에 저장된 Entity가 변경될 때마다 Update SQL이 쓰기 지연 저장소에 저장된다면 -> 하나의 Update SQL로 처리할 수 있는 상황을 여러번 Update SQL을 요청하게 되기 때문에 비효율적
- JPA에서의 Update 처리

- JPA는 영속성 컨텍스트에 Entity를 저장할 때 최초 상태(LoadedState)를 저장함
- 트랜잭션이 commit되고,
em.flush();가 호출되면 Entity의 현재 상태와 저장한 최초 상태를 비교함 - 변경 내용이 있다면 Update SQL을 생성하여 쓰기 지연 저장소에 저장하고 모든 쓰기 지연 저장소의 SQL을 DB에 요청
- 마지막으로 DB의 트랜잭션이 commit 되면서 반영됨
- 트랜잭션이 commit되고,
- 따라서 변경하고 싶은 데이터가 있다면 먼저 데이터를 조회하고 해당 Entity 객체의 데이터를 변경하면 자동으로 Update SQL이 생성되고 DB에 반영됨
- 이러한 과정을 변경 감지, Dirty Checking이라 부름
- JPA는 영속성 컨텍스트에 Entity를 저장할 때 최초 상태(LoadedState)를 저장함
(flush와 commit 실행순서)
- flush 발생 (SQL은 실행되지만, 아직 rollback 가능한 상태, DB에 반영은 됨(임시 상태) -> commit 할 때 자동으로 호출됨) -> update SQL 발생 -> DB에 보냄
- commit -> 트랜잭션 확정
Entity의 상태
- 비영속 (Transient)
Memo memo = new Memo();- new 연산자를 통해 인스턴스화 된 Entity 객체
- 아직 영속성 컨텍스트에 저장되지 않았기 때문에 JPA의 관리를 받지 않음
- 비영속 상태는 JPA가 관리하지 못하기 때문에 해당 객체의 테이터를 변경해도 변경 감지가 이루어지지 않음
- 영속 (Managed)
em.persist(memo)- persist(entity): 비영속 Entity를 EntityManager를 통해 영속성 컨텍스트에 저장하여 관리되고 있는 상태로 만듦
- 준영속 (Detached)
영속성 컨텍스트에 저장되어 관리되다가 분리된 상태를 의미함
영속 상태에서 준영속 상태로 바꾸는 방법
em.detach(memo);- detach(entity): 특정 Entity만 준영속 상태로 전환
- 영속성 컨텍스트에서 관리되다(Managed)가 분리된 상태(Detached)로 전환됨
- 준영속 상태로 전환되면 1차 캐시 즉, 캐시 저장소에서 제거되기 때문에 JPA의 관리를 받지 못해 영속성 컨텍스트의 어떠한 기능도 사용할 수 없음
em.contains(memo);는 해당 객체가 영속성 컨텍스트에 저장되어 관리되는 상태인지 확인하는 메서드
- detach(entity): 특정 Entity만 준영속 상태로 전환
em.clear();- clear(): 영속성 컨텍스트를 완전히 초기화함
- 영속성 컨텍스트의 모든 Entity를 준영속 상태로 전환
- 영속성 컨텍스트 틀은 유지하지만, 내용은 비워 새로 만든 것과 같은 상태가 됨 -> 따라서 계속해서 영속성 컨텍스트를 이용할 수 있음
- clear(): 영속성 컨텍스트를 완전히 초기화함
em.close();- close(): 영속성 컨텍스트를 종료함
- 해당 영속성 컨텍스트가 관리하던 영속성 상태의 Entity들은 모두 준영속 상태로 변경됨
- 영속성 컨테스트가 종료되었기 때문에 계속해서 영속성 컨텍스트를 사용할 수 없음
- close(): 영속성 컨텍스트를 종료함
준영속 상태에서 다시 영속 상태로 바꾸는 방법
em.merge(memo);- merge(entity): 전달받은 Entity를 사용하여 새로운 영속 상태의 Entity를 반환함
- 파라미터로 전달된 Entity의 식별자 값으로 영속성 컨텍스트를 조회함
- 해당 Entity가 영속성 컨텍스트에 없다면? a. DB를 새롭게 조회함 b. 조회한 Entity를 영속성 컨텍스트에 저장 c. 전달 받은 Entity의 값을 사용하여 병합 d. Update SQL이 수행됨 (수정)
- 만약 DB에도 없다면? a. 새롭게 생성한 Entity를 영속성 컨텍스트에 저장 b. Insert SQL이 수행됨 (저장)
- 파라미터로 전달된 Entity의 식별자 값으로 영속성 컨텍스트를 조회함
- 따라서, merge(entity) 메서드는 비영속, 준영속 모두 파라미터로 받을 수 있으며, 상황에 따라 ‘저장’을 할 수도 ‘수정’을 할 수도 있음
em.contains(memo)에서 비영속 상태의 memo는 merge() 호출 후에 해당 memo 객체가 영속성 컨텍스트에 저장된게 아니라, mergedMemo로 새롭게 생성되어 영속성 컨텍스트에 저장되었기 때문에 false가 반환됨- memo 객체는 준영속 상태이기 때문에 현재 영속성 컨텍스트에는 해당 객체가 존재하지 않음 -> 따라서 DB에서 식별자 값을 사용하여 조회한 후 영속성 컨텍스트에 저장하고 파라미터로 받아온 준영속 상태의 memo 객체의 값을 새롭게 저장한 영속 상태의 객체에 병합하고 반환함
- merge(entity): 전달받은 Entity를 사용하여 새로운 영속 상태의 Entity를 반환함
- 삭제 (Removed)
em.remove(memo); - remove(entity): 삭제하기 위해 조회해온 영속 상태의 Entity를 파라미터로 전달받아 삭제 상태로 전환함
(.find(): 캐시 저장소에 없다면, DB 조회 결과를 바로 1차 캐시에 넣음, 그리고 다음부터는 캐시에서 꺼냄)
SpringBoot의 JPA
- build.gradle
1 2
// JPA 설정 implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
- application.properties: Hibernate 설정 ``` groovy spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.show_sql=true spring.jpa.properties.hibernate.format_sql=true spring.jpa.properties.hibernate.use_sql_comments=true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
- Hibernate가 DB에 요청하는 모든 SQL을 보기좋게 출력해줌
- ddl-auto
- create: 기존 테이블 삭제 후 다시 생성 (DROP + CREATE)
- create-drop: create와 같으나 종료시점에 테이블을 DROP 함
- update: 변경된 부분만 반영
- validate: Entity와 테이블이 정상 매핑되었는지만 확인
- none: 아무것도 확인하지 않음
- SpringBoot 환경에서는 EntityManagerFactory와 EntityManager를 자동으로 생성해줌
- application.properties에 DB 정보를 전달해 주면 이를 토대로 EntityManagerFactory가 생성됨
- `@PersistenceConext` 어노테이션을 사용해 자동으로 생성된 EntityManager를 주입받아 사용할 수 있음
#### Spring의 트랜잭션
- Spring 프레임워크에서는 DB의 트랜잭션 개념을 애플리케이션에 적용할 수 있도록 트랜잭션 관리자를 제공함
- `@Transactional` 어노테이션을 클래스나 메서드에 추가하면 쉽게 트랜잭션 개념을 적용할 수 있음
- 메서드가 호출되면, 해당 메서드 내에서 수행되는 모든 DB 연산 내용은 하나의 트랜잭션으로 묶음
- 이때, 해당 메서드가 정상적으로 수행되면 트랜잭션을 커밋하고, 예외가 발생하면 롤백함
- 클래스에 선언한 `@Transactional`은 해당 클래스 내부의 모든 메서드에 트랜잭션 기능을 부여함
- 이때, save 메서드는 클래스 이외에 메서드에 따로 `@Transactional` 어노테이션이 추가되어있기 때문에 `readOnly = true` 옵션인 `@Transactional`을 덮어쓰게 되어 `readOnly = false` 옵션으로 적용됨 (조회만 하면 안되고, 변경을 해야하기 때문)
#### `@Transactional`
- JPA를 사용해 DB에 데이터를 저장, 수정, 삭제하려면 트랜잭션 적용이 반드시 필요함
- 조회 작업은 단순하게 데이터를 읽기만 하기 때문에 트랜잭션 적용이 필수는 아님
- 다만, 조회의 경우에도 트랜잭션 환경이 필요한 경우가 있을 수 있기 때문에
- 조회 작업 기능만 존재하는 메서드일 경우에만 `readOnly = true` 옵션이 설정된 `@Transactional`을 적용하면 좋음
#### 영속성 컨텍스트와 트랜잭션의 생명주기

- 스프링 컨테이너 환경에서는 영속성 컨텍스트와 트랜잭션의 생명주기가 일치함
- 트랜잭션이 유지되는 동안은 영속성 컨텍스트도 계속 유지가 되기 때문에 영속성 컨텍스트의 기능을 사용할 수 있음
**Spring은 어떻게 Service부터 Repository까지 Transaction을 유지할 수 있는 걸까**
- Spring 환경에서는 이러한 상황에서 트랜잭션을 제어할 수 있도록 **트랜잭션 전파** 기능을 제공함
#### 트랜잭션 전파
- `@Transactional`에서 트랜잭션 전파 옵션을 지정할 수 있음
`@Transactional(propagation = Propagation.REQUIRED)`
- 트랜잭션 전파의 기본 옵션은 **REQUIRED**임
- **REQUIRED** 옵션은 부모 메서드에 트랜잭션이 존재하면 자식 메서드의 트랜잭션은 부모의 트랜잭션에 합류하게됨
### Spring Data JPA

> **Spring Data JPA**
: JPA를 쉽게 사용할 수 있게 만들어놓은 모듈
- JPA를 추상화시킨 **Repository 인터페이스**를 제공함
- Repository 인터페이스는 Hibernate와 같은 JPA 구현체를 사용해 구현한 클래스를 통해 사용됨
#### Spring Data JPA의 SimpleJpaRepository
- Spring Data JPA에서는 JpaRepository 인터페이스를 구현하는 클래스를 자동으로 생성해줌
- Spring 서버가 뜰 때 JpaRepository 인터페이스를 상속받은 인터페이스가 자동으로 스캔되면,
- 해당 인터페이스의 정보를 토대로 자동으로 SimpleJpaRepository 클래스를 생성해주고, 이 클래스를 Spring 'Bean'으로 등록함
-> 인터페이스의 구현 클래스를 직접 작성하지 않아도 JpaRepository 인터페이스를 통해 JPA의 기능을 사용할 수 있음
#### Spring Data JAP 사용방법
- JpaRepository 등록
``` java
public interface MemoRepository extends JpaRepository<Memo, Long> {
}
JpaRepository<"@Entity 클래스", "@Id의 데이터 타입">를 상속받는 interface로 선언함- Spring Data JPA에 의해 자동으로 Bean 등록됨
- 제네릭스의
@Entity 클래스위치에 Memo Entity를 추가했기 때문에 해당 MemoRepository는 DB의 memo 테이블과 연결되어 CRUD 작업을 처리하는 인터페이스가 됨
메모장 프로젝트 Spring Data JPA 적용
- save
1 2 3 4 5 6 7 8 9 10 11 12
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
1 2 3 4
public List<MemoResponseDto> getMemos() { // DB 조회 return memoRepository.findAll().stream().map(MemoResponseDto::new).toList(); }
- 반환을 List 자료형으로 받기 때문에 변환해서 반환해줘야함
- findById
1 2 3 4 5
private Memo findMemo(Long id) { return memoRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("선택한 메모는 존재하지 않습니다.") ); }
- SimpleJpaRepository의 findById 메서드를 확인해보면, 반환 타입이 Optional인 것을 확인할 수 있음

- Optional
을 반환 타입으로 받고 추가적으로 null을 체크하거나 - 위 코드와 같이 orElseThrow를 사용해 반환 값이 null일 경우 예외를 던지도록 처리할 수 있음
- Optional 반환 타입은 값이 있을 수도 있고, 없을 수도 있다를 표현하는 박스이기 때문에, null인지 확인해줘야함
- update
1 2 3 4 5 6 7 8 9 10
@Transactional public Long updateMemo(Long id, MemoRequestDto requestDto) { // 해당 메모가 DB에 존재하는지 확인 Memo memo = findMemo(id); // memo 내용 수정 memo.update(requestDto); return id; }
- SimpleJpaRepository에 update라는 메서드는 존재하지 않음
- 따라서, 위 코드처럼 영속성 컨텍스트의 변경감지를 통해 update를 진행해야함
변경감지가 적용되기 위해 해당 메서드에
@Transactional을 추가함- delete
1 2 3 4 5 6 7 8 9
public Long deleteMemo(Long id) { // 해당 메모가 DB에 존재하는지 확인 Memo memo = findMemo(id); // memo 삭제 memoRepository.delete(memo); return id; }
- delete 메서드를 사용해 해당 Entity(데이터)를 테이블에서 삭제할 수 있음

- 파라미터로는 삭제하려는 entity 객체를 넣어주면 됨
- delete 메서드에
@Transactional이 적용되어있는 것을 확인할 수 있음




