๐Ÿง JPA์˜ ์˜์†์„ฑ ์ƒํƒœ์™€ ๋ฐ์ดํ„ฐ Log ์ด์Šˆ

๊ฐœ์š”

์—…๋ฌด ์ค‘ ํ•œ ๊ฐ€์ง€ ์ด์Šˆ๊ฐ€ ์žˆ์—ˆ๊ณ , JPA์˜ ์˜์†์ƒํƒœ์™€ ๋™์ž‘์›๋ฆฌ์— ๋Œ€ํ•ด์„œ ์ž์„ธํžˆ ์งš๊ณ  ๋„˜์–ด๊ฐ€์•ผํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค๊ณ  ๋А๊ปด ํ•ด๋‹น ๊ธ€์„ ์“ฐ๊ฒŒ ๋˜์—ˆ๋‹ค.

์ƒํ™ฉ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  1. ์‹ ํ•œ์€ํ–‰์˜ ๊ณ„์ขŒ์ž”์•ก(์˜ˆ์น˜๊ธˆ)๊ณผ ์ž…๊ธˆ๋‚ด์—ญ์„ ๊ด€๋ฆฌํ•˜๋Š” DB์˜ ๊ณ„์ขŒ ์ž”์•ก๊ฐ„์˜ ์ฐจ์•ก์ด ๋ฐœ์ƒํ–ˆ๋‹ค๋Š” ์•Œ๋ฆผ์„ ๋ฐ›์•˜๋‹ค.

๐Ÿน ์˜ˆ์น˜๊ธˆ ์ฐจ์•ก ๋น„๊ต Spring Batch ๋ฆฌํŽ™ํ† ๋ง

  1. ์›์ธ์„ ์ฐพ๊ธฐ์œ„ํ•ด ์‹ ํ•œ ์ „๋ฌธ์„ ์Œ“๋Š” logํ…Œ์ด๋ธ”๊ณผ, ํšŒ์› ์ž…๊ธ‰๋‚ด์—ญ logํ…Œ์ด๋ธ”์„ ๋น„๊ตํ•˜์˜€๋‹ค.
  2. ์ด๋–„, ์‹ ํ•œDB ์—๋Š” ๊ฐ™์€ ๊ธˆ์•ก์˜ ์ค‘๋ณต log๊ฐ€ ์—†์œผ๋‚˜, ์ž…๊ธˆ ๋‚ด์—ญ DB์—๋Š” ์ฐจ์•ก๋งŒํผ์˜ ์ค‘๋ณต ์ž…๊ธˆ log๋ฅผ ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.


๋ถ„์„


์›์ธ์ด ๋ฌด์—‡์ผ๊นŒ?

์ฐจ์•ก์ด 138๋งŒ์› ๋ฐœ์ƒํ–ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ–ˆ์„ ๋•Œ,

  1. ์ž…๊ธˆ๋‚ด์—ญDB์—๋Š” 10์‹œ / 11์‹œ ์ด 2๊ฐœ์˜ 138๋งŒ์› ์ž…๊ธˆ๋‚ด์—ญ์ด LOG๋กœ ๋‚จ์•„์žˆ์—ˆ๋‹ค.
    • ์—ฌ๊ธฐ์„œ ํ•ด๋‹น ์ž…๊ธˆ๋‚ด์—ญ์ด ์ฐจ์•ก์„ ๋ฐœ์ƒ์‹œ์ผฐ์„ ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

  1. ์‹ค์ œ ์‹ ํ•œ์˜ ์ „๋ฌธ์„ ์ €์žฅํ•˜๋Š” DB์—๋Š” 11์‹œ์˜ ์ž…๊ธˆ๋‚ด์—ญ๋งŒ์ด ์กด์žฌํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

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

๋‹ค์Œ ์ƒํ™ฉ์œผ๋กœ ๋ฏธ๋ฃจ์–ด ๋ณด์•˜์„ ๋•Œ, ์ฒซ ๋ฒˆ์งธ(10์‹œ ์ž…๊ธˆ ์ „๋ฌธ)์ด ์‚ฌ์กŒ๋‹ค๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.


์™œ ์‚ฌ๋ผ์กŒ์„๊นŒ?

๋ณดํ†ต์˜ ์ƒํ™ฉ์ด๋ผ๋ฉด, ๋™์ผํ•œ(id๊ฐ’ ๋™์ผ) ์ „๋ฌธ์„ ๋ฐ›์•„ DB์— insert๋œ๋‹ค๋ฉด SQL Exception์ด ํ„ฐ์กŒ์„ ๊ฒƒ์ด๋ผ ์ƒ๊ฐํ•˜์˜€๋‹ค. ํ•˜์ง€๋งŒ ์ƒํ™ฉ์œผ๋กœ ๋ฏธ๋ฃจ์–ด ๋ณผ๋•Œ, insert๊ฐ€ ์•„๋‹Œ update๊ฐ€ ๋™์ž‘ํ–ˆ์„ ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

  1. ํ•ด๋‹น Insert์ฟผ๋ฆฌ๋Š” Spring DATA JPA์˜ SAVE๋ฉ”์„œ๋“œ๋กœ ๊ตฌํ˜„๋˜์–ด์žˆ๋‹ค.
  2. ์ฒซ๋ฒˆ์งธ save ํ›„ ๋‘ ๋ฒˆ์งธ save๋™์ž‘ ๊นŒ์ง€, 1์‹œ๊ฐ„์˜ ์‹œ๊ฐ„ ์ฐจ์ด๊ฐ€ ์žˆ๋‹ค.

ํŠธ๋ Œ์ ์…˜์ด ์ข…๋ฃŒ๋œ ์ดํ›„(commit)์ž„์—๋„ ๊ธฐ์กด์˜ id๋ฅผ ๊ธฐ์–ตํ•˜์—ฌ update๋ฅผ ํ•  ์ˆ˜ ์žˆ์—ˆ๋˜ ์ด์œ ๊ฐ€ ๋ฌด์—ˆ์ผ๊นŒ?

์ด์— ๋Œ€ํ•œ ํ•ด๋‹ต์„ ์•Œ๊ธฐ์œ„ํ•ด ์˜์† ์ƒํƒœ์˜ ๊ฐœ๋…์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ๋‹ค.



์˜์† ์ƒํƒœ

์˜์†์ƒํƒœ์— ๊ด€ํ•œ ๊ด€๋ จ๋œ ๋˜ ๋‹ค๋ฅธ ์ด์Šˆ.

์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์™€ ์˜์† ์ƒํƒœ๋ž€?

์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ๋Œ€ํ•œ ์„ค๋ช….

โ–ถ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ

Pasted image 20250519144842.png

๋‚˜์˜ ์˜ˆ์ƒ๋Œ€๋กœ ๋ผ๋ฉด...

  • ์•„๋ž˜ ์ฝ”๋“œ์˜ ์‹คํ–‰ ๊ฒฐ๊ณผ๋กœ ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด, ๋น„์˜์† ์ƒํƒœ์˜ ์—”ํ‹ฐํ‹ฐ๋ฅผ save()ํ•˜์—ฌ persist()๋ฅผ ์ˆ˜ํ–‰ํ–ˆ์„ ๋•Œ, DB์— ์ด๋ฏธ ๋™์ผํ•œ ID(PK)๊ฐ€ ์žˆ๋‹ค๋ฉด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.
@Autowired  
private EntityManager em;  
  
@Test  
@Transactional  
void MemberPersistenceTest() {  
  
    // 1) ์ƒˆ ์—”ํ‹ฐํ‹ฐ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ โ†’ Transient ์ƒํƒœ  
    HfMarketingCode testcode = new HfMarketingCode();  
  
    testcode.setHitCode("testCode1");  
    testcode.setCodeName("testName1");  
  
    em.persist(testcode);  
  
    em.flush();  
}

Pasted image 20250520142253.png

์ฃผ์˜

์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ๋“ฑ๋กํ•  ๊ฐ์ฒด์˜ id ์„ค์ •์˜ @GeneratedValue(strategy = GenerationType.IDENTITY)
์—ฌ๋ถ€์— ๋”ฐ๋ผ ์ฃผ์˜ํ•ด์•ผ ํ•  ์‚ฌํ•ญ์ด ์žˆ๋‹ค.

  1. ์ง€์—ฐ ์“ฐ๊ธฐ๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค. ๊ธฐ๋ณธํ‚ค ์ƒ์„ฑ์— ๋Œ€ํ•œ ๊ถŒํ•œ์„ DB์— ์œ„์ž„ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, JPA๊ฐ€ ๊ณง๋ฐ”๋กœ id๊ฐ’์„ ์•Œ๊ธฐ์œ„ํ•ด ์ง€์—ฐํ•˜์ง€ ์•Š๊ณ , ๋ฐ”๋กœ insert์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰์‹œํ‚จ๋‹ค(IDENTITY ๋ฐฉ์‹์˜ ๊ฒฝ์šฐ).
  2. id๊ฐ’์„ ๋ช…์‹œ์ ์œผ๋กœ ์ง€์ •ํ•œ ํ›„, persist()๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ๊ทธ ์ด์œ ๋Š”, ๋ช…์‹œ์ ์œผ๋กœid๋ฅผ ์ง€์ •ํ•˜๋Š” ์ˆœ๊ฐ„ non-null์˜ id๊ฐ’์„ ๊ฐ–๊ฒŒ๋˜๊ณ , isNew()์˜ ์ฒซ ํ˜ธ์ถœ๋ถ€ํ„ฐ id๊ฐ€ null์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์—, ๊ธฐ์กด์— ์กด์žฌํ•˜๋Š” id๊ฐ’์— ๋Œ€ํ•ด persist๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋˜์–ด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

์•„๋ž˜๋Š” JPA save()์˜ isNew() ๋ถ„๊ธฐ๋ฌธ

@Transactional  
public <S extends T> S save(S entity) {  
    Assert.notNull(entity, "Entity must not be null.");  
    if (this.entityInformation.isNew(entity)) {  
        this.em.persist(entity);  
        return entity;  
    } else {  
        return this.em.merge(entity);  
    }  
}

์งˆ๋ฌธ?

๋™์ผํ•œ idx(pk)์˜ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋„˜๊ฒจ save๋™์ž‘์„ ์ˆ˜ํ–‰ํ–ˆ์„ ๋•Œ, persist(insert) ๊ฐ€ ์•„๋‹Œ merge(update) ๊ฐ€ ๋˜์—ˆ๋‹ค๋ฉด,
ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ์˜ ์˜์† ์ƒํƒœ๋Š” ์–ด๋–ป๊ฒŒ ๋˜๋Š”๊ฐ€?

ํ•ด๋‹ต :

์—”ํ‹ฐํ‹ฐ ๋ฉ”๋‹ˆ์ €๋Š” ํŠธ๋ Œ์ ์…˜์ด ์ข…๋ฃŒ๋ ๋•Œ close๋˜๋ฉฐ, ์ด๋•Œ ๋ชจ๋“  ์˜์†์„ ์ปจํ…์ŠคํŠธ์— ๋“ฑ๋ก๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ค€์˜์† ์ƒํƒœ๋กœ ๋Œ๋ฆฐ๋‹ค. ๋”ฐ๋ผ์„œ, ์ค€์˜์† ์ƒํƒœ๋กœ ๊ด€๋ฆฌ๋˜๊ณ  ์žˆ๋˜ ๊ฐ์ฒด์— save() ์—ฐ์‚ฐ์ด ์ˆ˜ํ–‰๋˜๋ฉด์„œ, update์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋œ ๊ฒƒ.


๊ทธ๋ ‡๋‹ค๋ฉด ์ค€์˜์† ์ƒํƒœ์˜ ์ง€์† ๋ฒ”์œ„๋Š” ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

  • ๋ณดํ†ต ์—”ํ‹ฐํ‹ฐ ๊ฐ์ฒด๊ฐ€ ์ค€์˜์† ์ƒํƒœ๋กœ ์ง„์ž…ํ•˜๊ฒŒ ๋˜๋ฉด, ์—”ํ‹ฐํ‹ฐ ๋งค๋‹ˆ์ €์™€ ๋ชจ๋“  ์˜์กด์„ฑ์„ ๋Š๊ธฐ ๋•Œ๋ฌธ์— ์ผ๋ฐ˜์ ์ธ POJO ๊ฐ์ฒด์™€ ๊ฐ™์ด GC(๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ) ์— ์˜ํ•ด ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ•ด์ œํ•˜๊ฒŒ ๋œ๋‹ค.
  • ๊ทธ๋Ÿผ์—๋„ 1์‹œ๊ฐ„์˜ ์‹œ๊ฐ„ ์ฐจ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Œ์—๋„ GC๋กœ ์ •๋ฆฌ๊ฐ€ ๋˜์ง€ ์•Š์€ ๋ถ€๋ถ„์€ ์กฐ๊ธˆ ์˜์•„ํ•˜๋‹ค.
  • ํ•ด๋‹น ๋ถ€๋ถ„์€ ๋” ๊นŠ๊ฒŒ ์ฐพ์•„๋ณด์•ผ ํ• ๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค.


+ ์ถ”๊ฐ€

  • ์œ„์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ‘œํ˜„ํ•œ ๋ถ€๋ถ„์ด ์žˆ๋‹ค. ์ด๋Š” ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค..

    โ€œ ๋น„์˜์† ์ƒํƒœ์˜ ์—”ํ‹ฐํ‹ฐ๋ฅผ save()ํ•˜์—ฌ persist()๋ฅผ ์ˆ˜ํ–‰ํ–ˆ์„ ๋•Œ,

    DB์— ์ด๋ฏธ ๋™์ผํ•œ ID(PK)๊ฐ€ ์žˆ๋‹ค๋ฉด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค โ€

  • ๋ฌผ๋ก , ์ด๋ฏธ ์กด์žฌํ•˜๋Š” id๊ฐ’์„ ๊ฐ–๋Š” ์—”ํ‹ฐํ‹ฐ๋ฅผ persist()ํ•˜๋ฉด pk์ค‘๋ณต ์˜ˆ์™ธ๊ฐ€ ๋ฐฉ์ƒํ•˜๋Š” ๊ฒƒ์€ ๋งž๋‹ค. persist()๋Š” ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ๋“ฑ๋ก์—ฌ๋ถ€๋ฅผ ํŒ๋‹จ ํ•  ๋ฟ, id๊ฐ’์˜ ์œ ๋ฎค๋ฅผ ๋”ฐ์ง€์ง€(selectํ•˜์ง€) ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

  • ํ•˜์ง€๋งŒ ์ด๋Ÿฌํ•œ ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ด ์ •์ƒ์ ์ธ ์ƒํ™ฉ์—์„œ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

  • ๊ทธ ์ด์œ ์— ๋Œ€ํ•ด์„œ ์„ค๋ช…ํ•˜๊ฒ ๋‹ค.

์ด์œ 

: Assigned(์‚ฌ์šฉ์ž id ์ง์ ‘ ์ง€์ •) ์ „๋žต์ผ ๋•Œ, id == null or 0 ์ผ๋•Œ๋งŒ โ€œ์ƒˆ ์—”ํ‹ฐํ‹ฐ๋กœ์จ ํŒ๋‹จํ•œ๋‹ค.โ€

public boolean isNew(T entity) {  
    ID id = this.getId(entity);  
    Class<ID> idType = this.getIdType();  
    if (!idType.isPrimitive()) {  
        return id == null;  //null ์ด๊ฑฐ๋‚˜,
    } else if (id instanceof Number) {  
        return ((Number)id).longValue() == 0L;   //0 ์ผ๋•Œ๋งŒ ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋กœ ํŒ๋‹จ
    } else {  
        throw new IllegalArgumentException(String.format("Unsupported primitive id type %s", idType));  
    }  
}

ํ•˜์ง€๋งŒ ์ด๋•Œ, Assigned์œผ๋กœ ์ง์ ‘ id์— ๊ฐ’์„ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ง์ ‘ ์ง€์ •ํ–ˆ๋‹ค๋ฉด, (id๊ฐ’์„ ํฌํ•จํ•œ ์—”ํ‹ฐํ‹ฐ) persist()๊ฐ€ ์•„๋‹Œ merge()๋กœ ๋„˜์–ด๊ฐˆ ์ˆ˜๋ฐ–์—์—†๊ฒŒ ๋œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

์ฆ‰, ์–ด๋– ํ•œ ๊ฐ์ฒด๊ฐ€ save() ๋˜๋Š” ์‹œ์ ์— persist()๊ฐ€ ๋™์ž‘๋˜์–ด PK์ค‘๋ณต ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ์ผ์€ ์—†๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.




๊ฒฐ๋ก 

: JPA์˜ save()๋Š” ๋‹จ์ˆœํžˆ insert์™€ update์˜ ํ†ตํ•ฉ์ด ์•„๋‹ˆ๋‹ค. ๊ฐ ๋™์ž‘์˜ ์›๋ฆฌ์™€ ํŠน์„ฑ์„ ํŒŒ์•…ํ•˜์—ฌ ์˜ˆ์™ธ์‚ฌํ•ญ์„ ์ •ํ™•ํžˆ ํŒ๋‹จํ•˜์—ฌ ๊ฒฐ๊ณผ๋ฅผ ์˜ˆ์ƒ ๊ฐ€๋Šฅํ•˜๋„๋ก ์„ค๊ณ„ํ•ด์•ผํ•œ๋‹ค.