JPA

[μžλ°” ORM ν‘œμ€€ JPA ν”„λ‘œκ·Έλž˜λ°] 15μž₯ κ³ κΈ‰ μ£Όμ œμ™€ μ„±λŠ₯ μ΅œμ ν™”

joaa 2023. 9. 7. 19:29

🎈15.1 μ˜ˆμ™Έ 처리

15.1.1 JPA ν‘œμ€€ μ˜ˆμ™Έ 정리

JPA ν‘œμ€€ μ˜ˆμ™Έ

- νŠΈλžœμž­μ…˜ 둀백을 ν‘œμ‹œν•˜λŠ” μ˜ˆμ™Έ

- νŠΈλžœμž­μ…˜ 둀백을 ν‘œμ‹œν•˜μ§€ μ•ŠλŠ” μ˜ˆμ™Έ

νŠΈλžœμž­μ…˜ 둀백을 ν‘œμ‹œν•˜λŠ” μ˜ˆμ™ΈλŠ” μ‹¬κ°ν•œ μ˜ˆμ™Έμ΄λ―€λ‘œ λ³΅κ΅¬ν•΄μ„œλŠ” μ•ˆ 됨. 

 

15.1.2 μŠ€ν”„λ§ ν”„λ ˆμž„μ›Œν¬μ˜ JPA μ˜ˆμ™Έ λ³€ν™˜

μ„œλΉ„μŠ€ κ³„μΈ΅μ—μ„œ 데이터 μ ‘κ·Ό κ³„μΈ΅μ˜ κ΅¬ν˜„ κΈ°μˆ μ— 직접 μ˜μ‘΄ν•˜λŠ” 것이 쒋은 섀계가 μ•„λ‹Œ κ²ƒμ²˜λŸΌ

μ„œλΉ„μŠ€ κ³„μΈ΅μ—μ„œ JPA의 μ˜ˆμ™Έλ₯Ό 직접 μ‚¬μš©ν•˜λ©΄ μ•ˆλ¨. 

이런 문제 해결을 μœ„ν•΄ μŠ€ν”„λ§ ν”„λ ˆμž„μ›Œν¬λŠ” 데이터 μ ‘κ·Ό 계측에 λŒ€ν•œ μ˜ˆμ™Έλ₯Ό μΆ”μƒν™”ν•΄μ„œ κ°œλ°œμžμ—κ²Œ μ œκ³΅ν•¨. 

 

15.1.3 μŠ€ν”„λ§ ν”„λ ˆμž„μ›Œν¬μ— JPA μ˜ˆμ™Έ λ³€ν™˜κΈ° 적용

JPA μ˜ˆμ™Έλ₯Ό μŠ€ν”„λ§ ν”„λ ˆμž„μ›Œν¬κ°€ μ œκ³΅ν•˜λŠ” μΆ”μƒν™”λœ μ˜ˆμ™Έλ‘œ λ³€κ²½ν•˜λ €λ©΄

μŠ€ν”„λ§ 빈으둜 PersistenceExceptionTranslationPostProcessorλ₯Ό λ“±λ‘ν•˜λ©΄ 됨. 

 

15.1.4 νŠΈλžœμž­μ…˜ λ‘€λ°± μ‹œ μ£Όμ˜μ‚¬ν•­

νŠΈλžœμž­μ…˜μ„ λ‘€λ°±ν•˜λŠ” 것은 λ°μ΄ν„°λ² μ΄μŠ€μ˜ λ°˜μ˜μ‚¬ν•­λ§Œ λ‘€λ°±ν•˜λŠ” 것이지

μˆ˜μ •ν•œ μžλ°” κ°μ²΄κΉŒμ§€ μ›μƒνƒœλ‘œ λ³΅κ΅¬ν•΄μ£Όμ§€λŠ” μ•ŠμŒ. 

 

κΈ°λ³Έ μ „λž΅μΈ νŠΈλžœμž­μ…˜λ‹Ή μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ μ „λž΅μ€ λ¬Έμ œκ°€ λ°œμƒν•˜λ©΄ νŠΈλžœμž­μ…˜ AOP μ’…λ£Œ μ‹œμ μ— νŠΈλžœμž­μ…˜μ„ λ‘€λ°±ν•˜λ©΄μ„œ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ„ ν•¨κ»˜ μ’…λ£Œν•˜λ―€λ‘œ λ¬Έμ œκ°€ λ°œμƒν•˜μ§€ μ•ŠλŠ”λ‹€. 

 

OSIV처럼 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ λ²”μœ„λ₯Ό νŠΈλžœμž­μ…˜ λ²”μœ„λ³΄λ‹€ λ„“κ²Œ μ‚¬μš©ν•  λ•Œ.

μŠ€ν”„λ§ ν”„λ ˆμž„μ›Œν¬λŠ” μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ˜ λ²”μœ„λ₯Ό νŠΈλžœμž­μ…˜μ˜ λ²”μœ„λ³΄λ‹€ λ„“κ²Œ μ„€μ •ν•˜λ©΄ νŠΈλžœμž­μ…˜ λ‘€λ°±μ‹œ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό μ΄ˆκΈ°ν™”ν•΄μ„œ 잘λͺ»λœ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό μ‚¬μš©ν•˜λŠ” 문제λ₯Ό μ˜ˆλ°©ν•¨. 

 

 

 

 

🎈15.2 μ—”ν‹°ν‹° 비ꡐ

15.2.1 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈκ°€ 같을 λ•Œ μ—”ν‹°ν‹° 비ꡐ

member와 findMemberκ°€ μ™„μ „νžˆ 같은 μΈμŠ€ν„΄μŠ€. 같은 νŠΈλžœμž­μ…˜ λ²”μœ„μ— μžˆμœΌλ―€λ‘œ 같은 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό μ‚¬μš©ν•˜κΈ° λ•Œλ¬Έ.

동일성: == 비ꡐ가 κ°™μŒ. 

동등성: equals() 비ꡐ가 κ°™μŒ.

λ°μ΄ν„°λ² μ΄μŠ€ 동등성: @Id 인 λ°μ΄ν„°λ² μ΄μŠ€ μ‹λ³„μžκ°€ κ°™μŒ. 

 

 

 

15.2.2 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈκ°€ λ‹€λ₯Ό λ•Œ μ—”ν‹°ν‹° 비ꡐ

 

member와 findMemberλŠ” 각각 λ‹€λ₯Έ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ—μ„œ κ΄€λ¦¬λ˜μ—ˆκΈ° λ•Œλ¬Έμ— λ‘˜μ€ λ‹€λ₯Έ μΈμŠ€ν„΄μŠ€μž„. 

동일성(identical): == 비ꡐ가 μ‹€νŒ¨ν•¨.

동등성(equivalent): equals() 비ꡐ가 λ§Œμ‘±ν•¨. 

λ°μ΄ν„°λ² μ΄μŠ€ 동등성: @Id인 λ°μ΄ν„°λ² μ΄μŠ€ μ‹λ³„μžκ°€ κ°™μŒ. 

 

 

동일성 λΉ„κ΅λŠ” 같은 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ˜ 관리λ₯Ό λ°›λŠ” μ˜μ† μƒνƒœμ˜ μ—”ν‹°ν‹°μ—λ§Œ μ μš©ν•  수 μžˆλ‹€. 

κ·Έλ ‡μ§€ μ•Šμ„ λ•ŒλŠ” λΉ„μ¦ˆλ‹ˆμŠ€ ν‚€λ₯Ό μ‚¬μš©ν•œ 동등성 비ꡐλ₯Ό ν•΄μ•Ό ν•œλ‹€. 

 

 

 

🎈15.3 ν”„λ‘μ‹œ 심화 주제

15.3.1 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ™€ ν”„λ‘μ‹œ

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλŠ” ν”„λ‘μ‹œλ‘œ 쑰회된 엔티티에 λŒ€ν•΄μ„œ 같은 μ—”ν‹°ν‹°λ₯Ό μ°ΎλŠ” μš”μ²­μ΄ λ“€μ–΄μ˜€λ©΄ 원본 μ—”ν‹°ν‹°κ°€ μ•„λ‹Œ 처음 쑰회된 ν”„λ‘μ‹œλ₯Ό λ°˜ν™˜ν•¨. 

λ”°λΌμ„œ ν”„λ‘μ‹œλ‘œ μ‘°νšŒν•΄λ„ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλŠ” μ˜μ† μ—”ν‹°ν‹°μ˜ 동일성을 보μž₯함. 

 

원본 μ—”ν‹°ν‹°λ₯Ό λ¨Όμ € μ‘°νšŒν•˜λŠ” 경우, μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλŠ” 원본 μ—”ν‹°ν‹°λ₯Ό 이미 λ°λ² μ—μ„œ μ‘°νšŒν–ˆμœΌλ―€λ‘œ ν”„λ‘μ‹œλ₯Ό λ°˜ν™˜ν•  μ΄μœ κ°€ μ—†μŒ. λ”°λΌμ„œ em.getReference()λ₯Ό ν˜ΈμΆœν•΄λ„ ν”„λ‘μ‹œκ°€ μ•„λ‹Œ 원본을 λ°˜ν™˜ν•˜κ³ , μ˜μ† μ—”ν‹°ν‹°μ˜ 동일성을 보μž₯함. 

 

 

15.3.2 ν”„λ‘μ‹œ νƒ€μž… 비ꡐ

ν”„λ‘μ‹œλŠ” 원본 μ—”ν‹°ν‹°λ₯Ό 상속 λ°›μ•„μ„œ λ§Œλ“€μ–΄μ§€λ―€λ‘œ ν”„λ‘μ‹œλ‘œ μ‘°νšŒν•œ μ—”ν‹°ν‹°μ˜ νƒ€μž…μ„ 비ꡐ할 λ•ŒλŠ” == 비ꡐλ₯Ό ν•˜λ©΄ μ•ˆ 되고 instanceofλ₯Ό μ‚¬μš©ν•΄μ•Ό 함. 

@Test
public void ν”„λ‘μ‹œ_νƒ€μž…λΉ„κ΅() {
	Member newMember = new Member("member1", "νšŒμ›1");
    em.persist(newMember);
    em.flush();
    em.clear();
    
    Member refMember = em.getReference(Member.class, "member1");
    
    System.out.println("refMember Type = " + refMember.getClass());
    
    Assert.assertFalse(Member.class == refMember.getClass()); //false
    Assert.assertTrue(refMember instanceof Member); //true
}

 

 

15.3.3 ν”„λ‘μ‹œ 동등성 비ꡐ

ν”„λ‘μ‹œμ™€ equals() 비ꡐλ₯Ό ν•  λ•Œμ˜ 주의점

 

- ν”„λ‘μ‹œλŠ” 원본을 상속받은 μžμ‹ νƒ€μž…μ΄λ―€λ‘œ ν”„λ‘μ‹œμ˜ νƒ€μž… λΉ„κ΅λŠ” == 비ꡐ λŒ€μ‹ μ— instanceofλ₯Ό μ‚¬μš©ν•΄μ•Ό ν•œλ‹€. 

- ν”„λ‘μ‹œλŠ” μ‹€μ œ 데이터λ₯Ό κ°€μ§€κ³  μžˆμ§€ μ•ŠμœΌλ―€λ‘œ ν”„λ‘μ‹œμ˜ λ©€λ²„λ³€μˆ˜μ— 직접 μ ‘κ·Όν•˜λ©΄ μ•ˆ 되고 λŒ€μ‹ μ— μ ‘κ·Όμž λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•΄μ•Ό ν•œλ‹€. 

 

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (!(obj instanceof Member)) return false;
    
    Member member = (Member) obj;
    
    if (name != null ? !name.equals(member.getName()) :
    	member.getName() != null)
        return false;
        
    return true;
}

 

 

15.3.4 상속관계와 ν”„λ‘μ‹œ

ν”„λ‘μ‹œλ₯Ό λΆ€λͺ¨ νƒ€μž…μœΌλ‘œ μ‘°νšŒν•˜λ©΄ λΆ€λͺ¨μ˜ νƒ€μž…μ„ 기반으둜 ν”„λ‘μ‹œκ°€ μƒμ„±λ˜λŠ” λ¬Έμ œκ°€ 있음. 

- instanceof 연산을 μ‚¬μš©ν•  수 μ—†μŒ. 

- ν•˜μœ„ νƒ€μž…μœΌλ‘œ λ‹€μš΄μΊμŠ€νŒ…μ„ ν•  수 μ—†μŒ. 

@Test
public void 상속관계와_ν”„λ‘μ‹œ_도메인λͺ¨λΈ() {
	//ν…ŒμŠ€νŠΈ 데이터 μ€€λΉ„
    Book book = new Book();
    book.setName("jpabook");
    book.setAuthor("kim");
    em.persist(book);
    
    OrderItem saveOrderItem = new OrderItem();
    saveOrderItem.setItem(book);
    em.persist(saveOrderItem);
    
    em.flush();
    em.clear();
    
    //ν…ŒμŠ€νŠΈ μ‹œμž‘
    OrderItem orderItem = em.find(OrderItem.class, saveOrderItem.getId());
    Item item = orderItem.getItem();
    
    System.out.println("item = " + item.getClass());
    
    //κ²°κ³Ό 검증
    Assert.assertFalse(item.getClass() == Book.class);
    Assert.assertFalse(item instanceof Book);
    Assert.assertTrue(item instanceof Item);

 

 

문제 ν•΄κ²° 방법

JPQL둜 λŒ€μƒ 직접 쑰회

μ²˜μŒλΆ€ν„° μžμ‹ νƒ€μž…μ„ 직접 μ‘°νšŒν•΄μ„œ ν•„μš”ν•œ 연산을 ν•˜λ©΄ 됨. ν•˜μ§€λ§Œ λ‹€ν˜•μ„±μ„ ν™œμš©ν•  수 μ—†μŒ. 

Book jpqlBook = em.createQuery
    ("select b from Book b where b.id=:bookId", Book.class)
    	.setParameter("bookId", item.getId())
    	.getSingleResult();

 

ν”„λ‘μ‹œ λ²—κΈ°κΈ°

//ν•˜μ΄λ²„λ„€μ΄νŠΈκ°€ μ œκ³΅ν•˜λŠ” ν”„λ‘μ‹œμ—μ„œ 원본 μ—”ν‹°ν‹°λ₯Ό μ°ΎλŠ” κΈ°λŠ₯을 μ‚¬μš©ν•˜λŠ” λ©”μ†Œλ“œ
public static <T> T unProxy(Object entity) {
    if(entity instanceof HibernateProxy) {
    	entity = ((HibernateProxy) entity)
        			.getHibernateLazyInitializer()
                    .getImplementation();
    }
    return (T) entity;
}

ν•œ 번 ν”„λ‘μ‹œλ‘œ λ…ΈμΆœν•œ μ—”ν‹°ν‹°λŠ” 계속 ν”„λ‘μ‹œλ‘œ λ…ΈμΆœν•΄μ„œ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈκ°€ μ˜μ† μ—”ν‹°ν‹°μ˜ 동일성을 보μž₯ν•œλ‹€κ³  ν–ˆλŠ”λ° 

이 방법은 ν”„λ‘μ‹œμ—μ„œ 원본 데이터λ₯Ό 직접 κΊΌλ‚΄κΈ° λ•Œλ¬Έμ— ν”„λ‘μ‹œμ™€ 원본 μ—”ν‹°ν‹°μ˜ 동일성 비ꡐ가 μ‹€νŒ¨ν•œλ‹€λŠ” 문제점이 있음. (item == unProxyItem의 κ²°κ³ΌλŠ” falseλΌλŠ” 문제.)

 

 

κΈ°λŠ₯을 μœ„ν•œ λ³„λ„μ˜ μΈν„°νŽ˜μ΄μŠ€ 제곡

μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ œκ³΅ν•˜κ³  각각의 ν΄λž˜μŠ€κ°€ μžμ‹ μ— λ§žλŠ” κΈ°λŠ₯을 κ΅¬ν˜„ν•˜ν•˜λŠ” 것은 λ‹€ν˜•μ„±μ„ ν™œμš©ν•˜λŠ” 쒋은 방법.

ν΄λΌμ΄μ–ΈνŠΈ μž…μž₯μ—μ„œ λŒ€μƒ 객체가 ν”„λ‘μ‹œμΈμ§€ μ•„λ‹Œμ§€λ₯Ό κ³ λ―Όν•˜μ§€ μ•Šμ•„λ„ λ˜λŠ” μž₯점이 있음. 

 

λΉ„μ§€ν„° νŒ¨ν„΄ μ‚¬μš©

Visitor와 Visitorλ₯Ό λ°›μ•„λ“€μ΄λŠ” λŒ€μƒ 클래슀둜 ꡬ성됨. Item이 accept λ©”μ†Œλ“œλ‘œ Visitorλ₯Ό λ°›μ•„λ“€μ΄κΈ°λ§Œ ν•˜κ³  μ‹€μ œ λ‘œμ§μ€ Visitorκ°€ μ²˜λ¦¬ν•¨. 

//Visitor μΈν„°νŽ˜μ΄μŠ€
public interface Visitor {
    void visit(Book book);
    void visit(Album album);
    void visit(Movie movie);
}
//λΉ„μ§€ν„° κ΅¬ν˜„

public class PrintVisitor implements Visitor {
    @Override
    public void visit(Book book) {
    	//λ„˜μ–΄μ˜€λŠ” book은 Proxyκ°€ μ•„λ‹Œ 원본 μ—”ν‹°ν‹°λ‹€.
        System.out.println("book.class = " + book.getClass());
        System.out.println("[PrintVisitor]
        	[제λͺ©:" + book.getName() + "μ €μž:" + book.getAuthor() + "]");
	}
    
    @Override
    public void visit(Album album) {...}
    @Override
    public void visit(Movie movie) {...}
}

public class TitleVisitor implements Visitor {
    private String title;
    
    public String getTitle() {
    	return title;
    }
    
    @Override
    public void visit(Book book) {
    	title = "[제λͺ©:" + book.getName() + "μ €μž:" +
        	book.getAuthor() + "]";
    }
    
    @Override
    public void visit(Album album) {...}
    @Override
    public void visit(Movie movie) {...}
}

 

//λΉ„μ§€ν„° λŒ€μƒ 클래슀
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item {
	
    @Id@GeneratedValue
    @Column(name = "ITEM_ID")
    private Long id;
    
    private String name;
    
    ...
    
    public abstract void accept(Visitor visitor);
}

@Entity
@DiscriminatorValue("B")
public class Book extends Item {
	
    private String author;
    private String isbn;
    //Getter, Setter
    public String getAuthor() {
    	return author;
    }
}

@Entity
@DiscriminatorValue("M")
public class Movie extends Item {
	...
    @Override
    public void accept(Visitor visitor) {
    	visitor.visit(this);
    }
}

@Entity
@DiscriminatorValue("A")
public class Album extends Item {
	...
    @Override
    public void accept(Visitor visitor) {
    	visitor.visit(this);
    }
}

λΉ„μ§€ν„° νŒ¨ν„΄μ„ μ‚¬μš©ν•˜λ©΄ ν”„λ‘μ‹œμ— λŒ€ν•œ κ±±μ • 없이 μ•ˆμ „ν•˜κ²Œ 원본 엔티티에 μ ‘κ·Όν•  수 있고 instanceofλ‚˜ νƒ€μž…μΊμŠ€νŒ… 없이 μ½”λ“œλ₯Ό κ΅¬ν˜„ν•  수 μžˆλŠ” μž₯점이 μžˆλ‹€. 

 

λΉ„μ§€ν„° νŒ¨ν„΄μ˜ μž₯점

- ν”„λ‘μ‹œμ— λŒ€ν•œ κ±±μ • 없이 μ•ˆμ „ν•˜κ²Œ 원본 엔티티에 μ ‘κ·Όν•  수 μžˆλ‹€. 

- instnaceof와 νƒ€μž…μΊμŠ€νŒ… 없이 μ½”λ“œλ₯Ό κ΅¬ν˜„ν•  수 μžˆλ‹€. 

- μ•Œκ³ λ¦¬μ¦˜κ³Ό 객체 ꡬ쑰λ₯Ό λΆ„λ¦¬ν•΄μ„œ ꡬ쑰λ₯Ό μˆ˜μ •ν•˜μ§€ μ•Šκ³  μƒˆλ‘œμš΄ λ™μž‘μ„ μΆ”κ°€ν•  수 μžˆλ‹€. 

 

λΉ„μ§€ν„° νŒ¨ν„΄μ˜ 단점

- λ„ˆλ¬΄ λ³΅μž‘ν•˜κ³  더블 λ””μŠ€νŒ¨μΉ˜λ₯Ό μ‚¬μš©ν•˜κΈ° λ•Œλ¬Έμ— μ΄ν•΄ν•˜κΈ° μ–΄λ ΅λ‹€.

- 객체 ꡬ쑰가 λ³€κ²½λ˜λ©΄ λͺ¨λ“  Visitorλ₯Ό μˆ˜μ •ν•΄μ•Ό ν•œλ‹€. 

 

 

 

🎈15.4 μ„±λŠ₯ μ΅œμ ν™”

15.4.1 N+1 문제

μ¦‰μ‹œ λ‘œλ”©κ³Ό N+1

νŠΉμ • νšŒμ›μ„ em.find() λ©”μ†Œλ“œλ‘œ μ‘°νšŒν•˜λ©΄ μ¦‰μ‹œ λ‘œλ”©μœΌλ‘œ μ„€μ •ν•œ 주문정보도 ν•¨κ»˜ μ‘°νšŒν•œλ‹€. 

em.find(Member.class, id);

-> μ‹€ν–‰λ˜λŠ” SQL:

SELECT M.*, O.*

FROM

MEMBER M

OUTER JOIN ORDERS O ON M.ID=O.MEMBER_ID

SQL을 두 번 μ‹€ν–‰ν•˜λŠ” 것이 μ•„λ‹ˆλΌ 쑰인을 μ‚¬μš©ν•΄μ„œ ν•œ 번의 SQL둜 νšŒμ›κ³Ό 주문정보λ₯Ό ν•¨κ»˜ μ‘°νšŒν•¨. 

 

λ¬Έμ œλŠ” JPQL을 μ‚¬μš©ν•  λ•Œ λ°œμƒν•¨. 

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

JPQL을 μ‹€ν–‰ν•˜λ©΄ JPAλŠ” 이것을 λΆ„μ„ν•΄μ„œ SQL을 생성함. μ΄λ•ŒλŠ” μ¦‰μ‹œ λ‘œλ”© μ§€μ—° λ‘œλ”©μ— λŒ€ν•΄ μ „ν˜€ μ‹ κ²½ μ“°μ§€ μ•Šκ³  JPQL만 μ‚¬μš©ν•΄μ„œ SQL을 μƒμ„±ν•΄μ„œ λ‹€μŒκ³Ό 같은 SQL이 싀행됨. 

SELECT * FROM MEMBER
SELECT * FROM ORDERS WHERE MEMBER_ID=1
SELECT * FROM ORDERS WHERE MEMBER_ID=2
...
SELECT * FROM ORDERS WHERE MEMBER_ID=N

이처럼 처음 μ‹€ν–‰ν•œ SQL의 κ²°κ³Ό 수만큼 μΆ”κ°€λ‘œ SQL을 μ‹€ν–‰ν•˜λŠ” 것을 N+1 문제라 ν•œλ‹€. 

 

 

μ§€μ—° λ‘œλ”©κ³Ό N+1

λ‹€μŒμ²˜λŸΌ λͺ¨λ“  νšŒμ›μ— λŒ€ν•΄ μ—°κ΄€λœ μ£Όλ¬Έ μ»¬λ ‰μ…˜μ„ μ‚¬μš©ν•  λ•Œ N+1 λ¬Έμ œκ°€ λ°œμƒν•¨. 

for (Member member : members) {
    //μ§€μ—° λ‘œλ”© μ΄ˆκΈ°ν™”
    System.out.println("member = " + member.getOrders().size());
}

μ£Όλ¬Έ μ»¬λ ‰μ…˜μ„ μ΄ˆκΈ°ν™”ν•˜λŠ” 수만큼 SQL이 싀행됨. νšŒμ›μ΄ 5λͺ…이면 νšŒμ›μ— λ”°λ₯Έ 주문도 5번 쑰회됨. 

 

 

페치 쑰인 μ‚¬μš©

페치 쑰인은 SQL 쑰인을 μ‚¬μš©ν•΄μ„œ μ—°κ΄€λœ μ—”ν‹°ν‹°λ₯Ό ν•¨κ»˜ μ‘°νšŒν•˜λ―€λ‘œ N+1 λ¬Έμ œκ°€ λ°œμƒν•˜μ§€ μ•ŠμŒ. 

페치 쑰인을 μ‚¬μš©ν•˜λŠ” JPQL select m from Member m join fetch m.orders λ₯Ό μ‚¬μš©ν•˜λ©΄

SELECT M.*, O.* FROM MEMBERS M

INNER JOIN ORDERS O ON M.ID=O.MEMBER_ID κ°€ 싀행됨.

 

 

ν•˜μ΄λ²„λ„€μ΄νŠΈ @BatchSize

ν•˜μ΄λ²„λ„€μ΄νŠΈκ°€ μ œκ³΅ν•˜λŠ” BatchSize μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜λ©΄ μ—°κ΄€λœ μ—”ν‹°ν‹°λ₯Ό μ‘°νšŒν•  λ•Œ μ§€μ •ν•œ size만큼 SQL의 INμ ˆμ„ μ‚¬μš©ν•΄μ„œ μ‘°νšŒν•¨. μ‘°νšŒν•œ νšŒμ›μ΄ 10λͺ…인데 size=5둜 μ§€μ •ν•˜λ©΄ 2번의 SQL만 μΆ”κ°€λ‘œ 싀행함. 

 

 

ν•˜μ΄λ²„λ„€μ΄νŠΈ @Fetch(FetchMode.SUBSELECT)

μ—°κ΄€λœ 데이터λ₯Ό μ‘°νšŒν•  λ•Œ μ„œλΈŒ 쿼리λ₯Ό μ‚¬μš©ν•΄μ„œ N+1문제λ₯Ό 해결함. 

select m from Member m where m.id > 10

SELECT O FROM ORDERS O
  WHERE O.MEMBER_ID IN (
  	SELECT
    	M.ID
    FROM
    	MEMBER M
    WHERE M.ID > 10
  )

 

 

15.4.2 읽기 μ „μš© 쿼리의 μ„±λŠ₯ μ΅œμ ν™”

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλŠ” λ³€κ²½ 감지λ₯Ό μœ„ν•΄ μŠ€λƒ…μƒ· μΈμŠ€ν„΄μŠ€λ₯Ό λ³΄κ΄€ν•˜λ―€λ‘œ 더 λ§Žμ€ λ©”λͺ¨λ¦¬λ₯Ό μ‚¬μš©ν•œλ‹€λŠ” 단점이 있음. 

μ—”ν‹°ν‹°λ₯Ό λ”± ν•œ 번만 μ½μ–΄μ„œ 화면에 좜λ ₯ν•˜κ³  λ‹€μ‹œ μ‘°νšŒν•˜κ±°λ‚˜ μˆ˜μ •ν•  일이 μ—†λ‹€λ©΄ 읽기 μ „μš©μœΌλ‘œ μ—”ν‹°ν‹°λ₯Ό μ‘°νšŒν•˜λ©΄ λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰μ„ μ΅œμ ν™”ν•  수 μžˆλ‹€. 

슀칼라 νƒ€μž…μœΌλ‘œ 쑰회

select o.id, o.name, o.price from Order p

슀칼라 νƒ€μž…μ€ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈκ°€ κ²°κ³Όλ₯Ό κ΄€λ¦¬ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ κ°€μž₯ ν™•μ‹€ν•œ 방법은 μ—”ν‹°ν‹°κ°€ μ•„λ‹Œ 슀칼라 νƒ€μž…μœΌλ‘œ λͺ¨λ“  ν•„λ“œλ₯Ό μ‘°νšŒν•˜λŠ” 것. 

 

읽기 μ „μš© 쿼리 힌트 μ‚¬μš©

ν•˜μ΄λ²„λ„€μ΄νŠΈ μ „μš© 힌트인 org.hibernate.readOnlyλ₯Ό μ‚¬μš©ν•˜λ©΄ μ—”ν‹°ν‹°λ₯Ό 읽기 μ „μš©μœΌλ‘œ μ‘°νšŒν•  수 있음. 

 

읽기 μ „μš© νŠΈλžœμž­μ…˜ μ‚¬μš©

μŠ€ν”„λ§ ν”„λ ˆμž„μ›Œν¬λ₯Ό μ‚¬μš©ν•˜λ©΄ νŠΈλžœμž­μ…˜μ„ 읽기 μ „μš© λͺ¨λ“œλ‘œ μ„€μ •ν•  수 있음. 

@Transactional(readOnly = true)

νŠΈλžœμž­μ…˜μ„ 컀밋해도 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό ν”ŒλŸ¬μ‹œν•˜μ§€ μ•ŠμœΌλ―€λ‘œ μ—”ν‹°ν‹°μ˜ 등둝, μˆ˜μ •, μ‚­μ œλŠ” λ‹Ήμ—°νžˆ λ™μž‘ν•˜μ§€ μ•Šκ³ , μŠ€λƒ…μƒ· 비ꡐ와 같은 무거운 λ‘œμ§λ“€μ„ μˆ˜ν–‰ν•˜μ§€ μ•Šμ•„ μ„±λŠ₯이 ν–₯상됨. 

 

νŠΈλžœμž­μ…˜ λ°–μ—μ„œ 읽기

νŠΈλžœμž­μ…˜ 없이 μ—”ν‹°ν‹°λ₯Ό μ‘°νšŒν•˜λŠ” 방법. 

μŠ€ν”„λ§ ν”„λ ˆμž„μ›Œν¬μ—μ„œλŠ”

@Transactional(propagation = Propagation.NOT_SUPPORTED)

둜 섀정함. 

 

 

15.4.3 배치 처리

수백만 건의 데이터λ₯Ό 배치 μ²˜λ¦¬ν•΄μ•Ό ν•œλ‹€κ³  κ°€μ •ν•˜λ©΄

일반적인 λ°©μ‹μœΌλ‘œ μ‘°νšŒν•˜λ©΄ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— λ§Žμ€ μ—”ν‹°ν‹°κ°€ μŒ“μ—¬ λ©”λͺ¨λ¦¬ λΆ€μ‘± 였λ₯˜κ°€ λ°œμƒν•¨. 

 

JPA 등둝 배치

λ§Žμ€ μ—”ν‹°ν‹°λ₯Ό ν•œ λ²ˆμ— 등둝할 λ•ŒλŠ”

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— μ—”ν‹°ν‹°κ°€ 계속 μŒ“μ΄μ§€ μ•Šλ„λ‘ 일정 λ‹¨μœ„λ§ˆλ‹€ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ˜ μ—”ν‹°ν‹°λ₯Ό λ°μ΄ν„°λ² μ΄μŠ€μ— ν”ŒλŸ¬μ‹œν•˜κ³  μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό μ΄ˆκΈ°ν™”ν•΄μ•Ό 함. 

EntityManager em = entityManagerFactory.createEntityManager();
EntityTranssaction tx = em.getTransaction();
tx.begin();

for (int i = 0; i < 10000; i++) {
	Product product = new Product("item" + i, 10000);
    em.persist(product);
    
    //100κ±΄λ§ˆλ‹€ ν”ŒλŸ¬μ‹œμ™€ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ μ΄ˆκΈ°ν™”
    if(i % 100 == 0) {
    	em.flush();
        em.clear();
    }
}

tx.commit();
em.close();

 

 

λ‹€μŒμœΌλ‘œ μˆ˜μ • 배치 처리λ₯Ό ν•  λ•ŒλŠ” νŽ˜μ΄μ§• 처리 λ˜λŠ” μ»€μ„œ 방법을 μ‚¬μš©ν•¨. 

JPA νŽ˜μ΄μ§• 처리

100건씩 νŽ˜μ΄μ§• 쿼리둜 μ‘°νšŒν•˜λ©΄μ„œ μƒν’ˆμ˜ 가격을 100원씩 μ¦κ°€ν•˜κ³ , νŽ˜μ΄μ§€ λ‹¨μœ„λ§ˆλ‹€ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό ν”ŒλŸ¬μ‹œν•˜κ³  쑰기화함. 

 

ν•˜μ΄λ²„λ„€μ΄νŠΈ scroll μ‚¬μš©

JPAλŠ” JDBC μ»€μ„œλ₯Ό μ§€μ›ν•˜μ§€ μ•Šμ•„ μ»€μ„œλ₯Ό μ‚¬μš©ν•˜λ €λ©΄ ν•˜μ΄λ²„λ„€μ΄νŠΈ μ„Έμ…˜μ„ μ‚¬μš©ν•΄μ•Ό 함. 

em.unwrap() λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•΄μ„œ ν•˜μ΄λ²„λ„€μ΄νŠΈ μ„Έμ…˜μ„ ꡬ함. λ‹€μŒμœΌλ‘œ 쿼리λ₯Ό μ‘°νšŒν•˜λ©΄μ„œ scroll() λ©”μ†Œλ“œλ‘œ ScrollableResults 객체λ₯Ό λ°˜ν™˜λ°›μŒ. 이 객체의 next()  λ©”μ†Œλ“œλ₯Ό ν˜ΈμΆœν•˜λ©΄ μ—”ν‹°ν‹°λ₯Ό ν•˜λ‚˜μ”© μ‘°νšŒν•  수 있음. 

 

ν•˜μ΄λ²„λ„€μ΄νŠΈ λ¬΄μƒνƒœ μ„Έμ…˜ μ‚¬μš©

λ¬΄μƒνƒœ μ„Έμ…˜μ€ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό λ§Œλ“€μ§€ μ•Šκ³  2μ°¨ μΊμ‹œλ„ μ‚¬μš©ν•˜μ§€ μ•ŠμŒ. λ”°λΌμ„œ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό ν”ŒλŸ¬μ‹œν•˜κ±°λ‚˜ μ΄ˆκΈ°ν™”ν•˜μ§€ μ•Šμ•„λ„ 됨. 

μ—”ν‹°ν‹°λ₯Ό μˆ˜μ •ν•˜λ €λ©΄ λ¬΄μƒνƒœ μ„Έμ…˜μ΄ μ œκ³΅ν•˜λŠ” update() λ©”μ†Œλ“œλ₯Ό 직접 ν˜ΈμΆœν•΄μ•Ό 함. 

 

 

15.4.4 SQL 쿼리 힌트 μ‚¬μš©

JPQλŠ” λ°μ΄ν„°λ² μ΄μŠ€ SQL 힌트 κΈ°λŠ₯을 μ œκ³΅ν•˜μ§€ μ•Šμ•„ SQL 힌트λ₯Ό μ‚¬μš©ν•˜λ €λ©΄ ν•˜μ΄λ²„λ„€μ΄νŠΈλ₯Ό 직접 μ‚¬μš©ν•΄μ•Ό 함. 

Session session = em.unwrap(Session.class); //ν•˜μ΄λ²„λ„€μ΄νŠΈ 직접 μ‚¬μš©

List<Member> list = session.createQuery("select m from Member m")
	.addQueryHint("FULL (MEMBER)") //SQL HINT μΆ”κ°€
    .list();
    
    

select
	/* + FULL (MEMBER) */ m.id, m.name
from
	Member m

 

 

15.4.5 νŠΈλžœμž­μ…˜μ„ μ§€μ›ν•˜λŠ” μ“°κΈ° μ§€μ—°κ³Ό μ„±λŠ₯ μ΅œμ ν™”

νŠΈλžœμž­μ…˜μ„ μ§€μ›ν•˜λŠ” μ“°κΈ° μ§€μ—°κ³Ό JDBC 배치

JDBCκ°€ μ œκ³΅ν•˜λŠ” SQL 배치 κΈ°λŠ₯을 μ‚¬μš©ν•˜λ©΄ SQL을 λͺ¨μ•„μ„œ λ°μ΄ν„°λ² μ΄μŠ€μ— ν•œλ²ˆμ— 보낼 수 있음. 

SQL λ°°μΉ˜λŠ” 같은 SQL일 λ•Œλ§Œ μœ νš¨ν•΄μ„œ 쀑간에 λ‹€λ₯Έ μ²˜λ¦¬κ°€ λ“€μ–΄κ°€λ©΄ SQL 배치λ₯Ό λ‹€μ‹œ μ‹œμž‘ν•¨. 

 

νŠΈλžœμž­μ…˜μ„ μ§€μ›ν•˜λŠ” μ“°κΈ° μ§€μ—°κ³Ό μ• ν”Œλ¦¬μΌ€μ΄μ…˜ ν™•μž₯μ„±

νŠΈλžœμž­μ…˜μ„ μ§€μ›ν•˜λŠ” μ“°κΈ° μ§€μ—°κ³Ό λ³€κ²½ 감지 κΈ°λŠ₯의 μž₯점은 λ°μ΄ν„°λ² μ΄μŠ€ ν…Œμ΄λΈ” λ‘œμš°μ— 락이 κ±Έλ¦¬λŠ” μ‹œκ°„μ„ μ΅œμ†Œν™”ν•œλ‹€λŠ” 점이닀. 

update(memberA); //UPDATE SQL A
λΉ„μ¦ˆλ‹ˆμŠ€λ‘œμ§A(); //UPDATE SQL ...
λΉ„μ¦ˆλ‹ˆμŠ€λ‘œμ§B(); //UPDATE SQL ...
commit();

JPQλŠ” 컀밋을 ν•΄μ•Ό ν”ŒλŸ¬μ‹œλ₯Ό ν˜ΈμΆœν•˜κ³  λ°μ΄ν„°λ² μ΄μŠ€μ— μˆ˜μ • 쿼리λ₯Ό 보냄. 쿼리λ₯Ό 보내고 λ°”λ‘œ νŠΈλžœμž­μ…˜μ„ μ»€λ°‹ν•˜λ―€λ‘œ 결과적으둜 λ°μ΄ν„°λ² μ΄μŠ€μ— 락이 κ±Έλ¦¬λŠ” μ‹œκ°„μ„ μ΅œμ†Œν™”ν•œλ‹€.