๐Ÿ” Spring Data JPA - ์ƒˆ๋กœ์šด ์—”ํ‹ฐํ‹ฐ ์—ฌ๋ถ€ ํŒ๋‹จ ๋ฐ ์ง์ ‘ ID ํ• ๋‹น ์‹œ ์ฃผ์˜์ 

์—…๋ฐ์ดํŠธ:
2 ๋ถ„ ์†Œ์š”

๐Ÿ” Spring Data JPA - ์ƒˆ๋กœ์šด ์—”ํ‹ฐํ‹ฐ ์—ฌ๋ถ€ ํŒ๋‹จ ๋ฐ ์ง์ ‘ ID ํ• ๋‹น ์‹œ ์ฃผ์˜์ 


๐Ÿ“Œ 1. Spring Data JPA์—์„œ ์—”ํ‹ฐํ‹ฐ์˜ ์‹ ๊ทœ ์—ฌ๋ถ€ ํŒ๋‹จ

Spring Data JPA์—์„œ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ƒˆ๋กœ์šด์ง€ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.
์ด๊ฒƒ์€ JPA๊ฐ€ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•  ๋•Œ persist๋ฅผ ์‚ฌ์šฉํ• ์ง€, ์•„๋‹ˆ๋ฉด merge๋ฅผ ์‚ฌ์šฉํ• ์ง€๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ๋ฐ ์ง์ ‘์ ์ธ ์˜ํ–ฅ์„ ์ค๋‹ˆ๋‹ค.

Spring Data JPA์—์„œ๋Š” JpaEntityInformation์˜ isNew(T entity) ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—”ํ‹ฐํ‹ฐ์˜ ์‹ ๊ทœ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•ฉ๋‹ˆ๋‹ค.
์ด ๋ฉ”์„œ๋“œ๋Š” JpaMetamodelEntityInformation ํด๋ž˜์Šค๋ฅผ ํ†ตํ•ด ๋™์ž‘ํ•˜๋ฉฐ, ์—”ํ‹ฐํ‹ฐ์˜ @Version ํ•„๋“œ๋‚˜ @Id ํ•„๋“œ์˜ ์ƒํƒœ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์‹ ๊ทœ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

โœ… ์ฆ‰, ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ƒˆ๋กœ์šด ๊ฒฝ์šฐ (isNew()๊ฐ€ true ๋ฐ˜ํ™˜)
โ†’ persist()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋กœ ์‚ฝ์ž…

โœ… ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋กœ ํŒ๋‹จ๋˜๋Š” ๊ฒฝ์šฐ (isNew()๊ฐ€ false ๋ฐ˜ํ™˜)
โ†’ merge()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •


๐Ÿ“Œ 2. ์ง์ ‘ ID ํ• ๋‹น ์‹œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ

์ผ๋ฐ˜์ ์œผ๋กœ JPA์—์„œ๋Š” @GeneratedValue(strategy = GenerationType.IDENTITY)์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž๋™์œผ๋กœ ID๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ์—”ํ‹ฐํ‹ฐ์˜ ID๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฒฝ์šฐ, ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”น ๋ฌธ์ œ ์ƒํ™ฉ

@Entity
public class User {

    @Id
    private String id;  // ์ง์ ‘ ID ํ• ๋‹น

    private String name;

    public User(String id, String name) {
        this.id = id;
        this.name = name;
    }
}

์œ„์™€ ๊ฐ™์ด @GeneratedValue ์—†์ด ์ง์ ‘ ID๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฒฝ์šฐ,
Spring Data JPA๋Š” ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋กœ ์ธ์‹ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”น ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ

User user = new User("123", "John Doe");
userRepository.save(user);
  • save()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด Spring Data JPA๋Š” ํ•ด๋‹น ID(โ€œ123โ€)๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ๋จผ์ € ์กฐํšŒ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰
  • ID๊ฐ€ ์—†์œผ๋ฉด persist()๋ฅผ ์‹คํ–‰ํ•˜์ง€๋งŒ, ID๊ฐ€ ์กด์žฌํ•œ๋‹ค๊ณ  ํŒ๋‹จํ•˜๋ฉด ๋ถˆํ•„์š”ํ•œ merge()๋ฅผ ์ˆ˜ํ–‰
  • ์ฆ‰, ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐํšŒ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉฐ ์„ฑ๋Šฅ ์ €ํ•˜ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Œ

๐Ÿ“Œ 3. ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• - Persistable ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„

์ง์ ‘ ID๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฒฝ์šฐ, Spring Data JPA๊ฐ€ ์—”ํ‹ฐํ‹ฐ์˜ ์‹ ๊ทœ ์—ฌ๋ถ€๋ฅผ ์ •ํ™•ํžˆ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋„๋ก Persistable ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

๐Ÿ”น Persistable์„ ํ™œ์šฉํ•œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

import org.springframework.data.domain.Persistable;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Transient;

@Entity
public class User implements Persistable<String> {

    @Id
    private String id;
    private String name;

    @Transient // JPA๊ฐ€ ์ธ์‹ํ•˜์ง€ ์•Š๋„๋ก ์„ค์ •
    private boolean isNewEntity = false;

    public User(String id, String name) {
        this.id = id;
        this.name = name;
        this.isNewEntity = true; // ์ƒˆ ์—”ํ‹ฐํ‹ฐ์ž„์„ ํ‘œ์‹œ
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public boolean isNew() {
        return isNewEntity; // ์ƒˆ ์—”ํ‹ฐํ‹ฐ ์—ฌ๋ถ€๋ฅผ ์ง์ ‘ ๊ด€๋ฆฌ
    }
}

โœ… ์„ค๋ช…

  • Persistable<T> ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ Spring Data JPA๊ฐ€ isNew() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ์œ ๋„
  • isNew()๊ฐ€ true์ด๋ฉด persist()๊ฐ€ ์‹คํ–‰๋˜๋ฉฐ, false์ด๋ฉด merge()๊ฐ€ ์‹คํ–‰๋จ
  • @Transient ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ JPA๊ฐ€ ๊ด€๋ฆฌํ•˜์ง€ ์•Š๋„๋ก ์„ค์ • (isNewEntity ํ•„๋“œ๋Š” DB์— ์ €์žฅ๋˜์ง€ ์•Š์Œ)

๐Ÿ“Œ 4. isNew() ๋‚ด๋ถ€ ๋™์ž‘ ๋ฐฉ์‹

Spring Data JPA์—์„œ isNew()๋Š” ์—”ํ‹ฐํ‹ฐ์˜ ID ๊ฐ’ ๋ฐ @Version ๊ฐ’์„ ํ™•์ธํ•˜์—ฌ ์‹ ๊ทœ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
๊ธฐ๋ณธ์ ์œผ๋กœ JpaMetamodelEntityInformation์„ ํ†ตํ•ด ์—”ํ‹ฐํ‹ฐ์˜ ์ƒํƒœ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

@Override
public boolean isNew(T entity) {

    if(versionAttribute.isEmpty()
          || versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) {
        return super.isNew(entity);
    }

    BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);

    return versionAttribute.map(it -> wrapper.getPropertyValue(it.getName()) == null).orElse(true);
}

โœ… ์„ค๋ช…

  • @Version์ด ์—†๋Š” ๊ฒฝ์šฐ, ๋˜๋Š” @Version์ด primitive ํƒ€์ž…์ด๋ฉด ๊ธฐ๋ณธ isNew()๋ฅผ ํ˜ธ์ถœ
  • @Version์ด wrapper ํƒ€์ž…์ด๋ฉด ํ•ด๋‹น ํ•„๋“œ๊ฐ€ null์ธ์ง€ ํ™•์ธํ•˜์—ฌ ์‹ ๊ทœ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จ

๐Ÿ”น AbstractEntityInformation์—์„œ์˜ ๋™์ž‘

public boolean isNew(T entity) {
    Id id = getId(entity);
    Class<ID> idType = getIdType();

    if (!idType.isPrimitive()) {
        return id == null;
    }

    if (id instanceof Number) {
        return ((Number) id).longValue() == 0L;
    }

    throw new IllegalArgumentException(String.format("Unsupported primitive id type %s", idType));
}

โœ… ์„ค๋ช…

  • @Version์ด ์—†์œผ๋ฉด @Id ํ•„๋“œ๋ฅผ ํ™•์ธ
  • primitive ํƒ€์ž…์ด ์•„๋‹ˆ๋ฉด null ์—ฌ๋ถ€, ์ˆซ์ž(Number) ํƒ€์ž…์ด๋ฉด 0 ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜์—ฌ ์‹ ๊ทœ ์—ฌ๋ถ€ ํŒ๋‹จ

๐Ÿ“Œ 5. ์ •๋ฆฌ

โœ… Spring Data JPA๋Š” JpaEntityInformation#isNew(T entity)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—”ํ‹ฐํ‹ฐ์˜ ์‹ ๊ทœ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จ
โœ… ID๋ฅผ ์ง์ ‘ ์„ค์ •ํ•˜๋ฉด JPA๊ฐ€ ๊ธฐ์กด ์—”ํ‹ฐํ‹ฐ๋กœ ์˜ค์ธํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Œ
โœ… ๋ถˆํ•„์š”ํ•œ SELECT ๋ฐ merge()๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด Persistable ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Œ
โœ… isNew()์˜ ๋‚ด๋ถ€ ๋™์ž‘ ๋ฐฉ์‹์€ @Version ๋ฐ @Id ํ•„๋“œ์˜ ์ƒํƒœ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฒฐ์ •๋จ

๐Ÿ“Œ ์ด๋Ÿฌํ•œ ๋‚ด์šฉ์„ ๊ณ ๋ คํ•˜์—ฌ ID๋ฅผ ์ง์ ‘ ํ• ๋‹นํ•  ๋•Œ JPA์˜ ๋™์ž‘์„ ์ดํ•ดํ•˜๊ณ  ์ตœ์ ํ™”ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ๐Ÿš€


์ถœ์ฒ˜

๋งค์ผ๋ฉ”์ผ : Spring Data JPA์—์„œ ์ƒˆ๋กœ์šด Entity์ธ์ง€ ํŒ๋‹จํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ผ๊นŒ์š”?

ํƒœ๊ทธ: , , , , , , , ,

์นดํ…Œ๊ณ ๋ฆฌ: , ,

์—…๋ฐ์ดํŠธ:

๋Œ“๊ธ€๋‚จ๊ธฐ๊ธฐ