본문 바로가기
🪲 bugs

`@Cachable` 사용시 SpelEvaluationException 발생

by iirin 2023. 9. 18.

발생 상황

  • Spring data Redis 의존성 주입 후, @Cachable 적용하여 테스트하다가 다음의 에러를 만났습니다.
2023-09-18 10:44:26.783 ERROR 7339 --- [nio-8080-exec-4] c.t.secondhand.global.dto.ErrorResponse  : EL1030E: The operator 'SUBTRACT' is not supported between objects of type 'java.lang.Long' and 'null'
org.springframework.expression.spel.SpelEvaluationException: EL1030E: The operator 'SUBTRACT' is not supported between objects of type 'java.lang.Long' and 'null'
    at org.springframework.expression.spel.ExpressionState.operate(ExpressionState.java:265)
    at org.springframework.expression.spel.ast.OpMinus.getValueInternal(OpMinus.java:144)
    at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:114)
    at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:273)
    at org.springframework.cache.interceptor.CacheOperationExpressionEvaluator.key(CacheOperationExpressionEvaluator.java:104)
    at org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContext.generateKey(CacheAspectSupport.java:795)
    at org.springframework.cache.interceptor.CacheAspectSupport.generateKey(CacheAspectSupport.java:592)
    at org.springframework.cache.interceptor.CacheAspectSupport.findCachedItem(CacheAspectSupport.java:535)
    at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:402)
    at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:345)
    at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:64)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)
    at com.team5.secondhand.api.item.service.ItemReadService$$EnhancerBySpringCGLIB$$5bab2775.getItemList(<generated>)
    at com.team5.secondhand.api.item.controller.v2.ItemFacade.findAllFilteredItems(ItemFacade.java:22)
    at com.team5.secondhand.api.item.controller.v2.ItemControllerV2.getItemList(ItemControllerV2.java:31)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1072)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:529)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:623)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:209)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
    at com.team5.secondhand.global.jwt.config.AuthenticationFilter.doFilterInternal(AuthenticationFilter.java:39)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:481)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.base/java.lang.Thread.run(Thread.java:834)
  • 조금 바꿨더니 발생한 비슷한 오류
    org.springframework.expression.spel.SpelEvaluationException: EL1030E: The operator 'SUBTRACT' is not supported between objects of type 'java.lang.Long' and 'java.util.ArrayList'
      at org.springframework.expression.spel.ExpressionState.operate(ExpressionState.java:265)
      at org.springframework.expression.spel.ast.OpMinus.getValueInternal(OpMinus.java:144)
      at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:114)
      at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:273)
      at org.springframework.cache.interceptor.CacheOperationExpressionEvaluator.key(CacheOperationExpressionEvaluator.java:104)
      at org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContext.generateKey(CacheAspectSupport.java:795)
      at org.springframework.cache.interceptor.CacheAspectSupport.generateKey(CacheAspectSupport.java:592)
      at org.springframework.cache.interceptor.CacheAspectSupport.findCachedItem(CacheAspectSupport.java:535)
      at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:402)
      at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:345)
      at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:64)
      at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
      at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
      at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)
      at com.team5.secondhand.api.item.service.ItemReadService$$EnhancerBySpringCGLIB$$10898061.getItemList(<generated>)
      at com.team5.secondhand.api.item.controller.v2.ItemFacade.findAllFilteredItems(ItemFacade.java:22)
      at com.team5.secondhand.api.item.controller.v2.ItemControllerV2.getItemList(ItemControllerV2.java:31)
      at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
      at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      at java.base/java.lang.reflect.Method.invoke(Method.java:566)
      at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
      at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
      at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
      at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
      at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
      at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
      at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1072)
      at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965)
      at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
      at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
      at javax.servlet.http.HttpServlet.service(HttpServlet.java:529)
      at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
      at javax.servlet.http.HttpServlet.service(HttpServlet.java:623)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:209)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
      at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
      at com.team5.secondhand.global.jwt.config.AuthenticationFilter.doFilterInternal(AuthenticationFilter.java:39)
      at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
      at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
      at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
      at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
      at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
      at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
      at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
      at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
      at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
      at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:481)
      at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130)
      at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
      at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
      at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
      at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390)
      at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
      at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926)
      at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791)
      at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
      at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
      at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
      at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
      at java.base/java.lang.Thread.run(Thread.java:834)

발생 원인

  • 비교적 가벼운 오류로, 간단하게 말하자면 어노테이션 안에 작성한 SpEL 문법에 오류가 있기 때문이었습니다.
  • SpEL은 다음의 문서를 참조하였습니다. 참고링크
@Cacheable(value = "items", key = "#region.id-#itemsRequest.last") // 이부분이 문제입니다.
    @Transactional(readOnly = true)
    public ItemsResponse getItemList(ItemsRequest itemsRequest, Region region, MemberDetails loginMember) {
        Pageable pageable = PageRequest.ofSize(PAGE_SIZE);
        Slice<Item> pageResult = itemRepository.findAllByIdAndRegion(itemsRequest.getLast(), itemsRequest.getCategoryId(), itemsRequest.getSellerId(), Status.isSales(itemsRequest.getIsSales()), region.getId(), pageable);

        // 생략
    }
  • last가 null인경우가 있는데 그부분 처리를 안해주었습니다.
  • 가운데 문자열로 '-'를 넣어서 구분자를 쓰고 싶었지만 사칙연산 중 -로 처리하고 있었기 때문에 발생한 오류였습니다.

대처

  • 문서와 GPT의 도움을 받아 다음과 같이 수정해주었습니다.
    @Cacheable(value = "items", key = "#region.id+'-'+#request.page")

Redis에는 캐시가 잘 저장되었습니다.


Refs.