DevLog ๐ถ
[์ฐํ ์ฝ 5๊ธฐ] ์ฅ๋ฐ๊ตฌ๋ ํ์ ๋ฏธ์ ํ๊ณ ๋ณธ๋ฌธ
[์ฐํ ์ฝ 5๊ธฐ] ์ฅ๋ฐ๊ตฌ๋ ํ์ ๋ฏธ์ ํ๊ณ
dolmeng2 2023. 6. 16. 01:23๋ผ์จ, ์ฅฌ๋, ์ธ์ธ, ์ตธํ๋ ์งํํ๋ ๋ ๋ฒจ 2 ๋ง์ง๋ง ๋ฏธ์ , ์ฅ๋ฐ๊ตฌ๋ ํ์ ๋ฏธ์ ์ด๋ค!
ํ๋ก ํธ ํฌ๋ฃจ๋ถ๋ค์ด๋๋ ํจ๊ป ํ ์ ์์์ด์ ๋๋ฌด ์ฌ๋ฐ์๋ ์๊ฐ์ด์๋ค ๐
์ด๋ฒ ๋ฏธ์ ์ ์ ๋ฒ์ ์งํํ๋ ๋ฏธ์ ์ ๋ฐํ์ผ๋ก ํ๋ค ๋ณด๋๊น ์ฝ๋์ ์ธ ๋ถ๋ถ๋ณด๋ค, ๋ฐฐํฌ ์ชฝ์ ๋ ์ ๊ฒฝ์ผ๋ ๊ฒ ๊ฐ๋ค.
๋ง์ง๋ง ๋ฏธ์ ์ธ ๋งํผ ์ ๋ง ๋ง์ด ๋ฐฐ์ ๋ ์๊ฐ์ด์๋ค ใ ใ
โ๏ธ ์์ฑํ ์ฝ๋
โ๏ธ PR
๋ง์ง๋ง ์ฝ๋ ๋ฆฌ๋ทฐ๋ผ๋ ๊ฒ ๋๋ฌด ์ฌํ๋ค... ๐ฅฒ
โ๏ธ ๊ธฐ๋ฅ ์๊ตฌ์ฌํญ
- AWS๋ก ๋ฐฐํฌํ๊ธฐ (์๋ฒ ๋ฐฐํฌ์ฉ ์ธ์คํด์ค / DB์ฉ ์ธ์คํด์ค)
: ์ด๋, DB์ฉ ์ธ์คํด์ค๋ ์ธ๋ถ IP๋ก ๊ณต๊ฐ๋์ง ์๋๋ค.
- ์ฅ๋ฐ๊ตฌ๋์ ๋ด์ ์ํ์ ์ฃผ๋ฌธํ ์ ์๋ค.
- ์ํ ์ฃผ๋ฌธ ์ ํ์ค ์ธ๊ณ์ ์ผํ ์๋น์ค๊ฐ ์ ๊ณตํ๋ ์ฌํ ๊ด๋ จ ์์๋ฅผ ์ต์ 1๊ฐ์ง ์ด์ ์ถ๊ฐํ๋ค.
: ์ฌํ ๊ด๋ จ ์์: ์ฟ ํฐ, ํฌ์ธํธ, ํ ์ธ ๋ฑ
ex) 5๋ง์ ์ด์ ์ฃผ๋ฌธ ์ ์ ์ฒด ๊ธ์ก์์ 10% ํ ์ธ์ด ๋๋ค.
- ์ฌ์ฉ์ ๋ณ๋ก ์ฃผ๋ฌธ ๋ชฉ๋ก์ ํ์ธํ ์ ์๋ค.
- ํน์ ์ฃผ๋ฌธ์ ์์ธ ์ ๋ณด๋ฅผ ํ์ธํ ์ ์๋ค.
โ๏ธ AWS ๋ฐฐํฌ ๋ฐ ํ์ ํ๊ฒฝ ๊ตฌ์ถํ๊ธฐ
ํ๋ก ํธ์๋ ํฌ๋ฃจ๋ถ๋ค๊ณผ ํ์ ํ๋ค๋ ์๊ฐ์ ์ ๋์ ๋ฐฐํฌ ์ชฝ์ ์ด์ฌํ ๋ค๋ฃจ์๋ค.
๊ฑฐ์ ๋ค ๋ธ๋ก๊ทธ๋ก ์ฌ๋ฆฐ ๋ด์ฉ์ด์ด์ ๋ฐ๋ก ์ค๋ช ํ์ง๋ ์๊ณ ๋์ด๊ฐ๊ณ ์ ํ๋ค.
1. HTTP -> HTTPS ์ ์ฉํ๊ธฐ (SSL / Nginx๋ก ๋ฆฌ๋ฒ์ค ํ๋ก์ ์ ์ฉํ๊ธฐ)
2. ์ ํจ์ค๋ก CI / CD ๊ตฌ์ถํ๊ธฐ
3. Logback์ผ๋ก ์๋ฌ๋ฅผ ํ์ผ๋ก ๋ก๊น ํ๊ธฐ
๊ธฐ์ด ์ค์ ๊ธฐ์ด์ง๋ง, ๋ด ์์ผ๋ก ์ง์ ๋ฐฐํฌ๋ฅผ ํด๋ณธ๋ค๋ ๊ฒ ๋๋ฌด ๋ฟ๋ฏํ๊ณ ์ ๊ธฐํ๋ ๊ฒฝํ์ด์๋ค ๐ถ
๋ค์ ํ๋ก์ ํธ ํ ๋๋ ์๋ฒ๋ง ์๋ค๋ฉด...! ๋ ๋ฉ์๊ฒ ๊ตฌ์ถํด์ผ๊ฒ ๋ค ใ _ใ
๐ฌ ๋๋ฉ์ธ ์ค๊ณํ๊ธฐ - ์ธํฐํ์ด์ค๋ก ๋ถ๋ฆฌํ๊ธฐ
์ด๋ฒ ๋ฏธ์ ์ ํต์ฌ ์๊ตฌ์ฌํญ ์ค์ ํ๋๊ฐ ์ฌํ ์ ์ฑ ์ ๊ฒฐ์ ํ๋ ๊ฒ์ด์๋ค.
์ฐ๋ฆฌ ํ์ '์ฟ ํฐ'์ด๋ผ๋ ๋๋ฉ์ธ์ ์ ํํ์๊ณ , ์ฒ์์ผ๋ก ๊ฐ์ ํ์ ๋์ ์ฒ์์ผ๋ก ์ํ์ ์ฃผ๋ฌธํ์ ๋ ๋ฐ๊ธ๋ฐ๋๋ก ์งํํ์๋ค.
์ฟ ํฐ ๋๋ฉ์ธ์ ๋ ํ์ฉํ๋ฉด ์ข์์ ๊ฒ ๊ฐ์๋ฐ, ์ต๋ํ ๊ฐ๊ฒฐํ ๋ฐฉํฅ์ผ๋ก ์งํํ๋ค ๋ณด๋ ์ด๋ ๊ฒ ๋๋ค ๐ฅฒ
๋ํ, ์์ ์ฝ๋์ ๋น์ทํ ๋ฏ ๋ค๋ฅด๊ฒ ๋๋ฉ์ธ์ ์ค๊ณํ์๋ค.
๊ทธ๋ฆผ์ผ๋ก ๋ํ๋ด๋๊น ๋ณต์กํด ๋ณด์ด๋๋ฐ, ์ฟ ํฐ๊ณผ ์ฃผ๋ฌธ, ํ๋ถ ์ ์ฑ ๋๋ฉ์ธ์ด ์ถ๊ฐ๋ ๊ฑฐ ๋นผ๊ณ ๋ ์ง๋ ๋ฏธ์ ๊ณผ ๊ฑฐ์ ๋ค๋ฅผ ๊ฒ ์๋ค.
ํ๋ถ ์ ์ฑ ์ ๊ฒฝ์ฐ ์งํ์ฒ ๋ฏธ์ ์์ ์งํํ๋ ์ ์ฑ ๊ด๋ฆฌ ํด๋์ค์ ๋ฐฉ๋ฒ์ ๊ทธ๋๋ก ๋ฐ๋๋ค. ๋ง์์ ๋ ๋ค!
- ํ ๊ฐ์ง ์์ฌ์ด ๊ฑด, ์ฃผ๋ฌธ ๋๋ฉ์ธ์ด ์ฅ๋ฐ๊ตฌ๋ ์์ดํ ๋๋ฉ์ธ ๋์ ์ ๋ณ๋์ ์ฃผ๋ฌธ ์ํ ๋๋ฉ์ธ์ ๊ฐ์ง๊ณ ์๋ ๊ฒ ๋ ์ด์ธ๋ฆฌ์ง ์์์๊น ์ถ๋ค. ์ฃผ๋ฌธ์ด ์๋ฃ๋ ์ดํ์ ์ฃผ๋ฌธ์ ๋ํด ์กฐํ๋ฅผ ํด์ฌ ๋ ์ฅ๋ฐ๊ตฌ๋ ์์ดํ ๋๋ฉ์ธ์ ๊ฐ์ง๊ณ ์๋ค ๋ณด๋ ๋ ผ๋ฆฌ์ ์ผ๋ก ์ด์ํ๋ค๋ ๋๋์ด ๋ค์๊ธฐ ๋๋ฌธ์ด๋ค... ๐ฅฒ ๋ฌผ๋ก ๋ฆฌ๋ทฐ์ด๋์ ๋ณ๋ค๋ฅธ ํผ๋๋ฐฑ์ ์ฃผ์์ง ์์์ง๋ง, ๊ฐ์ธ์ ์ผ๋ก ๋ค์์ ๊ตฌํํ๋ค๋ฉด ๋ถ๋ฆฌํ ํฌ์ธํธ๋ผ๊ณ ๋๊ปด์ก๋ค.
๐ก ๋๋ฉ์ธ๊ณผ ID
๋ํ, ๋๋ฉ์ธ์ด id๋ฅผ ๊ฐ์ง๊ณ ์๋๋ก ๋ง๋ค์๋๋ฐ, ์ฌ์ค ์ฒ์์๋ ์ด ๋ถ๋ถ์ ๋ถ๋ฆฌํ๋ค๊ฐ ๋์ค์ ํผ๋๋ฐฑ์ ๋ฐ๊ณ ์์ ํ๋ค.
(๋ช๋ช ํฌ๋ฃจ๋ถ๋ค๋ ๋ด๊ฐ ์ค๊ณํ ๊ฒ์ ๋ณด๊ณ ๋ง์์ ํด์ฃผ์ จ์๋๋ฐ, ์ต์ข ์ ์ผ๋ก๋ ๊ฐ์ง๊ณ ์๋ ๊ตฌ์กฐ๋ก ๋ง๋ค์์๋ค ใ ใ ใ )
๋ด๊ฐ ๋๋ฌด ์์์ฑ ๋ ์ด์ด์ ๊ฐ์ ๋๋ฉ์ธ ๊ฐ์ฒด๊ฐ ๊ฐ์ง๊ณ ์๋ ๊ฒ์ ์ง์ฐฉํ์๋ค๋ ๊ฒ ๋๊ปด์ก๋ค... ใ ใ
JPA๋ฅผ ์ฐ๋ค ๋ณด๋ฉด ์ด์ฐจํผ ์ผ์ด๋ ์๋ฐ์ ์๋ ๊ฒ์ด๊ณ , ์คํ๋ ค ๋ถ๋ฆฌํจ์ผ๋ก์ ๋ณต์ก๋๊ฐ ์ฆ๊ฐํ ๊ฒ์ด ์ฌ์ค์ด๋๊น...
์์ผ๋ก๋ ๊ฐ๋ฐํ๋ฉด์ ์ด๋ ๊ฒ ๋ถ๋ฆฌํ๋ ๊ฒฝ์ฐ๋ ๊ฑฐ์ ์์ง ์์๊น ์ถ๋ค. ๋ ๋ค์ ์ํฐํฐ ๊ฐ์ฒด์ ๋ํด์ ๊ณ ๋ คํด๋ณผ ์ ์๋ ์๊ฐ์ด์๋ค ใ ใ
๐ก ์ธํฐํ์ด์ค ํ์ฉํ๊ธฐ
๋ ํ ๊ฐ์ง ํน์ดํ ์ ์ ์ธํฐํ์ด์ค๋ก ํ์ด๋๊ฐ ์ ์ธ๋ฐ, ์ฐ์ ์ฌ์ฉ์์ ๋น๋ฐ๋ฒํธ๋ถํฐ ๋ณด์.
public interface MemberPassword {
String getPassword();
}
์์ ๊ฐ์ด ์ฌ์ฉ์์ ๋น๋ฐ๋ฒํธ์ ๋ํ ์ธํฐํ์ด์ค๋ฅผ ์ ์ธํด๋์๋ค.
์ด๋, ์ฌ์ฉ์๊ฐ ํ์๊ฐ์ ์ ์ ๋ ฅํ๋ ๋น๋ฐ๋ฒํธ์ ๋ํ ์ ์ฝ ์กฐ๊ฑด์ ์ํด ์์ฑ์์์ ๊ฐ์ฒด๋ฅผ ๋ง๋ค ๋ ๊ฒ์ฆํ๊ฒ ๋๋๋ฐ, DB์์ ์กฐํํด์จ ๊ฐ (์ํธํ ์ ์ฉ)์ผ๋ก ๋๋ฉ์ธ ๊ฐ์ฒด๋ฅผ ๋ง๋ค๊ฒ ๋๋ฉด ์ ์ฝ ์กฐ๊ฑด์ด ํ์์์ผ๋, ๋ณ๋์ ๋ฉ์๋๋ฅผ ๋ง๋๋ ๊ฒ ๊ฝค๋ ๋ฒ๊ฑฐ๋กญ๋ค๊ณ ๋๊ปด์ก๊ธฐ ๋๋ฌธ์ด๋ค.
๋ํ, ์ํธํ๊ฐ ๋์ด ์๋ ๋น๋ฐ๋ฒํธ์ ์ํธํ๊ฐ ๋์ง ์์ ๋น๋ฐ๋ฒํธ๊ฐ ํ๋์ ๊ฐ์ฒด๋ก์ ํํ๋๋ ๊ฒ 2๊ฐ์ ์ฑ ์์ ๊ฐ์ง๊ณ ์๋ค๊ณ ๋๊ปด์ก๊ธฐ ๋๋ฌธ์, ์ํ๊ฐ ๋ค๋ฅธ ๊ฐ์ฒด๋ก์ ํํํ๋ ๊ฒ ์ข๋ค๊ณ ์๊ฐํ๋ค.
๊ทธ๋์ ์๋์ ๊ฐ์ด ๊ฐ๊ฐ์ ๋ํ ๊ตฌํ์ฒด๋ฅผ ๋ถ๋ฆฌํ์๋ค.
public class NaturalPassword implements MemberPassword {
private final String password;
...
public static NaturalPassword create(final String password) {
validatePassword(password);
return new NaturalPassword(password);
}
private static void validatePassword(final String password) {
if (password.length() < PASSWORD_MIN_LENGTH || password.length() > PASSWORD_MAX_LENGTH) {
throw new BadRequestException(MEMBER_PASSWORD_LENGTH);
}
}
}
์ด๊ฑฐ๋ ์ฌ์ฉ์์ ์ ๋ ฅ์ผ๋ก ๋ค์ด์จ ๋น๋ฐ๋ฒํธ ๊ฐ์ฒด์ด๋ค. validatePassword ๋ฉ์๋๋ฅผ ํตํด ๊ธธ์ด๋ฅผ ๊ฒ์ฆํ์๋ค.
public class EncryptedPassword implements MemberPassword {
private final String password;
...
public static EncryptedPassword create(final String password) {
return new EncryptedPassword(password);
}
}
๋ฐ๋ฉด์, ์ํธํ๋ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ด๋ ๊ฐ์ฒด์ด๋ค. ๋ณ๋์ ๊ฒ์ฆ ์์ด ๋ฐ๋ก ๊ฐ์ฒด๋ฅผ ์์ฑํ๋๋ก ๋ง๋ค์๋ค.
์ฐธ๊ณ ๋ก, ๋น๋ฐ๋ฒํธ ์ํธํ์ ๊ฒฝ์ฐ SHA256์ ํตํด์ ์งํํ์๋ค. (ํ์ฌ ์ฐ๋ฆฌ ๊ตฌ์กฐ์ ์๋ณธ ๋น๋ฐ๋ฒํธ๋ฅผ ์ ์ด์ ๊ฐ ์๊ธฐ ๋๋ฌธ์ ๋ณตํธํ๊ฐ ์ ๋๋ ์๊ณ ๋ฆฌ์ฆ์ ์ฑํํ์๋ค.)
public static String encrypt(final String target) {
try {
final MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(target.getBytes());
return bytesToHex(md.digest());
} catch (NoSuchAlgorithmException ignored) {
throw new RuntimeException("์ํธํ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์์ต๋๋ค.");
}
}
์ถ๊ฐ์ ์ผ๋ก ์ฃผ๋ฌธ ์ญ์ ์ธํฐํ์ด์ค๋ก ๊ตฌํํ์๋๋ฐ, ์ฌ์ฉ์์ ๋น๋ฐ๋ฒํธ์ ๊ฐ์ ์ด์ ๋ก ์ฟ ํฐ์ ์ฌ์ฉํ ์ฃผ๋ฌธ๊ณผ, ์ฟ ํฐ์ ์ฌ์ฉํ์ง ์์ ์ฃผ๋ฌธ์ผ๋ก ๋ถ๋ฆฌํ์๋ค.
public class CouponOrder implements Order {
...
private final Coupon coupon;
// ์ด ์ฃผ๋ฌธ ๊ธ์ก์ ๊ณ์ฐํ๋ ๋ฉ์๋
private BigDecimal calculateTotalOrderPrice() {
return OrderPriceCalculator.calculateTotalOrderPrice(cartItems);
}
// ์ฟ ํฐ์ ์ํด์ ํ ์ธ๋ ๊ธ์ก์ ๊ณ์ฐํ๋ ๋ฉ์๋
private BigDecimal calculateDiscountPrice() {
final int discountRate = coupon.discountRate();
final BigDecimal convertedDiscountRate = BigDecimal.valueOf(
(PERCENTAGE - discountRate) * DECIMAL_CONVERSION);
return totalPrice.multiply(convertedDiscountRate).setScale(0, RoundingMode.DOWN);
}
}
์ฟ ํฐ์ ์ฌ์ฉํ ์ฃผ๋ฌธ์ ์ฟ ํฐ ๋๋ฉ์ธ์ ๋ํ ์์กด ๊ด๊ณ๋ฅผ ์ถ๊ฐ์ ์ผ๋ก ๊ฐ์ง๊ณ ์์ผ๋ฉฐ, ์ฟ ํฐ์ ์ํด ํ ์ธ๋ ๊ธ์ก๋ ํจ๊ป ๊ด๋ฆฌํ๋ค.
๋ค๋ง, ์ฃผ๋ฌธ์ ๋ํ ํ๋๊ฐ ๋์ด๋๋ฉด์ ์ธํฐํ์ด์ค์ getter๊ฐ ์์ฒญ ๋ง์์ก๋ค๋ ๋จ์ ์ ์๋ค.
์ด์ ๋ํด์ ๋ค๋ฅด๊ฒ ํ์ด๋๊ฐ๊ณ ์ถ์๋๋ฐ, ๋ช ์์ ์ธ ์ฐ๊ด๊ด๊ณ๋ฅผ ์ธํฐํ์ด์ค๋ก ํ์ฑํ์ง ์์๋ค๋ฉด ํด๊ฒฐํ ์ ์์์๊น ์ถ๊ธด ํ๋ค.
์๋๋ฉด, ๊ณตํต๋๋ ํ๋๋ฅผ ๊ด๋ฆฌํ๋ OrderDetail ๊ฐ์ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ ๊ด๋ฆฌํ์ผ๋ฉด ๋ ์ข์์ ๊ฒ ๊ฐ๋ค.
๐ฌ ์์ธ ํด๋์ค ์ค๊ณํ๊ธฐ
์ฌ์ค ์ด์ ๋ถํฐ ๋๋ฅผ ๊ดด๋กญํ๋ ์ฃผ์ ์ค์ ํ๋์๋ค. '์์ธ์ ๊ดํ ํด๋์ค์ ์ด๋ค ์ ๋ณด๊น์ง ๊ฐ์ง๊ณ ์์ด์ผ ํ๋๊ฐ?'
๊ฐ์ธ์ ์ผ๋ก ์๋ฒ์์ ๋์ง๋ ์์ธ ๋ฉ์์ง๋ ์ฌ์ฉ์์๊ฒ ์ถ๋ ฅ๋๋ ์ฉ๋๋ณด๋ค๋, ํด๋ผ์ด์ธํธ ๊ฐ๋ฐ์๊ฐ ํ์ธํ๋ ๋ฉ์์ง๊ณ , ํด๋ผ์ด์ธํธ ๊ฐ๋ฐ์๊ฐ ํด๋น ๋ฉ์์ง์ ๋ฐ๋ผ์ ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ง๋ ๋ฉ์์ง๋ฅผ ๊ด๋ฆฌํด์ผ ํ๋ค๊ณ ์๊ฐํ๋ค.
ํ์ง๋ง ๋ฆฌ๋ทฐ์ด๋์ ์คํ๋ ค 2๋ฒ์ ๋ ์ ํธํ์ จ๋ค.
์๊ฐํด๋ณด๋ฉด ๋ด๊ฐ ์์ง ํฐ ์๋น์ค๋ฅผ ๊ฒฝํํด๋ณด์ง ๋ชปํด์ 1๋ฒ์ ๋ ์ ํธํ๋ ๊ฑด๊ฐ ์ถ๊ธฐ๋ ํ์ง๋ง... ๐
์์ ๊ฐ์ ์ํฉ์ด ์ฃผ์ด์ก์ ๋ ์ฌ์ฉ์์๊ฒ '์ํ ID 1๋ฒ์ด ์กด์ฌํ์ง ์์ต๋๋ค' ๊ฐ์ ๋ฉ์์ง๋ ์ ํฉํ์ง ์๋ค๊ณ ์๊ฐํ๋ค.
์์ ์ ๊ฐ๋ฐํ์ ๋๋ ํด๋ผ์ด์ธํธ์๊ฒ ์๋ฌ ์ฝ๋ ๋ชจ์์ ๋ด๋ ค์ฃผ๋ ์ ์ฉ api๊ฐ ์์ด์, ํด๋น api ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก ํด๋ผ์ด์ธํธ๊ฐ ๋งค์นญ์์ผ์คฌ๋ ๊ธฐ์ต์ด ๋๋ค. ์๋ง ์ด ๋ถ๋ถ์ ํด๋ผ์ด์ธํธ ๊ฐ๋ฐ์๊ณผ ๊ณ์ ํ์ํ๋ฉด์ ๋ฐ๊พธ์ด๋๊ฐ์ง ์์๊น ์ถ๋ค ๐ถ
๋ํ, ์ด๋ฒ์ ์์ฑํ ์์ธ ํด๋์ค์์๋ ์ํ ์ฝ๋๋ฅผ ํจ๊ป ๊ด๋ฆฌํ๋๋ก ๋ง๋ค์๋ค.
public class ForbiddenException extends RuntimeException {
private static final HttpStatus httpStatus = HttpStatus.FORBIDDEN;
private final ErrorCode errorCode;
...
}
๋๋ถ์ ExceptionHandler์์์ ์ฝ๋๊ฐ ์ค์ด์ ๋ง์์ ๋ ๋ค. ์ง๊ธ๊น์ง๋ ์ ์ด๋ฐ ์๊ฐ์ ๋ชป ํ์๊น...! ๐ฅฒ
์์ผ๋ก๋ ์ด๋ฐ ์์ผ๋ก ์์ฑํ์ง ์์๊น ์ถ๋ค. (errorCode๋ ํ์ํ๋ ๊ฑฐ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง ๊ฒ ๊ฐ๋ค!)
๐ฌ ์ฟ ํฐ ๋ฐํ - ๋๋ฉ์ธ ์ด๋ฒคํธ ํ์ฉํ๊ธฐ
์ ์ ๋ง๋์ ํ ์ฝํก ๋ณด๊ณ ์ด๋ฒคํธ ๋ฐฉ์์ ๊ผญ ํ์ฉํด๋ณด๊ณ ์ถ์๋๋ฐ, ์ฟ ํฐ ๋ฐํ์ ๋ํด์ ์ด๋ฒคํธ ๋ฐฉ์์ ์ฌ์ฉํ๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ด ๋ค์ด์ ๋ฐ๋ก ๋์ ํด๋ณด์๋ค. ๊ฐ์ธ์ ์ผ๋ก ๋งค์ฐ ๋ฟ๋ฏํ๋ค... ๐ถ
๋๋ฉ์ธ ์ด๋ฒคํธ๋ฅผ ํ์ฉํ ๋ฐฐ๊ฒฝ์ ๋ค์๊ณผ ๊ฐ๋ค.
1. ์ฟ ํฐ ๋ฐํ์ ๊ฒฝ์ฐ ๋ค์ํ ์๋น์ค์์ ํ์ฉํ ์ ์๋ ๋น์ฆ๋์ค ๋ก์ง์ด๋ค.
๋ค๋ฅธ ์๋น์ค์์ ์ฌ์ฉํ ๋๋ง๋ค ์ฟ ํฐ์ ๋ํ ์์กด ๊ด๊ณ๊ฐ ๋ป์ด๋๊ฐ๋ ๊ฒ๋ณด๋ค, ์ด๋ฒคํธ ๋ฐฉ์์ ํตํด ์์กด์ฑ์ ์ญ์ ์ํค๋ ๊ฒ ๋ ๊น๋ํ ๊ฒ ๊ฐ๋ค๊ณ ์๊ฐํ๋ค.
2. ํ์๊ฐ์ ๊ณผ ์ฟ ํฐ ๋ฐํ์ด ํ๋์ ํธ๋์ญ์ ์ผ๋ก ๋ฌถ์ฌ์์ ๋, ์ฟ ํฐ ๋ฐํ ์ค์ ์์ธ๊ฐ ๋ฐ์ํ๋ค๋ฉด ํ์๊ฐ์ ๋ก์ง๊น์ง ๋กค๋ฐฑ์ด ๋ ๊ฒ์ด๋ค. ์ฌ์ฉ์์ ์ ์ฅ์์ ํ์๊ฐ์ ์ ๊ต์ฅํ ๋ฒ๊ฑฐ๋ก์ด ์์ ์ด๊ณ , ์ฌ์ฉ์๊ฐ ๊ฐ์ ํ๋ ์์ ์์๋ ์ฟ ํฐ์ด ๋ฐํ๋๋ค๋ ๊ฒ์ ์์ง ๋ชปํ๊ธฐ ๋๋ฌธ์ ์ฟ ํฐ ๋๋ฌธ์ ํ์๊ฐ์ ์์ฒด๊ฐ ์คํจํด๋ฒ๋ฆฌ๋ฉด ๋ค์ ๊ฐ์ ํ์ง ์์ ๊ฒ์ด๋ผ๋ ์๊ฐ์ด ๋ค์๋ค. ์ด๋ฅผ ์ํด ๊ฐ์ ๋ก์ง๊ณผ ์ฟ ํฐ ๋ฐํ ๋ก์ง์ ๋ํ ํธ๋์ญ์ ์ ๋ถ๋ฆฌํ ํ์์ฑ์ ๋๊ผ๋ค.
๊ทธ๋์, ์๋์ ๊ฐ์ด ํ์ ๊ฐ์ ์ ์ฟ ํฐ ๋ฐํ์ ์งํํ๋๋ก ์ด๋ฒคํธ๋ฅผ ๋ฐํํ์๋ค.
@Transactional
public long save(final MemberJoinRequest memberJoinRequest) {
if (memberRepository.existByName(memberJoinRequest.getName())) {
throw new BadRequestException(ErrorCode.MEMBER_DUPLICATE_NAME);
}
final Member member = convertMember(memberJoinRequest);
final long savedMemberId = memberRepository.insert(member);
applicationEventPublisher.publishEvent(new JoinMemberCouponEvent(savedMemberId));
return savedMemberId;
}
๊ทธ๋ฆฌ๊ณ , ์ฟ ํฐ ์ ์ฅ ๋ก์ง์ ์๋์ ๊ฐ์ด ์งํํ์๋ค.
@TransactionalEventListener
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveJoinMemberCoupon(final JoinMemberCouponEvent joinMemberCouponEvent) {
final Long memberId = joinMemberCouponEvent.getMemberId();
final Coupon coupon = couponRepository.findByNameAndDiscountRate(JOIN_MEMBER_COUPON.getName(),
JOIN_MEMBER_COUPON.getDiscountRate());
final LocalDateTime issuedAt = LocalDateTime.now();
validateAlreadyIssued(memberId, coupon.couponId());
final MemberCoupon memberCoupon = convertMemberCoupon(coupon, issuedAt);
memberCouponRepository.save(memberId, memberCoupon);
}
๋ก์ง ์์ฒด๋ ์ฟ ํฐ์ ์กฐํํด์ ์ด๋ฏธ ๋ฐ์ํ๋์ง ํ์ธํ๊ณ ์ ์ฅํ๋ค.
๐ก @TransactionalEventListener๋?
- phase ์ต์ ์ ํตํด์ ํธ๋์ญ์ ์ ๋ฐ๋ฅธ ์ด๋ฒคํธ ์ฒ๋ฆฌ ๋ฐฉ๋ฒ์ ์ง์ํ๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก๋ 'AFTER_COMMIT'์ผ๋ก ์ค์ ๋์ด ์๊ธฐ ๋๋ฌธ์ ํธ๋์ญ์ ์ปค๋ฐ ์ ์ด๋ฒคํธ๋ฅผ ์คํํ๋ค.
ํ์ง๋ง, ์ฌ๊ธฐ์ @Transactional(propagation = Propagation.REQUIRES_NEW)๋ผ๋ ์ต์ ์ด ๋์ ๋ ๊ฒ์ด๋ค.
๊ธฐ์กด์๋ ์ ํ ์์ฑ์ ์ค์ ํ์ง ์์์๋๋ฐ, ๋ฆฌ๋ทฐ์ด๋๊ป ๋ค์๊ณผ ๊ฐ์ ํผ๋๋ฐฑ์ ๋ฐ์๋ค.
์ ๊ทธ๋ด๊น ๊ณ ๋ฏผ์ ํ์๋๋ฐ, ๋๋ ์๋์ ๊ฐ์ด ์ดํดํ๋ค.
1. JdbcTemplate์ ๊ฒฝ์ฐ @transactional ์ด๋ ธํ ์ด์ ์ด ์์ด๋ ๊ธฐ๋ณธ์ ์ผ๋ก jdbcTemplate.update๋ query() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ ์์ ์์ DB์ ์ฟผ๋ฆฌ๊ฐ ๋ ๋ผ๊ฐ๋ค. (JPA์ ๋ค๋ฅธ ์ )
2. ์์ธ๊ฐ ๋ฐ์ํ์ ๋ ๋กค๋ฐฑ์ด ์ผ์ด๋๊ธฐ ์ํด์๋ @transactional์ ํตํด์ ๋ฌถ์ด์ค์ผ ํ๋ค. ์๋๋ฉด JdbcTemplate์์ ์ด๋ฏธ save๋ฅผ ์งํํ๊ธฐ ๋๋ฌธ์ ์ด์ ๋ํด ๋กค๋ฐฑํ ์ ์๋ค.
3. ๊ธฐ๋ณธ์ ์ผ๋ก @TransactionalEventListener๋ ์์๋ ํธ๋์ญ์ commit ํ์ ๋์ํ๋ค.
4. ํ์ง๋ง, ๋จ์ํ @transactional์ ๋ถ์ด๋ฉด ๊ธฐ์กด ํ์ ๊ฐ์ ์์ ๊ฑธ๋ ค์๋ ํธ๋์ญ์ ์ ์ฐธ์ฌํ๊ฒ ๋๋ค. ๊ทธ๋ฌ๋, ์ปค๋ฐ๋ ํธ๋์ญ์ ์ ๋ํด์๋ ๋ค์ ์ปค๋ฐ์ด ๋ถ๊ฐ๋ฅํ๊ธฐ ๋๋ฌธ์ ๋กค๋ฐฑ์ด๋ , ๋ค๋ฅธ ์ฐ์ฐ์ ์ํํ๋๋ผ๋ ๋ฌด์๋ฏธํ๋ค.
-> ๊ทธ๋์ ๋ง์ฝ JPA ๊ฐ์ด ์ฝ์ , ์์ , ์ญ์ ์ฐ์ฐ์์ @Transcational์ด ํ์ํ ๊ฒฝ์ฐ์๋ค๋ฉด ์ ์ด์ ์ฟผ๋ฆฌ ์์ฒด๊ฐ ๋ ๋ผ๊ฐ์ง ์์์ ๊ฒ์ด๋ค. (๋ฌผ๋ก ๋์ ๊ฐ์ ์ด๋ค)
5. โญ๏ธ ํ์ง๋ง, ์ง๊ธ์ jdbcTemplate์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์ฟผ๋ฆฌ๋ ๋ ๋ผ๊ฐ๋ค. ํ์ง๋ง, ์์ธ ๋ฐ์ ์ ๋กค๋ฐฑ์ด ๋ถ๊ฐ๋ฅํ๋ค.
6. ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๊ธฐ์กด์ ํธ๋์ญ์ ๊ณผ ๋ค๋ฅธ ํธ๋์ญ์ ์ผ๋ก ๋ถ๊ธฐ์์ผ์ ์์ธ ๋ฐ์ ์ ๋กค๋ฐฑ์ด ๋๋๋ก ๋ง๋ ๋ค!
์๋๋ ์ด๋ฏธ ์ปค๋ฐ๋ ํธ๋์ญ์ ์ ์ฐธ์ฌํ๊ธฐ ๋๋ฌธ์ ์์ ์ด๋ ์ญ์ ๋ ๋ถ๊ฐ๋ฅํ์ง๋ง (JPA ๊ฐ์ด ์์์ฑ ์ปจํ ์คํธ๋ฅผ ํตํด์ flush๊ฐ ๋ฐ์ํ๋ ๊ฒฝ์ฐ), jdbcTemplate์ ๊ฒฝ์ฐ ํธ๋์ญ์ ์ด๋ ธํ ์ด์ ์ ์๊ด์์ด ์ฟผ๋ฆฌ๋ฅผ execute ํ๋ ์๊ฐ DB์ ์ฟผ๋ฆฌ๊ฐ ๋ ๋ผ๊ฐ๊ธฐ ๋๋ฌธ์ @Transactional ์ด๋ ธํ ์ด์ ์ด ์์ด๋ ์ ์ฅ ์์ฒด๋ ์ ๋์๋ค.
ํ์ง๋ง, ์์ธ๊ฐ ๋ฐ์ํ์ ๋ '์ด๋ฏธ ์ปค๋ฐ๋ ํธ๋์ญ์ '์ ์ฐธ์ฌํ๊ธฐ ๋๋ฌธ์ ๋กค๋ฐฑ ์ญ์ ๋ถ๊ฐ๋ฅํ๊ฒ ๋ ๊ฒ์ด๋ค.
๊ทธ๋์, ํธ๋์ญ์ ์ ํ ์์ฑ์ ํ์ฉํ์ฌ ๋ณ๋์ ํธ๋์ญ์ ์ผ๋ก ๋ถ๊ธฐํด์ฃผ์๋ค.
ํธ๋์ญ์ ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ํ ์์ฑ์ด 'REQUIRED'๋ก ์ค์ ๋์ด ์๊ธฐ ๋๋ฌธ์, ๊ธฐ์กด ํธ๋์ญ์ ์ด ์์ผ๋ฉด ํด๋น ํธ๋์ญ์ ์ ์ฐธ์ฌํ๋ค. (์์ผ๋ฉด ์๋ก ์์ฑ) ๋ฐ๋ฉด์, REQUIRES_NEW๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ฉด ํญ์ ์๋ก์ด ํธ๋์ญ์ ์์ ์คํ๋๋ค.
๋ ํธ๋์ญ์ ์ด ๋ถ๋ฆฌ๋์๊ธฐ ๋๋ฌธ์ B์ ๋กค๋ฐฑ์ด A์๋ ์ํฅ์ ๋ผ์น์ง ์๊ฒ ๋์์ผ๋ฉฐ, ๋๋ถ์ ์ฟ ํฐ ๋ฐํ ๋์ค ์์ธ๊ฐ ๋ฐ์ํ๋๋ผ๋ ํ์๊ฐ์ ๋ก์ง๊ณผ ๊ด๊ณ ์์ด ์ ๋กค๋ฐฑ๋๋ ๊ฒ์ ํ์ธํ ์ ์์๋ค.
์๋๋ ์ด์ ๋ํ ํ ์คํธ ์ฝ๋์ด๋ค.
ํ์ ๊ฐ์ ์ ์ด๋ฏธ ๋ฐํ๋ ์ฟ ํฐ์ด ์๋ค๋ฉด ์์ธ๊ฐ ๋ฐ์ํ๊ณ , ํด๋น ์ฟ ํฐ์ ๋ํด์ ๋ค์ ๋ฐ๊ธ์ด ๋๋์ง ํ ์คํธํ๋ค.
@Test
@DisplayName("ํ์ ๊ฐ์
์ถํ ์ฟ ํฐ ๋ฐํ ์ ์ด๋ฏธ ๋ฐํ์ด ์๋ฃ๋์๋ค๋ฉด ์์ธ๊ฐ ๋ฐ์ํ๋ค.")
void saveJoinMemberCoupon_already_issued() {
// given
final String ์ ธ๋_์ด๋ฆ = "journey2";
final Long ์ ์ฅ๋_์ ธ๋_์์ด๋ = ์ฌ์ฉ์๋ฅผ_์ ์ฅํ๋ค(์ ธ๋_์ด๋ฆ);
final JoinMemberCouponEvent ํ์_๊ฐ์
_์ฟ ํฐ_๋ฐํ_์ด๋ฒคํธ = new JoinMemberCouponEvent(์ ์ฅ๋_์ ธ๋_์์ด๋);
memberCouponService.saveJoinMemberCoupon(ํ์_๊ฐ์
_์ฟ ํฐ_๋ฐํ_์ด๋ฒคํธ);
// then
assertThatThrownBy(() -> memberCouponService.saveJoinMemberCoupon(ํ์_๊ฐ์
_์ฟ ํฐ_๋ฐํ_์ด๋ฒคํธ))
.isInstanceOf(BadRequestException.class)
.extracting("errorCode")
.isEqualTo(ErrorCode.COUPON_ALREADY_EXIST);
// then
final List<MemberCouponDto> ์ ธ๋_์ฟ ํฐ๋ค = memberCouponDao.findMyCouponsByName(์ ธ๋_์ด๋ฆ);
assertThat(์ ธ๋_์ฟ ํฐ๋ค)
.hasSize(1);
}
์์ธ ๋ฐ์ ํ ์ฟ ํฐ์ ์กฐํํ์ ๋ ์ด๋ฏธ ๋ฐํ๋ 1์ฅ์ ์ฟ ํฐ๋ง ์กด์ฌํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
๐ฌ ์ฃผ๋ฌธ ์ทจ์ ์ ์ฑ ์ค๊ณํ๊ธฐ
์ฌ์ค ์ด๊ฑฐ๋ ํ์ด ํ๋ก๊ทธ๋๋ฐ์ผ๋ก ์งํํ ๊ฑด ์๋๊ณ , ๋ด๊ฐ ๋ฐ๋ก ๋ง๋ค๊ณ ์ถ์ด์ ๋ง๋ api์ด๋ค.
์ฃผ๋ฌธํ ์ง 3์ผ ์ด๋ด๋ผ๋ฉด ์ ์ก, 7์ผ ์ด๋ด๋ผ๋ฉด ๋ฐ์ก์ ๋ฐํํ๋๋ก ๋ง๋ค์๋ค. ๋ฌผ๋ก ๊ทธ ์ด์์ ํ๋ถ ๋ถ๊ฐ๋ฅํ๋ค.
์ฌ์ค ๋ฐฐ์ก ์ ์ฑ ์ด ์๋ค๋ฉด ๋ฐฐ์ก ์ถ๋ฐ ๊ฐ์ด ๊ทธ๋ฐ ๊ฒ๊น์ง ๊ณ ๋ คํ๊ฒ ์ง๋ง... ํ์ฌ ์๋น์ค ํน์ฑ์ ๊ฑฐ๊ธฐ๊น์ง ๋ป์ด๋๊ฐ์ง ์์๊ธฐ ๋๋ฌธ์ ์ด๊ฒ ์ญ์ ๊ฐ๊ฒฐํ๊ฒ ๊ตฌํํ์๋ค. (์ด๋ฒ ๋ฏธ์ ์ ํ๋ฉด์ ์ ๋๋ก ๋ ์ผํ๋ชฐ ์ฌ์ดํธ๋ฅผ ๋ง๋ค์ด๋ณด๊ณ ์ถ๋ค๋ ์๊ฐ์ด ๋ค์๋ค ๐ถ)
// Before
public RefundPolicyComposite refundPolicyComposite(
final RefundPolicy fullRefundPolicy, final RefundPolicy halfRefundPolicy) {
return new RefundPolicyComposite(
List.of(fullRefundPolicy, halfRefundPolicy)
);
}
// After
public RefundPolicyComposite refundPolicyComposite(final List<RefundPolicy> refundPolicies) {
return new RefundPolicyComposite(refundPolicies);
}
์ ์ฑ ๊ด๋ จ ํด๋์ค๋ฅผ ๋น์ผ๋ก ์ฃผ์ ํ ๋ ๊ธฐ์กด์๋ RefundPolicy์ ๋ํด์ ํ๋์ฉ ์ง์ด๋ฃ์๋๋ฐ ๋งค๋ฒ Config๋ฅผ ์์ ํด์ผ ํ๋ค๋ ๋จ์ ์ด ์กด์ฌํด์ ์๋์ ๊ฐ์ด List๋ฅผ ํตํด์ ์ฃผ์ ๋ฐ๋๋ก ๋ณ๊ฒฝํ์๋ค.
์ด๋ ๊ฒ ํ๋ฉด ์ปดํฌ๋ํธ ์ค์บ ์์๋๋ก ๋ฑ๋ก์ด ๋์ด์ (์ฌ์ ์์ผ๋ก ๋ณด์ธ๋ค) Full, Half๊ฐ ๋น์ผ๋ก ์ฃผ์ ๋์๋ค. ์ค์บ ์์์ ๋ฐ๋ฅธ ๋น์ ๋ํ ์์ ๋ณด์ฅ์ด ํ์คํ์ง ์๊ธฐ ๋๋ฌธ์ ์ ์ฑ ์ด ์ ์ฉ ๊ฐ๋ฅํ์ง ํ๋จํ๋ ๋ฉ์๋์ ๋ํด ๊ฒฝ๊ณ๊ฐ์ ์ฒ ์ ํ๊ฒ ๊ฒ์ฆํ๋ ๋ฐฉํฅ์ผ๋ก ๋ณ๊ฒฝํ์๋ค.
// Before - 50% ํ ์ธ ์ 3์ผ ์ด์์ธ์ง๋ง ๊ฒ์ฆ
@Override
public boolean isAvailable(final Order order, final LocalDateTime currentTime) {
return currentTime.isBefore(order.getOrderedAt().plusDays(HALF_REFUND.getDay()));
}
// After - 3์ผ ~ 7์ผ์ธ์ง ๊ฒ์ฆ
@Override
public boolean isAvailable(final Order order, final LocalDateTime currentTime) {
final LocalDateTime orderedAt = order.getOrderedAt();
return currentTime.isAfter(orderedAt.plusDays(FULL_REFUND.getDay())) &&
currentTime.isBefore(orderedAt.plusDays(HALF_REFUND.getDay()));
}
๋ํ, ๊ธ์ก์ ๊ฒฝ์ฐ ์ ๋ฐํ ๊ณ์ฐ์ ํด์ผ ํ๊ธฐ ๋๋ฌธ์ BigDecimal์ ์ฌ์ฉํ๋ ๊ฒ ์ข๋ค๊ณ ๋ค์ด์ ์๋ ๊ฒ ์ ์ฉํ์๋ค.
@Override
public BigDecimal calculatePrice(final BigDecimal price) {
final BigDecimal refundRate = BigDecimal.valueOf((PERCENTAGE - HALF_REFUND_RATE) * DECIMAL_CONVERSION);
return price.multiply(refundRate).setScale(0, RoundingMode.DOWN);
}
setScale(0, RoundingMode.DOWN)์ ํตํด์ ์์์ ์ดํ์ ๊ฐ์ ๋ฒ๋ฆฌ๋๋ก ๋ง๋ค์๋ค. (์๋๋ฉด ๊ฐ์ด ๋๋ฌ์์ง๋ค!)
๐ฌ ํ์ด์ง ์ฟผ๋ฆฌ ์์ฑํ๊ธฐ
์ด ๋ถ๋ถ๋ ํฉ์๋ API๋ ์๋๊ณ , ๊ฐ์ธ ํ์ต์ฉ์ผ๋ก ์์ฑํ๋ค.
ํญ์ JPA๋ก ๋ง๋ค์๋ค๊ฐ ์ฉ ์ฟผ๋ฆฌ๋ก ์์ฑํด๋ณธ ๊ฑด ์ฒ์์ด์๋๋ฐ, ์๊ฐ๋ณด๋ค ์ด๋ ต์ง ์์๋ค.
@GetMapping("/pages")
public ResponseEntity<ProductPageResponse> getProductsByPage(@RequestParam("page") final int page,
@RequestParam("size") final int size) {
return ResponseEntity.ok(productService.getProductsByPage(page, size));
}
์ํ ๋ชฉ๋ก์ ๋ํ ํ์ด์ง API์ด๋ค. ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก ์กฐํํ ํ์ด์ง ๋ฒํธ์ ํ ํ์ด์ง์ ๋ช ๊ฐ์ฉ ๋ฐ์ ๊ฒ์ธ์ง ์ ๋ ฅ๋ฐ์๋ค.
public class ProductPageResponse {
private final long totalPage;
private final List<ProductResponse> productResponse;
...
}
๋ฐํ ๊ฐ์ผ๋ก๋ ์ด ํ์ด์ง ์์ ํด๋น ํ์ด์ง์ ๋ํ ์ํ ์๋ต์ ๋ฐํํ์๋ค.
public List<ProductEntity> getProductsByPage(final int page, final int size) {
final int offset = calculateOffset(page, size);
final String sql = "SELECT id, name, image_url, price, is_deleted FROM product "
+ "ORDER BY id LIMIT ? OFFSET ?";
return jdbcTemplate.query(sql, productEntityRowMapper, size, offset);
}
๋๋ฆ ํต์ฌ์ด ๋๋ ํ์ด์ง ์ฒ๋ฆฌ ์ฟผ๋ฆฌ๋ค.
โญ๏ธ LIMIT์ ๊ฒฝ์ฐ ๊ฒฐ๊ณผ ์ค์์ ๋ช ๊ฐ๋ง ์กฐํํ ๊ฒ์ธ์ง ์ค์ ํ๊ณ , OFFSET์ ์ด๋์๋ถํฐ ๊ฐ์ ธ์ฌ์ง๋ฅผ ๋ํ๋ธ๋ค.
SELECT * FROM table_name LIMIT 10 OFFSET 20;
= 20๋ฒ์งธ (๊ณ์ฐ๋ ์คํ์ ) ํ๋ถํฐ 10๊ฐ (size) ์กฐํ
= 21๋ฒ์งธ ~ 30๋ฒ์งธ ํ๊น์ง ์ถ๋ ฅ
์ฌ๊ธฐ์ offset์ ๊ณ์ฐํ๋ ๋ฉ์๋๋ฅผ ํธ์ถํ๋๋ฐ, ์ด๋ ๋ค์๊ณผ ๊ฐ๋ค.
private int calculateOffset(final int page, final int size) {
if (page == 1) {
return 0;
}
return (page - 1) * size;
}
ํด๋ผ์ด์ธํธ ๊ฐ๋ฐ์์๊ฒ 1๋ถํฐ ๊ฐ์ ๋ฐ๋๋ค๊ณ ๊ฐ์ ํ์๋ค.
1์ด๋ผ๋ฉด ์ฒซ ๋ฒ์งธ ํ์ด์ง๋๊น offset์ 0์, ๊ทธ ์ด์์ด๋ผ๋ฉด (page - 1) * size๋งํผ์ ์ค์ ํด์ฃผ์๋ค.
๋ง์ฝ page = 3, size = 10์ด๋ผ๋ ๊ฐ์ด ๋ค์ด์๋ค๋ฉด 2 * 10 = 20๋ฒ์งธ ํ๋ถํฐ 10๊ฐ๋ฅผ ๊ฐ์ ธ์ค๊ฒ ๋๋ ๊ฒ์ด๋ค.
์ด ํ์ด์ง์ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ด ๊ณ์ฐํ์๋ค.
private long calculateTotalPage(final int size, final long totalProductCount) {
long totalPage = totalProductCount / size;
if (totalProductCount % size > 0) {
totalPage++;
}
return totalPage;
}
์ํ์ ์ด ๊ฐ์์์ ํ ํ์ด์ง์ ์ถ๋ ฅํ ์ํ์ ๊ฐ์๋ฅผ ๋๋ ๊ฐ์ ์ด ํ์ด์ง๋ก ์ค์ ํ์๋ค.
์ด๋, ์ด ๊ฐ์์์ ํ ํ์ด์ง์ ์ถ๋ ฅํ ์ํ์ ๊ฐ์์ ๋๋จธ์ง ๊ฐ์ด 0๋ณด๋ค ํฌ๋ฉด 1์ ์ฆ๊ฐ์์ผ์ฃผ์๋ค.
์ด๋, ์ด 25๊ฐ์ ์ํ์ด ์์ ๋ 10๊ฐ์ฉ ํ ํ์ด์ง์ ์ถ๋ ฅํ๋ค๋ฉด ์ด ํ์ด์ง๋ 3๊ฐ์ด๊ธฐ ๋๋ฌธ์ ๋จ์ ๊ฐ๋ค์ ๋ํด ํ์ด์ง๋ก ์ฒ๋ฆฌํด์ฃผ๋ ค๊ณ ์์ ๊ฐ์ด ์ค์ ํด์ฃผ์๋ค.
์ฌ์ค page๋ size์ ๋ํด์๋ ์ ์ฝ ์กฐ๊ฑด์ ๊ฑธ์ด๋ฌ์ผ ํ๊ณ (0 ์ด์์ด๋ผ๋ ์ง, ์ ํด์ง ๊ฐ์ผ๋ก size๋ฅผ ๋ฐ๋๋ค๋ ์ง...)
๋ฐํ ๊ฐ์๋ ํ์ฌ ํ์ด์ง๋ ์ฌ์ด์ฆ ๊ฐ์ด ์กฐ๊ธ ๋ ๋ค์ํ๊ฒ ๋ด๋ ค์คฌ์ด์ผ ํ๋๋ฐ ๊ฐ์ธ ํ์ต์ฉ์ด๋ผ ๊ฐ๋จํ๊ฒ ๋ง๋ค์๋ค. ใ ใ
๋๋ฆ ์ฌ๋ฐ์๋ ๊ฒฝํ์ด์๋ค!
์ด๋ฒ ๋ฏธ์ ์ ํ์คํ ๊ธฐ์กด ์ฝ๋ ๋ฆฌํฉํฐ๋ง + ์ธํ๋ผ ๊ตฌ์ถ์ ์ ๊ฒฝ์ ๋ง์ด ์จ์ ๊ทธ๋ฐ๊ฐ ํฌ๊ฒ ํ๊ณ ํ ๋ด์ฉ์ด ์์๋ค.
ํ์ง๋ง ๋ชฉํํ๋ ๋๋ฉ์ธ ์ด๋ฒคํธ๋ ์จ๋ณด๊ณ , ์ฌ๋ฌ ๊ฐ์ง๋ก ๋ฐฐ์ด ์ ์ด ๋ง์์ด์ ๊ทธ๋ฐ๊ฐ ๋ฟ๋ฏํ๋ ์๊ฐ์ด์๋ค.
๋ง์ง๋ง ๋ฏธ์ ์ธ ๋งํผ ์ด์ฌํ ํ๋ ๊ฒ ๊ฐ์์ ์ข๋ค!
๋ ๋ฒจ 2 ํ๊ณ ๋ ์ผ๋ฅธ ์ฐ๊ณ ๋ฐฉํ ์ ๋๊ฒ ๋์์ผ์ง
๐
'์ฐ์ํํ ํฌ์ฝ์ค > ๋ ๋ฒจ 2' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[์ฐํ ์ฝ 5๊ธฐ] ๋ ๋ฒจ 2 ๋ ๋ฒจ ์ธํฐ๋ทฐ ์ ๋ฆฌ ๋ฐ ๊ฐ์ ํ๊ณ (5) | 2023.06.16 |
---|---|
[์ฐํ ์ฝ 5๊ธฐ] ์งํ์ฒ ๋ฏธ์ ํ๊ณ (4) | 2023.05.22 |
[์ฐํ ์ฝ 5๊ธฐ] ์ฅ๋ฐ๊ตฌ๋ ๋ฏธ์ ํ๊ณ (0) | 2023.05.15 |
[์ฐํ ์ฝ 5๊ธฐ] ์น ์๋์ฐจ ๊ฒฝ์ฃผ ๋ฏธ์ ํ๊ณ (2) | 2023.04.20 |