๐ https://github.com/Floney-2023
์ฐ๋ฆฌ ๊ฐ๊ณ๋ถ์์ ์์ฐ์ ๊ฐ๊ณ๋ถ ๋ด์ญ ์ค ์ง์ถ(-)๊ณผ ์์ (+)์ ํฉ์ ์๋ฏธํ๋ค.
์ด ์์ฐ์ด๋ผ๋ ๊ฐ๋ ์ ์ค๊ณ ๊ณผ์ ๋ถํฐ ์ด๋ ค์์ด ์์๋๋ฐ, ํ๋์ ์ง์ถ ํน์ ์์ ์ด ์๊ธฐ๋ฉด ๊ทธ์ ๋ฐ๋ผ ํด๋น ๋ด์ญ ๋ ์ง ์ดํ๋ก ๋งค ๋ฌ์ ์ง์ถ ๋ฐ์ดํฐ๊ฐ ๊ฐฑ์ ๋์ด์ผ ํ๊ธฐ ๋๋ฌธ์ด๋ค.
๋ค๋ฅธ ๊ฐ๊ณ๋ถ ์๋น์ค๋ฅผ ์ฐธ๊ณ ํด ์์ฐ์ด ๊ฐฑ์ ๋๋ ํน์ ๊ธฐ๊ฐ(5๋ )์ ์ ํด์ ๊ฐ๊ณ๋ถ ๋ด์ญ ๋ณ๊ฒฝ ์ ๋งค ๋ฒ 60๊ฐ(5๋ * 12๋ฌ)์ ์์ฐ ๋ฐ์ดํฐ๊ฐ ๊ฐฑ์ ๋๋๋ก ์ ํ๋ฉด์ ์ด ๋ฌธ์ ๋ ๋ง๋ฌด๋ฆฌ๋์๋ค.
๋ฌธ์ ๋ฐ์ - ์๋๋์ง ์์ ์์ฐ ๋ฐ์ดํฐ ์ถ๊ฐ ์์ฑ
์ฑ์ด ์ถ์๋ ํ, ๊ฐ๊ณ๋ถ ๋ด์ญ์ ์์ ํ๋ ๊ณผ์ ์ค ์์ฐ ์ญ์ ๋ฉ์๋์์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
NonUniqueResultException์ผ๋ก ํ๋์ ์์ฐ ๋ฐ์ดํฐ๋ง ์กฐํํ๊ธฐ๋ฅผ ๊ธฐ๋ํ์ผ๋ ์ฌ๋ฌ ๊ฐ๊ฐ ์กฐํ๋๋ฉฐ ์๋ฌ๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ค. ์ด์ ๋๋น์์ ์ค์ ๋ก ์ค๋ณต ๋ฐ์ดํฐ๊ฐ ์๋ ๊ฒ์ ํ์ธํ๊ณ , ์์ฐ ๊ด๋ จ ์ฝ๋์์ ๋ค์ํ ์ ํฉ์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์์ ์๊ฒ ๋์๋ค.
๊ธฐ์กด ์ํฉ
2000๋ 1์ X์ผ์ ํ ๊ฐ๊ณ๋ถ ์์ ๋ด์ญ ๊ธ์ก์ 1000์์์ 500์์ผ๋ก ์์ ํ๋ค๊ณ ๊ฐ์ ํด๋ณด์. ์ด ๋ 2000๋ 1์๋ถํฐ 2004๋ 12์๊น์ง ์ด 60๋ฌ ๋์ ํฌํจ๋์ด ์๋ ์์ฐ 1000์์ 500์์ผ๋ก ์์ ํด์ผ ํ๋ค.
๊ธฐ์กด ์ฝ๋์์๋ '๊ฐ๊ณ๋ถ ๋ด์ญ ์์ '์ด๋ผ๋ ํ๋์ ํธ๋์ญ์ ์์ ์์ฐ์ ํฌํจํ ๊ด๋ จ๋ ๋ชจ๋ ๋ฐ์ดํฐ์ ์์ ์ด ์ด๋ฃจ์ด์ง๊ณ ์์๋ค. ๊ทธ๋ฆฌ๊ณ ์์ฐ ์์ ์ '๊ธฐ์กด ๊ธ์ก ์ญ์ → ๋ณ๊ฒฝ ๊ธ์ก ์ถ๊ฐ' ์์๋ก ์ด๋ฃจ์ด์ก๋ค.
๊ตณ์ด ํ ๋ฒ์ ์์ ํ์ง ์๊ณ ์ญ์ ์ ์ถ๊ฐ๋ผ๋ ๊ณผ์ ์ ๊ฑฐ์น๋ ์ด์ ๋, ๊ฐ๊ณ๋ถ ๋ด์ญ ์์ ์ ํด๋น ๋ด์ญ์ ๋ ์ง๋ ์์ ํ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค. (๋ด๊ฐ ์์ฑํ ์ฝ๋๋ ์๋์ง๋ง ๊ทธ๋ด ๊ฒ์ด๋ค...) JPA์ ๋ณ๊ฒฝ ๊ฐ์ง๋ฅผ ์ด์ฉํ๊ธฐ ๋๋ฌธ์ ์ฟผ๋ฆฌ๊ฐ 2๋ฐฐ๋ก ๋๊ฐ์ง ์์ผ๋ฏ๋ก ์ฑ๋ฅ ๋ฉด์์๋ ๊ด์ฐฎ์์ง๋ง Repeatable Read ๊ฒฉ๋ฆฌ ์์ค์์ ๊ฐฑ์ ์์ค์ด ๋ฐ์ํ ์ ๋ฐ์ ์๋ค.
ํ์ง๋ง ์ด๋ NonUniqueResultException์ด ๋ฐ์ํ ์ค์ ์์ธ์ ์๋๋ค. ๊ฐ๊ณ๋ถ ๋ด์ญ์ ์์ ํ๋ค๋ ๊ฒ์ ์ด๋ฏธ ํด๋น ๋ด์ญ์ด ์ ์ฅ๋์์์ ์๋ฏธํ๋ฏ๋ก ์์ฐ ๋ฐ์ดํฐ๊ฐ ์์ฑ๋ ์ผ์ด ์๋ค.
// ๊ฐ๊ณ๋ถ ๋ด์ญ์ money๋ฅผ ์์ฐ์ ์ถ๊ฐ
public void createAsset() {
for (int i = 0; i < FIVE_YEARS; i++) {
Optional<Asset> asset = assetRepository.find(๋ ์ง, ๊ฐ๊ณ๋ถ);
if (asset.isEmpty()) {
Asset newAsset = new Asset();
assetRepository.save(newAsset);
} else {
asset.get().update();
}
}
}
์ค์ ์์ธ์ '๊ฐ๊ณ๋ถ ๋ด์ญ ์ถ๊ฐ'์ ์๋ค. ์ด ๋๋ง ์์ฐ ๋ฐ์ดํฐ๊ฐ ์๋ก ๋ง๋ค์ด์ง๊ณ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ insert ๋๋ ์ํฉ์ด ๋ฐ์ํ ์ ์๋ค.
์ ์์์ ๊ฐ์ ์ํฉ์์ ์ค๋ณต ์์ฑ์ด ๋ฐ์ํ๋ค.
์ฒซ๋ฒ์งธ ์์ - ๋น๊ด์ ๋ฝ ์ฌ์ฉ
์ด์ ์๋ฒ์์ ๋ฐ์ํ๋ hotfix ์ด์๋ผ, ์ต๋ํ ์ฝ๋๋ฅผ ์ ๊ฒ ๋ฐ๊พธ๋ ๋ฐฉ์์ผ๋ก ์ค๋ฅ๋ฅผ ๊ณ ์น๊ณ ์ถ์๋ค.
๋ฐ๋ก ์๊ฐ๋๋ ๊ฑด ์ญ์๋ ํ ๋ฒ ์จ๋ณธ ๋น๊ด์ ๋ฝ์ด์๋ค.
// ๊ฐ๊ณ๋ถ ๋ด์ญ์ money๋ฅผ ์์ฐ์ ์ถ๊ฐ
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void createAsset() {
for (int i = 0; i < FIVE_YEARS; i++) {
Optional<Asset> asset = assetRepository.findExclusively(); // ๋น๊ด์ ๋ฝ
if (asset.isEmpty()) {
Asset newAsset = new Asset();
assetRepository.save(newAsset);
} else {
asset.get().update();
}
}
}
// ๊ฐ๊ณ๋ถ ๋ด์ญ์ money๋ฅผ ์์ฐ์์ ์ฐจ๊ฐ
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void deleteAsset() {
BookLine bookLine = bookLineRepository.findExclusively(); // ๋น๊ด์ ๋ฝ
for (int i = 0; i < FIVE_YEARS; i++) {
assetRepository.find().ifPresent(asset -> {
asset.delete();
});
}
}
์์ ๊ฐ์ด ์์ฐ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ ๋ ๋น๊ด์ ๋ฝ์ ๊ฑธ์๊ณ , ์์ฐ ๋ฉ์๋๋ฅผ ํธ์ถํ ๋ ์ ๋ฌผ๋ฆฌ์ ํธ๋์ญ์ ์ ์์ฑํ๋๋ก ํด ํด๋น ๋ฒ์์์๋ง ๋ฝ์ด ์ ์ง๋๋๋ก ์ค์ ํ๋ค.
๋์น์ฑ๊ฒ ์ง๋ง ์์ ํ ์๋ชป๋ ์ฝ๋์ด๋ค.
- ํธ๋์ญ์ ์ด ๋ถ๋ฆฌ๋์ด ์์ฐ ๋ฉ์๋์ Exception์ด ๋ฐ์ํด๋ ๊ฐ๊ณ๋ถ ๋ด์ญ ๋ฉ์๋๊ฐ ์ ์์ ์ผ๋ก ์คํ๋๋ค.
- ์์ฐ ๋ฐ์ดํฐ๊ฐ ์์ฑ๋ ๋ ์ ํฉ์ฑ์ด ์ ์ง๋์ด์ผ ํ๋๋ฐ, ๋ฝ์ผ๋ก๋ insert ์ค๋ณต์ ๋ง์ ์ ์๋ค.
- ๊ฐ๊ณ๋ถ id์ ๋ ์ง๋ก ์์ฐ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋๋ฐ ๊ฐ๊ณ๋ถ id์๋ง ์ธ๋ฑ์ค๊ฐ ๊ฑธ๋ ค ์์ด ํด๋น ๊ฐ๊ณ๋ถ id๋ฅผ ๊ฐ์ง ๋ชจ๋ ์์ฐ ๋ฐ์ดํฐ์ ๋ฝ์ด ์ ํ๋๋ค.
๊ฒ๋ค๊ฐ nGrinder๋ก ์ฑ๋ฅ ํ ์คํธ๋ฅผ ํด๋ณด์๋๋ Vuser๊ฐ 10๋ช ์ ๋๋ก ๋งค์ฐ ์ ์ ๋๋ DB ์ปค๋ฅ์ ์ด ๋ชจ์๋ผ๋ค๋ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค. DB ์ปค๋ฅ์ ํ์ ๊ธฐ๋ณธ ์ค์ ์ ์ฌ์ฉํ๊ณ ์๋๋ฐ 10๊ฐ์ ์ปค๋ฅ์ ๊ณผ 30์ด์ ์ปค๋ฅ์ timeout ์๊ฐ์ด ์์์๋ ๋ฐ์ํ ๊ฒ์ด๋ค.
๋๋ฒ์งธ ์์ - MySQL ์ฟผ๋ฆฌ ์ด์ฉ
์ฒซ๋ฒ์งธ ์์ ์์ ์ป์ ๊ฒฐ๋ก ์ ์๋์ ๊ฐ์๋ค.
1. ํ๋์ ํธ๋์ญ์ ๋ด์์ insert ์ค๋ณต์ ํด๊ฒฐํ๋ค.
2. ๋ฝ์ ๋ฒ์๋ฅผ ์์ฐ ๋ฐ์ดํฐ ํ ๊ฐ๋ก ์ค์ธ๋ค.
์์ฐ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ ๋ฐ๋ณต๋ฌธ ๋ด์์ ๋ถ์ฐ ๋ฝ์ ์ฌ์ฉํด์ผ ํ๋ ๊ณ ๋ฏผ์ด ๋ ๋, MySQL์ upsert ๋ฌธ๋ฒ์ ์๊ฒ ๋์๋ค.
public void createAsset() {
for (int month = 0; month < SAVED_MONTHS; month++) {
final LocalDate currentMonth = startMonth.plusMonths(month);
assetRepository.upsert();
}
}
unique ์ ์ฝ์ด ๊ฑธ๋ฆฐ ์ปฌ๋ผ๋ค์ ๊ธฐ์ค์ผ๋ก, ํด๋น ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด update ์์ผ๋ฉด insert๋ฅผ ํ๋ ๋ฌธ๋ฒ์ด๋ค. ์ด๋ฅผ ์ฌ์ฉํด ์์ฐ ์ถ๊ฐ ๋ฉ์๋์์ ๋ฐ์ดํฐ ๋น ํ ๊ฐ๋ก ์ฟผ๋ฆฌ๋ฅผ ์ค์ด๊ณ , ๋ฝ๋ ์ฌ์ฉํ์ง ์์ ์ ์์๋ค.
upsert ์ฟผ๋ฆฌ์ ์ฅ์ ๋ง ์๋ ๊ฒ์ ์๋๋ค. ์ฐ์ MySQL์์๋ง ์ง์ํ๋ ๋ฌธ๋ฒ์ด๊ธฐ ๋๋ฌธ์ ํน์ RDBMS์ ๋ํ ์์กด์ฑ์ด ์๊ธด๋ค. ๊ทธ๋ฆฌ๊ณ JPA EntityListeners ๋ ์ฌ์ฉํ์ง ๋ชปํด ๋ฆฌ์ค๋์์ ๋ณ๊ฒฝ๋๋ ๋ถ๋ถ์ ์ง์ ์ฟผ๋ฆฌ์ ์ถ๊ฐํด์ผ ํ๋ค.
ํ์ง๋ง ๋ถ์ฐ ๋ฝ์ ์ฌ์ฉํ๋ ๋์๋ณด๋ค๋ ๊ณต์๊ฐ ํจ์ฌ ์ ๊ฒ ๋ค์ด ์ ํํ๊ฒ ๋์๋ค.
@Modifying
@Query(
value = "insert into ASSET (date, money, book_id) values (:date, :money, :book) " +
"on duplicate key update money = money + :money, updated_at = now()",
nativeQuery = true
)
void upsertMoneyByDateAndBook(LocalDate date, Book book, double money);
์์ฐ์ ์ ์ธํ๋ ๋ฉ์๋์์๋ JPA ๋ณ๊ฒฝ๊ฐ์ง ๋์ update ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํด ๋ฝ ์ฌ์ฉ ์์ด ์ ํฉ์ฑ์ ์ ์งํ ์ ์๋ค.
์๋ก์ด ๋ฌธ์ ๋ฐ์ - SQL ๋ฌธ๋ฒ ์๋ฌ
๋๋ฒ์งธ ์์ ์ ๋๋ด๊ณ ๊ฐ๋จํ ์ฑ๋ฅ ํ ์คํธ๊น์ง ์งํํด ๋์์ฑ ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋์์์ ํ์ธํ๋ค.
๊ทธ๋ฐ๋ฐ ํด๋น ์ฝ๋๋ฅผ ๋จธ์งํ๋ ๋ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค. ๐ฌโ ๏ธ
๊ฒ๋ค๊ฐ ํฐ์ง Exception์ SQLGrammarException์ผ๋ก ๋ฌธ๋ฒ์ด ํ๋ ธ๋ค๊ณ ํด์ ๋์ฑ ๋นํฉ์ค๋ฌ์ ๋ค.
์์ - ํ ์ด๋ธ๋ช ๋/์๋ฌธ์ ๊ตฌ๋ถ
์ด๋ค ๋ฌธ์ ์ธ์ง ์๊ฐํ๋ค๊ฐ, ๋ก๊ทธ์์ ์ฟผ๋ฆฌ๊ฐ ํ ์ด๋ธ๋ช ๋ง ๋๋ฌธ์๋ก ์ฐํ๋ ๊ฒ ๋ ์ฌ๋๋ค. (์ด๊ฒ ๊ธฐ์ต์ ๋จ์์๋ ๊ฑธ ๋ณด๋ฉด ๋ฌด์์์ ์ผ๋ก ๊ฑฐ์ฌ๋ ธ๋๋ณด๋ค...)
@Modifying
@Query(
value = "insert into ASSET (date, money, book_id) values (:date, :money, :book) " +
"on duplicate key update money = money + :money, updated_at = now()",
nativeQuery = true
)
void upsertMoneyByDateAndBook(LocalDate date, Book book, double money);
upsert ์ฟผ๋ฆฌ์์ ๋จ์ํ ๊ฐ๋ ์ฑ์ ์ํด ํ ์ด๋ธ๋ช ๋ง ๋๋ฌธ์๋ก ์์ฑํ๋ ๊ฒ์ด๋ค.
MySQL ๊ณต์ ๋ฌธ์์์ case sensitivity๋ก ๊ฒ์ํ๋๋ ์๋์ ๊ฐ์ ๋ด์ฉ์ ์ฐพ์ ์ ์์๋ค.
https://dev.mysql.com/doc/refman/8.0/en/identifier-case-sensitivity.html
How table and database names are stored on disk and used in MySQL is affected by the lower_case_table_names system variable. This variable does not affect case sensitivity of trigger identifiers. On Unix, the default value of lower_case_table_names is 0. On Windows, the default value is 1. On macOS, the default value is 2.
ํ ์ด๋ธ๊ณผ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ด๋ฆ์ด ๋์คํฌ์ ์ ์ฅ๋๊ณ ์ฌ์ฉ๋๋ ๋ฐฉ์์ lower_case_table_names ์์คํ ๋ณ์์ ์ํด ์ข์ฐ๋๋ค. ์ด ๋ณ์๋ ํธ๋ฆฌ๊ฑฐ ์๋ณ์์ ๋์๋ฌธ์ ๊ตฌ๋ถ์ ์ํฅ์ ์ฃผ์ง ์๋๋ค. Unix์์ ๊ธฐ๋ณธ ๊ฐ์ 0์ด๊ณ , Windows์์๋ 1์ด๋ฉฐ macOS์์๋ 2์ด๋ค.
๋ก์ปฌ ์ด์์ฒด์ ๋ macOS ์ด๊ธฐ ๋๋ฌธ์ MySQL ์๋ฒ์์ ์๋ฌธ์๋ก ์ฌ์ฉํด ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง ์์๋ค.
ํ์ง๋ง ์ด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์๋ฒ๋ RDS์ ์๊ณ , RDS๋ Linux๋ก unix ๊ณ์ด์ด๋ค. ๋ฐ๋ผ์ ๋์คํฌ์ ์ ์ฅ๋ ์๋ฌธ์ ํ ์ด๋ธ๋ช ๊ณผ ์ฟผ๋ฆฌ์ ๋๋ฌธ์ ํ ์ด๋ธ๋ช ์ด ๋ค๋ฅด๋ค๊ณ ํ๋จํด ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
MySQL ์๋ฒ๊ฐ ์ฌ์ฉ ์ค์ผ ๋๋ ํด๋น ๋ณ์ ๊ฐ์ ๋ณ๊ฒฝํ ์ ์์ผ๋ฏ๋ก ์ฝ๋์ ๋๋ฌธ์๋ฅผ ์์ ํ๋ค.
@Modifying
@Query(
value = "insert into asset (date, money, book_id) values (:date, :money, :book) " +
"on duplicate key update money = money + :money, updated_at = now()",
nativeQuery = true
)
void upsertMoneyByDateAndBook(LocalDate date, Book book, double money);
๊ทผ๋ณธ์ ์์ธ ํด๊ฒฐ
๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณ ํ์๋ถ๊ป ๊ทธ ๊ณผ์ ์ ์ค๋ช ํ๋ ์ค, ๋ค์๊ณผ ๊ฐ์ ์ง๋ฌธ์ ๋ฐ์๋ค.
"๊ทธ๋ฐ๋ฐ API ์คํ ์๊ฐ์ด ๊ธธ์ด์ผ 1์ด์ธ๋ฐ, ์ด๋ป๊ฒ ์ด์ ํ๊ฒฝ์์ ๋์์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์๊น์?"
์ง๋ฌธ์ ๋ฐ์ ๋น์์๋ ์ถ์ ์งํ๋ผ ์ด์ฉํ๋ ์ฌ๋์ด ๋ง์์ ์ฐ์ฐํ ๊ฒน์น ๊ฒ ๊ฐ๋ค๋ ์์ผ๋ก ๋๋ตํ๋ค. ์๋ฌ๋ ๋ฐ์ํ๊ณ , ๊ทธ์ ๋ํ ์์ธ์ ๋ช ํํ๋๊น.
๊ทธ๋ฌ๋ค ๋ฌธ๋ ์ถ์ ์ ํ๋ QA๊ฐ ๋ ์ฌ๋๋ค. ํ ํ์์ด ์ฑ์์ '๊ฐ๊ณ๋ถ ์์ฑํ๊ธฐ' ๋ฒํผ์ ์ฐํํด์ API ์์ฒญ์ด ์ฌ๋ฌ ๋ฒ ์ ์ก๋์๊ณ , ๊ฒฐ๊ณผ์ ์ผ๋ก ๋๊ฐ์ ๊ฐ๊ณ๋ถ๊ฐ ์ฌ๋ฌ ๊ฐ ์์ฑ๋์ด์ ์ฑ ๊ฐ๋ฐ์๋ถ์ด ์ฐํ๋ฅผ ๋ง์์ฃผ์ จ๋ค.
๊ฐ๊ณ๋ถ ๋ด์ญ ์ถ๊ฐ API๋ฅผ ํตํด ์์ฐ ๋ฐ์ดํฐ๊ฐ ์ค๋ณต ์์ฑ๋ ์ด์ ๋ ์ฌ๋ฌ ๋ช ์ด ๋์์ ์์ฒญํด์๊ฐ ์๋๋ผ 'ํ ๋ช ์ด ์ฌ๋ฌ ๋ฒ ์์ฒญํด์'๊ฐ ์๋๊น ์ถ๋ค.
ํด๋น ํ์์ ์ฑ์์ ๋ง์ ์ดํ๋ถํฐ, ์์ ํ ์ฝ๋๊ฐ ๋จธ์ง๋ ๋๊น์ง ์ค๋ณต ๋ฐ์ดํฐ๊ฐ ๋ฐ์ํ์ง ์์๋ ๊ฒ์ ์๊ฐํ๋ฉด ์ด์ชฝ์ด ์ ๋น์ฑ์ด ๋๋ค.
ํํ ๋์์ฑ ์ด์์์ง๋ง ๋ค์ํ ๋ด์ฉ์ ๋ฐฐ์ฐ๊ณ ์๊ฐํด๋ด์ ์๋ฏธ์๋ ์ฝ์ง๊ธฐ์๋ค. โ๏ธ
'ํ๋ก์ ํธ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[ZI9ZA9] ๊ฒฐ์ ์ฑ๋ฅ ๊ฐ์ ๊ธฐ (0) | 2024.03.17 |
---|---|
[Floney] ์นดํ ๊ณ ๋ฆฌ ๋ฆฌํฉํ ๋ง ์ผ๋๊ธฐ (2) | 2024.03.11 |