작성자 : 황지성

Redis 설치 reference

Redis(레디스) | Windows 10 설치 및 기본 명령어

Spring boot 2.7.7 - Redisson

implementation group: 'org.redisson', name: 'redisson-spring-boot-starter', version: '3.17.5'

Redisson 의존성 주입 시 문제 발생

의존성 주입 시, 문제가 생기지 않을 수도 있다고 하는데 나는 문제가 발생했다. Redis에 관련된 의존성을 주입했을 때는 오류가 나지 않지만, Redisson에 관련된 의존성을 주입할 시, 오류가 발생하고 Spring boot가 뜨지 않는다.

해결 방법은 아래와 같다.

main/java/springfox/documentation/spring/web/plugins directory를 생성한다. 해당 디렉토리에 WebMvcRequestHandlerProvider.java 파일을 생성한 후, 아래 코드를 복붙한다.

package springfox.documentation.spring.web.plugins;

import org.redisson.api.FunctionLibrary;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import springfox.documentation.RequestHandler;
import springfox.documentation.spi.service.RequestHandlerProvider;
import springfox.documentation.spring.web.OnServletBasedWebApplication;
import springfox.documentation.spring.web.WebMvcRequestHandler;
import springfox.documentation.spring.web.readers.operation.HandlerMethodResolver;

import javax.servlet.ServletContext;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import static java.util.stream.Collectors.*;
import static springfox.documentation.builders.BuilderDefaults.*;
import static springfox.documentation.spi.service.contexts.Orderings.*;
import static springfox.documentation.spring.web.paths.Paths.*;

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@Conditional(OnServletBasedWebApplication.class)
public class WebMvcRequestHandlerProvider implements RequestHandlerProvider {

    private final List<RequestMappingInfoHandlerMapping> handlerMappings;
    private final HandlerMethodResolver methodResolver;
    private final String contextPath;

    @Autowired
    public WebMvcRequestHandlerProvider(
            Optional<ServletContext> servletContext,
            HandlerMethodResolver methodResolver,
            List<RequestMappingInfoHandlerMapping> handlerMappings) {
        this.handlerMappings = handlerMappings.stream().filter(mapping -> mapping.getPatternParser() == null)
                .collect(Collectors.toList());
        this.methodResolver = methodResolver;
        this.contextPath = servletContext
                .map(ServletContext::getContextPath)
                .orElse(ROOT);
    }

    @Override
    public List<RequestHandler> requestHandlers() {
        returnnullToEmptyList(handlerMappings).stream()
                .filter(requestMappingInfoHandlerMapping ->
                        !("org.springframework.integration.http.inbound.IntegrationRequestMappingHandlerMapping"
                                .equals(requestMappingInfoHandlerMapping.getClass()
                                        .getName())))
                .map(toMappingEntries())
                .flatMap((entries -> StreamSupport.stream(entries.spliterator(), false)))
                .map(toRequestHandler())
                .sorted(byPatternsCondition())
                .collect(toList());
    }

    private Function<RequestMappingInfoHandlerMapping,
                Iterable<Map.Entry<RequestMappingInfo, HandlerMethod>>> toMappingEntries() {
        return input -> input.getHandlerMethods()
                .entrySet();
    }

    private Function<Map.Entry<RequestMappingInfo, HandlerMethod>, RequestHandler> toRequestHandler() {
        return input -> new WebMvcRequestHandler(
                contextPath,
                methodResolver,
                input.getKey(),
                input.getValue());
    }
}

그러면 해결된다.

Redisson을 활용한 동시성 제어 테스트 현황

  1. 1차
@Transactional
public MsgResponseDto enterStore(Long storeId) {        // need to update
    RLock lock = redissonClient.getLock("key 이름");

    int availableCnt   =   0;                                                       // 이용 가능 좌석

    try{
        boolean isLocked = lock.tryLock(1,3, TimeUnit.SECONDS);

        if(isLocked) {

            try {
                // 1. find store
                Store store = storeRepository.findById(storeId).orElseThrow(()->
                        new CustomException(ErrorCode.NOT_FOUND_STORE_ERROR)
                );

                // 2. storeStatus check
                StoreStatus storeStatus = storeStatusRepository.findByStore(store);

                // 3. counting availableCnt
                if((storeStatus.getAvailableTableCnt() - 1) > 0){
                    availableCnt = storeStatus.getAvailableTableCnt() - 1;
                }
                else if(storeStatus.getAvailableTableCnt() == 0){
                    throw new CustomException(ErrorCode.NOT_ENOUGH_TABLE);
                }
                else if((storeStatus.getAvailableTableCnt() - 1) < 0){
                    String message  = Integer.toString(Math.abs(storeStatus.getAvailableTableCnt() - 1));
                    availableCnt    = 0;

                    storeStatus.update(availableCnt);

                    return new MsgResponseDto(HttpStatus.OK.value(),"빈 자석이 없습니다. " + message + "분은 예약 등록 부탁드립니다.");
                }

                // 4. update storeStatus
                storeStatus.update(availableCnt);

                return new MsgResponseDto(SuccessCode.CONFIRM_ENTER);
            }catch(Exception e){

            }finally{
                lock.unlock();
            }

        }
    }catch(Exception e){
        Thread.currentThread().interrupt();
    }

    return null;
}