Post

[Spring] JPA

[Spring] JPA

2025.07.03 ~
멋쟁이사자처럼 세션 중 DTO, 연관관계 파트 이해하기 어려워 개인학습.
데이터베이스 미수강 상태. 중구난방한 개념정리.

JPA (Java Persistence API)

1
2
3
4
5
6
7
8
9
[ 순수 JPA ]
내가 직접 EntityManager를 사용해서
→ DB와 연결해서
→ 쿼리도 직접 짜고
→ 트랜잭션도 내가 관리

[ Spring Data JPA ]
interface만 만들면
→ Spring이 알아서 구현 + DB 연결 + 트랜잭션 관리

EntityManager 객체

  • DB와의 모든 상호작용을 책임지는 핵심 객체

JPQL의 실행 결과는 Entity 객체 인스턴스로 매핑되어 반환된다.

1
2
3
1.	JPA는 JPQL을 기반으로 대상 데이터베이스에 맞는 SQL로 변환한다.
2.	변환된 SQL이 실행되어, JDBC를 통해 ResultSet 형태로 결과를 수신한다.
3.	수신된 결과는 JPA 내부 매핑 전략에 따라 해당 Entity 클래스의 인스턴스로 자동 변환된다.
1
2
3
4
List<Member> result = em.createQuery(
    "SELECT m FROM Member m WHERE m.name = :name", Member.class)
    .setParameter("name", "민준")
    .getResultList();

JPQL은 엔티티 객체를 대상으로 쿼리
SQL은 데이터베이스 테이블을 대상으로 쿼리

영속성 컨텍스트

비영속 (new/transient) 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
객체를 생성한 상태(비영속)

영속 (managed) 영속성 컨텍스트에 관리되는 상태
객체를 저장한 상태(영속)

준영속 (detached) 영속성 컨텍스트에 저장되었다가 분리된 상태

  • 원래 영속 상태였던 객체를 더 이상 관리하지 않게 만든 상태
  • em.detach(entity)
  • em.clear() (전체 초기화)
  • em.close() (종료)

삭제 (removed)삭제된 상태

1
2
3
4
1.	애플리케이션이 시작될 때 EntityManagerFactory가 생성됨
2.	사용자 요청이 들어오면 → 해당 요청 전용 EntityManager 생성
3.	EntityManager는 내부적으로 커넥션 풀을 통해 DB와 통신
4.	요청이 끝나면 EntityManager는 종료/폐기됨

영속성 컨텍스트

  • JPA가 관리하는 엔티티 객체들을 메모리에 보관하고 추적하는 공간
  • 엔티티를 영구 저장하는 환경
  • 논리적인 개념
  • 엔티티 매니저를 통해서 영속성 컨텍스트에 접근

em.persist() 하면 바로 DB에 저장되는 게 아니라,
먼저 “영속성 컨텍스트”에 저장. (== 메모리 캐시에 올라감)

1
EntityManager.persist(entity);

영속성 컨텍스트를 통해서 Entity 를 영속화 한다는 뜻
persist 메서드는 db 에 저장하는 것이 아니라 entity 를 영속성 컨텍스트 라는 곳에 저장

1차 캐시란?
JPA는 한 번 조회한 엔티티는 캐시에 저장함
같은 ID로 다시 조회하면 DB 안 가고 캐시에서 반환

쓰기 지연(트랜잭션 지원)

1
2
3
4
5
6
em.persist(memberA);
em.persist(memberB);
// → INSERT SQL 실행 ❌ (아직 DB에 안 감)

em.getTransaction().commit(); 
// → 이때 실제 DB에 INSERT SQL 실행됨

변경 감지 (Dirty Checking)
em.find()로 조회한 영속 상태 엔티티의 필드를 수정하면
em.update() 없이도 자동으로 UPDATE SQL 실행됨

1
2
3
Member m = em.find(Member.class, "id1");
m.setName("변경된 이름"); // 자동 감지됨
em.getTransaction().commit(); // UPDATE 쿼리 날아감
flush = 메모리 ↔ DB를 동기화하는 행위
DB에 저장되는 시점:
	1.	em.flush() 직접 호출할 때
	2.	em.getTransaction().commit() 할 때 (자동 flush됨)
	3.	JPQL 실행 전 자동으로 flush됨

[비영속 객체 생성]
↓
em.persist()
↓
[영속성 컨텍스트 (1차 캐시) 등록]
↓
tx.commit() or flush()
↓
[DB에 SQL 실행됨]
↓
em.clear() or detach()
↓
[준영속 상태]

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# Entity Mapping
## 객체와 테이블 매핑
• 객체와 테이블 매핑: @Entity, @Table
• 필드와 컬럼 매핑: @Column
• 기본 키 매핑: @Id
• 연관관계 매핑: @ManyToOne,@JoinColumn

`@Entity`
entity 가 붙은 클래스는 JPA가 관리

운영 장비에는 절대 create, create-drop, update 사용❌

개발 초기에는 create or update
테스트 서버에는 update or validate
운영 서버에는 validate or none

# REST API
`API` Application Programming Interface
- 응용 프로그램에서 사용할 수 있도록 다른 응용 프로그램을 제어할 수 있게 만든 인터페이스.

`REST` Representational State Transfer
- 자원(DATA)의 이름으로 구분하여 해당 자원의 상태를 교환하는 것을 의미.
- HTTP URI 를 통해 자원을 명시하고 HTTP Method (Create, Read, Update, Delete) 를 통해 자원을 교환하는 것.

`REST API` REST 아키텍처의 조건을 준수하는 어플리케이션 프로그래밍 인터페이스
- Rest 기반으로 시스템을 분산하여 확장성과 재사용성을 높임

# Spring Annotation 정리
![](/assets/img/velog/8916fba7-4c88-4768-aa6a-374feba00f14/64e0072300e7f3ff74efe65003073a9c444a6fe2d45b7fc25593bc442b963c9a.png)

## Get API
**@RestController**
`@RestController` @Controller 에 @ResponseBody 가 결합된 어노테이션.

**@RequestMapping**
`@RequestMapping` URL을 매핑하여 경로를 설정하여 해당 메소드에서 처리.

![](/assets/img/velog/8916fba7-4c88-4768-aa6a-374feba00f14/764d0fa43faa8451b718e25bd792ef96e72f5b8b15e106c62546e6ec176e267f.png)

⬆️ 고전적인 방식

**@GetMapping**
`@GetMapping` 별도의 파라미터 없이 GET API 호출하는 경우 사용되는 방법.

**@PathVariable**
`
@PathVariable` GET 형식의 요청에서 파라미터를 전달하기 위해 URL에 값을 담아 요청하는 방법.
아래 방식은 {변수} 의 이름과 메소드의 매개변수와 일치시켜야함.
```java
@GetMapping(value = "/variable1/{variable}")
public String getVariable1(@PathVariable String variable) {
    return variable;
}
```
```java
// http://localhost:8080/api/v1/get-api/variable1/{String 값}
@GetMapping(value = "/variable2/{variable}")
public String getVariable2(@PathVariable("variable") String var) {
    return var;
}
```
**@RequestParam**
`@RequestParam` GET 형식의 요청에서 쿼리 문자열을 전달하기 위해 사용되는 방법
'?' 를 기준으로 우측에 {키}={값} 의 형태로 전달되며, 복수 형태로 전달할 경우 & 를 사용함. 
```
http://localhost:8080/api/v1/get-api/request1?name=flature&email=thinkground.flature@gmail.com&organization=thinkground
```
```java
@GetMapping(value = "/request1")
public String getRequestParam1(
        @RequestParam String name,
        @RequestParam String email,
        @RequestParam String organization) {
    return name + " " + email + " " + organization;
}
```
⬇️ 이 코드는 쿼리 파라미터가 몇 개가 들어올지 모를 때 전부 받아 처리하는 예시입니다.

http://localhost:8080/api/v1/get-api/request2?name=flature&email=thinkground.flature@gmail.com&organization=thinkground

````java
@GetMapping(value = "/request2")
public String getRequestParam2(@RequestParam Map<String, String> param) {
    StringBuilder sb = new StringBuilder();

    param.entrySet().forEach(map -> {
        sb.append(map.getKey() + " : " + map.getValue() + "\n");
    });

    return sb.toString();
}

Post API

리소스를 추가하기 위해 사용되는 API
@PostMapping
@PostMapping POST API 를 제작하기 위해 사용되는 어노테이션
일반적으로 추가하고자 하는 Resource 를 http body에 추가하여 서버에 요청
→ 그렇기 때문에 @RequestBody를 이용하여 body에 담겨있는 값을 받아야함.

1
2
3
4
5
6
7
8
9
10
11
@PostMapping("/member1")
public class PostController {
    // http://localhost:8080/api/v1/post-api/member
    public String postMember(@RequestBody Map<String, Object> postData) {
        StringBuilder sb = new StringBuilder();

        postData.entrySet().forEach(map -> {
                sb.append(map.getKey() + " : " + map.getValue() + "\n");
        });
        return sb.toString();
    }
1
2
3
4
@PostMapping(value = "/member2")
public String postMemberDto(@RequestBody MemberDTO memberDTO) {
    return memberDTO.toString();
}

🔗 GET ↔ POST 요청의 차이

🔹 GET은 데이터를 URL(주소)에 붙여서 전송

1
GET /api/user?name=minjun&email=test@gmail.com

→ @RequestParam, @PathVariable로 처리하는 게 기본 방식

🔹 POST는 데이터를 주소에 안 붙이고, HTTP Body에 담아서 전송.

1
2
3
4
POST /api/user
Content-Type: application/json

{ "name": "minjun", "email": "test@gmail.com" }

이 Body 데이터를 자바 객체로 변환해주는 게 바로 @RequestBody

1
2
3
4
@PostMapping("/user")
public String createUser(@RequestBody UserDTO user) {
    return user.getName();
}

Put API

해당 리소스가 존재하면 갱신하고, 리소스가 없을 경우에는 새로 생성해주는 API
업데이트를 위한 메서드.
기본적인 동작 방식은 Post API 와 동일.

Delete API

서버를 통해 리소스를 삭제 하기 위해 사용되는 API.
일반적으로 @PathVaraible 을 통해 리소스 ID 를 받아 처리.

Lombok

반복되는 메서드를 annotation 을 사용하여 자동으로 작성해주는 라이브러리.
일반적으로 VO, DTO, Model, Entity 등의 데이터 클래스에서 주로 사용됨.

@Getter
@Setter
@NoArgsContructor 파라미터가 없는 생성자를 생성
@AllArgsConstructor 모든 필드값을 파라미터로 갖는 생성자를 생성
@RequiredArgsConstructor 필드값 중 final 이나 @NotNull인 값을 갖는 생성자를 생성
@ToString toString 메서드를 자동으로 생성해주는 기능

이외에도 다른 에노테이션이 많지만 생략.

DTO, DAO, Repository, Entity

☑️ Entity

데이터베이스에 쓰일 컬럼과 여러 엔티티 간의 연관관계를 정의
데이터베이스의 테이블을 하나의 엔티티로 생각해도 무방함
실제 데이터베이스와 1:1 로 매핑됨

☑️ Repository

Entity 에 의해 생성된 데이터베이스에 접근하는 메서드를 사용하기 위한 인터페이스
Service 와 DB 를 연결하는 고리의 역할을 수행
데이터베이스에 적용하고자 하는 CRUD 를 정의하는 영역

☑️ DAO (Data Access Object)

데이터베이스에 접근하는 객체를 의미
Service 가 DB 에 연결할 수 있게 해주는 역할
DB 를 사용하여 데이터를 조회하거나 조작하는 기능을 전담

  • DAO는 Repository보다 더 기술적으로 유연하게 커스터마이징하고 싶을 때 쓰는 계층.
  • DAO는 “정말 복잡한 쿼리 작업을 따로 떼고 싶다” 싶을 때 쓰는 확장 계층.

① 간단한 구조

1
Client → Controller → Service → Repository → DB

DB 접근은 JpaRepository로 직접

② DAO를 따로 둔 구조 (복잡한 로직 분리용)

1
Client → Controller → Service → DAO → Repository → DB

DAO는 복잡한 DB 쿼리, 조건 등을 분리해서 담당
Service는 업무 흐름만 담당
이 구조는 MyBatis, JDBC 등에서 특히 유용함

☑️ DTO (Data transfer Object)

DTO 는 VO(Value Object)로 불리기도 하며, 계층간 데이터 교환을 위한 객체를 의미
VO 의 경우 Read Only 의 개념을 가지고 있음

☑️ toEntity(), toDto()

toEntity()와 toDto()는 DTO ↔ Entity 간의 변환을 담당하는 메서드

1
2
3
4
5
6
7
public ProductEntity toEntity() {
    return ProductEntity.builder()
  		.productName("제로콜라")  // 값 넣고
  		.productPrice(1800)       // 또 넣고
  		.productStock(10)         // 또 넣고
  		.build();                 // 이제 조립 완료 → 객체 생성
}

Builder는 “필드를 하나씩 지정해가면서, 마지막에 .build()로 객체를 만드는 방식”
.필드명() 필드를 하나씩 설정 (Builder 내부에 저장)
.build() 그동안 저장한 값으로 진짜 ProductEntity 객체를 생성

DTO → Entity 변환 코드엔 id가 없어도 @GeneratedValue를 사용 중이라면,
id는 DB가 자동으로 만들어주기 때문에 DTO에서 따로 넣지 않아도 됨

1
2
3
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@GeneratedValue: 값을 자동 생성하라는 의미
IDENTITY: DB가 자동 증가 시켜주는 방식 (MySQL의 AUTO_INCREMENT처럼)

1
2
3
4
5
6
7
[ProductDto]                   [ProductEntityBuilder]             [ProductEntity]
------------------            ------------------------           -------------------
productName = "아메리카노"  →  .productName("아메리카노")     →  생성된 진짜 객체
productPrice = 3500        →  .productPrice(3500)           →  build()로 완성
productStock = 50          →  .productStock(50)
                             →  .build() 호출 → 객체 리턴

이 메서드들은 일반적으로 “매핑 메서드(mapping method)”,
또는 “객체 변환 메서드(object conversion method)”라고 한다.

ORM (Object Relational Mapping)

어플리케이션의 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑해주는 것을 의미

Java의 데이터 클래스와 관계형 데이터베이스의 테이블을 매핑

객체지향 프로그래밍과 관계형 데이터베이스의 차이로 발생하는 제약사항을 해겨랳주는 역할을 수행

Spring Data Jpa

Spring Framework 에서 JPA를 편리하게 사용할 수 있게 지원하는 라이브러리

  • CRUD 처리용 인터페이스 제공
  • Repository 개발 시 인터페이스만 자것ㅇ하면 구현 객체를 동적으로 생성해서 주입
  • 데이터 접근 계층 개발시 인터페이스만 작성해도 됨

Hibernate 에서 자주 사용되는 기능을 조금 더 쉽게 사용할 수 있게 구현한 것

This post is licensed under CC BY 4.0 by the author.