고민
- 서버(spring, java)와 mysql에서 emoji길이 인식의 차이가 있다.
- 같은 이모지인데 왜 길이 값을 다르게 인식하는지 이해가 안되었다.
- 또한 글자 길이 제한로직을 처리하기위해서는 이들의 일관된 기준이 필요하였다.
결론
- db의 collataion을 utf8mb4임을 확인하고 서버(java)에서 String을 아래처럼 분해하여 같은 글자길이로 인식하도록 설정해서 해결하였다.
접근방법
mysql 확인
- mysql에서는 emoji가 제대로 잘 보였고 어떻게 그런지 확인해보았다.
흔히 글자수를 제한한 필드를 선언할때 varchar(20)과 같이 선언하고 있는데 여기서 varchar의 20이 어떤 collataion으로 선언되어있는지가 중요하다.
다음 명령어로 확인이 가능하였다.
SHOW VARIABLES LIKE 'character_set_database';
나같은 경우는 character_set_database: utf8mb4 로 나왔다.
- 최대 4byte를 사용하고 BMP나 supplementary characters를 지원한다고 써있습니다.
결론적으로는 varchar(20)이란 4byte로 한글자를 인식했을때의 글자길이를 20개까지 받을수 있는것이다.
- 해당 문자가 길이가 어떤지 알기 위해 mysql function을 이용했다.
LENGTH()는 위에 설명된것처럼 byte수의 합이고 CHAR_LENGTH()는 한글자를 collation기준에 따라 인식한 글자수이다.
ex) LENGTH(👩🚀) == 11, CHAR_LENGTH(👩🚀) == 3 // 11/4byte == 2.xxx올림해서 3 ex) LENGTH(👩🚀한글) == 17(11byte+3byte+3byte), CHAR_LENGTH(👩🚀한글) == 5 // 11byte에서 3글자로 인식하고 한글은 3byte여서 나누기 4를 하면 1글자씩으로 인식해서 전체 길이는 5가 된다. ex) LENGTH(👩🚀한글g) == 18(11byte+3byte+3byte+1byte), CHAR_LENGTH(👩🚀한글g) == 6 // 11byte 3글자, 3byte 2글자, 1byte(영어) 1글자로 해서 6이 된다.
결론적으로 db에서는 4byte기준으로 글자를 인식하고 위와같은 길이로 인식하고 있었기에 db를 바꿀순 없고 서버로직을 db에 맞추어 일관되게 맞춰보기로 했습니다.
서버에서 mysql utf8mb4와 같은 효과내기
- java에서 mysql utf8mb4을 대응할 수 있는 코드가 있었으면 좋으려만 잘 찾지 못하였다.
꿩대신 닭이라고 결국 원리는 한 글자를 몇바이트로 인식할것이냐는 관점에서 출발했다.
utf8mb4에서는 한글자를 4byte로 보고 있기에 utf-32로 해보면 어떨까?
일단 32비트(4byte)단위의 글자로 변경함: "".getBytes("UTF-32")
StandardCharset에는 UTF-32는 없다. 텍스트로 대체하기로 했다.
- 1번을 거치면 UTF-32인코딩으로 byte개수를 리턴하기에 4byte를 1글자로 보기위해 나누기 4를 한다.
전체적으로 다 확인해보진 못했습니다만 아래와 같은 결과를 얻을 수 있다.
ex) "👩🚀".getBytes("UTF-32").length = 12 ex) "👩🚀".getBytes("UTF-32").length/4 = 3 //4byte를 한글자로 보니 4로 나눔 ex) "👩🚀한글".getBytes("UTF-32").length/4 = 5 //4byte를 한글자로 보니 4로 나눔
참고
- https://dev.mysql.com/doc/refman/5.6/en/charset-metadata.html
- 테이블이나 필드에 따른 collation확인도 해볼 수 있다.: https://hyorock.tistory.com/52
- collation 엿보기: https://mysqldba.tistory.com/154
- https://stackoverflow.com/questions/1734334/mysql-length-vs-char-length
- https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/nio/charset/StandardCharsets.html
- https://dev.mysql.com/doc/refman/8.0/en/charset-unicode-utf8mb4.html
- 라인에서도 같은 고민을 했다: https://engineering.linecorp.com/ko/blog/the-7-ways-of-counting-characters/#
- 1바이트는 왜 8비트일까?: https://zepeh.tistory.com/313
- Difference between String.length() and String.getBytes().length: ttps://stackoverflow.com/questions/16270994/difference-between-string-length-and-string-getbytes-length
- char vs varchar mysql: https://dev.mysql.com/doc/refman/5.6/en/char.html
- max varchar : http://sqlines.com/mysql/datatypes/varchar
- mysql functions: https://dev.mysql.com/doc/refman/8.0/en/string-functions.html#function_length
- java emoji count library: https://github.com/unicode-org/icu
- https://stackoverflow.com/questions/36393811/how-can-i-convert-utf-16-to-utf-32-in-java-
- BE = Big Endian이라는 개념도 있는데 자세히는 이해못했다: https://stackoverflow.com/questions/13628868/string-getbytesutf-32-returns-different-results-on-jvm-and-dalvik-vm
'새로운 로직접근' 카테고리의 다른 글
.findout(Redis를 이용해 CyclicBarrier만들어보기) (0) | 2020.12.21 |
---|---|
.findout(RedisTemplate에서 prefix key만들기) (0) | 2020.12.20 |
.findout(react에서 spring으로 배열 파라미터 넘기기) (0) | 2020.12.20 |