DevLog ๐Ÿ˜ถ

[JPA] OSIV ์ฐ๋จนํ•˜๊ธฐ 2ํŽธ - ์ค€์˜์† ์ƒํƒœ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ง€์—ฐ ๋กœ๋”ฉํ•˜๊ธฐ (FetchType.EAGER์™€ N+1 ๋ฌธ์ œ, fetch join) ๋ณธ๋ฌธ

Back-end/JPA

[JPA] OSIV ์ฐ๋จนํ•˜๊ธฐ 2ํŽธ - ์ค€์˜์† ์ƒํƒœ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ง€์—ฐ ๋กœ๋”ฉํ•˜๊ธฐ (FetchType.EAGER์™€ N+1 ๋ฌธ์ œ, fetch join)

dolmeng2 2023. 5. 21. 19:22

๐ŸŒฑ ๋“ค์–ด๊ฐ€๊ธฐ ์ „

์ง€๋‚œ ํฌ์ŠคํŒ…์—์„œ๋Š” ์ค€์˜์† ์ƒํƒœ์˜ ์—”ํ‹ฐํ‹ฐ๋Š” ์ง€์—ฐ ๋กœ๋”ฉํ•  ์ˆ˜ ์—†๋‹ค๊ณ  ๋งํ–ˆ๋‹ค.

์ด๋Š”, ์ง€์—ฐ ๋กœ๋”ฉ์„ ์œ„ํ•œ ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ธฐ ์œ„ํ•ด ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•œ๋ฐ, ์ค€์˜์† ์ƒํƒœ์˜ ์—”ํ‹ฐํ‹ฐ๋Š” ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์˜ ๊ด€๋ฆฌ ๋ฒ”์œ„์—์„œ ๋ฒ—์–ด๋‚ฌ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด, ์ค€์˜์† ์ƒํƒœ์˜ ์—”ํ‹ฐํ‹ฐ๋Š” ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•ด์•ผ ํ• ๊นŒ?

 


 

๐ŸŒฑ ๊ธ€๋กœ๋ฒŒ ํŽ˜์น˜ ์ „๋žต์„ LAZY์—์„œ EAGER๋กœ ์ˆ˜์ •ํ•˜๊ธฐ

๊ฐ„๋‹จํ•˜๊ฒŒ ๋งํ•˜๋ฉด ์ง€์—ฐ ๋กœ๋”ฉ์„ ์‚ฌ์šฉํ•˜์ง€ ๋ง๊ณ , ์ฆ‰์‹œ ๋กœ๋”ฉ์„ ์‚ฌ์šฉํ•˜์ž๋Š” ๊ฒƒ์ด๋‹ค ๐Ÿ˜…

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Crew {

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

    @Column(nullable = false)
    private String name;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "wootecho_id", nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
    private Wootecho wootecho;
}

ํฌ๋ฃจ ํด๋ž˜์Šค์˜ fetch ์ „๋žต์„ EAGER๋กœ ์„ค์ •ํ•˜์˜€๋‹ค. (์–ด์ฐจํ”ผ @ManyToOne์˜ ๋””ํดํŠธ ์ „๋žต์ด fetch์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •ํ•˜์ง€ ์•Š์•„๋„ ๋˜‘๊ฐ™๋‹ค.)

์ฆ‰์‹œ ๋กœ๋”ฉ์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด, Crew๋ฅผ ์กฐํšŒํ•  ๋•Œ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ์ธ Wootecho ์—ญ์‹œ ํ•จ๊ป˜ ๊ฐ€์ ธ์˜ค๊ฒŒ ๋œ๋‹ค.

์‹ค์ œ๋กœ ์ฟผ๋ฆฌ๋ฅผ ํ™•์ธํ•ด๋ณด์ž.

๋‹ค์Œ๊ณผ ๊ฐ™์ด Join์„ ํ†ตํ•ด ํ•œ ๋ฒˆ์— ๊ฐ€์ ธ์˜จ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

 

๐Ÿ’ฌ join์„ ํ†ตํ•ด ๊ฐ€์ ธ์˜ค๋Š” ๊ฑฐ๋ฉด ๋” ์ข‹์€ ๊ฑฐ ์•„๋‹ˆ์•ผ? 

๋งŒ์•ฝ, crew ์—”ํ‹ฐํ‹ฐ์— ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ง€๊ธˆ์ฒ˜๋Ÿผ ํ•˜๋‚˜๊ฐ€ ์•„๋‹ˆ๋ผ ์—ฌ๋Ÿฌ ๊ฐœ๋ผ๋ฉด?

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Crew {

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

    @Column(nullable = false)
    private String name;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "wootecho_id", nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
    private Wootecho wootecho;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "wootecho2_id", nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
    private Wootecho wootecho2;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "wootecho3_id", nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
    private Wootecho wootecho3;
}

์ƒ๋‹นํžˆ ์–ด์ง€๋Ÿฌ์šด ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ€๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ์‹ฌ์ง€์–ด, ์œ„ ์˜ˆ์ œ๋Š” ์ •๋ง ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ์ธ๋ฐ๋„ ์—ฌ๋Ÿฌ join์ด ๊ฑธ๋ฆฌ๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

 

 

๐Ÿ’ฌ N+1 ๋ฌธ์ œ

์ด๋Š” JPQL์„ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ์˜ ๋ฌธ์ œ์ด๋‹ค.

JPQL์„ ํ†ตํ•ด JPA๊ฐ€ SQL์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด, ๊ธ€๋กœ๋ฒŒ ํŽ˜์น˜ ์ „๋žต์€ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š๊ณ  JPQL ์ž์ฒด ์ •๋ณด๋งŒ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•ด์„œ join ๋Œ€์‹  where ์ ˆ๋กœ ๋ฌถ์ธ ๋‹ค์ˆ˜ ๊ฐœ์˜ ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ€๊ฒŒ ๋œ๋‹ค.

 

์ด ๋ฌธ์ œ๋ฅผ ์žฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” OneToMany ๊ด€๊ณ„๋ฅผ ์‚ดํŽด๋ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์šฐํ…Œ์ฝ”๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์กฐํšŒ API๋ฅผ ์ƒ์„ฑํ•  ๊ฒƒ์ด๋‹ค.

์šฐํ…Œ์ฝ” ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•˜์ž.

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Wootecho {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Enumerated(EnumType.STRING)
    private Course course;

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "wootecho")
    private List<Crew> crews;
}

crew์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ๊ณ , ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ์„ Wootecho๋กœ ์„ค์ •ํ•ด์ฃผ์—ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ , JPQL์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ์œ„์™€ ๊ฐ™์ด ์ปค์Šคํ…€ ๋ฉ”์„œ๋“œ๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์—ˆ๋‹ค.

public interface WootechoRepository extends JpaRepository<Wootecho, Long> {

    @Query("select w from Wootecho w")
    List<Wootecho> findWootechos();
}

 

์„œ๋น„์Šค์™€ ์ปจํŠธ๋กค๋Ÿฌ, DTO์ด๋‹ค. ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ถ”๊ฐ€ ์„ค๋ช…์€ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ฒ ๋‹ค.

// WootechoService.java
@Service
@RequiredArgsConstructor
public class WootechoService {
    private final WootechoRepository wootechoRepository;

    public List<Wootecho> getAll() {
        return wootechoRepository.findWootechos();
    }
}


// WootechoController.java
@RestController
@RequestMapping("/wootecho")
@RequiredArgsConstructor
public class WootechoController {
    private final WootechoService wootechoService;

    @GetMapping
    public ResponseEntity<List<WootechoResponse>> getAll() {
        final List<Wootecho> wootechos = wootechoService.getAll();
        final List<WootechoResponse> result = wootechos.stream()
            .map(WootechoResponse::of)
            .toList();
        return ResponseEntity.ok(result);
    }
}

์ƒˆ๋กœ์šด WootechoService์™€ WootechoController๋ฅผ ์ƒ์„ฑํ•˜์˜€๋‹ค.

 

์ด์ œ, ์šฐํ…Œ์ฝ” ์ „์ฒด๋ฅผ ์กฐํšŒํ•ด๋ณด์ž. ํ˜„์žฌ ํ…Œ์ด๋ธ”์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฐ์ดํ„ฐ๊ฐ€ ์ €์žฅ๋˜์–ด ์žˆ๋‹ค.

Hibernate: 
    select
        w1_0.id,
        w1_0.course 
    from
        wootecho w1_0
Hibernate: 
    select
        c1_0.wootecho_id,
        c1_0.id,
        c1_0.name 
    from
        crew c1_0 
    where
        c1_0.wootecho_id=?
Hibernate: 
    select
        c1_0.wootecho_id,
        c1_0.id,
        c1_0.name 
    from
        crew c1_0 
    where
        c1_0.wootecho_id=?
Hibernate: 
    select
        c1_0.wootecho_id,
        c1_0.id,
        c1_0.name 
    from
        crew c1_0 
    where
        c1_0.wootecho_id=?

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ต‰์žฅํžˆ ๊ธธ์–ด์ง„ ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ„ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ํ•˜๋‚˜์”ฉ ๋ณด์ž.

์œ„์™€ ๊ฐ™์ด Wootecho๋งŒ ์กฐํšŒํ•˜๋ ค๊ณ  ํ–ˆ์œผ๋‚˜, ์ฆ‰์‹œ ๋กœ๋”ฉ์œผ๋กœ ์ธํ•ด Crew์— ๋Œ€ํ•œ ์ •๋ณด๋„ ๊ฐ€์ ธ์˜ค๋ฉด์„œ 1+N๊ฐœ์˜ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒƒ์ด๋‹ค.

 

JPQL์ด ์ฝ”๋“œ ๋ถ„์„ ์‹œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ ˆ์ฐจ๋ฅผ ๋ฐŸ์œผ๋ฉฐ ๋ฐœ์ƒํ•œ ๋ฌธ์ œ์ด๋‹ค.

1. select w from Wootecho w๋ฅผ ๋ณด๊ณ  select * from wootecho ์ฟผ๋ฆฌ ์ƒ์„ฑ
2. ์ƒ์„ฑ๋œ ์ฟผ๋ฆฌ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ List<Wootecho> ์—”ํ‹ฐํ‹ฐ ์ƒ์„ฑ
3. ์ฟผ๋ฆฌ๊ฐ€ ์ƒ์„ฑ๋œ ์ดํ›„, ์ฆ‰์‹œ ๋กœ๋”ฉ ์ „๋žต์œผ๋กœ ์ธํ•ด์„œ Wootecho์™€ ์—ฐ๊ด€๋œ Crew ์—”ํ‹ฐํ‹ฐ ์—ญ์‹œ ํ•จ๊ป˜ ์ฆ‰์‹œ ๋กœ๋”ฉ
4. Wootecho ์—”ํ‹ฐํ‹ฐ์˜ ๊ฐœ์ˆ˜๋งŒํผ select * from crew where wootecho_id = ? ์ฟผ๋ฆฌ ๋ฐœ์ƒ

 


 

๐ŸŒฑ LAZY๋กœ ์œ ์ง€ํ•˜๋ฉด์„œ fetch join ์‚ฌ์šฉํ•˜๊ธฐ

์ง€์—ฐ ๋กœ๋”ฉ์„ ์‚ฌ์šฉํ•˜๋ฉด์„œ, jpql์„ ํ˜ธ์ถœํ•˜๋Š” ์‹œ์ ์— ํ•œ ๋ฒˆ์— ๊ฐ€์ ธ์˜ค๋„๋ก ์ตœ์ ํ™”๋ฅผ ํ•˜๋Š” ์ „๋žต์ด๋‹ค.

public interface WootechoRepository extends JpaRepository<Wootecho, Long> {

    @Query("select w from Wootecho w join fetch w.crews")
    List<Wootecho> findWootechos();
}

์œ„์™€ ๊ฐ™์ด jpql์— join fetch๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

์ด ์ƒํƒœ๋กœ ์ฟผ๋ฆฌ๋ฅผ ๋‹ค์‹œ ์‚ดํŽด๋ณด์ž.

๊น”๋”ํ•˜๊ฒŒ join์„ ์ด์šฉํ•˜์—ฌ ํ•œ ๋ฒˆ์— ๊ฐ€์ ธ์˜จ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค ๐Ÿ˜Š

 


 

๐ŸŒฑ ๊ฐ•์ œ๋กœ ์ดˆ๊ธฐํ™”ํ•˜๊ธฐ - Hibernate.initialize()

์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ์‚ด์•„์žˆ์„ ๋•Œ ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๊ณ„์ธต์—์„œ ํ•„์š”ํ•œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ฐ•์ œ๋กœ ์ดˆ๊ธฐํ™”ํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

JPA๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ด€๋ จ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, ํ•˜์ด๋ฒ„๋„ค์ดํŠธ์—์„œ ์ œ๊ณตํ•˜๋Š” Hibernate.initialize()๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์šฐ๋ฆฌ๊ฐ€ ์ด์ „ ํฌ์ŠคํŒ…๋ถ€ํ„ฐ ๊ณ ๋ฏผํ–ˆ๋˜ 'ํฌ๋ฃจ๋ฅผ ์กฐํšŒํ•  ๋•Œ ์šฐํ…Œ์ฝ” ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ•จ๊ป˜ ์กฐํšŒํ•˜๋Š” ๊ฒฝ์šฐ'์— ๋Œ€ํ•ด ์ ์šฉํ•ด๋ณด์ž.

public class Crew {
	...
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "wootecho_id", nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
    private Wootecho wootecho;
}

๋‹ค์‹œ Crew ์—”ํ‹ฐํ‹ฐ์—์„œ Wootecho ์—”ํ‹ฐํ‹ฐ์˜ ํŽ˜์น˜ ์ „๋žต์„ LAZY๋กœ ์„ค์ •ํ•ด์ค€๋‹ค. (๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Wootecho ์—”ํ‹ฐํ‹ฐ์˜ Crew ์—ญ์‹œ LAZY๋กœ ์„ค์ •ํ•ด์ฃผ์—ˆ๋‹ค.)

// CrewService.java
public Crew getById(final Long wootechoId) {
    final Crew findCrew = crewRepository.findById(wootechoId)
        .orElseThrow(() -> new IllegalArgumentException("์กด์žฌํ•˜์ง€ ์•Š๋Š” ์•„์ด๋””์ž…๋‹ˆ๋‹ค."));
    Hibernate.initialize(findCrew.getWootecho());
    return findCrew;
}

๊ทธ๋ฆฌ๊ณ , ๋‹ค์Œ๊ณผ ๊ฐ™์ด getById() ๋ฉ”์„œ๋“œ์— Hibernate.initialize()๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ๋‹ค.

 

๊ธฐ์กด์—๋Š” no Session Exception์ด ํ„ฐ์กŒ๋˜ ๊ฒƒ๊ณผ ๋‹ค๋ฅด๊ฒŒ, ์ž˜ ์กฐํšŒ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

Hibernate ๊ธฐ์ˆ ์„ ์ง์ ‘์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋ถ€๋‹ด์Šค๋Ÿฝ๋‹ค๋ฉด, ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ์‚ด์•„์žˆ๋Š” ๋ฒ”์œ„์—์„œ ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๊ณ„์ธต์ด ์‚ฌ์šฉํ•  ๋ฐ์ดํ„ฐ๋ฅผ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•ด๋ฒ„๋ฆฌ๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๋‹ค.

// CrewService.java
public Crew getById(final Long wootechoId) {
    final Crew findCrew = crewRepository.findById(wootechoId)
        .orElseThrow(() -> new IllegalArgumentException("์กด์žฌํ•˜์ง€ ์•Š๋Š” ์•„์ด๋””์ž…๋‹ˆ๋‹ค."));
    findCrew.getWootecho().getCourse(); // ์‹ค์ œ ์‚ฌ์šฉํ•ด๋ฒ„๋ฆฌ๊ธฐ
    return findCrew;
}

์ด๋ ‡๊ฒŒ ํ•ด๋„ ์œ„์™€ ๋™์ผํ•œ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋œ๋‹ค.

 

ํ•˜์ง€๋งŒ, ์ด๋ ‡๊ฒŒ ํ”„๋ก์‹œ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ์—ญํ• ์„ ์„œ๋น„์Šค ๊ณ„์ธต์ด ๋‹ด๋‹นํ•˜๋ฉด ๋ทฐ๊ฐ€ ํ•„์š”ํ•œ ์—”ํ‹ฐํ‹ฐ์— ๋”ฐ๋ผ์„œ ์„œ๋น„์Šค ๊ณ„์ธต์˜ ๋กœ์ง์„ ๋ณ€๊ฒฝํ•ด์•ผ ํ•œ๋‹ค.

๋งŒ์•ฝ CrewResponse๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ DTO ๊ฐ์ฒด๊ฐ€ ์ƒ๊ธฐ๊ณ , ๊ฑฐ๊ธฐ์„œ๋Š” course๋งŒ ํ•„์š”ํ•œ ๊ฒƒ์ด ์•„๋‹Œ name์ด๋‚˜ ๋‹ค๋ฅธ ํ•„๋“œ๋“ค์˜ ๊ฐ’๋“ค์ด ํ•„์š”ํ•˜๋‹ค๋ฉด? ๋ทฐ์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์„œ๋น„์Šค๊นŒ์ง€ ํ•จ๊ป˜ ์˜ํ–ฅ์„ ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— ์œ ์ง€๋ณด์ˆ˜ ํฌ์ธํŠธ๊ฐ€ ์ฆ๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

์ด๋ฅผ ์œ„ํ•ด ์„œ๋น„์Šค ๊ณ„์ธต์—์„œ ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๊ณ„์ธต์„ ์œ„ํ•œ ํ”„๋ก์‹œ ์ดˆ๊ธฐํ™” ์—ญํ• ์„ ๋ถ„๋ฆฌํ•ด์•ผ ํ•˜๋ฉฐ, FACADE ํŒจํ„ด์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์„œ๋น„์Šค ์•ž๋‹จ์—์„œ Facade๊ฐ€ 1์ฐจ์ ์œผ๋กœ ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๊ณ„์ธต๊ณผ ๋„๋ฉ”์ธ ๋ชจ๋ธ ์‚ฌ์ด์˜ ๋…ผ๋ฆฌ์  ์˜์กด์„ฑ์„ ๋ถ„๋ฆฌํ•ด์คŒ์œผ๋กœ์„œ, ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๊ณ„์ธต์—์„œ ํ•„์š”ํ•œ ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ์—ญํ• ์„ ๋‹ด๋‹นํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

@Component
@RequiredArgsConstructor
public class CrewFacade {

    private final CrewService crewService;
    
    public Crew getById(final Long crewId) {
        final Crew crew = crewService.getById(crewId);
        // ํ”„๋ก์‹œ ๊ฐ์ฒด๊ฐ€ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”
        crew.getWootecho().getCrews();
        return crew;
    }
}
// CrewController.java
@GetMapping("/{crewId}")
public ResponseEntity<CrewResponse> getById(@PathVariable Long crewId) {
    final Crew crew = crewFacade.getById(crewId);
    return ResponseEntity.ok(CrewResponse.of(crew));
}

 

ํ•˜์ง€๋งŒ... ์ด๋Ÿฐ ์‹์œผ๋กœ ์ค‘๊ฐ„ ๊ณ„์ธต์ด ๋“ค์–ด๊ฐ€๋ฉด ๊ฒฐ๊ณผ์ ์œผ๋กœ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ๋งŽ์•„์ง„๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

๋˜ํ•œ, JPA์— ๋Œ€ํ•œ ์ดํ•ด๋„๊ฐ€ ์—†๋Š” ๊ฐœ๋ฐœ์ž์™€ ํ˜‘์—…์„ ํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด ํ•ด๋‹น ์ฝ”๋“œ์— ๋Œ€ํ•ด ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜์ง€ ๋ชปํ•œ ์ƒํƒœ๋กœ ์ œ๊ฑฐํ•ด๋ฒ„๋ฆฌ๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•  ์ˆ˜๋„ ์žˆ๋‹ค ๐Ÿฅฒ

 

๊ฒฐ๊ตญ ์ง€๊ธˆ๊นŒ์ง€์˜ ๋ชจ๋“  ๋ฌธ์ œ๋Š” '์—”ํ‹ฐํ‹ฐ๊ฐ€ ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๊ณ„์ธต์—์„œ ์ค€์˜์† ์ƒํƒœ์ด๊ธฐ ๋•Œ๋ฌธ์—' ๋ฐœ์ƒํ–ˆ๋˜ ๋ฌธ์ œ์ด๋‹ค.

์ด๋Ÿฐ ์›์ดˆ์ ์ธ ๋ฌธ์ œ๋ถ€ํ„ฐ ํ•ด๊ฒฐ์„ ํ•ด๋ณด์ž.

 


๐ŸŒฑ OSIV (Open session In View)

OSIV๋Š” ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ๋ทฐ๊นŒ์ง€ ์—ด์–ด๋‘”๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค. ๋ทฐ๊นŒ์ง€ ์ปจํ…์ŠคํŠธ๊ฐ€ ์—ด๋ ค ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ž๋™์œผ๋กœ ์ง€์—ฐ ๋กœ๋”ฉ์ด ๊ฐ€๋Šฅํ•ด์ง€๋Š” ๊ฒƒ์ด๋‹ค.

cf. ์ฐธ๊ณ ๋กœ, OSIV๋Š” ํ•˜์ด๋ฒ„๋„ค์ดํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์šฉ์–ด์—ฌ์„œ JPA์—์„œ๋Š” OEIV (Open EntityManager In View)๋ฅผ ๋” ๋งŽ์ด ์‚ฌ์šฉํ•˜์ง€๋งŒ, OSIV๊ฐ€ ๋” ์ต์ˆ™ํ•˜๊ธฐ ๋•Œ๋ฌธ์— OSIV๋ผ๊ณ  ๋ถ€๋ฅด๊ฒ ๋‹ค.

 

OSIV๋Š” ์—ฌ๋Ÿฌ ๋ฐฉ์‹์ด ์žˆ๋Š”๋ฐ, ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ๊ณผ ์ƒ๋ช…์ฃผ๊ธฐ๊ฐ€ ๋™์ผํ•œ Transaction per request OSIV๊ฐ€ ์กด์žฌํ•œ๋‹ค.

์š”์ฒญ์ด ๋“ค์–ด์˜ค์ž๋งˆ์ž ์„œ๋ธ”๋ฆฟ ํ•„ํ„ฐ๋‚˜ ์Šคํ”„๋ง ์ธํ„ฐ์…‰ํ„ฐ์—์„œ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ๋งŒ๋“ค๋ฉด์„œ ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•˜๊ณ , ์š”์ฒญ์ด ๋๋‚  ๋•Œ ์ปจํ…์ŠคํŠธ์™€ ํŠธ๋žœ์žญ์…˜์„ ํ•จ๊ป˜ ์ข…๋ฃŒํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. ์ด๋Ÿฌ๋ฉด ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ๋๊นŒ์ง€ ์‚ด์•„ ์žˆ์–ด์„œ ์–ด๋””์„œ ์กฐํšŒํ•˜๋“  ์˜์† ์ƒํƒœ๊ฐ€ ๋˜๋‹ค.

 

์ง€์—ฐ ๋กœ๋”ฉ ์—ญ์‹œ ์ž์œ ๋กญ๊ฒŒ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ์—”ํ‹ฐํ‹ฐ์™€ ํŠธ๋žœ์žญ์…˜ ์ƒ๋ช…์ฃผ๊ธฐ๊ฐ€ ๋„ˆ๋ฌด ๊ธธ๊ธฐ ๋•Œ๋ฌธ์— ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ์˜์—ญ์—์„œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ž์œ ๋กญ๊ฒŒ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ์—”ํ‹ฐํ‹ฐ๋Š” ์ฝ”์–ดํ•œ ์˜์—ญ์ด๊ธฐ ๋•Œ๋ฌธ์— ์™ธ๋ถ€์—์„œ ์‰ฝ๊ฒŒ ์ฐธ์กฐํ•˜๋Š” ๊ฑด ์ข‹์ง€ ์•Š๋‹ค. ๋„๋ฉ”์ธ์ด DTO๋ฅผ ์ฐธ์กฐํ•˜์ง€ ์•Š๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•œ ์˜์—ญ์ด๋ผ๊ณ  ๋ด๋„ ๋œ๋‹ค. ใ…Žใ…Ž

(๊ฐœ์ธ์ ์ธ ์ƒ๊ฐ์œผ๋กœ๋Š” entity๊ฐ€ controller ์˜์—ญ๊นŒ์ง€ ์ฐธ์กฐ๋˜๋Š” ๊ฑธ ์„ ํ˜ธํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๊ทธ๋Ÿฌํ•œ ๊ด€์ ์—์„œ๋„ ๋ณ„๋กœ๋‹ค)

 

๊ทธ๋Ÿฌ๋ฉด, ์Šคํ”„๋ง์—์„œ๋Š” ์–ด๋–ค OSIV ์ „๋žต์„ ์ฑ„ํƒํ•˜๊ณ  ์žˆ์„๊นŒ?

- ๋‹ค์–‘ํ•œ OSIV ํด๋ž˜์Šค๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ํ•„ํ„ฐ๋‚˜ ์ธํ„ฐ์…‰ํ„ฐ๊ฐ€ ๋“ฑ๋กํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. (์ค‘์š”ํ•œ ๋‚ด์šฉ์€ ์•„๋‹ˆ๋‹ˆ ์ƒ๋žตํ•˜๊ฒ ๋‹ค)

 

 

๐Ÿ’ฌ ์Šคํ”„๋ง ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์ œ๊ณตํ•˜๋Š” OSIV

OSIV๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ๋น„์ฆˆ๋‹ˆ์Šค ๊ณ„์ธต๊นŒ์ง€๋งŒ ํŠธ๋žœ์žญ์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ์ „๋žต์ด๋‹ค.

ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด ์„œ๋ธ”๋ฆฟ ํ•„ํ„ฐ๋‚˜ ์Šคํ”„๋ง ํ•„ํ„ฐ์—์„œ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ์ƒ์„ฑํ•˜์ง€๋งŒ, ํŠธ๋žœ์žญ์…˜์€ ์‹œ์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค.

์ดํ›„, ์„œ๋น„์Šค ๊ณ„์ธต์—์„œ @Transactional์„ ์‹œ์ž‘ํ•  ๋•Œ ์ดˆ๊ธฐ์— ์ƒ์„ฑํ•œ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ํŠธ๋žœ์žญ์…˜์„ ์‹œ์ž‘ํ•œ๋‹ค.

์„œ๋น„์Šค ๊ณ„์ธต์ด ์ข…๋ฃŒ๋˜๋ฉด ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ํ›„ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋ฅผ ํ”Œ๋Ÿฌ์‹œํ•˜๊ณ , ํŠธ๋žœ์žญ์…˜์€ ์ข…๋ฃŒํ•˜์ง€๋งŒ ์ปจํ…์ŠคํŠธ๋Š” ์ข…๋ฃŒํ•˜์ง€ ์•Š๋Š”๋‹ค.

 

์ด๋ ‡๊ฒŒ ๋˜๋ฉด ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๊ณ„์ธต๊นŒ์ง€ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ์‚ด์•„์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์—”ํ‹ฐํ‹ฐ๋Š” ์˜์† ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๊ฒŒ ๋˜๊ณ , ์ง€์—ฐ ๋กœ๋”ฉ์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋œ๋‹ค!

์ดํ›„ ํ•„ํ„ฐ๋‚˜ ์ธํ„ฐ์…‰ํ„ฐ๋กœ ์š”์ฒญ์ด ๋‹ค์‹œ ๋˜๋Œ์•„์˜ค๋ฉด ์ปจํ…์ŠคํŠธ๋ฅผ ์ข…๋ฃŒํ•ด์ค€๋‹ค. (์ด๋•Œ, ํ”Œ๋Ÿฌ์‹œ๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š๋Š”๋‹ค!)

 

โญ๏ธ ๊ธฐ์กด์˜ ๋ฐฉ๋ฒ•์— ๋น„ํ•ด ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๊ณ„์ธต์—์„œ๋Š” ํŠธ๋žœ์žญ์…˜์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•œ ๋ณ€๊ฒฝ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

-> ์ •ํ™•ํ•˜๊ฒŒ๋Š”, ์ด๋ฏธ ์„œ๋น„์Šค ๊ณ„์ธต์—์„œ ํ”Œ๋Ÿฌ์‹œ๊นŒ์ง€ ์ง„ํ–‰๋˜์—ˆ๊ณ  ํ•„ํ„ฐ-์ธํ„ฐ์…‰ํ„ฐ์—์„œ๋„ ํ”Œ๋Ÿฌ์‹œ๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, ํ”Œ๋Ÿฌ์‹œ๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š์•„ ์—”ํ‹ฐํ‹ฐ์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์— ๋Œ€ํ•ด DB๋กœ ๋ฐ˜์˜ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด๋‹ค.

-> ๊ฐ•์ œ๋กœ ํ”Œ๋Ÿฌ์‹œํ•˜๋”๋ผ๋„ ํŠธ๋žœ์žญ์…˜ ๋ฒ”์œ„ ๋ฐ–์ด๋ผ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. (TransactionRequiredException)

 

ํ•˜์ง€๋งŒ, ์ง€์—ฐ ๋กœ๋”ฉ์„ ํ†ตํ•ด์„œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•ด์˜ฌ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋‹จ์ˆœํžˆ ์กฐํšŒ๋Š” ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

 

๐Ÿ’ก ์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฑด, ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ์‚ด์•„์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋‚ด๋ถ€์— ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์ €์žฅ๋˜์–ด ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.
ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๋ ˆ์ด์–ด์—์„œ @Transactional ํฌํ•จํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ํ˜ธ์ถœํ•˜๊ฒŒ ๋˜๋ฉด ํ•ด๋‹น ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์ข…๋ฃŒ๋  ๋•Œ ์ด๋ฏธ ์ด์ „์— ์ €์žฅ๋˜์—ˆ๋˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ํ•จ๊ป˜ flush ํ•ด๋ฒ„๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์— ์˜ˆ๊ธฐ์น˜ ๋ชปํ•œ ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
- ๋ณดํ†ต์€ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์„œ๋น„์Šค๋ฅผ ๋จผ์ € ํ˜ธ์ถœํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋Ÿฐ ์ผ์€ ๊ฑฐ์˜ ์—†๊ฒ ์ง€๋งŒ... 
ํ”Œ๋Ÿฌ์‹œ๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š์•„์„œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๋ฐ˜์˜ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์„ ๊ธฐ์–ตํ•˜์ž!

 


๐ŸŒฑ ๋‹ค์‹œ ๋Œ๊ณ  ๋Œ์•„์„œ...

์Šคํ”„๋ง๋ถ€ํŠธ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ OSIV๋ฅผ true๋กœ ์„ค์ •ํ•ด๋‘์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ทฐ๊นŒ์ง€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฐธ์กฐํ•œ๋‹ค.

ํ•˜์ง€๋งŒ ๊ฐœ์ธ์ ์œผ๋กœ OSIV ์˜ต์…˜์€ ๋„๊ณ  ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

์šฐ์„  DB ์ปค๋„ฅ์…˜์„ ๊ณ„์† ์žก๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฆฌ์†Œ์Šค์˜ ๋‚ญ๋น„๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

(์ง€์—ฐ ๋กœ๋”ฉ์„ ํ†ตํ•ด์„œ ๋ฐ›์•„์˜ค๋ ค๋ฉด ์•„๋ฌดํŠผ ์ปค๋„ฅ์…˜์ด ์‚ด์•„ ์žˆ์–ด์•ผ ํ•˜๋‹ˆ๊นŒ)

 

๋˜ํ•œ, ์Šคํ”„๋ง์˜ OSIV ์ „๋žต์€ ํ”„๋ ˆ์  ํ…Œ์ด์…˜์˜ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์€ ๋ง‰์ง€๋งŒ, ์• ์ดˆ์— ํ”„๋ ˆ์  ํ…Œ์ด์…˜๊นŒ์ง€ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ฐธ์กฐ ๊ด€๊ณ„๋ฅผ ๊ฐ€์ง€๋Š” ๊ฑด ๋ ˆ์ด์–ด์˜ ๋‹จ๋ฐฉํ–ฅ ํ๋ฆ„์„ ๊นฌ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. (๊ฐ™์€ ์˜๋ฏธ๋กœ DTO ์—ญ์‹œ ์„œ๋น„์Šค ๋ ˆ์ด์–ด์—์„œ ์ƒ์„ฑํ•œ ๋‹ค์Œ์— ๋ทฐ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ์„ ํ˜ธํ•œ๋‹ค.)

 

๊ฐ ๋ ˆ์ด์–ด๊ฐ„์˜ ๋‹จ๋ฐฉํ–ฅ ๊ด€๊ณ„๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•œ ์กฐ์ž‘์€ ์„œ๋น„์Šค ๋ ˆ์ด์–ด๊นŒ์ง€๋งŒ ์ง„ํ–‰ํ•˜๊ณ , ์ดํ›„ ๋ทฐ์— ์ฒ˜๋ฆฌ๋  ๋ฐ์ดํ„ฐ๋Š” DTO๋ฅผ ํ†ตํ•ด ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๋Š์€ ์ƒํƒœ๋กœ ์กฐ์ž‘ํ•˜๋Š” ๊ฒŒ ๋” ์ข‹๋‹ค๋Š” ๊ฒƒ์ด ๋‚˜์˜ ์˜๊ฒฌ์ด๋‹ค ๐Ÿ˜Š

(๊ทธ๋ฆฌ๊ณ  ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ฐฉ๋ฒ•์œผ๋กœ Command-Query separation ์ด๋ผ๋Š” ๊ฒƒ๋„ ์žˆ๋˜๋ฐ, ํ•ต์‹ฌ ๋กœ์ง๊ณผ ๋ทฐ ๊ด€๋ จ ์„œ๋น„์Šค ๋กœ์ง์„ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๋‚˜์ค‘์— ๋ฏธ์…˜ํ•  ๋•Œ ํ•œ ๋ฒˆ ์ด๊ฑธ๋กœ ์ ์šฉํ•ด๋ด์•ผ๊ฒ ๋‹ค...)

Comments