Back-end/TroubleShooting

[Spring Boot] JPA์—์„œ ์—”ํ‹ฐํ‹ฐ ์ˆ˜์ •๊ณผ Soft Delete์˜ ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ ๋ฐฉ์‹

์„œ์ฑ„๋ฆฌ 2024. 6. 26. 03:09

๐ŸŒฑ ๋ฌธ์ œ ์ƒํ™ฉ

@RequiredArgsConstructor
@UseCase
public class BidUseCase {

    private final BidRepository bidRepository;
    
    @Transactional
    public void deleteBid(User user, Long bidId) {
        Bid bid = bidRepository.findById(bidId).orElseThrow(() -> NOT_FOUND_BID);
        
        bid.cancel();
        bidRepository.delete(bid);
        
        log.info(bid.getStatus().name());
    }
}
@SQLDelete(sql = "UPDATE bid SET is_deleted = true WHERE id = ? AND version = ?")
@Entity
public class Bid extends BaseTimeEntity {

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

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private Status status;
    
    @Version
    private Long version;

    private Boolean isDeleted;
    
    ...

    public void cancel() {
        this.status = Status.CANCELLED;
    }
}

์„ค๋ช…์„ ์œ„ํ•ด ๊ธฐ์กด๋ณด๋‹ค ๊ฐ„๋‹จํ•˜๊ฒŒ ์ˆ˜์ •ํ•œ ์ฝ”๋“œ์ด๋‹ค.

 

๋‚ด๊ฐ€ ๊ฒช์—ˆ๋˜ ๋ฌธ์ œ๋Š” bid.cancel() ๋ฉ”์„œ๋“œ์—์„œ Bid ์—”ํ‹ฐํ‹ฐ์˜ status๋ฅผ ๋ณ€๊ฒฝํ•˜๊ณ , ๋กœ๊ทธ๋ฅผ ํ†ตํ•ด ์ƒํƒœ ๋ณ€๊ฒฝ์ด ์ž˜ ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋Š”๋ฐ ์ตœ์ข…์ ์œผ๋กœ ์ƒํƒœ ๋ณ€๊ฒฝ update ์ฟผ๋ฆฌ๋Š” ์‹คํ–‰๋˜์ง€ ์•Š๊ณ , SQLDelete์—์„œ ์ •์˜ํ•œ ์ฟผ๋ฆฌ๋งŒ ์‹คํ–‰๋˜์–ด ์ƒํƒœ ๋ณ€๊ฒฝ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ์šฉ๋˜์ง€ ์•Š๋Š” ์ƒํ™ฉ์ด์—ˆ๋‹ค.

 

๐ŸŒฑ ๋ฌธ์ œ ์›์ธ

์ด ๊ธ€์„ ๊ฒ€์ƒ‰ํ•˜๊ณ  ๋“ค์–ด์˜จ ์‚ฌ๋žŒ๋“ค์€ ์ด๋ฏธ ์•„๋Š” ์‚ฌ์‹ค์ด๊ฒ ์ง€๋งŒ @SQLDelete๋Š” ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์‚ญ์ œ๋  ๋•Œ ์‚ฌ์šฉ์ž๊ฐ€ ์ •์˜ํ•œ SQL์„ ์‹คํ–‰ํ•˜๋„๋ก ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  JPA๋Š” ์‚ญ์ œ๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ, ์—”ํ‹ฐํ‹ฐ๋ฅผ ์‚ญ์ œํ•œ ๊ฒƒ์œผ๋กœ ๋งˆํฌํ•˜๊ณ  ํ”Œ๋Ÿฌ์‹œ ์‹œ์ ์— @SQLDelete์— ์ •์˜๋œ SQL์„ ์‹คํ–‰ํ•œ๋‹ค.

 

๋ฉ”์„œ๋“œ์— ํŠธ๋žœ์žญ์…˜์„ ๊ฑธ์–ด๋†จ์œผ๋‹ˆ Bid์˜ ์ƒํƒœ๊ฐ€ ์—…๋ฐ์ดํŠธ ๋์„ ๋•Œ Dirty Check๋˜์–ด ํŠธ๋žœ์žญ์…˜์ด ์ข…๋ฃŒ๋˜๊ธฐ ์ „์— ํ”Œ๋Ÿฌ์‹œ ๋˜์–ด์•ผ ํ•˜์ง€๋งŒ, JPA๋Š” ์‚ญ์ œ๋œ ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•ด ์ƒํƒœ ๋ณ€๊ฒฝ์„ ๋ณ„๋„๋กœ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ฆ‰, ์‚ญ์ œ๋œ ์—”ํ‹ฐํ‹ฐ๋Š” ๋” ์ด์ƒ ์ƒํƒœ ๋ณ€๊ฒฝ์ด ๋ฐ˜์˜๋˜์ง€ ์•Š๋Š” ๊ฒƒ์ด๋‹ค.

 

๐ŸŒฑ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

์›์ธ์„ ์•Œ๋ฉด ํ•ด๊ฒฐ๋ฐฉ๋ฒ•์€ ๊ฐ„๋‹จํ•˜๋‹ค. delete ๋˜๊ธฐ ์ „์— ์ƒํƒœ ๋ณ€๊ฒฝ์„ ๋ช…์‹œ์ ์œผ๋กœ ํ”Œ๋Ÿฌ์‹œํ•ด ๋ณ€๊ฒฝ๋œ ์ƒํƒœ๊ฐ€ ๋จผ์ € ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ฐ˜์˜๋˜๋„๋ก ํ•˜๋ฉด ๋œ๋‹ค.

 

์ฃผ์˜ํ•  ์ ์€ ์—ฌ๊ธฐ์„œ bidRepository.save(bid); ๋ฅผ ํ†ตํ•ด ์—”ํ‹ฐํ‹ฐ์˜ ์ƒํƒœ ๋ณ€๊ฒฝ์„ ์ €์žฅํ•˜๋ ค๋Š” ๊ฒฝ์šฐ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค.

์ด๋Š” ํ”Œ๋Ÿฌ์‹œ ์ „๋žต ๋•Œ๋ฌธ์ธ๋ฐ JPA์—์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์„ค์ •๋˜์–ด ์žˆ๋Š” FlushModeType.AUTO ๋Š” ํŠธ๋žœ์žญ์…˜์ด ์ปค๋ฐ‹๋  ๋•Œ ๋˜๋Š” ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ํ”Œ๋Ÿฌ์‹œ ๋œ๋‹ค.

// ํ”Œ๋Ÿฌ์‹œ ๋ชจ๋“œ ํ™•์ธ ๋ฐฉ๋ฒ•
FlushModeType flushModeType = entityManager.getFlushMode();
log.info("flushMode: " + flushModeType);

// flushMode: AUTO

 

๋”ฐ๋ผ์„œ flush๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ํ˜ธ์ถœํ•ด ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ฆ‰์‹œ ๋ฐ˜์˜ํ•ด์•ผ ํ•œ๋‹ค.

@RequiredArgsConstructor
@UseCase
public class BidUseCase {
    
    private final BidRepository bidRepository;

    @Transactional
    public void deleteBid(User user, Long bidId) {
        Bid bid = bidRepository.findById(bidId).orElseThrow(() -> NOT_FOUND_BID);
        
        bid.cancel();
        bidRepository.flush();    // ์ถ”๊ฐ€!!
        
        bidRepository.delete(bid);
    }
}