๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Spring/JPA

@Lob ์‚ฌ์šฉ์‹œ DB์™€ ์—”ํ‹ฐํ‹ฐ ํ•„๋“œ ํƒ€์ž…์ด ๋‹ค๋ฅธ ๊ฒฝ์šฐ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ์™€ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

by ํ‘์‹œ๋ฐ” 2024. 1. 7.

๐Ÿ˜ซ ๋ฐฐ๊ฒฝ

์ง„ํ–‰ ์ค‘์ธ ํ”„๋กœ์ ํŠธ์˜ ๊ณผ๊ฑฐ ์ฝ”๋“œ ์ค‘ HTML ๋ฌธ์„œ๋ฅผ ๊ทธ๋Œ€๋กœ DB์— ์ €์žฅํ•˜๋Š” ๋‚ด์šฉ์ด ์žˆ์—ˆ๋‹ค. HTML ๋ฌธ์„œ ๋‚ด์šฉ์€ ๋™์ ์ด๊ณ , ๊ธธ์ด๊ฐ€ ๊ธธ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ธด ๋ฌธ์ž์—ด ์ €์žฅ์„ ์œ„ํ•ด LOB์„ ์‚ฌ์šฉํ–ˆ๊ณ , ๋ณ„๋‹ค๋ฅธ ๋ฌธ์ œ์—†์ด ์„œ๋น„์Šค๋ฅผ ์ด์šฉํ•ด ์™”์—ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‹ค ์–ด๋Š ์ˆœ๊ฐ„ ์„œ๋น„์Šค๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์—†๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. ์ด์™€ ๊ด€๋ จํ•ด์„œ ํŒŒ์•…ํ•œ ์›์ธ๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ๊ณต์œ ํ•˜๊ณ ์ž ํ•œ๋‹ค.

๐Ÿ”Ž ์›์ธ

๋ฌธ์ œ ๋‚ด์šฉ์„ ๋ถ„์„ํ•ด ๋ณด๋‹ˆ, 'DB์— ์„ ์–ธ๋œ LOB ํƒ€์ž…'๊ณผ '์—”ํ‹ฐํ‹ฐ์— ์‚ฌ์šฉ๋œ @LOB ํƒ€์ž…'์ด ๋‹ค๋ฅด๊ฒŒ ์‚ฌ์šฉ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•œ ๋ฌธ์ œ์˜€๋‹ค.

 

์„œ๋น„์Šค DB์—์„œ๋Š” BLOB ํƒ€์ž…์„ ์ ์šฉ์‹œ์ผฐ๋Š”๋ฐ, ๋ง‰์ƒ ์—”ํ‹ฐํ‹ฐ ํ•„๋“œ์—์„œ๋Š” String ํƒ€์ž…์œผ๋กœ ์„ ์–ธํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— CLOB ๊ด€๋ จ ๋กœ์ง์ด ์ ์šฉ๋˜๋ฉด์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ€์ ธ์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†๊ฒŒ ๋˜์–ด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒƒ์ด๋‹ค.

 

@Lob ์‚ฌ์šฉ ์‹œ ์Šคํ”„๋ง์€ ํ•„๋“œ ํƒ€์ž…์— ๋”ฐ๋ผ ์ž๋™์œผ๋กœ ๊ด€๋ จ jdbc ๊ฐ์ฒด๋กœ ์—ฐ๊ฒฐํ•ด ์ฃผ๋Š”๋ฐ, ์ปดํŒŒ์ผ ๋‹จ๊ณ„์—์„œ๋Š” ์ด๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ํ•ด๋‹น ๋ฌธ์ œ๋ฅผ ํŒŒ์•…ํ•  ์ˆ˜ ์—†์—ˆ๋‹ค.

๋ฐœ์ƒํ•œ ๋ฌธ์ œ

1. Invalid UTF8  

CLOB์€ ๊ธฐ๋ณธ์ ์œผ๋กœ UTF8 ์ธ์ฝ”๋”ฉ์„ ๊ฐ€์ •ํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ๊ธธ์ด๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ๊ณผ์ •์—์„œ UTF-8์„ ์ ์šฉํ•ด์„œ ๊ธธ์ด๋ฅผ ๊ณ„์‚ฐํ•œ๋‹ค.

MariaDbClob.java

  public long length() {
    // The length of a character string is the number of UTF-16 units (not the number of characters)
    long len = 0;
    int pos = offset;

    // set ASCII (<= 127 chars)
    while (len < length && data[pos] > 0) {
      len++;
      pos++;
    }

    // multi-bytes UTF-8
    while (pos < offset + length) {
      byte firstByte = data[pos++];
      if (firstByte < 0) {
        if (firstByte >> 5 != -2 || (firstByte & 30) == 0) {
          if (firstByte >> 4 == -2) {
            if (pos + 1 < offset + length) {
              pos += 2;
              len++;
            } else {
              throw new UncheckedIOException("invalid UTF8", new CharacterCodingException());
            }
          } else if (firstByte >> 3 != -2) {
            throw new UncheckedIOException("invalid UTF8", new CharacterCodingException());
          } else if (pos + 2 < offset + length) {
            pos += 3;
            len += 2;
          } else {
            // bad truncated UTF8
            pos += offset + length;
            len += 1;
          }
        } else {
          pos++;
          len++;
        }
      } else {
        len++;
      }
    }
    return len;
  }

 

ํ•ด๋‹น ๋ถ€๋ถ„์—์„œ BLOB ํƒ€์ž… ๋ฐ์ดํ„ฐ๋ฅผ ๊ธธ์ด๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ๊ณผ์ • ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ "invalid UTF8" ๋ฉ”์‹œ์ง€์™€ ํ•จ๊ป˜ UncheckedIOException ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋œ๋‹ค.

 

MariaDbBlob.java ํŒŒ์ผ์˜ length() ๋ฉ”์„œ๋“œ๋Š” ๋ฐ์ดํ„ฐ ๊ธธ์ด ๊ทธ๋Œ€๋กœ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

  public long length() {
    return length;
  }

2. Data type BLOB cannot be decoded as Clob

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

(์Šคํ”„๋ง ๋ถ€ํŠธ ๋ฒ„์ „์ด ์—…๊ทธ๋ ˆ์ด๋“œ๋˜๋ฉด์„œ length ์ด์ „์— ํ•„ํ„ฐ๋ง๋˜๋Š” ๊ฒƒ์œผ๋กœ ์ถ”์ธก๋œ๋‹ค.)

ClobCodec.java

  @SuppressWarnings("fallthrough")
  private Clob getClob(ReadableByteBuf buf, int length, Column column) throws SQLDataException {
    switch (column.getType()) {
      case BLOB:
      case TINYBLOB:
      case MEDIUMBLOB:
      case LONGBLOB:
        if (column.isBinary()) {
          buf.skip(length);
          throw new SQLDataException(
              String.format("Data type %s cannot be decoded as Clob", column.getType()));
        }
        // expected fallthrough
        // BLOB is considered as String if it has a collation (this is TEXT column)

      case STRING:
      case VARCHAR:
      case VARSTRING:
        Clob clob = new MariaDbClob(buf.buf(), buf.pos(), length);
        buf.skip(length);
        return clob;

      default:
        buf.skip(length);
        throw new SQLDataException(
            String.format("Data type %s cannot be decoded as Clob", column.getType()));
    }
  }

 

column ํƒ€์ž…์„ ํ™•์ธํ•ด์„œ, Blob ํƒ€์ž…์ธ ๊ฒฝ์šฐ SQLDataException ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

์กฐํšŒ ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ์ด๋ฏ€๋กœ, ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ธฐ๋งŒ ํ•œ๋‹ค๋ฉด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค. 

ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

HTML ๋ฌธ์ž๋ฅผ ๊ทธ๋Œ€๋กœ ์ €์žฅํ•˜๋‹ค ๋ณด๋‹ˆ ๋ฐ์ดํ„ฐ๊ฐ€ BLOB๋ณด๋‹ค CLOB ํƒ€์ž…์— ์–ด์šธ๋ฆฐ๋‹ค๊ณ  ์ƒ๊ฐํ•ด์„œ DB ๋ฐ์ดํ„ฐ ์œ ํ˜•์„ CLOB ํƒ€์ž…์œผ๋กœ ์ˆ˜์ •ํ•˜๋ ค๊ณ  ํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ณ€๊ฒฝ ๊ณผ์ •์—์„œ UTF8 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉฐ ์ˆ˜์ •์— ์‹คํŒจํ–ˆ๋‹ค.

(์ด๋ฏธ DB ๋‚ด์— ๋งŽ์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์Œ“์—ฌ ์žˆ์—ˆ๊ณ , ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ ์ค‘ UTF8๋กœ ์ธ์‹ํ•  ์ˆ˜ ์—†๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•˜์˜€์Œ)

 

@Lob ์–ด๋…ธํ…Œ์ด์…˜์„ ์ œ๊ฑฐํ•˜๋ฉด Lob ์ฒ˜๋ฆฌ ๊ณผ์ •์„ ์ƒ๋žตํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ๋ฐฉ๋ฒ•์„ ์„ ํƒํ•ด์„œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ๋‹ค.

๊ฒฐ๋ก 

@Lob์„ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ์ €์žฅ๋  ๋ฐ์ดํ„ฐ ์œ ํ˜•์ด BLOB, CLOB ์ค‘ ์–ด๋–ค ํƒ€์ž…์ด ๋งž์„์ง€ ์ž˜ ๊ณ ๋ฏผํ•ด์„œ ์ •ํ•ด์•ผ ํ•˜๊ณ , ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํƒ€์ž…๊ณผ ํ•„๋“œ ํƒ€์ž…์€ ๋ฐ˜๋“œ์‹œ ๋งž์ถฐ์•ผ ํ•œ๋‹ค.

๋Œ“๊ธ€