-
Spring์ ํฌํจํ Java ๊ธฐ๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ์์ **VO(Value Object)**์ **DTO(Data Transfer Object)**๋ ๊ฐ๋ ์ ์ผ๋ก ๋น์ทํด ๋ณด์ผ ์ ์์ง๋ง, ๊ทธ ๋ชฉ์ ๊ณผ ์ฌ์ฉํ๋ ๋งฅ๋ฝ์์ ์ฐจ์ด๊ฐ ์์ต๋๋ค. ์๋์ ๊ฐ๊ฐ์ ์ค๋ช ํ๊ณ ์ฐจ์ด๋ฅผ ์ ๋ฆฌํ ๊ฒ์.
1. VO (Value Object)
- ์๋ฏธ: ๊ฐ ์์ฒด๋ฅผ ํํํ๋ ๊ฐ์ฒด. ๊ฐ์ ๋๋ฑ์ฑ์ ๋น๊ตํ ๋, ๊ฐ์ฒด์ ์ฐธ์กฐ๊ฐ ์๋๋ผ ๊ฐ์ด ๊ฐ์์ง๋ก ํ๋จํฉ๋๋ค.
- ํน์ง:
- **๋ถ๋ณ(Immutable)**ํ๊ฒ ์ค๊ณํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์ ๋๋ค.
equals(),hashCode()๋ฅผ ์ฌ์ ์ํด์ ๊ฐ์ด ๊ฐ์ผ๋ฉด ๋์ผํ ๊ฐ์ฒด๋ก ๊ฐ์ฃผํฉ๋๋ค.- ์ฃผ๋ก ๋๋ฉ์ธ ๋ชจ๋ธ์์ ์ฌ์ฉ๋๋ฉฐ, ํน์ ๊ฐ๋ (์: ์ฃผ์, ๋, ์ด๋ฆ ๋ฑ)์ ์บก์ํํฉ๋๋ค.
public class Address { private final String city; private final String street; public Address(String city, String street) { this.city = city; this.street = street; } // equals, hashCode ๋ฐ๋์ ์ฌ์ ์ }
2. DTO (Data Transfer Object)
- ์๋ฏธ: ๊ณ์ธต ๊ฐ ๋ฐ์ดํฐ ์ ๋ฌ์ ์ํด ์ฌ์ฉํ๋ ๊ฐ์ฒด (์: Controller โ Service โ Repository)
- ํน์ง:
- ๋ถ๋ณ์ผ ํ์ ์์, Getter/Setter ์์
- ๋จ์ํ ๋ฐ์ดํฐ๋ฅผ ์ฎ๊ธฐ๋ ๋ชฉ์ (๋ก์ง์ด ์์ด์ผ ํจ)
- ๋ณดํต ์ ๋ ฅ(Request), **์ถ๋ ฅ(Response)**์ฉ์ผ๋ก ๋ง์ด ์ฐ์
public class UserResponseDto { private String name; private String email; // ๊ธฐ๋ณธ ์์ฑ์, getter/setter }
์ ๋ฆฌ: VO vs DTO
ํญ๋ชฉ VO (Value Object) DTO (Data Transfer Object) ๋ชฉ์ ๊ฐ ์์ฒด๋ฅผ ํํ ๋ฐ์ดํฐ ์ ๋ฌ ๋ถ๋ณ์ฑ ๋ณดํต ๋ถ๋ณ ๋ณดํต ๊ฐ๋ณ ์ฌ์ฉ ์์น ๋๋ฉ์ธ ๋ชจ๋ธ ๊ณ์ธต ๊ฐ ๋ฐ์ดํฐ ์ ๋ฌ equals/hashCode ๊ฐ ๊ธฐ์ค์ผ๋ก ์ฌ์ ์ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ ์ํ์ง ์์ ๋ก์ง ํฌํจ ๊ฐ๋ฅ ์์ (์์ ๋ฐ์ดํฐ๋ง)
์ฌ์ฉ ์์
๊ฐ๋จํ Spring ๊ธฐ๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ์กฐ์์ VO์ DTO์ ์ฐจ์ด๋ฅผ ์ค์ ์์ ์ฝ๋์ ๋๋ค. ์์๋ โ์ฌ์ฉ์(User) ์ ๋ณด ์ฒ๋ฆฌโ๋ฅผ ์ค์ฌ์ผ๋ก ๊ตฌ์ฑํฉ๋๋ค.
CreatedSun, 30 Mar 2025 17:14:05 +0900 -
JPA์ฟผ๋ฆฌ๋ฅผ ๋ก๊ทธ๋ก ์ถ๋ ฅํ๊ธฐ ์ํ yml ์ค์
Spring Boot์์ JPA ์ฟผ๋ฆฌ๋ฅผ ๋ก๊ทธ๋ก ์ถ๋ ฅํ๊ณ ์ถ๋ค๋ฉด
application.ymlํ์ผ์ ๋ค์๊ณผ ๊ฐ์ ์ค์ ์ ์ถ๊ฐํ๋ฉด ๋ฉ๋๋ค:spring: jpa: show-sql: true # SQL ์ฟผ๋ฆฌ๋ฅผ ์ฝ์์ ์ถ๋ ฅ properties: hibernate: format_sql: true # SQL ์ฟผ๋ฆฌ๋ฅผ ๋ณด๊ธฐ ์ข๊ฒ ํฌ๋งท logging: level: org.hibernate.SQL: debug # ์คํ๋๋ SQL ๋ก๊ทธ org.hibernate.type.descriptor.sql.BasicBinder: trace # ๋ฐ์ธ๋ฉ๋๋ ํ๋ผ๋ฏธํฐ ๊ฐ ๋ก๊ทธ์ค๋ช :
show-sql: Hibernate๊ฐ ์คํํ๋ SQL์ ํ์ค ์ถ๋ ฅ์ผ๋ก ๋ณด์ฌ์ค (๊ฐ๋จํ ๋ก๊ทธ).format_sql: SQL ์ฟผ๋ฆฌ๋ฅผ ์ฝ๊ธฐ ์ฝ๊ฒ ์ค๋ฐ๊ฟ ๋ฐ ๋ค์ฌ์ฐ๊ธฐ.logging.level.org.hibernate.SQL: ๋ ์์ธํ SQL ๋ก๊ทธ (๋ก๊น ํ๋ ์์ํฌ ํตํด ์ถ๋ ฅ).logging.level.org.hibernate.type.descriptor.sql.BasicBinder: SQL์ ๋ฐ์ธ๋ฉ๋๋ ํ๋ผ๋ฏธํฐ๊น์ง ์ถ๋ ฅํ๊ณ ์ถ์ ๋ ์ฌ์ฉ.
๐ก ํ๋ผ๋ฏธํฐ ๋ก๊ทธ๋ ๋ฏผ๊ฐ ์ ๋ณด๊ฐ ๋ ธ์ถ๋ ์ ์์ผ๋ ์ด์ ํ๊ฒฝ์์๋ ์ฃผ์ํด์ ์ฌ์ฉํ์ธ์.
CreatedFri, 28 Mar 2025 13:25:03 +0900 -
Spring Cloud Gateway์์ globalcors ์ค์
- ํน์ URI ํจํด์ ๋ํด์๋ง CORS๋ฅผ ํ์ฉํ๊ณ ์ถ๋ค๋ฉด, ์ค์ ํ cors-configurations ํค์ ํจํด์ ์ธ๋ถํํ๋ฉด ๋ฉ๋๋ค.
- ํ์ฌ๋ ‘/**‘๋ก ๋ชจ๋ ๊ฒฝ๋ก์ ๋ํด ํ์ฉํ๊ณ ์์ผ๋ฏ๋ก, ์ด๋ฅผ ์ ํํ๋ ค๋ฉด ์๋ฅผ ๋ค์ด /api/public/** ์ฒ๋ผ ํน์ ๊ฒฝ๋ก๋ก ์ขํ์ฃผ๋ฉด ๋ฉ๋๋ค.
Spring Cloud Gateway์์
globalcors์ค์ ์ ์ฌ์ฉํ ๋ ํน์ URI ํจํด์ ๋ํด์๋ง CORS๋ฅผ ํ์ฉํ๊ณ ์ถ๋ค๋ฉด, ์ค์ ํcors-configurationsํค์ ํจํด์ ์ธ๋ถํํ๋ฉด ๋ฉ๋๋ค. ํ์ฌ๋'/\*\*'๋ก ๋ชจ๋ ๊ฒฝ๋ก์ ๋ํด ํ์ฉํ๊ณ ์์ผ๋ฏ๋ก, ์ด๋ฅผ ์ ํํ๋ ค๋ฉด ์๋ฅผ ๋ค์ด/api/public/**์ฒ๋ผ ํน์ ๊ฒฝ๋ก๋ก ์ขํ์ฃผ๋ฉด ๋ฉ๋๋ค.CreatedFri, 28 Mar 2025 11:41:41 +0900 -
jwt ๊ด๋ จ Tip
Q. access token ์ด ๋ง๋ฃ๋๋ฉด, refresh token์ ํตํด ์ฌ๋ฐํ ํ๋ค. refresh token์ ๋ง๋ฃ ๊ธฐ๊ฐ์ด ์์๊น?
A.
Refresh Token๋ ๋ฐ๋์ ๋ง๋ฃ ๊ธฐ๊ฐ์ด ์์ด์ผ ํฉ๋๋ค.- ์ผ๋ฐ์ ์ผ๋ก๋
Access Token๋ณด๋ค ํจ์ฌ ๊ธด ๋ง๋ฃ ๊ธฐ๊ฐ์ ์ค์ ํ ๋ฟ์ด์์.
1. ์ผ๋ฐ์ ์ธ ๋ง๋ฃ ์๊ฐ ์ค์
- Access Token: ๋ณดํต 15๋ถ ~ 1์๊ฐ
- Refresh Token: ๋ณดํต 7์ผ ~ 30์ผ ๋๋ 90์ผ
2. ์ Refresh Token๋ ๋ง๋ฃ ๊ธฐ๊ฐ์ด ํ์ํ๊ฐ?
- Refresh Token์ด ์ ์ถ๋์์ ๊ฒฝ์ฐ, ๋ฌด์ ํ์ผ๋ก Access Token์ ์ฌ๋ฐ๊ธ๋ฐ์ ์ ์๋ค๋ฉด ๋ณด์์ ์น๋ช ์ ์ ๋๋ค.
- ์ผ์ ๊ธฐ๊ฐ์ด ์ง๋๋ฉด ๋ก๊ทธ์ธ ์ธ์ ์ ์ฌํ์ธํ๊ฑฐ๋ ์ฌ๋ก๊ทธ์ธ์ ์ ๋ํ๋ ๊ฒ์ด ๋ณด์์ ๋ ์์ ํฉ๋๋ค.
3. ์ ์ฅ ์์น๋?
- Refresh Token์ ๋ณดํต ์๋ฒ ์ ์ฅ ๋ฐฉ์ (e.g., Redis)์ ์ ์ฅํ๊ณ , ์ฌ์ฉ์์๊ฒ๋ ์๋ณ์๋ง ์ ๋ฌํ๊ฑฐ๋ ํด๋ผ์ด์ธํธ์ ์ ์ฅํ๋ ๋ณด์์ ์ผ๋ก ์์ ํ ์ ์ฅ์ (e.g., Secure Cookie, LocalStorage X) ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๊ถ์ฅ๋ฉ๋๋ค.
Q. Spring Boot์์์ ์์
A. Spring Boot์์ JWT ๊ธฐ๋ฐ์ผ๋ก Access Token๊ณผ Refresh Token์ ๋ฐ๊ธ ๋ฐ ์ฌ๋ฐ๊ธํ๋ ๊ธฐ๋ณธ์ ์ธ ๊ตฌ์กฐ๋ฅผ ์์
1. ํ ํฐ ์์ฑ ํด๋์ค (JwtProvider)
@Component public class JwtProvider { private final String secretKey = "yourSecretKey"; // ์ ์ ํ ์ํธํ ํ์ private final long accessTokenValidity = 1000L * 60 * 15; // 15๋ถ private final long refreshTokenValidity = 1000L * 60 * 60 * 24 * 14; // 14์ผ public String createAccessToken(String userId) { return createToken(userId, accessTokenValidity); } public String createRefreshToken(String userId) { return createToken(userId, refreshTokenValidity); } private String createToken(String userId, long validity) { Claims claims = Jwts.claims().setSubject(userId); Date now = new Date(); Date expiry = new Date(now.getTime() + validity); return Jwts.builder() .setClaims(claims) .setIssuedAt(now) .setExpiration(expiry) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); } public String getUserIdFromToken(String token) { return Jwts.parser().setSigningKey(secretKey) .parseClaimsJws(token).getBody().getSubject(); } public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); return true; } catch (ExpiredJwtException e) { return false; } } }
2. ๋ก๊ทธ์ธ ์ ํ ํฐ ๋ฐ๊ธ
@PostMapping("/login") public ResponseEntity<TokenResponse> login(@RequestBody LoginRequest request) { // ์ธ์ฆ ์ฒ๋ฆฌ (์๋ต) String userId = "user-id"; String accessToken = jwtProvider.createAccessToken(userId); String refreshToken = jwtProvider.createRefreshToken(userId); // Refresh Token์ DB๋ Redis์ ์ ์ฅ refreshTokenService.save(userId, refreshToken); return ResponseEntity.ok(new TokenResponse(accessToken, refreshToken)); }
3. Access Token ๋ง๋ฃ ์ Refresh Token์ผ๋ก ์ฌ๋ฐ๊ธ
@PostMapping("/token/refresh") public ResponseEntity<TokenResponse> refreshToken(@RequestBody TokenRequest request) { String refreshToken = request.getRefreshToken(); if (!jwtProvider.validateToken(refreshToken)) { throw new RuntimeException("Invalid refresh token"); } String userId = jwtProvider.getUserIdFromToken(refreshToken); // ์ ์ฅ๋ refresh token๊ณผ ๋น๊ต String stored = refreshTokenService.get(userId); if (!stored.equals(refreshToken)) { throw new RuntimeException("Token mismatch"); } String newAccessToken = jwtProvider.createAccessToken(userId); return ResponseEntity.ok(new TokenResponse(newAccessToken, refreshToken)); }
4. Refresh Token ์ ์ฅ ์์ (Redis ๊ธฐ๋ฐ)
@Service public class RefreshTokenService { private final RedisTemplate<String, String> redisTemplate; public RefreshTokenService(RedisTemplate<String, String> redisTemplate) { this.redisTemplate = redisTemplate; } public void save(String userId, String token) { redisTemplate.opsForValue().set(userId, token, 14, TimeUnit.DAYS); // ์ ํจ ๊ธฐ๊ฐ ์ค์ } public String get(String userId) { return redisTemplate.opsForValue().get(userId); } }
Q. ์์์์ RefreshToken ์์ refresh save์ ์ ๊ธฐ์กด๊บผ๋ ์์ง์๋ ๋ ๊น?
A. ์ผ๋ฐ์ ์ผ๋ก๋ ๊ธฐ์กด Refresh Token์ ์ญ์ ํ๊ฑฐ๋ ๋ฎ์ด์จ์ผ ํฉ๋๋ค.
์๋๋ฉด:
CreatedThu, 27 Mar 2025 11:36:45 +0900 - ์ผ๋ฐ์ ์ผ๋ก๋
-
Spring Boot์์ Kafka๋ฅผ ์ฌ์ฉํ ๋ ์ค๋ฅ๋ฅผ ์ฝ๋๋ก ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ์ค๋ช ํด๋ณผ๊ฒ์.
Kafka ํ๋ก๋์์ ์ปจ์๋จธ์์ Fallback ๋ฉ์๋, ์ฌ์๋ (Retry), ์์ธ ์ฒ๋ฆฌ ๋ฑ์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์๋ด ๋๋ค.
1. Kafka ํ๋ก๋์์์ ์ค๋ฅ ์ฒ๋ฆฌ (Fallback, Retry)
Kafka ํ๋ก๋์๋ ๋ฉ์์ง๋ฅผ ๋ธ๋ก์ปค๋ก ๋ณด๋ด๋ ๊ณผ์ ์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด, ๋คํธ์ํฌ ๋ฌธ์ , ๋ธ๋ก์ปค ๋ค์ด, ๋ฉ์์ง ํฌ๊ธฐ ์ด๊ณผ ๋ฑ์ ๋ฌธ์ ๊ฐ ์์ ์ ์๋๋ฐ์, ์ด๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋๋ค.โ (1) Fallback ๋ฉ์๋ ์ ์ฉ (Resilience4j ์ฌ์ฉ)
Resilience4j์
@Retry์@Recover๋ฅผ ํ์ฉํ๋ฉด
Kafka ์ ์ก์ด ์คํจํ์ ๋ ์๋์ผ๋ก ์ฌ์๋๋ฅผ ํ๊ณ , ์ต์ข ์ ์ผ๋ก ์คํจํ๋ฉด ๋์ฒด ๋ก์ง์ ์ํํ ์ ์์ต๋๋ค.CreatedWed, 26 Mar 2025 13:38:03 +0900 -
Apache Kafka ์ฌ์ฉ ์ ๋ฐ์ํ ์ ์๋ ์ฃผ์ ์ค๋ฅ ์ ํ๊ณผ ๊ทธ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ ๋ฆฌํด ๋ณด๊ฒ ์ต๋๋ค.
1. ๋คํธ์ํฌ ๋ฐ ์ฐ๊ฒฐ ์ค๋ฅ
โ ์ค๋ฅ:
Connection to broker <broker_id> failed๐ ์์ธ: ๋ธ๋ก์ปค์์ ๋คํธ์ํฌ ์ฐ๊ฒฐ ๋ฌธ์ , ๋ธ๋ก์ปค ๋ค์ด, ๋ฐฉํ๋ฒฝ ๋ฌธ์ ๋ฑ
โ ํด๊ฒฐ ๋ฐฉ๋ฒ:zookeeper์broker๊ฐ ์คํ ์ค์ธ์ง ํ์ธ (ps aux | grep kafka)listeners๋ฐadvertised.listeners์ค์ ํ์ธ (server.properties)- ๋ฐฉํ๋ฒฝ ์ค์ (
iptables -L,firewalld)์ ์ ๊ฒํ์ฌ ํฌํธ๊ฐ ์ด๋ ค ์๋์ง ํ์ธ
2. ๋ธ๋ก์ปค ๋ฐ ํ ํฝ ๊ด๋ จ ์ค๋ฅ
โ ์ค๋ฅ:
Leader not available for partition <partition_id>๐ ์์ธ:
CreatedWed, 26 Mar 2025 13:30:03 +0900