🐣 Spring/JPA

[JPA] JPA와 μ˜μ†μ„± 관리에 λŒ€ν•΄

TIlearn 2023. 12. 29. 16:27

* ν•΄λ‹Ή 글은 κΉ€μ˜ν•œ κ°•μ‚¬λ‹˜μ˜ μžλ°” ORM ν‘œμ€€ JPA ν”„λ‘œκ·Έλž˜λ° - κΈ°λ³ΈνŽΈμ„ 보고 κ°„λ‹¨ν•˜κ²Œ μ •λ¦¬ν•œ κΈ€μž…λ‹ˆλ‹€.(μžμ„Έν•œ λ‚΄μš©μ€ κ°•μ˜κ°€ μ΅œκ³±λ‹ˆλ‹€. κ°•μ˜ μ‚¬μ„Έμš”)

 

 

μžλ°” ORM ν‘œμ€€ JPA ν”„λ‘œκ·Έλž˜λ° - 기본편 κ°•μ˜ - μΈν”„λŸ°

μ €λŠ” μ•Όμƒν˜•μ΄ μ•„λ‹ˆλΌ ν•™μžν˜•μΈκ°€λ΄μš”^^ ν™œμš©νŽΈ λ„˜μ–΄κ°”λ‹€ 30% 정도 λ“£κ³  λ„μ €νžˆ λ‹΅λ‹΅ν•΄μ„œ κΈ°λ³ΈνŽΈμ„ λ“€μ–΄λ²„λ Έλ„€μš”^^. ν•œμ£Ό ν•œμ£Ό κΉ€μ˜ν•œλ‹˜ κ°•μ˜ λ“€μœΌλ‹ˆ λ ™μ—…λ˜λŠ” λͺ¨μŠ΅μ„ 슀슀둜 λŠλ‚λ‹ˆλ‹€. 특히 μ‹€

www.inflearn.com

 

JPA의 μ—­ν• κ³Ό 탄생 λ°°κ²½

 

 

 

πŸ”Ά JPA의 μ—­ν• 

JPAλŠ” 개발자 λŒ€μ‹  SQL문을 μž‘μ„±ν•΄μ€€λ‹€.

 

 

 

 

πŸ”Ά JPAλ₯Ό μ‹€λ¬΄μ—μ„œ μ‚¬μš©ν•  λ•Œ μš°λ¦¬κ°€ μ œλŒ€λ‘œ λͺ»μ‚¬μš©ν•˜λŠ” 이유

 

1️⃣ 객체와 ν…Œμ΄λΈ”μ„ μ˜¬λ°”λ₯΄κ²Œ λ§€ν•‘ν•˜κ³  μ„€κ³„ν•˜λŠ” 방법을 λͺ°λΌμ„œ

 

2️⃣ JPA λ‚΄λΆ€ λ™μž‘ 방식을 μ΄ν•΄ν•˜μ§€ λͺ»ν•΄μ„œ

 

 

 

 

 

JPA의 탄생 λ°°κ²½

 

 

 

μ§€κΈˆ μ‹œλŒ€λŠ” 객체λ₯Ό κ΄€κ³„ν˜• DB에 관리 ν•œλ‹€. 그런데 이 객체λ₯Ό κ΄€κ³„ν˜• DB에 λ„£μœΌλ €λ©΄ μˆ˜λ§Žμ€ SQL문을 μž‘μ„±ν•˜κ³€ ν•œλ‹€. μ΄λŸ¬ν•œ λ¬Έμ œλŠ” 객체와 κ΄€κ³„ν˜• DBκ°„μ˜ νŒ¨λŸ¬λ‹€μž„μ˜ 뢈일치둜 μœ λ°œλœλ‹€.

 

상속 μ‹œ μ‘°νšŒν• λ•ŒλŠ” JOIN SQL을 맀번 μž‘μ„±ν•΄μ•Ό ν•  λΏλ”λŸ¬, 객체도 계속 생성해주어야 ν•œλ‹€. 연관관계에 따라 객체 κ·Έλž˜ν”„λ₯Ό 탐색할 λ•Œλ„ μ΄ˆκΈ°μ— SQL둜 JOINν•œ 것에 따라 탐색 λ²”μœ„κ°€ 달라진닀. μ΄λŠ” 객체 κ·Έλž˜ν”„μ— λŒ€ν•œ μ‹ λ’°μ„± λ¬Έμ œλ‘œλ„ μ΄μ–΄μ§ˆ 수 μžˆλ‹€. λ˜ν•œ λΉ„κ΅ν• λ•Œλ„ 맀번 λ‹€λ₯Έ 객체λ₯Ό λ§Œλ“€μ–΄λ‚΄μ„œ 같은 값을 가지더라도 == 연산이 μ œλŒ€λ‘œ 이루어지지 μ•ŠλŠ”λ‹€.

 

즉, "SQL 쀑심 개발의 문제"κ°€ JPA 탄생 배경에 ν•œ λͺ«μ„ ν•œ 것이닀.

 

 

 

 

 

 

 

JPA와 JPAλ₯Ό μ‚¬μš©ν•˜λ©΄ 쒋은 점

 

 

 

 

JPAλž€?

 

 

JPAλŠ” Java Persistence API둜 μ• ν”Œλ¦¬μΌ€μ΄μ…˜κ³Ό JDBC μ‚¬μ΄μ—μ„œ λ™μž‘ν•˜λŠ” ORM의 일쒅이닀. ORM이라 함은 객체와 κ΄€κ³„ν˜• DB 사이λ₯Ό μ—°κ²°ν•΄μ£ΌλŠ” 것이라 생각할 수 μžˆλŠ”λ°, JPAλŠ” Entityλ₯Ό μ•Œμ•„μ„œ λΆ„μ„ν•˜κ³  JDBC APIλ₯Ό μ‚¬μš©ν•˜μ—¬ SQL을 생성해 DB와 μ—°κ²°μ‹œμΌœμ€€λ‹€.

 

 

 

 

πŸ”Ά JPA의 λ“±μž₯ 이전 μ‚¬μš©ν•˜λ˜ 것듀

EJB ➑️ ν•˜μ΄λ²„λ„€μ΄νŠΈ(μ˜€ν”ˆ μ†ŒμŠ€) ➑️ JPA(ν‘œμ€€λͺ…μ„Έ)

 

 

EJBκ°€ λ„ˆλ¬΄ μ–΄λ €μš΄ λ‚˜λ¨Έμ§€ ν™”λ‚œ κ°œλ°œμžλΆ„μ΄ μ˜€ν”ˆ μ†ŒμŠ€λ‘œ μ‰½κ²Œ λ§Œλ“  것이 ν•˜μ΄λ²„λ„€μ΄νŠΈμ΄λ‹€. 근데 ν•˜μ΄λ²„λ„€μ΄νŠΈκ°€ 생각보닀 훨씬 μ’‹μž, ν‘œμ€€ λͺ…μ„Έλ‘œ κ΅³μ–΄μ§„ 것이 λ°”λ‘œ JPA이닀.

 

 

 

 

 

 

 

μ™œ JPA인가?

 

1️⃣ SQL 쀑심적인 κ°œλ°œμ—μ„œ 객체 μ€‘μ‹¬μœΌλ‘œ κ°œλ°œν•  수 μžˆλ‹€.

 

2️⃣ 생산성이 μ’‹λ‹€.

 

3️⃣ μœ μ§€λ³΄μˆ˜κ°€ 쉽닀.

 

4️⃣ νŒ¨λŸ¬λ‹€μž„μ˜ 뢈일치λ₯Ό ν•΄κ²°ν•  수 μžˆλ‹€.

 

5️⃣ μ„±λŠ₯ ν–₯상할 수 μžˆλ‹€.(1μ°¨ μΊμ‹œμ™€ 동일성 보μž₯, νŠΈλžœμž­μ…˜μ„ μ§€μ›ν•˜λŠ” μ“°κΈ° 지원, μ§€μ—° λ‘œλ”©κ³Ό μ¦‰μ‹œ λ‘œλ”©)

 

 

πŸ“ 데이터 베이슀 λ°©μ–Έ

JPAλŠ” νŠΉμ • λ°μ΄ν„°λ² μ΄μŠ€μ— μ’…μ†λ˜μ§€ μ•ŠλŠ”λ‹€. 


각각의 λ°μ΄ν„°λ² μ΄μŠ€κ°€ μ œκ³΅ν•˜λŠ” SQL 문법과 ν•¨μˆ˜λŠ” μ‘°κΈˆμ”© λ‹€λ₯΄λ‹€.
μ΄λ•Œ, λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ λ§ν•˜λŠ” 방언은 SQL ν‘œμ€€μ„ μ§€ν‚€μ§€ μ•ŠλŠ” νŠΉμ • 데이터 베이슀만의 κ³ μœ ν•œ κΈ°λŠ₯을 λ§ν•œλ‹€.
 
즉, JPAμ—κ²Œ μ–΄λ– ν•œ 방언을 μ‚¬μš©ν•  μ§€ μ•Œλ €μ£Όλ©΄ 그에 λ§žμΆ”μ–΄μ„œ μ–΄λ–€ λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό μ‚¬μš©ν•  μ§€ μ•Œμ•„μ„œ κ²°μ •ν•œλ‹€. 
ν•˜μ΄λ²„λ„€μ΄νŠΈλŠ” 40κ°€μ§€ μ΄μƒμ˜ 방언을 μ œκ³΅ν•˜κ³  μžˆλ‹€.
 
javaxλŠ” ν‘œμ€€μ„ μ§€ν‚€λŠ” 것이고, hibernateκ°€ μžˆλŠ” 것은 μ „μš© μ˜΅μ…˜μ΄λΌ λ‹€λ₯Έ κ΅¬ν˜„ λΌμ΄λΈŒλŸ¬λ¦¬μ—μ„œλŠ” λ˜μ§€λŠ” μ•ŠλŠ”λ‹€.

 

 

 

 

 

 

 

 

 

JPAλŠ” μ–΄λ–»κ²Œ λ™μž‘ν•˜λŠ”κ°€?

 

 

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        
        try {
            Member findMember = em.find(Member.class, 1L);
            findMember.setName("HelloJPA");

            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }

        emf.close();
    }
}

 

 

1️⃣ λ¨Όμ € JPAκ°€ κ΅¬λ™λ λ•Œ, META-INF/persistence.xml에 μž…λ ₯된 μ„€μ • 정보에 따라 κΈ°λ³Έ μ…‹νŒ…μ΄ μ„€μ •λœλ‹€.

 

2️⃣ EntityManagerFactoryλ₯Ό μƒμ„±ν•œλ‹€.

 

3️⃣ EntityManagerFactoryμ—μ„œ EntityManagerλ₯Ό μƒμ„±ν•œλ‹€.

 

4️⃣ EntityManagerμ—μ„œλŠ” EntityTransaction을 λ§Œλ“€ 수 있고, JPA의 λͺ¨λ“  데이터 변경은 ν•΄λ‹Ή νŠΈλžœμž­μ…˜ μ•ˆμ—μ„œ μˆ˜ν–‰λœλ‹€.

 

 

 

μ£Όμ˜ν•  점은 μ—”ν‹°ν‹° λ§€λ‹ˆμ €λŠ” μ“°λ ˆλ“œ 간에 κ³΅μœ ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— μ‚¬μš©ν•œ ν›„μ—λŠ” λ²„λ €μ•Όν•œλ‹€.

 

 

 

 

 

 

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ

 

 

πŸ”² "μ—”ν‹°ν‹°λ₯Ό 영ꡬ μ €μž₯ν•˜λŠ” ν™˜κ²½" μ΄λΌλŠ” λœ»μ΄λ‹€.

 

πŸ”² EntityManager.persist(entity) λ₯Ό 톡해 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ•ˆμ— μ €μž₯ν•  수 μžˆλ‹€.

 

πŸ”² 논리적인 κ°œλ…μ΄λ©°, λˆˆμ— 보이지 μ•ŠλŠ”λ‹€. μ—”ν‹°ν‹° λ§€λ‹ˆμ €λ₯Ό 톡해 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— μ ‘κ·Όν•  수 μžˆλ‹€.

 

 

 

 

 

 

 

 

 

μ—”ν‹°ν‹°μ˜ 생λͺ…μ£ΌκΈ°

 

πŸ”² λΉ„μ˜μ†(new/transient)

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ™€ μ „ν˜€ 관계가 μ—†λŠ” μƒˆλ‘œμš΄ μƒνƒœμ΄λ‹€.

 

πŸ”² μ˜μ†(managed)

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— κ΄€λ¦¬λ˜λŠ” μƒνƒœμ΄λ‹€.

 

πŸ”² μ€€μ˜μ†(detached)

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— μ €μž₯λ˜μ—ˆλ‹€κ°€ λΆ„λ¦¬λœ μƒνƒœμ΄λ‹€.

 

πŸ”² μ‚­μ œ(removed)

μ‚­μ œλœ μƒνƒœμ΄λ‹€.

 

 

 

λΉ„μ˜μ† μƒνƒœ

Member member = new Member();
member.setId(100L);
member.setName("HelloJPA");

 

 

μ˜μ†

em.persist(memeber);

 

 

μ€€μ˜μ† μƒνƒœ

em.detach(member);

 

 

μ‚­μ œ

em.remove(member);

 

 

 

 

 

 

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ˜ 이점

 

 

πŸ”Ά μ—”ν‹°ν‹° 쑰회(1μ°¨ μΊμ‹œ)

 

em.persist()λ₯Ό 톡해 μ˜μ†ν™”ν•˜κ²Œ 되면 기본적으둜 1μ°¨ μΊμ‹œμ— ν•΄λ‹Ή Entity의 μƒνƒœκ°€ μ €μž₯λœλ‹€. λ”°λΌμ„œ 1μ°¨ μΊμ‹œμ— μ €μž₯된 μƒνƒœμ—μ„œ em.find()λͺ…령을 μˆ˜ν–‰ν•˜κ²Œ 되면 DBλ₯Ό μ‘°νšŒν•  ν•„μš”μ—†μ΄ λ°”λ‘œ 값을 찾을 수 μžˆλ‹€.

 

 

λΉ„μ¦ˆλ‹ˆμŠ€κ°€ λ³΅μž‘ν•˜λ©΄ μ–΄λŠμ •λ„ λ„μ›€μ΄λ˜λ‚˜, μ—¬λŸ¬ λͺ…μ˜ 고객이 μ‚¬μš©ν•˜λŠ” λ•ŒλŠ” μ²˜λ¦¬ν•˜μ§€ λͺ»ν•΄ 큰 이점은 μ—†λ‹€κ³  λ³Έλ‹€.

 

 

 

 

 

πŸ”Ά μ˜μ† μ—”ν‹°ν‹°μ˜ 동일성 보μž₯

Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");

System.out.println(a == b); // 동일성 비ꡐ true

 

 

λ‘˜λ‹€ 1μ°¨μΊμ‹œμ—μ„œ κ°€μ Έμ˜¨ κ°’μ΄λ―€λ‘œ λ™μΌν•œ 객체λ₯Ό 가리킨닀.

 

 

 

 

πŸ”Ά μ—”ν‹°ν‹° 등둝, νŠΈλžœμž­μ…˜μ„ μ§€μ›ν•˜λŠ” μ“°κΈ° μ§€μ—°

 

 

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();

transaction.begin();

em.persist(memberA);
em.persist(memberB);

transaction.commit();

 

 

em.persist()λ₯Ό μˆ˜ν–‰ν•˜λ©΄ μ˜μ†ν™”λ˜μ–΄ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— λ“€μ–΄κ°„λ‹€κ³  ν•˜μ˜€λ‹€. μ΄λ•Œ, λ™μ‹œμ— μ“°κΈ° μ§€μ—° SQL μ €μž₯μ†Œμ— INSERT SQL문이 λ™μ‹œμ— λ§Œλ“€μ–΄μ§„λ‹€.

 

 

그리고 transaction.commit()λ₯Ό ν•˜κ²Œλ˜λ©΄ κ·Έλ•Œμ„œμ•Ό μŒ“μ—¬μžˆλ˜ SQL문이 ν•œλ²ˆμ— DB둜 μ „μ†‘λ˜μ–΄ μˆ˜ν–‰λœλ‹€.

 

 

 

hibernate에 batch sizeλΌλŠ” 속성이 μžˆλ‹€. 마치 버퍼링 같은 κΈ°λŠ₯인데, μ–Όλ§ˆλ‚˜ ν•œλ²ˆμ— λͺ¨μ•„μ„œ 보낼지 μ •ν•˜λŠ” κΈ°λŠ₯도 μžˆλ‹€.

 

 

 

 

 

 

πŸ”Ά μ—”ν‹°ν‹° μˆ˜μ •, λ³€κ²½ 감지

 

 

Member member = em.find(Member.class, 150L);
member.setName("ZZZZZ");
tx.commit();

 

μˆ˜μ •μ— μžˆμ–΄μ„œλŠ” persist()λ₯Ό ν•˜μ§€μ•ŠλŠ”λ‹€. κ·Έμ € μžλ°” μ»¬λ ‰μ…˜ 닀루듯이 κ°’λ§Œ λ°”κΎΈμ–΄μ£Όλ©΄ 끝인 것이닀. 이게 κ°€λŠ₯ν•œ μ΄μœ λŠ” JPA의 λ³€κ²½ 감지(Dirty Checking)μ΄λΌλŠ” κΈ°λŠ₯ λ•Œλ¬Έμ΄λ‹€.

 

 

 

1μ°¨ μΊμ‹œ μ•ˆμ—λŠ” Id κ°’κ³Ό Entity, 그리고 졜초 μƒνƒœλ₯Ό 집어넣은 μŠ€λƒ…μƒ·μ„ κ°€μ§€κ³  μžˆλ‹€.

 

 

μ΄λ•Œ, memberAλΌλŠ” 값을 λ³€κ²½ν•˜κ²Œ 되면(flush 호좜 μ‹œ) μŠ€λƒ…μƒ·κ³Ό μ—”ν‹°ν‹°λ₯Ό λΉ„κ΅ν•œλ‹€. λ§Œμ•½ Entity와 μŠ€λƒ…μƒ·μ΄ λ‹€λ₯΄λ‹€λ©΄, UPDATE SQL을 μ“°κΈ° μ§€μ—° SQL μ €μž₯μ†Œμ— λ„£κ²Œ λœλ‹€. κ·Έλž˜μ„œ ꡳ이 μš°λ¦¬κ°€ persistλ₯Ό ν•˜μ§€ μ•Šλ”λΌλ„ μ•Œμ•„μ„œ 변경을 κ°μ§€ν•˜κ³  SQL문을 μž‘μ„±ν•΄μ€„ 수 μžˆλ‹€.

 

 

μ—”ν‹°ν‹°μ˜ μ‚­μ œλ„ λ§ˆμ°¬κ°€μ§€λ‘œ μž‘λ™λœλ‹€.

 

 

Member memberA = em.find(Member.class, "memberA");

em.remove(memberA);

 

 

 

 

 

 

 

ν”ŒλŸ¬μ‹œ(flush)

 

 

πŸ”² μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ˜ λ‚΄μš©μ„ λ°μ΄ν„°λ² μ΄μŠ€μ— λ°˜μ˜ν•˜λŠ” 것

 

 

 

 

 

ν”ŒλŸ¬μ‹œ(flush)λŠ” μ–Έμ œ λ°œμƒν•˜λŠ”κ°€?

 

πŸ”² λ³€κ²½ 감지(Dirty Checking)

 

πŸ”² μˆ˜μ •λœ μ—”ν‹°ν‹° μ“°κΈ° μ§€μ—° SQL μ €μž₯μ†Œμ— 등둝

 

πŸ”² μ“°κΈ° μ§€μ—° SQL μ €μž₯μ†Œμ˜ 쿼리λ₯Ό λ°μ΄ν„°λ² μ΄μŠ€μ— 전솑

 

 

 

 

 

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— ν”ŒλŸ¬μ‹œν•˜λŠ” 방법

 

 

πŸ”² 직접 호좜 : em.flush()

 

πŸ”² μžλ™ 호좜 : νŠΈλžœμž­μ…˜ 컀밋, JPQL 쿼리 μ‹€ν–‰ μ‹œ

 

 

 

 

πŸ€” flushλ₯Ό ν•˜κ²Œ 되면 1μ°¨ μΊμ‹œλ„ μˆ˜μ •λ˜λŠ”κ°€?

μ•„λ‹ˆλ‹€. 1μ°¨ μΊμ‹œλŠ” μˆ˜μ •λ˜μ§€ μ•ŠλŠ”λ‹€.

 

 

 

πŸ€” JPQL μ‹€ν–‰ μ‹œ μžλ™μœΌλ‘œ ν”ŒλŸ¬μ‹œκ°€ ν˜ΈμΆœλ˜λŠ” 이유

em.persist(memberA);
em.persist(memberB);
em.persist(memberC);

query = em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();



em.persist()μ‹œμ—λŠ” μ‹€μ œλ‘œ 쿼리가 λ‚ λΌκΈ°μ§€λŠ” μ•ŠλŠ”λ‹€λŠ” 것을 κΈ°μ–΅ν•˜μž.
κ·Έλ ‡κΈ° λ•Œλ¬Έμ— 멀버λ₯Ό JPQL둜 가져와도 Insert λ˜μ§€ μ•Šμ•„μ„œ 가져와지지 μ•ŠλŠ”λ‹€.

 

이λ₯Ό λ°©μ§€ν•˜κ³ μž JPQL 쿼리 μ‹€ν–‰ μ‹œ ν”ŒλŸ¬μ‹œκ°€ μžλ™μœΌλ‘œ ν˜ΈμΆœν•˜μ—¬ DB에 미리 λ°˜μ˜ν•˜λŠ” 것이닀.

 

 

 

 

 

 

μ€€μ˜μ† μƒνƒœ(detached)

 

 

μ˜μ† μƒνƒœμ˜ μ—”ν‹°ν‹°κ°€ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ—μ„œ λΆ„λ¦¬λ˜λŠ” μƒνƒœμ΄λ‹€. κ·Έλž˜μ„œ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈκ°€ μ œκ³΅ν•˜λŠ” κΈ°λŠ₯을 μ΄μš©ν•  수 μ—†λ‹€.

 

 

 

 

πŸ”² em.detach(entity)

νŠΉμ • μ—”ν‹°ν‹°λ§Œ μ€€μ˜μ† μƒνƒœλ‘œ μ „ν™˜ν•œλ‹€.

 

πŸ”² em.clear()

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό μ™„μ „νžˆ μ΄ˆκΈ°ν™”ν•œλ‹€.

 

πŸ”² em.close()

μ˜μ†μ„± μ»€ν…μŠ€νŠΈλ₯Ό μ’…λ£Œν•œλ‹€. μ™„μ „νžˆ 1μ°¨μΊμ‹œλ₯Ό ν†΅μœΌλ‘œ λΉ„μš΄λ‹€.

 

 

 

 

 

 

 

 

 

 

μžλ°” ORM ν‘œμ€€ JPA ν”„λ‘œκ·Έλž˜λ° - 기본편 κ°•μ˜ - μΈν”„λŸ°

μ €λŠ” μ•Όμƒν˜•μ΄ μ•„λ‹ˆλΌ ν•™μžν˜•μΈκ°€λ΄μš”^^ ν™œμš©νŽΈ λ„˜μ–΄κ°”λ‹€ 30% 정도 λ“£κ³  λ„μ €νžˆ λ‹΅λ‹΅ν•΄μ„œ κΈ°λ³ΈνŽΈμ„ λ“€μ–΄λ²„λ Έλ„€μš”^^. ν•œμ£Ό ν•œμ£Ό κΉ€μ˜ν•œλ‹˜ κ°•μ˜ λ“€μœΌλ‹ˆ λ ™μ—…λ˜λŠ” λͺ¨μŠ΅μ„ 슀슀둜 λŠλ‚λ‹ˆλ‹€. 특히 μ‹€

www.inflearn.com