@
Initial commit: SB 백엔드 (Spring Boot + MyBatis) - 사용자 CRUD REST API (/api/users) - MariaDB(MyBatis) + Redis 캐싱 구성 - CORS 설정 및 헬스 체크 엔드포인트 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> @
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
package com.example.sb_bt;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
@MapperScan("com.example.sb_bt.mapper")
|
||||
public class SbBtApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SbBtApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.example.sb_bt.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* Vue 개발 서버(기본 5173)에서의 호출을 허용하기 위한 CORS 설정.
|
||||
* 운영 환경에서는 allowedOrigins 를 실제 도메인으로 제한하세요.
|
||||
*/
|
||||
@Configuration
|
||||
public class CorsConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/api/**")
|
||||
.allowedOrigins("http://localhost:5173")
|
||||
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
|
||||
.allowedHeaders("*")
|
||||
.allowCredentials(true)
|
||||
.maxAge(3600);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.example.sb_bt.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* RedisTemplate 구성.
|
||||
* - Key : String 직렬화
|
||||
* - Value : JSON 직렬화 (객체 저장 가능)
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
|
||||
StringRedisSerializer stringSerializer = new StringRedisSerializer();
|
||||
GenericJackson2JsonRedisSerializer jsonSerializer =
|
||||
new GenericJackson2JsonRedisSerializer(new ObjectMapper());
|
||||
|
||||
template.setKeySerializer(stringSerializer);
|
||||
template.setHashKeySerializer(stringSerializer);
|
||||
template.setValueSerializer(jsonSerializer);
|
||||
template.setHashValueSerializer(jsonSerializer);
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.example.sb_bt.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class HealthController {
|
||||
|
||||
@GetMapping("/health")
|
||||
public Map<String, Object> health() {
|
||||
return Map.of(
|
||||
"status", "UP",
|
||||
"service", "sb_bt"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.example.sb_bt.controller;
|
||||
|
||||
import com.example.sb_bt.domain.User;
|
||||
import com.example.sb_bt.service.UserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/users")
|
||||
@RequiredArgsConstructor
|
||||
public class UserController {
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
@GetMapping
|
||||
public List<User> list() {
|
||||
return userService.findAll();
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<User> get(@PathVariable Long id) {
|
||||
User user = userService.findById(id);
|
||||
return user == null ? ResponseEntity.notFound().build() : ResponseEntity.ok(user);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<User> create(@RequestBody User user) {
|
||||
User created = userService.create(user);
|
||||
return ResponseEntity.created(URI.create("/api/users/" + created.getId())).body(created);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<User> update(@PathVariable Long id, @RequestBody User user) {
|
||||
return ResponseEntity.ok(userService.update(id, user));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> delete(@PathVariable Long id) {
|
||||
userService.delete(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.example.sb_bt.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 샘플 도메인. users 테이블과 매핑.
|
||||
* Redis 캐싱(JSON 직렬화)을 위해 Serializable 구현.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class User implements Serializable {
|
||||
|
||||
private Long id;
|
||||
private String name;
|
||||
private String email;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.example.sb_bt.mapper;
|
||||
|
||||
import com.example.sb_bt.domain.User;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* MyBatis 매퍼. SQL 은 resources/mapper/UserMapper.xml 에 정의.
|
||||
*/
|
||||
@Mapper
|
||||
public interface UserMapper {
|
||||
|
||||
List<User> findAll();
|
||||
|
||||
User findById(@Param("id") Long id);
|
||||
|
||||
int insert(User user);
|
||||
|
||||
int update(User user);
|
||||
|
||||
int delete(@Param("id") Long id);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.example.sb_bt.service;
|
||||
|
||||
import com.example.sb_bt.domain.User;
|
||||
import com.example.sb_bt.mapper.UserMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 샘플 서비스. 단건 조회 시 Redis 캐시를 사용하는 예시 포함.
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserService {
|
||||
|
||||
private final UserMapper userMapper;
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
private static final String CACHE_KEY_PREFIX = "user:";
|
||||
private static final Duration CACHE_TTL = Duration.ofMinutes(10);
|
||||
|
||||
public List<User> findAll() {
|
||||
return userMapper.findAll();
|
||||
}
|
||||
|
||||
public User findById(Long id) {
|
||||
String key = CACHE_KEY_PREFIX + id;
|
||||
|
||||
Object cached = redisTemplate.opsForValue().get(key);
|
||||
if (cached instanceof User user) {
|
||||
log.debug("[cache hit] {}", key);
|
||||
return user;
|
||||
}
|
||||
|
||||
User user = userMapper.findById(id);
|
||||
if (user != null) {
|
||||
redisTemplate.opsForValue().set(key, user, CACHE_TTL);
|
||||
log.debug("[cache put] {}", key);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public User create(User user) {
|
||||
userMapper.insert(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public User update(Long id, User user) {
|
||||
user.setId(id);
|
||||
userMapper.update(user);
|
||||
redisTemplate.delete(CACHE_KEY_PREFIX + id);
|
||||
return userMapper.findById(id);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void delete(Long id) {
|
||||
userMapper.delete(id);
|
||||
redisTemplate.delete(CACHE_KEY_PREFIX + id);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user