11-12์ฃผ์ฐจ ์๋ฃ์ ๋ชจ๋ ํ ํฝ์ ๋ ์ฃผ์ ๊ฑธ์ณ ์ ๋ฆฌํ ํ์ต ๊ฒฝ๋ก.
1) 11์ฃผ์ฐจ โ JPA์ ์ ์ฒด์ ์์์ฑ ์ปจํ ์คํธ (์ฑ๊ธํค โ SQL Mapper โ ORM โ ์ํฐํฐ ๋งคํ โ ์์์ฑ ์ปจํ ์คํธ)
2) 12์ฃผ์ฐจ โ ์ฐ๊ด๊ด๊ณ์ ์ฑ๋ฅ ์ต์ ํ (4๊ฐ์ง ์ฐ๊ด๊ด๊ณ โ ํ๋ก์ โ N+1 ๋ฌธ์ โ CASCADE โ JPQL/QueryDSL)7์ฃผ์ฐจ์์ JPA์ ์ ๋ฌธ์ ๋ดค๋ค๋ฉด, 11-12์ฃผ์ฐจ๋ JPA์ ๋ชจ๋ ๋ฉ์ปค๋์ฆ์ ๊น์ด ํํค์น๋ค.
์๋ฐยทSpring ํ์ต์ ๋ ๋ค๋ฅธ ์ ์ ์ด๋ฉฐ, ์ค๋ฌด์์ ๊ฐ์ฅ ์์ฃผ ๋ง๋๋ ์์ญ์ด๋ค.
[Part A โ 11์ฃผ์ฐจ: JPA์ ์ ์ฒด์ ์์์ฑ ์ปจํ
์คํธ]
[Phase 1] ์ฑ๊ธํค + SQL Mapper์ ์ญ์ฌ
โ
[Phase 2] ORM๊ณผ JPA์ ๋ณธ์ง
โ
[Phase 3] ์ํฐํฐ์ ํ
์ด๋ธ ๋งคํ ๊ธฐ์ด
โ
[Phase 4] EntityManager + ์์์ฑ ์ปจํ
์คํธ
โ
[Phase 5] ์์์ฑ ์ปจํ
์คํธ์ 4๊ฐ์ง ์ฅ์ โ 11์ฃผ์ฐจ ์ ์
[Part B โ 12์ฃผ์ฐจ: ์ฐ๊ด๊ด๊ณ์ ์ฑ๋ฅ ์ต์ ํ]
[Phase 6] JPA ์ฐ๊ด๊ด๊ณ 4๊ฐ์ง (1:1, N:1, 1:N, N:M)
โ
[Phase 7] mappedBy์ ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ
โ
[Phase 8] ํ๋ก์์ ์ง์ฐ/์ฆ์ ๋ก๋ฉ
โ
[Phase 9] N+1 ๋ฌธ์ ์ ํด๊ฒฐ โ 12์ฃผ์ฐจ ์ ์
โ
[Phase 10] ์์์ฑ ์ ์ด + JPQL/QueryDSL
์ด 10 Phase ร 39 Unit โ 8-9์ฃผ์ฐจ์ ๋น์ทํ ๋ถ๋์ ํตํฉ ์ปค๋ฆฌํ๋ผ.
| ์ฃผ์ฐจ | ์ฃผ์ | ํต์ฌ ๋ณํ |
|---|---|---|
| 1์ฃผ์ฐจ | OOPยทJVMยทGCยท์ปฌ๋ ์ ยทI/O ๊ฐ๋ก | ์๋ฐ ํฐ ๊ทธ๋ฆผ |
| 2์ฃผ์ฐจ | JVM ๋ด๋ถยท๋ฐ์ดํธ์ฝ๋ยทG1 GC | "์ด๋ป๊ฒ ๋์๊ฐ๋" |
| 3์ฃผ์ฐจ | ์ปฌ๋ ์ ยท์ ๋ค๋ฆญยทํจ์ํ | ์๋ฐ ํํ๋ ฅ |
| 4์ฃผ์ฐจ | ๋ฉํฐ์ค๋ ๋ฉยท๋์์ฑยทExecutor | ๋์์ฑ ์ ๋ณต |
| 5์ฃผ์ฐจ | Atomic + Spring IoC/DI ์ ๋ฌธ | ์๋ฐ โ Spring ๋ค๋ฆฌ |
| 6์ฃผ์ฐจ | ํ ์คํธ + ์น ์ธํ๋ผ + DB ์ ๊ทผ ์งํ | Spring ์ค์ ํ๊ฒฝ |
| 7์ฃผ์ฐจ | JPA/ORM ์ ๋ฌธ + ํธ๋์ญ์ ์ถ์ํ | DB ์ถ์ํ ์ ๋ฌธ |
| 8์ฃผ์ฐจ | ํ๋ก์์ ์งํ | AOP ๋ฉ์ปค๋์ฆ |
| 9์ฃผ์ฐจ | Spring AOP ์ค์ + ํธ๋์ญ์ ์ ํ | AOP ์ค์ ํ์ฉ |
| 10์ฃผ์ฐจ | ํธ๋์ญ์ ์ ๋ฆฌ + ๋น ๋ผ์ดํ์ฌ์ดํด ํจ์ + ๊ฒฉ๋ฆฌ ์์ค | ํธ๋์ญ์ ๋ง๋ฌด๋ฆฌ |
| 11์ฃผ์ฐจ (์ง๊ธ) | JPA์ ์ ์ฒด์ ์์์ฑ ์ปจํ ์คํธ | JPA ๋ฉ์ปค๋์ฆ ์์ ์ดํด |
| 12์ฃผ์ฐจ (์ง๊ธ) | ์ฐ๊ด๊ด๊ณ + ์ฑ๋ฅ ์ต์ ํ (N+1 ๋ฑ) | JPA ์ค์ ํ์ฉ |
| Day | Phase | ํ์ต ๋ชฉํ |
|---|---|---|
| Week 1 (11์ฃผ์ฐจ) | ||
| 1์ผ์ฐจ | Phase 1 | ์ฑ๊ธํค + SQL Mapper ์ญ์ฌ |
| 2์ผ์ฐจ | Phase 2 | ORM/JPA ๋ณธ์ง + Hibernate |
| 3์ผ์ฐจ | Phase 3 | ์ํฐํฐ ๋งคํ + ์๋ฒ ๋๋ ํ์ |
| 4-5์ผ์ฐจ | Phase 4 | EntityManager + ์์์ฑ ์ปจํ ์คํธ + ์๋ช ์ฃผ๊ธฐ |
| 6-7์ผ์ฐจ | Phase 5 | 4๊ฐ์ง ์ฅ์ (โ ์ ์ ) |
| Week 2 (12์ฃผ์ฐจ) | ||
| 8-9์ผ์ฐจ | Phase 6 | 4๊ฐ์ง ์ฐ๊ด๊ด๊ณ |
| 10์ผ์ฐจ | Phase 7 | mappedBy์ ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ |
| 11์ผ์ฐจ | Phase 8 | ํ๋ก์์ ์ง์ฐ/์ฆ์ ๋ก๋ฉ |
| 12-13์ผ์ฐจ | Phase 9 | N+1 ๋ฌธ์ + ํด๊ฒฐ์ฑ (โ ์ ์ ) |
| 14์ผ์ฐจ | Phase 10 | CASCADE + JPQL + QueryDSL |
์ฌ์ ์ผ์ (21์ผ): Phase 5์ 9์ ๊ฐ +2์ผ. ์์์ฑ ์ปจํ ์คํธ์ N+1์ ์ง์ SQL ๋ก๊ทธ๋ฅผ ๋ณด๋ฉฐ ์ฒดํ.
๋ชฉํ: JPA๊ฐ ์ด๋ค ํ๋ฆ์ ์ฐ๋ฌผ์ธ์ง๋ฅผ SQL Mapper(iBatis/MyBatis)์์ ๋น๊ต๋ก ์ดํดํ๋ค.
์ ์ ์ง์: 5์ฃผ์ฐจ Phase 8 (์ฑ๊ธํค ๋น)
ํต์ฌ ๊ฐ๋
์ฑ๊ธํค ํจํด์ ์ ์:
"ํด๋์ค์ ์ธ์คํด์ค๊ฐ ๋จ ํ๋๋ง ์กด์ฌ ํ๋๋ก ๋ณด์ฅ + ์ ์ญ ์ ๊ทผ"
ํต์ฌ 3์์:
1. static ๋ณ์๋ก ์ ์ผ ์ธ์คํด์ค ์ ์ฅ
2. private ์์ฑ์๋ก ์ธ๋ถ ์์ฑ ์ฐจ๋จ
3. getInstance() ์ ์ ๋ฉ์๋๋ก ๋ฐํ
public class Singleton {
private static Singleton instance;
private Singleton() {} // private!
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
๋ํ์ ์ธ ์ฑ๊ธํค ์ฌ๋ก:
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 1.1, 4์ฃผ์ฐจ Phase 4, 10์ฃผ์ฐจ Phase 4
ํต์ฌ ์ํ
๋ฌธ์ :
ํด๊ฒฐ ๋ฐฉํฅ:
1. stateless ์ค๊ณ (๊ฐ์ฅ ๊ถ์ฅ) โ ๋ฉค๋ฒ ๋ณ์์ ๊ฐ๋ณ ์ํ๋ฅผ ๋์ง ์์
2. synchronized (์ฑ๋ฅ ๋น์ฉ)
3. Atomic (5์ฃผ์ฐจ Phase 2 โ CAS)
4. ThreadLocal (8์ฃผ์ฐจ Phase 1 โ ์ค๋ ๋๋ณ ๋
๋ฆฝ ์ ์ฅ์)
JPA์์์ ์ ์ฉ:
EntityManagerFactory = ์ฑ๊ธํค (์ ํ๋ฆฌ์ผ์ด์
์ ์ญ)EntityManager = ํธ๋์ญ์
๋จ์ (์ค๋ ๋๋ณ๋ก ๋ค๋ฆ)์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: 6์ฃผ์ฐจ Phase 7 (JdbcTemplate)
ํต์ฌ ๊ฐ๋
iBatis (Internet Based Abstraction for Tabular Information Systems):
MyBatis = iBatis์ ํ๊ณ์:
@Select, @Insert)<if>, <foreach>)JDBC vs MyBatis ๋น๊ต:
| JDBC ์ง์ | MyBatis | |
|---|---|---|
| SQL ์์ฑ | ์๋ฐ ์ฝ๋ ์ | XML ๋๋ ์ด๋ ธํ ์ด์ |
| ์์ ๊ด๋ฆฌ | ์๋ (try/finally) | ์๋ |
| ๊ฒฐ๊ณผ ๋งคํ | ์๋ (rs.getInt ๋ฑ) | ์๋ (resultType) |
| ์ฝ๋๋ | ๋ง์ | ์ ์ |
SQL Mapper์ ํ๊ณ:
์๊ธฐ ์ ๊ฒ
๋ชฉํ: ORM์ ํจ๋ฌ๋ค์๊ณผ JPA-Hibernate์ ๊ด๊ณ๋ฅผ ๋ช ํํ ์ก๋๋ค.
์ ์ ์ง์: Phase 1, 7์ฃผ์ฐจ Phase 2~3
ํต์ฌ ๋น๊ต
| SQL Mapper (MyBatis, JdbcTemplate) | ORM (JPA/Hibernate) | |
|---|---|---|
| ๋งคํ | SQL โ ๊ฐ์ฒด | ๊ฐ์ฒด โ ํ ์ด๋ธ |
| SQL ์์ฑ | ๊ฐ๋ฐ์ ์ง์ | JPA๊ฐ ์๋ ์์ฑ |
| ํ์ต ๊ณก์ | ๋ฎ์ | ๋์ |
| DBMS ์ข ์์ฑ | ์์ | ์ ์ |
| ๋ณต์ก ํต๊ณ ์ฟผ๋ฆฌ | ์์ ๋กญ๊ฒ | ์ด๋ ต (JPQL/๋ค์ดํฐ๋ธ ์ฟผ๋ฆฌ ํ์) |
SQL Mapper์ ์๊ฐ:
"SQL์ ์์ฑํ๊ณ , ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ฒด์ ์ฑ์์ฃผ์ธ์"
ORM์ ์๊ฐ:
"๊ฐ์ฒด๋ฅผ ๋ค๋ฃจ์ธ์. SQL์ ์ ๊ฐ ๋ง๋ค๊ฒ ์ต๋๋ค"
๊ฐ์ฒด-๊ด๊ณ ํจ๋ฌ๋ค์ ๋ถ์ผ์น:
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 2.1
ํต์ฌ ๊ตฌ์กฐ
JPA (Java Persistence API):
EntityManager, EntityManagerFactory ์ธํฐํ์ด์คJPA ๊ตฌํ์ฒด:
์์กด ๊ด๊ณ:
[Application Code]
โ
[JPA Interface] โ javax.persistence (๋๋ jakarta.persistence)
โ
[Hibernate] โ JPA ๊ตฌํ
โ
[JDBC]
โ
[DB]
ILIC ์ฌ๋ก:
spring-boot-starter-data-jpa ์์กด์ฑ ์ถ๊ฐSpring Data JPA์์ ๊ตฌ๋ถ:
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 2.2
ํต์ฌ ๋ถ์ผ์น 5๊ฐ์ง:
| ์ธก๋ฉด | ๊ฐ์ฒด (OOP) | ๊ด๊ณ DB |
|---|---|---|
| ๋ชจ๋ธ๋ง | ์ํ + ํ๋ | ํ๊ณผ ์ด |
| ์์ | ์์ | ์์ |
| ์ฐ๊ด | ์ฐธ์กฐ (order.member) | ์ธ๋ํค |
| ์๋ณ | ๊ฐ์ฒด ๋์ผ์ฑ (==) | PK |
| ๋ฐ์ดํฐ ํ์ | ํ๋ถ (List, Map) | ์ ํ์ |
๋ถ์ผ์น๋ฅผ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ (JPA์ ์ผ):
JPA์ ๋ณธ์ง:
"๊ฐ์ฒด์ RDB ์ฌ์ด์ ๋ฒ์ญ๊ธฐ"
์๊ธฐ ์ ๊ฒ
Booking ๊ฐ์ฒด์ bookings ํ
์ด๋ธ์ ๋งคํ์ด ์๋ํ๋๋ ์ด์ ์?๋ชฉํ: ์๋ฐ ๊ฐ์ฒด โ DB ํ ์ด๋ธ ๋งคํ์ ๊ธฐ๋ณธ ์ด๋ ธํ ์ด์ ์ ์์ ์ตํ๋ค.
์ ์ ์ง์: 7์ฃผ์ฐจ Phase 4
ํต์ฌ ๊ฐ๋
@Entity:
์ํฐํฐ์ ํ์ ์กฐ๊ฑด โญ :
@Id)final ํด๋์ค ์ฌ์ฉ ๋ถ๊ฐ (Hibernate๊ฐ ํ๋ก์ ๋ง๋ค์ด์ผ ํจ)enum, interface, inner class ๋ถ๊ฐfinal ์ฌ์ฉ ๋ถ๊ฐ๊ธฐ๋ณธ ์์ฑ์๊ฐ ์ ํ์ํ๊ฐ?:
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
public Member() {} // ๊ธฐ๋ณธ ์์ฑ์ ํ์!
public Member(String name) {
this.name = name;
}
}
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 3.1
ํต์ฌ ๊ฐ๋
๊ธฐ๋ณธ ๋งคํ:
Member ํด๋์ค โ member ํ
์ด๋ธ@Table ์ผ๋ก ๋ช
์:
@Entity
@Table(name = "MBR") // ์ค์ ํ
์ด๋ธ๋ช
์ MBR๋ก
public class Member {
// ...
}
@Column ๋งคํ (7์ฃผ์ฐจ Phase 4 ๋ณต์ต):
@Column(name = "user_name", length = 100, nullable = false)
private String userName;
Spring Boot์ ์๋ ๋ณํ (7์ฃผ์ฐจ Unit 4.5):
userName (camelCase)user_name (snake_case)์๊ธฐ ์ ๊ฒ
@Table ์ ์๋ตํ๋ฉด ์ด๋ป๊ฒ ๋๋๊ฐ?@SecondaryTable)์ ์ ์ง์: Unit 3.2
ํต์ฌ ๊ฐ๋
์๋ฒ ๋๋ ํ์ (Embedded Type):
์์ โ ์ฃผ์๋ฅผ ์๋ฒ ๋๋ ํ์ ์ผ๋ก:
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
protected Address() {} // ๊ธฐ๋ณธ ์์ฑ์ ํ์
public Address(String city, String street, String zipcode) {
this.city = city;
this.street = street;
this.zipcode = zipcode;
}
// Getter๋ง (๊ฐ ํ์
์ ๋ถ๋ณ ๊ถ์ฅ)
}
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@Embedded
private Address address; // ์๋ฒ ๋๋ ์ฌ์ฉ
}
DB ํ ์ด๋ธ ๊ฒฐ๊ณผ:
CREATE TABLE Member (
id BIGINT PRIMARY KEY,
name VARCHAR(255),
city VARCHAR(255), -- Address์ ํ๋
street VARCHAR(255),
zipcode VARCHAR(255)
);
@AttributeOverrides โ ๊ฐ์ ์๋ฒ ๋๋ ํ์
์ ์ฌ๋ฌ ๋ฒ ์ฌ์ฉ ์:
@Entity
public class Employee {
@Id @GeneratedValue
private Long id;
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "city", column = @Column(name = "home_city")),
@AttributeOverride(name = "street", column = @Column(name = "home_street")),
@AttributeOverride(name = "zipcode", column = @Column(name = "home_zipcode"))
})
private Address homeAddress;
@Embedded
@AttributeOverrides({...})
private Address workAddress;
}
๊ฐ ํ์ ์ ์์น:
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 3.3
ํต์ฌ ๊ฐ๋
spring.jpa.hibernate.ddl-auto ์ต์
:
| ์ต์ | ๋์ |
|---|---|
create | ์์ ์ ๊ธฐ์กด ํ ์ด๋ธ ์ญ์ + ์๋ก ์์ฑ |
create-drop | create + ์ข ๋ฃ ์ ์ญ์ |
update | ๋ณ๊ฒฝ ์ฌํญ๋ง ๋ฐ์ (์์ ํ์ง ์์) |
validate | ์คํค๋ง ๋งคํ๋ง ๊ฒ์ฆ (๋ณ๊ฒฝ ์์) |
none | ์๋ ์ฒ๋ฆฌ ์ ํจ |
โ ๏ธ ์ด์ ํ๊ฒฝ ์ ๋ ๊ธ์ง โญ :
create, create-drop, update โ ๋ฐ์ดํฐ ์์ค ์ํ๊ฐ๋ฐ ๋จ๊ณ๋ณ ๊ถ์ฅ:
create ๋๋ updatevalidatenone (Flyway/Liquibase ์ฌ์ฉ)SQL ๋ถ๋ฅ ์ฐธ๊ณ :
| ๋ถ๋ฅ | ์๋ฏธ | ๋ช ๋ น |
|---|---|---|
| DDL (Data Definition Language) | ๊ณจ๊ฒฉ ์ ์ | CREATE, ALTER, DROP, TRUNCATE |
| DML (Data Manipulation Language) | ๋ฐ์ดํฐ ์กฐ์ | SELECT, INSERT, UPDATE, DELETE |
| DCL (Data Control Language) | ๊ถํยทํธ๋์ญ์ | GRANT, REVOKE, COMMIT, ROLLBACK |
์๊ธฐ ์ ๊ฒ
๋ชฉํ: JPA์ ํต์ฌ ๋ฉ์ปค๋์ฆ โ ์์์ฑ ์ปจํ ์คํธ์ ์ ์ฒด์ ์ํฐํฐ ์๋ช ์ฃผ๊ธฐ๋ฅผ ์ดํดํ๋ค.
์ ์ ์ง์: Phase 1, 2
ํต์ฌ ๊ฐ๋
EntityManagerFactory (EMF):
EntityManager๋ฅผ ์์ฑํ๋ ํฉํ ๋ฆฌSpring Boot์์์ ๋์:
@SpringBootApplication ์คํ ์ ์๋ ์์ฑ์๋ฐ ํ์ค ์ฌ์ฉ (์ฐธ๊ณ ):
EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-pu");
Spring ํ๊ฒฝ (์ค๋ฌด):
@PersistenceUnit
private EntityManagerFactory emf; // ์๋ ์ฃผ์
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 4.1, 7์ฃผ์ฐจ Phase 7
ํต์ฌ ๊ฐ๋
EntityManager (EM):
Spring + @Transactional ์กฐํฉ:
ํต์ฌ ๋ฉ์๋:
persist(entity): ์์ํ (์ ์ฅ)find(Class, id): ์กฐํ (1์ฐจ ์บ์ ํ์ฉ)getReference(Class, id): ํ๋ก์ ๋ฐํ (Phase 8)remove(entity): ์ญ์ merge(entity): ์ค์์ โ ์์detach(entity): ์์ โ ์ค์์์ค์ โ ์ค๋ ๋ ์์ X:
"EntityManager๋ ๊ณต์ X โ ํธ๋์ญ์ ๋ง๋ค ๋ณ๋ ์ธ์คํด์ค"
์ด๊ฒ EntityManagerFactory(์ฑ๊ธํค)์์ ๊ฒฐ์ ์ ์ฐจ์ด.
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 4.2
ํต์ฌ ๊ฐ๋
"์ํฐํฐ ๋งค๋์ ๋ด๋ถ์์ ๋์ํ๋ ๋ฉ๋ชจ๋ฆฌ ๊ณต๊ฐ โ ์ํฐํฐ๋ฅผ ๋ณด๊ดํ๊ณ ๊ด๋ฆฌํ๋ ๊ณณ"
์์น:
EntityManager
โโโ ์์์ฑ ์ปจํ
์คํธ (Persistence Context)
โโโ 1์ฐจ ์บ์ (์ํฐํฐ ๋ณด๊ด)
์ญํ :
์ค์ ํต์ฐฐ:
"JPA์ ๋ชจ๋ ์ ๊ธฐํ ๋์์ ์์์ฑ ์ปจํ ์คํธ ์์์ ์ผ์ด๋๋ค"
์ด๊ฒ Phase 5์์ ์ค๋ช ํ 4๊ฐ์ง ์ฅ์ ์ ์ถ๋ฐ์ .
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 4.3
ํต์ฌ 4๊ฐ์ง ์ํ โญ :
new โpersist()โโ> [์์(Managed)] โโremove()โโ> [์ญ์ (Removed)]
[๋น์์] โ
(Transient) โ detach()
โ close()
โ clear()
โ
[์ค์์(Detached)]
โ
โ merge()
โ
[์์(Managed)]
4๊ฐ์ง ์ํ ์์ธํ:
| ์ํ | ์ ์ | ์์์ฑ ์ปจํ ์คํธ | DB |
|---|---|---|---|
| ๋น์์(Transient) | new๋ก ์์ฑ, ์์ํ X | โ | โ |
| ์์(Managed) | persist ๋๋ find๋ก ๊ด๋ฆฌ ์ค | โ | (์ปค๋ฐ ์ ๋ฐ์) |
| ์ค์์(Detached) | ์์์ด์์ผ๋ ๋ถ๋ฆฌ๋จ | โ | (์ด์ ๋ฐ์ดํฐ ์กด์ฌ) |
| ์ญ์ (Removed) | remove๋ก ์ญ์ ์์ | โ (์ ๊ฑฐ ํ์) | (์ปค๋ฐ ์ ์ญ์ ) |
์ํ ๋ณ๊ฒฝ ๋ฉ์๋:
persist(entity): ๋น์์ โ ์์find(Class, id): โ ์์remove(entity): ์์ โ ์ญ์ detach(entity): ์์ โ ์ค์์ (๊ฐ๋ณ)clear(): ๋ชจ๋ ์์ โ ์ค์์close(): EM ์ข
๋ฃ โ ๋ชจ๋ ์ค์์merge(entity): ์ค์์ โ ์์์ค์ํ ์ฐจ์ด โญ :
detach(): ์์์ฑ ์ปจํ
์คํธ์์๋ง ๋ถ๋ฆฌ โ DB๋ ๊ทธ๋๋กremove(): DB์์๋ ์ญ์ (์ปค๋ฐ ์)์๊ธฐ ์ ๊ฒ
๋ชฉํ: JPA๊ฐ ์ ๊ฐ๋ ฅํ์ง๋ฅผ ์์์ฑ ์ปจํ ์คํธ์ 4๊ฐ์ง ๋์ ๋ฉ์ปค๋์ฆ์ผ๋ก ์ดํดํ๋ค. ๋ฉด์ ยท์ค๋ฌด ๋จ๊ณจ.
์ ์ ์ง์: Phase 4
ํต์ฌ ๊ฐ๋
1์ฐจ ์บ์:
๋์ ํ๋ฆ:
EntityManager em = emf.createEntityManager();
User user1 = em.find(User.class, 1L); // โ DB ์กฐํ + 1์ฐจ ์บ์ ์ ์ฅ
User user2 = em.find(User.class, 1L); // โก 1์ฐจ ์บ์์์ ์ฆ์ ๋ฐํ (SQL ์คํ X)
1์ฐจ ์บ์์ 6๋จ๊ณ:
1. ์ฒซ ์กฐํ: 1์ฐจ ์บ์ ๋น์ด์์
2. DB์์ ์ํฐํฐ ์กฐํ
3. 1์ฐจ ์บ์์ ์ ์ฅ
4. ๊ฒฐ๊ณผ ๋ฐํ
5. ๊ฐ์ ์ํฐํฐ ์ฌ์กฐํ ์ 1์ฐจ ์บ์์์ ๋ฐํ
6. ๊ฐ์ฒด ๋์ผ์ฑ (==) ๋ณด์ฅ
๋ฒ์:
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 5.1
ํต์ฌ ๊ฐ๋
User user1 = em.find(User.class, 1L);
User user2 = em.find(User.class, 1L);
System.out.println(user1 == user2); // true!
์ ๊ฐ๋ฅํ๊ฐ:
๊ฐ์ฒด ๋๋ฑ์ฑ vs ๋์ผ์ฑ:
JPA๋ ํธ๋์ญ์ ์์์ ๋์ผ์ฑ์ ๋ณด์ฅํ๋ค.
์์:
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 5.2
ํต์ฌ ๊ฐ๋
์ฐ๊ธฐ ์ง์ฐ(Write-Behind):
persist() ํธ์ถ ์ ์ฆ์ INSERT ์ ํจ์์:
em.getTransaction().begin();
em.persist(memberA); // INSERT SQL ์ ๋ณด๋
em.persist(memberB); // INSERT SQL ์ ๋ณด๋
em.persist(memberC); // INSERT SQL ์ ๋ณด๋
// ์ฌ๊ธฐ๊น์ง DB๋ ์๋ฌด๊ฒ๋ ๋ชจ๋ฆ
em.getTransaction().commit();
// โ ์ปค๋ฐ ์ง์ ์ INSERT SQL 3๊ฐ๋ฅผ ํ๊บผ๋ฒ์ ๋ณด๋!
๋ด๋ถ ๋์:
ํจ๊ณผ:
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 5.3
ํต์ฌ ๊ฐ๋ โ JPA์ ๊ฐ์ฅ ์ ๊ธฐํ ๋์
em.getTransaction().begin();
Member member = em.find(Member.class, 1L); // ์์ ์ํ
member.setUsername("hi"); // setter ํธ์ถ๋ง!
member.setAge(10);
em.getTransaction().commit();
// โ UPDATE ์ฟผ๋ฆฌ ์๋ ์คํ!
em.update() ํธ์ถ X. setter๋ง ํธ์ถํ๋๋ฐ UPDATE ๊ฐ ์ผ์ด๋ฌ๋ค.
๋ด๋ถ ๋์ โ ์ค๋
์ท ๋น๊ต:
1. ์ํฐํฐ๊ฐ ์์์ฑ ์ปจํ
์คํธ์ ๋ค์ด์ฌ ๋ ์ค๋
์ท ์ ์ฅ (1์ฐจ ์บ์ ์)
2. setter ํธ์ถ โ ์ํฐํฐ ๋ณ๊ฒฝ
3. ์ปค๋ฐ ์์ ์ ์ค๋
์ท๊ณผ ํ์ฌ ์ํฐํฐ ๋น๊ต
4. ์ฐจ์ด๊ฐ ์์ผ๋ฉด โ UPDATE SQL ์๋ ์์ฑ
5. ์ฐ๊ธฐ ์ง์ฐ ์ ์ฅ์์ ์ถ๊ฐ
6. DB ๋ฐ์
๋น๊ต ํ๋ฆ:
[์กฐํ ์์ ] [์ปค๋ฐ ์์ ]
์ํฐํฐ: {name: "A", age: 20} ์ํฐํฐ: {name: "B", age: 25}
์ค๋
์ท: {name: "A", age: 20} ์ค๋
์ท: {name: "A", age: 20}
โ
[์ฐจ์ด ๊ฒ์ถ] โ UPDATE SQL ์์ฑ
์ค์ํ ํจ์ :
merge() ๋๋ ๋ค์ ์์ํ ํ์์๊ธฐ ์ ๊ฒ
Hibernate.unproxy(), readOnly)์ ์ ์ง์: Unit 5.4
ํ๋ฌ์(Flush):
"์์์ฑ ์ปจํ ์คํธ์ ๋ณ๊ฒฝ ๋ด์ฉ์ DB์ ๋ฐ์ํ๋ ์์ "
ํ๋ฌ์ ๋ฐ์ 3๊ฐ์ง ์์ :
1. em.flush() ์ง์ ํธ์ถ
2. ํธ๋์ญ์
์ปค๋ฐ ์ง์ (์๋)
3. JPQL ์ฟผ๋ฆฌ ์คํ ์ง์ (์๋)
3๋ฒ์ด ์ค์ํ ์ด์ :
ํ๋ฌ์ โ ์ปค๋ฐ:
2์ฐจ ์บ์ (๋ณด๋์ค):
Ehcache, Redis, Hazelcast)์ ๋ณต์ฌ๋ณธ์ ๋ฐํํ๋๊ฐ:
1์ฐจ vs 2์ฐจ ์บ์:
| 1์ฐจ ์บ์ | 2์ฐจ ์บ์ | |
|---|---|---|
| ๋ฒ์ | ํธ๋์ญ์ | ์ ํ๋ฆฌ์ผ์ด์ ์ ์ญ |
| ๊ธฐ๋ณธ ์ ๊ณต | โ | โ (๋ณ๋ ์ค์ ) |
| ๊ฐ์ฒด ๋ฐํ | ๊ฐ์ ์ธ์คํด์ค | ๋ณต์ฌ๋ณธ |
| ๋์์ฑ | ์์ | ๋ณต์ฌ๋ณธ์ผ๋ก ๊ฒฉ๋ฆฌ |
์๊ธฐ ์ ๊ฒ
๋ชฉํ: ๊ฐ์ฒด ์ฐธ์กฐ์ ์ธ๋ ํค๋ฅผ ์ด๋ป๊ฒ ๋งคํํ๋์ง 4๊ฐ์ง ๊ด๊ณ๋ก ์ ๋ฆฌํ๋ค.
์ ์ ์ง์: Phase 5
ํต์ฌ ๊ฐ๋
4๊ฐ์ง ์ฐ๊ด๊ด๊ณ:
@OneToOne)@ManyToOne) โ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ โญ@OneToMany)@ManyToMany) โ ์ค๋ฌด์์ ์ ์ ์๋จ๋ฐฉํฅ vs ์๋ฐฉํฅ:
ํต์ฌ ํต์ฐฐ:
"์๋ฐฉํฅ์ ์ฌ์ค ๋จ๋ฐฉํฅ 2๊ฐ"
๊ฐ์ฒด ์ ์ฅ: A โ B ์ B โ A ์ ๋ ์ฐธ์กฐ
DB ์ ์ฅ: ์ธ๋ ํค 1๊ฐ๋ก ์ถฉ๋ถ
โ ๊ทธ๋์ ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ ๊ฐ๋ ์ด ๋ฑ์ฅ (Phase 7)
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 6.1
ํต์ฌ ๊ฐ๋
์ธ์ ์ฌ์ฉ:
FK ์์น ๊ฒฐ์ :
๋จ๋ฐฉํฅ ๋งคํ:
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@OneToOne
@JoinColumn(name = "locker_id") // FK๋ Member ํ
์ด๋ธ์
private Locker locker;
}
@Entity
public class Locker {
@Id @GeneratedValue
private Long id;
}
์๋ฐฉํฅ ๋งคํ:
@Entity
public class Locker {
@Id @GeneratedValue
private Long id;
@OneToOne(mappedBy = "locker") // ์ฃผ์ธ์ด ์๋
private Member member;
}
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 6.2
ํต์ฌ ๊ฐ๋
๊ฐ์ฅ ์์ฃผ ๋ฑ์ฅํ๋ ๊ด๊ณ:
FK ์์น:
@ManyToOne โญ@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne(fetch = FetchType.LAZY) // ์ง์ฐ ๋ก๋ฉ ๊ถ์ฅ!
@JoinColumn(name = "team_id") // FK๋ Member์
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
}
โ ๏ธ ์ฃผ์ ์ฌํญ:
1. FK๋ N์ชฝ์ โ ํญ์
2. fetch = FetchType.LAZY ๊ถ์ฅ โ N+1 ๋ฐฉ์ง (Phase 9)
3. ์๋ชป๋ ์๋ฐฉํฅ ์ค์ ์ ๋ฌดํ ๋ฃจํ โ toString, JSON ์ง๋ ฌํ ์
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 6.3
ํต์ฌ ๊ฐ๋
๋ณดํต ์๋ฐฉํฅ์์ N:1 + 1:N ์กฐํฉ์ผ๋ก ์ฌ์ฉ:
mappedBy (์ฝ๊ธฐ ์ ์ฉ)@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "team") // ์ฃผ์ธ์ด ์๋!
private List<Member> members = new ArrayList<>();
}
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "team_id") // ์ฃผ์ธ (FK ๊ด๋ฆฌ)
private Team team;
}
@OneToMany ๊ฐ ์ฃผ์ธ์ด ๋๋ ๊ฒฝ์ฐ๋?:
โ ๏ธ ์ฃผ์ ์ฌํญ:
mappedBy ์์ผ๋ฉด โ ์กฐ์ธ ํ
์ด๋ธ ์๋ ์์ฑ (๋นํจ์จ)@ManyToOne ์ชฝ์ด ์ฃผ์ธ์ด์ด์ผ ํจ์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 6.4
ํต์ฌ ๊ฐ๋
N:M์ ์ง์ ์ฌ์ฉ โ ๋น์ถ์ฒ:
@ManyToMany
@JoinTable(name = "MEMBER_PRODUCT")
private List<Product> products = new ArrayList<>();
์ ์ ์ข์๊ฐ:
์ค๊ฐ ์ํฐํฐ ํจํด (์ค๋ฌด ํ์ค):
@Entity
public class MemberProduct { // ๋๋ Order๋ก ์ด๋ฆ ๋ณ๊ฒฝ
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product;
private int amount; // ๋ถ๊ฐ ์ ๋ณด!
private int price; // ๋ถ๊ฐ ์ ๋ณด!
private LocalDateTime orderDateTime; // ๋ถ๊ฐ ์ ๋ณด!
}
์์น:
"N:M์ ํญ์ 1:N + N:1๋ก ํ์ด๋ผ"
์๋ฏธ ๋ถ์ฌ:
MemberProduct โ Order ๊ฐ์ด ๋น์ฆ๋์ค ์๋ฏธ ์๋ ์ด๋ฆ์ผ๋ก ๋ณ๊ฒฝ์๊ธฐ ์ ๊ฒ
๋ชฉํ: ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ์ ๊ฐ์ฅ ์ด๋ ค์ด ๊ฐ๋ ์ ์ ๋ฆฌํ๋ค.
์ ์ ์ง์: Phase 6
ํต์ฌ ๋ฌธ์
์๋ฐฉํฅ์ ๊ฐ์ฒด ์
์ฅ: A โ B + B โ A (2๊ฐ ์ฐธ์กฐ)
DB ์
์ฅ: FK 1๊ฐ๋ก ํํ
๋ฌธ์ :
ํด๊ฒฐ:
"ํ์ชฝ๋ง ์ฃผ์ธ ์ผ๋ก ์ ํ๊ณ , ๊ทธ ์ชฝ๋ง DB FK ๊ด๋ฆฌ"
๋ค๋ฅธ ์ชฝ์ ์ฝ๊ธฐ ์ ์ฉ
๊ท์น:
@ManyToOne ์ชฝ)mappedBy์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 7.1
ํต์ฌ ๊ฐ๋
mappedBy:
@OneToMany(mappedBy = "team")
// โ Member ์ํฐํฐ์ team ํ๋๋ฅผ ๊ฐ๋ฆฌํด
private List<Member> members;
์๋ฏธ:
"๋๋ ์ฃผ์ธ์ด ์๋๊ณ , ์ ์ชฝ(Member.team)์ด ์ฃผ์ธ์ด๋ค"
JPA๊ฐ ํ๋ ์ผ:
์์ฃผ ํ๋ ์ค์:
์ฐ๊ด๊ด๊ณ ํธ์ ๋ฉ์๋ (์ค๋ฌด ํ์ค):
@Entity
public class Member {
public void setTeam(Team team) {
this.team = team;
team.getMembers().add(this); // ์์ชฝ ๋๊ธฐํ!
}
}
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 7.2
ํต์ฌ ํจํด
๊ฐ์ฅ ํํ ์๋ฐฉํฅ โ ํ์๊ณผ ํ:
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team; // ์ฃผ์ธ (FK ๊ด๋ฆฌ)
// ํธ์ ๋ฉ์๋
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "team") // ์ฃผ์ธ X
private List<Member> members = new ArrayList<>();
}
5๊ฐ์ง ๊ถ์ฅ ์ฌํญ:
1. โ
N์ชฝ์ด ์ฃผ์ธ (@ManyToOne)
2. โ
fetch = FetchType.LAZY ๋ช
์
3. โ
์ปฌ๋ ์
์ ํ๋ ์ด๊ธฐํ (new ArrayList<>())
4. โ
ํธ์ ๋ฉ์๋๋ก ์์ชฝ ๋๊ธฐํ
5. โ
Lombok @ToString ์์ ์ปฌ๋ ์
์ ์ธ (๋ฌดํ ๋ฃจํ ๋ฐฉ์ง)
์๊ธฐ ์ ๊ฒ
๋ชฉํ: JPA์ ํ๋ก์ ๋ฉ์ปค๋์ฆ๊ณผ ๋ก๋ฉ ์ ๋ต โ 8-9์ฃผ์ฐจ ํ๋ก์์ ๋ค๋ฅธ ๋งฅ๋ฝ์ ํ๋ก์.
์ ์ ์ง์: Phase 5
ํต์ฌ ์ฐจ์ด:
em.find() | em.getReference() | |
|---|---|---|
| ๋ฐํ | ์ค์ ์ํฐํฐ | ํ๋ก์ ๊ฐ์ฒด |
| SQL ์คํ | ์ฆ์ | ์ง์ฐ (ํ๋ ์ ๊ทผ ์) |
| ํ์ | User | User$Proxy1 (CGLIB) |
์์:
User user = em.find(User.class, 1L); // ์ฆ์ SELECT
System.out.println(user.getName()); // SQL ์คํ X (์ด๋ฏธ ๋ก๋๋จ)
User proxyUser = em.getReference(User.class, 1L); // SQL ์ ํจ
System.out.println(proxyUser.getName()); // ์ฌ๊ธฐ์ SELECT ์คํ
ํ๋ก์ ๊ฐ์ฒด ํ์ธ:
System.out.println(proxyUser.getClass().getName());
// ์ถ๋ ฅ: com.sun.proxy.$Proxy1
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 8.1
ํต์ฌ ๋์ ํ๋ฆ:
Step 1 โ getReference() ํธ์ถ:
User proxyUser = em.getReference(User.class, 1L);
// ํ๋ก์ ๊ฐ์ฒด ๋ฐํ
// proxyUser.target = null (์ค์ ์ํฐํฐ ๋ฏธ๋ก๋)
Step 2 โ ํ๋ ์ ๊ทผ ์์ :
proxyUser.getName();
// โ ์ด ์๊ฐ:
// 1. ์์์ฑ ์ปจํ
์คํธ์ ์ค์ ์ํฐํฐ ๋ก๋ ์์ฒญ
// 2. DB SELECT ์คํ
// 3. ์์์ฑ ์ปจํ
์คํธ์ ์ค์ ์ํฐํฐ ๋ณด๊ด
// 4. proxyUser.target = ์ค์ ์ํฐํฐ ์ฐ๊ฒฐ
// 5. proxyUser.target.getName() ํธ์ถ
ํ๋ก์์ ํน์ง:
โ ๏ธ ์ฃผ์ โ ๋น๊ต ์ instanceof ์ฌ์ฉ:
if (user instanceof User) { ... } // ํ๋ก์๋ ์กํ โ
if (user.getClass() == User.class) { ... } // ํ๋ก์๋ false โ
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 8.2
ํต์ฌ ๊ฐ๋
์ง์ฐ ๋ก๋ฉ(Lazy Loading):
@Entity
public class Member {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
}
๋์ ์๋๋ฆฌ์ค:
Member member = em.find(Member.class, 1L);
// SELECT * FROM member WHERE id = 1;
// member.team = ํ๋ก์ ๊ฐ์ฒด (Team$Proxy1)
System.out.println(member.getTeam()); // SQL X (ํ๋ก์๋ง ์ถ๋ ฅ)
System.out.println(member.getTeam().getName());
// โ ์ด ์์ ์ SELECT * FROM team WHERE id = ?;
์ธ์ ์ฌ์ฉ:
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 8.3
ํต์ฌ ๊ฐ๋
์ฆ์ ๋ก๋ฉ(Eager Loading):
@ManyToOne(fetch = FetchType.EAGER)
private Team team;
Member member = em.find(Member.class, 1L);
// SELECT m.*, t.* FROM member m JOIN team t ON ... WHERE m.id = 1;
// ๋๋: SELECT * FROM member WHERE id = 1; + SELECT * FROM team WHERE id = ?;
์ฐ๊ด๊ด๊ณ๋ณ ๊ธฐ๋ณธ๊ฐ (์ธ์์ผ ํจ) โญ :
| ๊ด๊ณ | ๊ธฐ๋ณธ fetch |
|---|---|
@ManyToOne | EAGER (์ํ!) |
@OneToOne | EAGER (์ํ!) |
@OneToMany | LAZY |
@ManyToMany | LAZY |
โ ๏ธ ์ฆ์ ๋ก๋ฉ์ ๋ฌธ์ โญ :
์ค๋ฌด ๊ฒฐ๋ก :
"๋ชจ๋ ์ฐ๊ด๊ด๊ณ๋ฅผ LAZY๋ก ๋ช ์"
EAGER๊ฐ ํ์ํ๋ฉด fetch join (Phase 9)
@ManyToOne(fetch = FetchType.LAZY) // ํญ์ ๋ช
์!
@OneToOne(fetch = FetchType.LAZY) // ํญ์ ๋ช
์!
์๊ธฐ ์ ๊ฒ
๋ชฉํ: ์ค๋ฌด์์ ๊ฐ์ฅ ์์ฃผ ๋ง๋๋ JPA ์ฑ๋ฅ ๋ฌธ์ ๋ฅผ 4๋จ๊ณ๋ก ํด๊ฒฐํ๋ค.
์ ์ ์ง์: Phase 8
ํต์ฌ ์ ์
N+1 ๋ฌธ์ :
"ํ ๋ฒ์ ์ฟผ๋ฆฌ(1)๋ฅผ ์คํํ ํ, ์ถ๊ฐ๋ก N๊ฐ์ ์ฟผ๋ฆฌ ๊ฐ ์คํ๋๋ ๋ฌธ์ "
์ฆ์ ๋ก๋ฉ(EAGER) ์ ๋ฐ์:
// User์ @OneToMany(fetch = EAGER) private Set<Article> articles;
List<User> users = em.createQuery("SELECT u FROM User u").getResultList();
์คํ๋๋ SQL:
-- (1) User 100๋ช
์กฐํ
SELECT * FROM user;
-- (N=100) User๋ง๋ค article ์กฐํ
SELECT * FROM article WHERE user_id = 1;
SELECT * FROM article WHERE user_id = 2;
SELECT * FROM article WHERE user_id = 3;
... (100๋ฒ)
โ ์ด 101๊ฐ์ ์ฟผ๋ฆฌ!
์ง์ฐ ๋ก๋ฉ(LAZY) ์์๋ ๋ฐ์ ๊ฐ๋ฅ:
List<User> users = em.createQuery("SELECT u FROM User u").getResultList();
// (1) User 100๋ช
์กฐํ โ LAZY๋ผ์ article์ ์ ๊ฐ์ ธ์ด
for (User user : users) {
System.out.println(user.getArticles().size());
// โ getter ํธ์ถ ์์ ์ 100๋ฒ SQL ์คํ!
}
โ ๊ฒฐ๊ตญ ๊ฐ์ N+1 ๋ฌธ์
ํต์ฌ ํต์ฐฐ:
"๋จ์ํ LAZY๋ก ๋ฐ๊พผ๋ค๊ณ ํด๊ฒฐ ์ ๋จ. JPQL/getter ํจํด ์์ฒด ๊ฐ ๋ฌธ์ "
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 9.1
ํต์ฌ ์ฐจ์ด โญ :
์ผ๋ฐ JOIN โ N+1 ํด๊ฒฐ ์ ๋จ:
List<Member> members = em.createQuery(
"SELECT m FROM Member m JOIN m.team t", Member.class)
.getResultList();
์คํ๋๋ SQL:
SELECT m.* -- โ Member๋ง SELECT, Team์ ๋ฏธํฌํจ
FROM Member m
JOIN Team t ON m.team_id = t.id;
โ Team์ ์์์ฑ ์ปจํ
์คํธ์ ์ ๋ค์ด์ด โ getTeam() ํธ์ถ ์ ์ถ๊ฐ SQL โ N+1 ์ฌ์ !
FETCH JOIN โ ํ ๋ฒ์ ํด๊ฒฐ โญ :
List<Member> members = em.createQuery(
"SELECT m FROM Member m JOIN FETCH m.team", Member.class)
.getResultList();
์คํ๋๋ SQL:
SELECT m.*, t.* -- โ Team๋ ํจ๊ป SELECT!
FROM Member m
JOIN Team t ON m.team_id = t.id;
โ Team๋ ์์์ฑ ์ปจํ
์คํธ์ ๋ค์ด์ด โ getTeam() SQL X โ N+1 ํด๊ฒฐ!
JOIN vs FETCH JOIN ์ ๋ฆฌ:
| JOIN | FETCH JOIN | |
|---|---|---|
| ๊ฒฐ๊ณผ | Member๋ง | Member + Team |
| ์์์ฑ ์ปจํ ์คํธ | Member๋ง | ๋ ๋ค |
| getter ์ ์ถ๊ฐ ์ฟผ๋ฆฌ | ๋ฐ์ | ์์ |
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 9.2
ํต์ฌ ํจ์ โญ :
@ManyToOne + ํ์ด์ง โ ์ ์ ๋์:
List<Member> members = em.createQuery(
"SELECT m FROM Member m JOIN FETCH m.team ORDER BY m.id", Member.class)
.setFirstResult(0)
.setMaxResults(3) // โ
์ ์
.getResultList();
SELECT m.*, t.* FROM Member m JOIN Team t ON ... ORDER BY m.id LIMIT 3;
โ Member ๊ธฐ์ค ํ์ด์ง OK
@OneToMany + ํ์ด์ง โ ๋ฐ์ดํฐ ์กฐํ ๋ฌธ์ ๋ฐ์ โญ :
List<Team> teams = em.createQuery(
"SELECT t FROM Team t JOIN FETCH t.members", Team.class)
.setFirstResult(0)
.setMaxResults(3) // โ ์ํ!
.getResultList();
๋ฌด์์ด ๋ฌธ์ ์ธ๊ฐ:
-- ์๋: Team 3๊ฐ
-- ์ค์ SQL:
SELECT t.*, m.* FROM Team t JOIN Member m ON t.id = m.team_id LIMIT 3;
LIMIT 3 ๊ฐ JOIN ๊ฒฐ๊ณผ์ ํ ์ ์ ์ ์ฉ๋จ!
Hibernate์ ๋์:
firstResult/maxResults specified with collection fetch; applying in memory!์ ์ด ๋ฌธ์ ๊ฐ ์ผ์ด๋๋๊ฐ:
"OneToMany๋ 1ํ์ด Nํ์ผ๋ก ๋์ด๋จ. ํ์ด์ง์ ๋จ์๊ฐ ๊นจ์ง"
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 9.3
ํต์ฌ ํด๊ฒฐ
@BatchSize:
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
@BatchSize(size = 10) // ์ต๋ 10๊ฐ์ฉ ๋ฌถ์ด์
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
private List<Member> members;
}
ํ์ด์ง + @BatchSize ์ฌ์ฉ:
List<Team> teams = em.createQuery(
"SELECT t FROM Team t ORDER BY t.id", Team.class)
.setFirstResult(0)
.setMaxResults(3) // โ
Team ๊ธฐ์ค ์ ํํ ํ์ด์ง
.getResultList();
for (Team team : teams) {
team.getMembers().size();
}
์คํ๋๋ SQL:
-- (1) Team ํ์ด์ง ์กฐํ
SELECT id FROM Team ORDER BY id LIMIT 3;
-- (2) IN ์ ๋ก ํ ๋ฒ์ ๋ฉค๋ฒ ์กฐํ
SELECT * FROM Member WHERE team_id IN (1, 2, 3);
โ 2๋ฒ์ ์ฟผ๋ฆฌ๋ก ๋! (1+N โ 2 ๋๋ 1+N/๋ฐฐ์น์ฌ์ด์ฆ)
์ ์ญ ์ค์ :
spring:
jpa:
properties:
hibernate:
default_batch_fetch_size: 100
โ ๏ธ ์ฃผ์ โ fetch join๊ณผ ํจ๊ป ์ฐ์ง ๋ง ๊ฒ:
3๊ฐ์ง ํด๊ฒฐ์ฑ ๋น๊ต โญ :
| ๋๊ตฌ | ์ ํฉ ์ํฉ |
|---|---|
| fetch join | ManyToOne ๋จ๊ฑด, ํ์ด์ง ์๋ ์ปฌ๋ ์ |
| @BatchSize | OneToMany + ํ์ด์ง, ์ผ๋ฐ LAZY ์ต์ ํ |
| EntityGraph | ๋์ ์ผ๋ก fetch ์ ๋ต ๊ฒฐ์ |
์๊ธฐ ์ ๊ฒ
๋ชฉํ: JPA ํ์ต์ ๋ง๋ฌด๋ฆฌํ๋ 4๊ฐ์ง ๋๊ตฌ.
์ ์ ์ง์: Phase 5, 6
ํต์ฌ ๊ฐ๋
CASCADE:
@Entity
public class Parent {
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> children = new ArrayList<>();
}
CASCADE ์ต์ :
| ์ต์ | ๋์ |
|---|---|
ALL | ๋ชจ๋ ์์ ์ ํ |
PERSIST | ์ ์ฅ๋ง ์ ํ |
REMOVE | ์ญ์ ๋ง ์ ํ |
MERGE | ๋ณํฉ๋ง ์ ํ |
REFRESH | ์๋ก๊ณ ์นจ |
DETACH | ๋ถ๋ฆฌ |
์ค๋ฌด ์ฌ๋ก:
Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent); // child1, child2๋ ์๋ ์ ์ฅ!
์ธ์ ์ฌ์ฉ:
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 10.1
ํต์ฌ ๊ฐ๋
orphanRemoval:
@Entity
public class Parent {
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> children = new ArrayList<>();
public void removeChild(Child child) {
children.remove(child);
child.setParent(null);
}
}
๋์:
Parent parent = em.find(Parent.class, 1L);
parent.removeChild(parent.getChildren().get(0));
// โ DELETE ์๋ ์คํ
CASCADE.REMOVE vs orphanRemoval โญ :
| CASCADE.REMOVE | orphanRemoval | |
|---|---|---|
| ํธ๋ฆฌ๊ฑฐ | ๋ถ๋ชจ ์์ฒด ์ญ์ | ์์ ์ปฌ๋ ์ ์ ๊ฑฐ |
| ์ฌ์ฉ ์์ | em.remove(parent) | parent.children.remove(child) |
๋ ๋ค ์์ด์ผ ์๋ฒฝํ ๋ถ๋ชจ-์์ ๊ด๋ฆฌ:
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
โ ๏ธ ์ฃผ์:
์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Phase 5
ํต์ฌ ๊ฐ๋
JPQL (Java Persistence Query Language):
List<User> users = em.createQuery(
"SELECT u FROM User u WHERE u.age > 20", User.class)
.getResultList();
JPQL โ SQL ๋ณํ:
SELECT * FROM user WHERE age > 20;
SQL๊ณผ์ ์ฐจ์ด:
SQL์ด ํ์ํ ์์ :
em.createNativeQuery(...)์๊ธฐ ์ ๊ฒ
์ ์ ์ง์: Unit 10.3
ํต์ฌ ๊ฐ๋
QueryDSL:
JPQL์ ๋ฌธ์ :
QueryDSL์ ํด๊ฒฐ:
List<User> users = queryFactory
.selectFrom(qUser)
.where(qUser.age.gt(20))
.orderBy(qUser.name.asc())
.offset(10)
.limit(5)
.fetch();
์๋ SQL ๋ณํ:
SELECT * FROM user WHERE age > 20 ORDER BY name ASC LIMIT 5 OFFSET 10;
QueryDSL์ ์ฅ์ โญ :
1. ํ์
์์ โ ์ปดํ์ผ ์์ ์ค๋ฅ ๊ฒ์ถ
2. ์๋์์ฑ โ IDE ์ง์
3. ๋์ ์ฟผ๋ฆฌ โ BooleanBuilder, ์กฐ๊ฑด๋ฌธ ์์ ๋กญ๊ฒ
4. ์ฌ์ฌ์ฉ์ฑ โ ๋ฉ์๋๋ก ์ถ์ถ ๊ฐ๋ฅ
์ค๋ฌด ํ์ค ์กฐํฉ:
[Spring Data JPA] - ๋จ์ CRUD, ๋ฉ์๋ ์ด๋ฆ ์ฟผ๋ฆฌ
+
[QueryDSL] - ๋ณต์ก ์ฟผ๋ฆฌ, ๋์ ์ฟผ๋ฆฌ
=
์ค๋ฌด JPA ํ๋ก์ ํธ
ILIC ์ฌ๋ก (๊ฐ๋ฅ์ฑ):
์๊ธฐ ์ ๊ฒ
โ โ โ ๋ฉด์ ยท์ค๋ฌด ๋จ๊ณจ (๋ฐ๋์):
โ โ ๋งค์ฐ ๊ถ์ฅ:
[ Part A โ 11์ฃผ์ฐจ ]
[ ] Phase 1 โ ์ฑ๊ธํค + SQL Mapper์ ์ญ์ฌ (Unit 1.1~1.3)
[ ] Phase 2 โ ORM/JPA ๋ณธ์ง (Unit 2.1~2.3)
[ ] Phase 3 โ ์ํฐํฐ ๋งคํ (Unit 3.1~3.4)
[ ] Phase 4 โ EntityManager + ์์์ฑ ์ปจํ
์คํธ (Unit 4.1~4.4)
[ ] Phase 5 โ 4๊ฐ์ง ์ฅ์ (Unit 5.1~5.5) โ
11์ฃผ์ฐจ ์ ์
[ Part B โ 12์ฃผ์ฐจ ]
[ ] Phase 6 โ 4๊ฐ์ง ์ฐ๊ด๊ด๊ณ (Unit 6.1~6.5)
[ ] Phase 7 โ mappedBy (Unit 7.1~7.3)
[ ] Phase 8 โ ํ๋ก์์ ๋ก๋ฉ (Unit 8.1~8.4)
[ ] Phase 9 โ N+1 ๋ฌธ์ ์ ํด๊ฒฐ (Unit 9.1~9.4) โ
12์ฃผ์ฐจ ์ ์
[ ] Phase 10 โ CASCADE + JPQL/QueryDSL (Unit 10.1~10.4)
[ ] ์ข
ํฉ ์๊ธฐ ์ ๊ฒ 33๋ฌธํญ ํต๊ณผ
11์ฃผ์ฐจ ์ ์ โ Phase 5 (4๊ฐ์ง ์ฅ์ ):
12์ฃผ์ฐจ ์ ์ โ Phase 9 (N+1 ๋ฌธ์ ):
Java ํ์ต์ ๋ ํ๋์ ํด๋ผ์ด๋งฅ์ค:
โ 8-9์ฃผ์ฐจ๊ฐ AOP์ ์ ์ , 11-12์ฃผ์ฐจ๊ฐ JPA์ ์ ์
์ด๋ฒ 2์ฃผ์ฐจ๋ ๋ฐ๋์ SQL ๋ก๊ทธ๋ฅผ ์ผ๊ณ ํ์ตํ์ธ์:
spring:
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
use_sql_comments: true
logging:
level:
org.hibernate.SQL: DEBUG
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
ํนํ Phase 5 (4๊ฐ์ง ์ฅ์ ) ์ Phase 9 (N+1) ๋ SQL ๋ก๊ทธ ์์ด๋ ์ดํด ๋ถ๊ฐ๋ฅ. ์ง์ ์คํํ๋ฉด์ ์ฟผ๋ฆฌ๊ฐ ์ด๋ป๊ฒ ๋๊ฐ๋์ง๋ฅผ ์๊ฐ์ ์ผ๋ก ํ์ธํ์ธ์.