| 개요
- RedisTemplate을 사용하여 각 회원들에 대한 장바구니를 저장
- serialize과 deserialize를 String으로 쓰는 StringRedisTemplate을 사용
- Redis에 저장되는 형태
구분 | 데이터 형태 |
key | cart:hash_id |
value | 각 회원의 장바구니 객체를 Json 타입으로 저장 |
| 절차
1. Build.gradle에 Redis 추가
// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
testImplementation('it.ozimov:embedded-redis:0.7.3'){
exclude group: 'org.slf4j', module: 'slf4j-simple'
}
2. application.properties 호스트와 포트 작성
spring.redis.host=localhost
spring.redis.port=6379
3. RedisConfig 작성
@Configuration
@EnableRedisRepositories
public class RedisConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisHost, redisPort);
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(stringRedisSerializer);
return redisTemplate;
}
}
4. Redis에 저장할 장바구니 객체
- 한 회원 당 하나의 장바구니가 저장되고, 각각의 장바구니는 서로 독립적인 개체이다.
- @RedisHash와 @Id는 Redis의 Key를 설정할 때 사용한다.
> 예를 들어, @RedisHash(value = "fund")이고, @Id 로 Long id 가 주어진다고 할 때,
> 아래의 그림과 같이 "fund: 1"로 키값이 설정된다.
> 이렇게되면 Redis에서 "get fund:1" 명령어를 칠 때 해당 값이 추출된다.
- 이번 프로젝트의 경우에는 "cart: 1"과 같이 값이 저장될 예정이다.
@Data
@NoArgsConstructor
@RedisHash("cart")
public class Cart {
@Id
private Long customId;
private List<Product> products = new ArrayList<>();
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class Product{
private Long id;
private Long sellerId;
private String name;
private String description;
private List<ProductItem> items = new ArrayList();
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class ProductItem{
private Long id;
private String name;
private Integer count;
private Integer price;
}
}
5. RedisClient
- RedisTemplate을 유용하게 사용하기 위해서 RedisClient를 썼다.
- 제너릭을 통해서 키와 값을 받으면 이를 redis에 저장하는 방식이다.
- 수업에는 @Service를 사용했는데, redis에 데이터를 저장하고 조회하는 메소드를 사용하기 때문에 이 어노테이션을 사용한 것 같다. 찾아보니, 여기서 한 발짜국 더 나아가면 RedisTemplate를 쓰지 않고 RedisRepository를 사용할 수도 있다고 한다.
※ RedisTemplate 메서드
- 출처 : https://sabarada.tistory.com/105
메서드 | 설명 |
opsForValue | Strings를 쉽게 Serialize / Deserialize 해주는 Interface |
opsForList | List를 쉽게 Serialize / Deserialize 해주는 Interface |
opsForSet | Set를 쉽게 Serialize / Deserialize 해주는 Interface |
opsForZSet | ZSet를 쉽게 Serialize / Deserialize 해주는 Interface |
opsForHash | Hash를 쉽게 Serialize / Deserialize 해주는 Interface |
- RedisTemplate은 각 자료구조에 맞는 메소드를 제공한다.
- String의 경우, opsForValue를 사용한다.
@Service
@RequiredArgsConstructor
@Slf4j
public class RedisClient {
private final RedisTemplate<String, Object> redisTemplate;
private static final ObjectMapper mapper = new ObjectMapper();
public <T> T get(Long key, Class<T> classType) {
return get(key.toString(), classType);
}
private <T> T get(String key, Class<T> classType) {
String redisValue = (String) redisTemplate.opsForValue().get(key);
if (ObjectUtils.isEmpty(redisValue)) {
return null;
} else {
try {
return mapper.readValue(redisValue, classType);
} catch (JsonProcessingException e) {
log.error("Parsing error", e);
return null;
}
}
}
public void put(Long key, Cart cart) {
put(key.toString(), cart);
}
private void put(String key, Cart cart) {
try {
redisTemplate.opsForValue().set(key, mapper.writeValueAsString(cart));
} catch (JsonProcessingException e) {
throw new CustomException(ErrorCode.CART_CHANGE_FAIL);
}
}
}
6. 서비스 클래스 작성
package org.zerobase.cms.order.service;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.zerobase.cms.order.client.RedisClient;
import org.zerobase.cms.order.domain.product.AddProductCartForm;
import org.zerobase.cms.order.domain.redis.Cart;
import org.zerobase.cms.order.domain.redis.Cart.ProductItem;
@Service
@Slf4j
@RequiredArgsConstructor
public class CartService {
private RedisClient redisClient;
public Cart addCart(Long customerId, AddProductCartForm form) {
Cart cart = redisClient.get(customerId, Cart.class);
if (cart == null) {
cart = new Cart();
cart.setCustomerId(customerId);
}
// 이전에 같은 상품이 있는지 확인
Optional<Cart.Product> productOptional = cart.getProducts().stream()
.filter(product1 -> product1.getId().equals(form.getId()))
.findFirst();
if (productOptional.isPresent()) {
Cart.Product redisProduct = productOptional.get();
// requested
List<Cart.ProductItem> items = form.getItems().stream().map(Cart.ProductItem::from).collect(
Collectors.toList());
Map<Long, ProductItem> redisItemMap = redisProduct.getItems().stream().collect(Collectors.toMap(it -> it.getId(), it -> it));
if (!redisProduct.getName().equals(form.getName())) {
cart.addMessage(redisProduct.getName() + "의 정보가 변경이 되었습니다. 확인 부탁드립니다.");
}
for (Cart.ProductItem item : items) {
Cart.ProductItem redisItem = redisItemMap.get(item.getId());
if (redisItem == null) {
// happy case
redisProduct.getItems().add(item);
} else {
if (redisItem.getPrice().equals(item.getPrice())) {
cart.addMessage(redisProduct.getName() + item.getName() + "의 가격이 되었습니다. 확인 부탁드립니다.");
}
redisItem.setCount(redisItem.getCount() + item.getCount());
}
}
} else {
Cart.Product product = Cart.Product.from(form);
cart.getProducts().add(product);
}
redisClient.put(customerId, cart);
return cart;
}
}
[ 출처 & 참고 ]
부트캠프 내용 정리
'Framework > 프로젝트로 스프링 이해하기' 카테고리의 다른 글
[이커머스 프로젝트] QueryDSL를 통해 상품 검색하기 (0) | 2022.12.13 |
---|---|
[이커머스 프로젝트] 이메일 전송 기능 구현 (Mailgun, Feign) (0) | 2022.11.25 |
[이커머스 프로젝트] Swagger2 적용 (0) | 2022.11.25 |
[이커머스 프로젝트] 멀티 모듈 생성하기 (0) | 2022.11.25 |
[이커머스 프로젝트] API Gateway를 활용한 MSA (Micro Service Architecture) (0) | 2022.11.25 |