직렬화란 무엇인가?
직렬화(Serialization)는 프로그램 메모리에 존재하는 데이터를 저장하거나 전송 가능한 형태로 변환하는 과정입니다.
프로그램이 실행되는 동안 JVM 메모리에는 다양한 객체들이 생성되고 소멸됩니다. 이러한 객체들은 프로그램이 실행 중일 때만 존재하며, 프로그램이 종료되면 함께 사라집니다. 만약 이 데이터를 파일에 저장하거나, 네트워크를 통해 전송하거나, 다른 프로세스와 공유하려면 메모리 상의 객체를 특정 형식으로 변환해야 합니다.
반대로, 저장되거나 전송된 데이터를 다시 메모리의 객체로 복원하는 과정을 **역직렬화(Deserialization)**라고 합니다.
왜 직렬화가 필요한가?
메모리와 저장소는 서로 다른 언어를 사용한다
다음과 같은 자바 객체가 있다고 가정해봅시다.
public class User {
private Long id;
private String name;
private String email;
private int age;
}
User user = new User(1L, "김철수", "kim@example.com", 25);
이 객체를 Redis에 저장하려고 할 때 문제가 발생합니다. Redis는 자바 객체가 무엇인지 모릅니다. Redis는 문자열이나 바이트 배열만 저장할 수 있는 key-value 저장소이기 때문입니다.
직렬화 없이 객체를 저장하려고 하면 의미 없는 메모리 주소만 저장되어 나중에 원본 객체를 복원할 수 없습니다.
직렬화를 통한 해결
적절한 직렬화 설정을 하면 객체가 JSON 형태로 변환되어 저장됩니다.
{"id":1,"name":"김철수","email":"kim@example.com","age":25}
이제 데이터를 읽어올 때 원본 객체를 정확하게 복원할 수 있습니다.
Spring Boot에서 직렬화의 핵심 원칙
Spring Boot 환경에서 직렬화의 필요 여부를 판단하는 핵심 원칙은 다음과 같습니다.
JVM 메모리를 벗어나는가?
- 메모리 안 → 직렬화 불필요
- 메모리 밖 → 직렬화 필요
하지만 여기에는 중요한 예외가 있습니다.
HTTP 통신을 사용하는가?
- HTTP 통신 → Spring Boot가 자동 처리
- 비HTTP 통신 (Redis, MQ 등) → 직렬화 설정 필요
직렬화 설정이 불필요한 경우
1. 동일한 프로젝트 내부에서의 객체 전달
@Service
public class OrderService {
private final PaymentService paymentService;
public OrderDTO createOrder(OrderRequestDTO request) {
OrderDTO order = new OrderDTO(request);
paymentService.process(order); // 같은 메모리, 직렬화 불필요
return order;
}
}
같은 프로젝트 내에서 Controller ↔ Service, Service ↔ Service 간에 DTO를 주고받을 때는 직렬화가 필요하지 않습니다. 같은 JVM 메모리 내에서 객체의 참조만 전달되기 때문입니다.
2. HTTP 기반 통신
Spring Boot는 HTTP 통신에서 자동으로 직렬화를 처리합니다.
REST API
@RestController
public class UserController {
@GetMapping("/users/{id}")
public UserDTO getUser(@PathVariable Long id) {
return new UserDTO(1L, "김철수", "kim@example.com", 25);
// Spring Boot가 자동으로 JSON 직렬화
}
@PostMapping("/users")
public UserDTO create(@RequestBody UserDTO dto) {
// @RequestBody: JSON을 자동으로 객체로 역직렬화
return userService.create(dto);
}
}
OpenFeign (마이크로서비스 간 통신)
@FeignClient(name = "payment-service", url = "http://payment-server:8081")
public interface PaymentClient {
@PostMapping("/payments")
PaymentDTO process(@RequestBody PaymentDTO request);
}
// 사용
PaymentDTO response = paymentClient.process(request);
// Spring Boot가 자동으로 JSON 직렬화/역직렬화
OpenFeign을 사용하면 완전히 다른 서버에 있는 서비스를 호출하더라도 Spring Boot가 자동으로 JSON 변환을 처리합니다.
RestTemplate / WebClient
@Service
public class ExternalApiService {
private final RestTemplate restTemplate;
public PaymentDTO callPaymentApi(PaymentDTO request) {
return restTemplate.postForObject(
"http://payment-server:8081/payments",
request, // 자동 JSON 직렬화
PaymentDTO.class // 자동 역직렬화
);
}
}
직렬화 설정이 필요한 경우
1. Redis 사용 시
Redis는 별도의 프로세스로 동작하며 Redis 프로토콜을 사용합니다. Spring Boot가 자동으로 처리하지 않기 때문에 명시적인 설정이 필요합니다.
설정
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, UserDTO> redisTemplate(
RedisConnectionFactory connectionFactory) {
RedisTemplate<String, UserDTO> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// JSON 직렬화 설정
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setKeySerializer(new StringRedisSerializer());
return template;
}
}
사용
@Service
@RequiredArgsConstructor
public class UserCacheService {
private final RedisTemplate<String, UserDTO> redisTemplate;
public void cacheUser(UserDTO user) {
redisTemplate.opsForValue().set("user:" + user.getId(), user);
}
public UserDTO getUser(Long userId) {
return redisTemplate.opsForValue().get("user:" + userId);
}
}
2. Spring Session (Redis 세션 저장)
분산 환경에서 세션을 공유하기 위해 Spring Session을 사용할 때도 직렬화 설정이 필요합니다.
@Configuration
@EnableRedisHttpSession
public class RedisSessionConfig {
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
}
빈의 이름이 springSessionDefaultRedisSerializer인 것이 중요합니다. Spring Session이 이 이름으로 빈을 찾아 사용합니다.
@RestController
public class AuthController {
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request,
HttpSession session) {
User user = authService.authenticate(request);
// 세션에 저장 (자동으로 Redis에 직렬화되어 저장)
session.setAttribute("userId", user.getId());
session.setAttribute("username", user.getUsername());
return ResponseEntity.ok("로그인 성공");
}
}
3. RabbitMQ 사용 시
RabbitMQ는 AMQP 프로토콜을 사용하며, 명시적인 직렬화 설정이 필요합니다.
설정
@Configuration
public class RabbitMQConfig {
@Bean
public Jackson2JsonMessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(messageConverter());
return template;
}
}
Producer (메시지 발행)
@Service
@RequiredArgsConstructor
public class OrderProducer {
private final RabbitTemplate rabbitTemplate;
public void sendOrderEvent(OrderEventDTO orderEvent) {
rabbitTemplate.convertAndSend(
"order.exchange",
"order.created",
orderEvent // 자동으로 JSON 직렬화
);
}
}
Consumer (메시지 구독)
@Component
public class OrderConsumer {
@RabbitListener(queues = "order.queue")
public void handleOrderEvent(OrderEventDTO orderEvent) {
// 자동으로 JSON에서 객체로 역직렬화
System.out.println("주문 ID: " + orderEvent.getOrderId());
processOrder(orderEvent);
}
}
4. Kafka 사용 시
@Configuration
public class KafkaProducerConfig {
@Bean
public ProducerFactory<String, OrderDTO> producerFactory() {
Map<String, Object> config = new HashMap<>();
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
return new DefaultKafkaProducerFactory<>(config);
}
}
실전 시나리오: MSA 환경
다음은 3개의 서비스가 협력하는 실제 MSA 환경의 예시입니다.
@Service
@RequiredArgsConstructor
public class OrderService {
private final PaymentClient paymentClient; // Feign
private final InventoryClient inventoryClient; // Feign
private final RedisTemplate<String, OrderDTO> redis; // Redis
private final RabbitTemplate rabbitTemplate; // RabbitMQ
public OrderDTO createOrder(OrderRequestDTO request) {
// 1. 재고 확인 - HTTP 통신 (자동 직렬화)
InventoryDTO inventory = inventoryClient.check(request.getProductId());
if (!inventory.isAvailable()) {
throw new OutOfStockException();
}
// 2. 결제 처리 - HTTP 통신 (자동 직렬화)
PaymentDTO payment = paymentClient.process(
new PaymentDTO(request.getUserId(), request.getAmount())
);
if (!"SUCCESS".equals(payment.getStatus())) {
throw new PaymentFailedException();
}
// 3. 주문 생성
OrderDTO order = OrderDTO.from(request);
// 4. Redis 캐싱 - Redis 프로토콜 (직렬화 설정 필요)
redis.opsForValue().set("order:" + order.getId(), order);
// 5. 이벤트 발행 - AMQP 프로토콜 (직렬화 설정 필요)
rabbitTemplate.convertAndSend("order.exchange", "order.created", order);
return order;
}
}
통신 방식별 직렬화 정리
| 통신 방식 | 프로토콜 | 직렬화 설정 | Spring Boot 자동 처리 |
| Controller ↔ Service | 메모리 참조 | 불필요 | - |
| REST API 응답 | HTTP | 불필요 | O |
| OpenFeign | HTTP | 불필요 | O |
| RestTemplate | HTTP | 불필요 | O |
| WebClient | HTTP | 불필요 | O |
| Redis | Redis 프로토콜 | 필요 | X |
| RabbitMQ | AMQP | 필요 | X |
| Kafka | Kafka 프로토콜 | 필요 | X |
직렬화 설정 체크리스트
직렬화 설정이 필요한지 판단하는 간단한 체크리스트입니다.
Q1. 같은 프로젝트 내부에서만 사용하는가?
- YES → 직렬화 불필요
- NO → Q2로
Q2. HTTP 통신을 사용하는가? (REST API, OpenFeign, RestTemplate 등)
- YES → 직렬화 불필요 (Spring Boot 자동 처리)
- NO → Q3로
Q3. 외부 저장소나 메시지 큐를 사용하는가? (Redis, RabbitMQ, Kafka 등)
- YES → 직렬화 설정 필요
- NO → 직렬화 불필요
JSON 직렬화를 권장하는 이유
직렬화 방식에는 여러 가지가 있지만, 대부분의 경우 JSON 직렬화를 권장합니다.
JDK 직렬화의 단점
- 바이너리 형태로 저장되어 사람이 읽을 수 없음
- 자바 간에만 사용 가능
- 클래스 구조 변경 시 호환성 문제 발생
- 용량이 상대적으로 큼
JSON 직렬화의 장점
- 사람이 읽을 수 있는 텍스트 형태
- 다른 언어로 작성된 서비스와도 호환 가능
- Redis CLI나 메시지 큐 관리 도구에서 데이터 확인 가능
- 상대적으로 작은 용량
결론
Spring Boot 환경에서 직렬화의 필요 여부는 통신 프로토콜에 따라 결정됩니다.
- HTTP 통신: Spring Boot가 자동으로 처리하므로 개발자가 신경 쓸 필요가 없습니다.
- 비HTTP 통신 (Redis, RabbitMQ, Kafka 등): 명시적인 직렬화 설정이 필요합니다.
이 원칙을 이해하면 언제 직렬화 설정을 해야 하는지 명확하게 판단할 수 있습니다. 특히 MSA 환경에서 여러 서비스와 저장소를 다룰 때, 각 통신 방식의 특성을 이해하는 것이 중요합니다.
'Spring > 이론' 카테고리의 다른 글
| DDD에서 AbstractAggregateRoot 이해하기 (0) | 2025.10.30 |
|---|---|
| Redis를 활용한 데이터베이스 캐싱 전략 (0) | 2025.10.23 |
| Entity 식별자 전략: UUID vs Auto Increment vs Snowflake (0) | 2025.10.01 |
| Spring Boot 검증 처리 아키텍처 전략: 컨트롤러에서 BindingResult를 직접 처리하지 말아야 하는 이유 (0) | 2025.09.16 |
| 데이터베이스 인덱스의 구조와 특징 (0) | 2025.06.18 |