๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Back-end ๋ฐ๋ธŒ์ฝ”์Šค/Clone Project

[JPA] Entity Custom ID Generator (@GenericGenerator)

by young-ji 2023. 1. 18.

Entity์˜ ๊ธฐ๋ณธํ‚ค (primary key)๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ๋ณดํ†ต Auto Increment ๋ฐฉ์‹์ด๋‚˜ UUID ๋ฐฉ์‹์„ ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค. 

 

  • Auto Increment

: ๊ตฌํ˜„์ด ๊ฐ„๋‹จํ•˜๊ณ  ์žฌ์ •๋ ฌ์ด ํ•„์š”์—†๋‹ค(์ˆœ์„œ๊ฐ€ ๋ณด์žฅ๋œ๋‹ค.)

: ํ•œ๋Œ€์˜ DB์—์„œ ์ƒ์„ฑํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์Šค์ผ€์ผ์•„์›ƒ์„ ํ†ตํ•œ ํ™•์žฅ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค. (์ฒ˜๋ฆฌ ์„ฑ๋Šฅ ํ™•์ •์ด ์–ด๋ ค์›€)

: insert ํ›„์—์•ผ PK ๊ฐ’์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

 

: ๋ณดํ†ต bigint๋กœ ๊ตฌํ˜„. 

: ์™ธ๋ถ€์— ๋…ธ์ถœ๋  ๊ฒฝ์šฐ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ๋œ๋‹ค. (๋‹ค๋ฅธ id๋ฅผ ์œ ์ถ” ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์—)

 

 

  • UUID

: ๋„คํŠธ์›Œํฌ ์ƒ์—์„œ ๊ณ ์œ ์„ฑ์ด ๋ณด์žฅ๋˜๋Š” ID๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ํ‘œ์ค€ ๊ทœ์•ฝ

: 128 bit ๋ฐ์ดํ„ฐ๋กœ ํ‘œํ˜„ (16 Byte)

: ๋ฉ€ํ‹ฐ ํ™˜๊ฒฝ์—์„œ๋„ ๋ณ‘๋ ฌ๋กœ ๋™์ž‘ํ•˜์—ฌ ์œ ์ผํ•œ ID๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

: 128bit๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋งŽ์€ ๊ณต๊ฐ„์„ ์ฐจ์ง€ํ•˜๋ฉฐ ์ธ๋ฑ์Šค ๊ตฌ์ถ•์— ์ ํ•ฉํ•˜์ง€์•Š๋‹ค.

: ํ‚ค์˜ ์˜๋ฏธ๋ฅผ ๊ฐ–๊ธฐ ์–ด๋ ต๋‹ค.

 

 

  • SnowFlake

: Twitter์—์„œ OSS๋กœ ๊ณต๊ฐœํ•˜๊ณ  ์žˆ๋Š” ID ์ƒ์„ฑ๊ธฐ

: Time-base๋กœ ํ•œ ID

(1๋ฐ€๋ฆฌ์ดˆ ์ด๋‚ด์— ๋™์‹œ์— ์ƒ์„ฑ๋œ ์ผ๋ จ๋ฒˆํ˜ธ๋ฅผ ํฌํ•จํ•˜์—ฌ, ๊ฐ™์€ ์‹œ๊ฐ„, ๊ฐ™์€ ๋จธ์‹ ์—์„œ ์ค‘๋ณต๋˜๋Š”๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ - 12bit)

: 64 Bit์˜ Long ๊ฐ’์œผ๋กœ ๋ฐ์ดํ„ฐ๋กœ ํ‘œํ˜„

: ๋ฉ€ํ‹ฐ ํ™˜๊ฒฝ์—์„œ๋„ ๋ณ‘๋ ฌ๋กœ ๋™์ž‘ํ•˜์—ฌ ์œ ์ผํ•œ ID๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

: timestamp ๊ฐ’์„ ๊ธฐ์ดˆ๋กœ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ID ์ •๋ ฌ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

: id๋ฅผ ํ†ตํ•ด ์ƒ์„ฑ์‹œ๋ฅผ timestamp ๊ฐ’์œผ๋กœ ๋ณต์›๊ฐ€๋Šฅํ•˜๋‹ค.

: OS์˜ ์‹œ๊ฐ ์ฐจ์ด์— ์•ฝํ•˜๋ฉฐ ์‹œ๊ฐ„์ด ์ž˜๋ชป ์—ญ์ „๋  ๊ฒฝ์šฐ ๊ณ ์žฅ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

(๋ถ„์‚ฐ ์‹œ์Šคํ…œ์— ์žˆ๋Š” ๋ชจ๋“  ์ธ์Šคํ„ด์Šค๊ฐ€ ๊ฐ™์€ ์‹œ์Šคํ…œ์˜ ์‹œ๊ฐ„์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ์–ด์•ผํ•œ๋‹ค.)

 

generate_id : https://github.com/callicoder/java-snowflake

 

Twitter์—์„œ ์ œ๊ณตํ•œ SnowFlake ๋ฐฉ์‹์€ UUID ๋ณด๋‹ค ์ž‘์€ ํฌ๊ธฐ๋กœ ๋žœ๋คํ•œ ๊ฐ’์„ ์ œ๊ณตํ•˜๊ธฐ๋•Œ๋ฌธ์— ์ข‹์•„๋ณด์ด์ง€๋งŒ git repo์— ์—…๋ฐ์ด๋“œ๊ฐ€ ์•ˆ๋œ์ง€ ๊ฝค ๋œ๊ฒƒ์œผ๋กœ ๋ณด์•„ ์ง€๊ธˆ ์‚ฌ์šฉํ•˜๊ธฐ๋Š” ํž˜๋“ค์–ด๋ณด์ธ๋‹ค.

 

 

 

โœ… Custom ID

 

๋ณดํ†ต UUID ๊ฐ’์„ ๋งŽ์ด ์‚ฌ์šฉํ•˜์ง€๋งŒ ํŠน์ • ๋ฐฉ์‹์œผ๋กœ ํ‚ค๊ฐ’์„ ๊ด€๋ฆฌํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์ฃผ๋ฌธ ๋ฒˆํ˜ธ์ฒ˜๋Ÿผ ์ผ์ • ํŒจํ„ด์œผ๋กœ ์ƒ์„ฑํ•˜๋Š” ๊ฐ’์„ ํ‚ค ๊ฐ’์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค.

๋‚ ์งœ๊ฐ€ ํฌํ•จ๋œ Naver Pay ์ฃผ๋ฌธ ๋ฒˆํ˜ธ

 

 

JPA Hibernate์—์„œ๋Š” Custom ID ์ƒ์„ฑ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

 

๊ธฐ๋ณธ ์ „๋žต์„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์ง€ ์•Š์„๋•Œ, ์ด๋ฅผ ์œ„ํ•ด IdentifierGenerator ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ง€์ • ์ƒ์„ฑ๊ธฐ๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

public class MyGenerator implements IdentifierGenerator {

    private String prefix;

    // ํ‚ค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋กœ์ง์„ ๊ฐ€์ง€๋Š” ๋ฉ”์†Œ๋“œ
    @Override
    public Serializable generate(
        SharedSessionContractImplementor session, Object obj)
        throws HibernateException {

        return prefix + System.currentTimeMillis() + (int)(Math.random() * 1000);

    }

     // ํ‚ค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ”„๋กœ์‹œ์ €์— ๊ฐ’์„ ๋„˜๊ฒจ์ฃผ๊ธฐ์œ„ํ•œ ๋ฉ”์†Œ๋“œ. ์ „๋‹ฌ๋ฐ›์„ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์—†๋‹ค๋ฉด Overrideํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.
     @Override
     public void configure(Type type, Properties properties, 
      ServiceRegistry serviceRegistry) throws MappingException {

        prefix = properties.getProperty("prefix");

    }
}

์ค‘๋ณต Id๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— currentTimeMillis์— ๋žœ๋ค ๊ฐ’์„ ๋”ํ•ด ๊ตฌํ˜„ํ•ด ๋ณด์•˜์Šต๋‹ˆ๋‹ค. millisecond์•ˆ์— ์–ผ๋งˆ๋‚˜ ๋งŽ์€ ์š”์ฒญ์ด ๋“ค์–ด์˜ฌ์ง€๋Š” ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ random๊ฐ’์€ ์ค‘๋ณต์„ ํ—ˆ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํฌ๋ฐ•ํ•œ ํ™•๋ฅ ๋กœ ์ค‘๋ณต๊ฐ’์ด ๋‚˜์˜ฌ์ˆ˜๋„ ์žˆ์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

 

 

  • IdentifierGenerator ์ธํ„ฐํŽ˜์ด์Šค๊ตฌํ˜„ ํด๋ž˜์Šค๋Š” ๋ฐ˜๋“œ์‹œ public์œผ๋กœ ์„ ์–ธ๋œ ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๊ฐ€ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

  • Properties ํด๋ž˜์Šค์˜ getProperty() ๋ฉ”์†Œ๋“œ๋Š” String ๊ฐ’๋งŒ์„ ๋ฐ˜ํ™˜ํ•˜์ง€๋งŒ, ConfigurationHelper ํด๋ž˜์Šค์—๋Š” getString(), getBoolean() ๋“ฑ์˜ ๋‹ค์–‘ํ•œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋“ค์ด ์žˆ์–ด์„œ ์‚ฌ์šฉํ• ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
 prefix = ConfigurationHelper.getString(SEQ_GENERATOR_PARAM_KEY, params);

 

  • ๋งค๊ฐœ๋ณ€์ˆ˜ session์„ ์ด์šฉํ•ด์„œ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ํ˜•ํƒœ๋กœ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. connetction์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์ง€๋งŒ ์ด Connection ์€ Hibernate ๊ฐ€ ๊ด€๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ง์ ‘ close๋Š” ํ•˜์ง€ ๋ง๊ฒƒ.
Connection connection = session.connection();

 

 

 

ํ•ด๋‹น Generator๋Š” @GenericGenerator ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ modifier์— ๊ตฌํ˜„ ํด๋ž˜์Šค ๋ช…์„ ์„ ์–ธํ•ด์ฃผ๋ฉด ์‚ฌ์šฉ๊ฐ€๋Šฅํ•˜๋‹ค. 

@Id
@GeneratedValue(generator = "order-generator")
@GenericGenerator(name = "orderId-generator", 
            	strategy = "com.prgrms.bdbks.domain.order.MyGenerator")
private String orderId;

 

ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ „๋‹ฌํ•  ๊ฒฝ์šฐ

@Id
@GeneratedValue(generator = "order-generator")
@GenericGenerator(name = "orderId-generator", 
                parameters = @Parameter(name = "prefix", value = "prod"), 
                strategy = "com.prgrms.bdbks.domain.order.MyGenerator")
private String orderId;

 


 

๋‹ค๋ฅธ ์˜ˆ์ œ ๋“ค์„ ์‚ดํŽด๋ณด์ž

public class StockCodeGenerator 
  implements IdentifierGenerator {
 
    @Override
    public Serializable generate(
    	SessionImplementor session, Object object)
            throws HibernateException {
 
        String prefix = "M";
        Connection connection = session.connection(); 
        try {
            PreparedStatement ps = connection
                    .prepareStatement("SELECT nextval ('seq_stock_code') as nextval"); 
                    // ๋‹ค์Œ ์‹œํ€€์Šค ๊ฐ’์„ ๊ฐ€์ ธ์˜จ๋‹ค.
 
            ResultSet rs = ps.executeQuery();
            if (rs.next()) {
                int id = rs.getInt("nextval");
                String code = prefix + StringUtils.leftPad("" + id,3, '0');
                return code;
            }
        } catch (SQLException e) {
            log.error(e);
            throw new HibernateException(
                    "Unable to generate Stock Code Sequence");
        }
        return null;
    }
}

https://kwonnam.pe.kr/wiki/java/hibernate/id_generator

prefix๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์•„๋‹Œ ๊ณ ์ •๊ฐ’์œผ๋กœ ์ง€์ •ํ•˜์˜€๊ณ , DB์— ์ ‘๊ทผํ•˜์—ฌ ์‹œํ€€์Šค ๊ฐ’์„ ๊ฐ€์ ธ์˜จ๋‹ค์Œ ๋‹ค์Œ ์‹œํ€€์Šค ๊ฐ’์„ prefix์™€ ํ•ฉํ•˜์—ฌ id๋กœ ๋ฐ˜ํ™˜ํ•ด์ค€๋‹ค.

 

 

public class MyGenerator 
  implements IdentifierGenerator {

    private String prefix;

    @Override
    public Serializable generate( 
      SharedSessionContractImplementor session, Object obj) 
      throws HibernateException {

        String query = String.format("select %s from %s", 
            session.getEntityPersister(obj.getClass().getName(), obj)
              .getIdentifierPropertyName(),
            obj.getClass().getSimpleName());

        Stream ids = session.createQuery(query).stream();

        Long max = ids.map(o -> o.replace(prefix + "-", ""))
          .mapToLong(Long::parseLong)
          .max()
          .orElse(0L);

        return prefix + "-" + (max + 1);
    }

    @Override
    public void configure(Type type, Properties properties, 
      ServiceRegistry serviceRegistry) throws MappingException {
        prefix = properties.getProperty("prefix");
    }
}

https://www.baeldung.com/hibernate-identifiers

QueryDSL์„ ์ด์šฉํ•œ ๊ตฌํ˜„์ธ๊ฒƒ ๊ฐ™๋‹ค. id๊ฐ’์„ ๊ฐ€์ ธ์™€ 'prefix-'๋ฅผ ์ œ๊ฑฐํ•œ ํ›„ max+1 ํ•ด์„œ ๋‹ค์‹œ prefix๋ฅผ ๋ถ™์—ฌ์„œ ๋ฐ˜ํ™˜. 

 

 

 

์ค‘๋ณต์„ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ์‹์œผ๋กœ ๋‹ค์–‘ํ•˜๊ฒŒ ์ปค์Šคํ…€ ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ๊ฐ™๋‹ค.

http://www.gisdeveloper.co.kr/?p=5623 

https://toshi15shkim.github.io/articles/2019-10/java-unique-id

 

 

 

reference.

https://www.baeldung.com/hibernate-identifiers

https://zet-it-story.tistory.com/1

https://techblog.woowahan.com/2607/

https://kapentaz.github.io/jpa/Hibernate์—์„œ-Custom-ID-์ƒ์„ฑํ•˜๊ธฐ/#

 

 

์ž˜๋ชป๋œ ์ •๋ณด๊ฐ€ ์žˆ๋‹ค๋ฉด ๋Œ“๊ธ€์„ ํ†ตํ•ด ์•Œ๋ ค์ฃผ์„ธ์š”. ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. 

'Back-end ๋ฐ๋ธŒ์ฝ”์Šค > Clone Project' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Spring] Map Struct ์‚ฌ์šฉํ•˜๊ธฐ  (0) 2023.01.21

๋Œ“๊ธ€