강의

멘토링

커뮤니티

인프런 커뮤니티 질문&답변

시그님의 프로필 이미지
시그

작성한 질문수

스프링부트를 이용한 웹 프로그래밍: 웹사이트 이렇게 만드는 거예요!

일정 삭제 시 예외 발생 질문과 이전 질문에 대한 추가질문드립니다.

작성

·

28

0

// 컨트롤러 메서드
@DeleteMapping("/plan/{no}")
public ResponseEntity<Map<String, Object>> removePlan(@PathVariable(value = "no") int no){

  Map<String, Object> resultMap = plannerService.removePlan(no);

  return ResponseEntity.ok(resultMap);
}

// 서비스 메서드
  @Transactional
  public Map<String, Object> removePlan(int no) {
    System.out.println("no :" + no);
    log.info("removePlan");
    Map<String, Object> resultMap = new HashMap<>();
    int result = plannerRepository.deleteByPlanNo(no);
    if(result > PLAN_DELETE_FAIL){
      log.info("REMOVE PLAN SUCCESS");
    }else{
      log.info("REMOVE PLAN FAIL");
    }
    resultMap.put("result", result);
    return resultMap;
  }

// 레퍼지토리 메서드
  int deleteByPlanNo(int planNo);  // 삭제된 행의 개수

첫번째 질문


위 코드처럼 작성을 하고 delete 버튼을 클릭하면
2025-12-21 20:36:32 ERROR [[dispatcherServlet]] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.ClassCastException: class com.office.calendar.planner.jpa.PlannerEntity cannot be cast to class java.lang.Integer (com.office.calendar.planner.jpa.PlannerEntity is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @34790124; java.lang.Integer is in module java.base of loader 'bootstrap')] with root cause

java.lang.ClassCastException: class com.office.calendar.planner.jpa.PlannerEntity cannot be cast to class java.lang.Integer (com.office.calendar.planner.jpa.PlannerEntity is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @34790124; java.lang.Integer is in module java.base of loader 'bootstrap')

at jdk.proxy4/jdk.proxy4.$Proxy153.deleteByPlanNo(Unknown Source) ~[?:?]

at com.office.calendar.planner.PlannerService.removePlan(PlannerService.java:124) ~[main/:?]

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?]

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]

at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[?:?]

at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:360) ~[spring-aop-6.2.11.jar:6.2.11]

at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) ~[spring-aop-6.2.11.jar:6.2.11]

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-6.2.11.jar:6.2.11]

at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) ~[spring-tx-6.2.11.jar:6.2.11]

at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.2.11.jar:6.2.11]

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.11.jar:6.2.11]

at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) ~[spring-aop-6.2.11.jar:6.2.11]

at com.office.calendar.planner.PlannerService$$SpringCGLIB$$0.removePlan(<generated>) ~[main/:?]

at com.office.calendar.planner.PlannerController.removePlan(PlannerController.java:123) ~[main/:?]

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?]

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]

at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[?:?]

at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) ~[spring-web-6.2.11.jar:6.2.11]

at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) ~[spring-web-6.2.11.jar:6.2.11]

at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.2.11.jar:6.2.11]

at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:991) ~[spring-webmvc-6.2.11.jar:6.2.11]

at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:896) ~[spring-webmvc-6.2.11.jar:6.2.11]

at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.2.11.jar:6.2.11]

at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) ~[spring-webmvc-6.2.11.jar:6.2.11]

at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) ~[spring-webmvc-6.2.11.jar:6.2.11]

at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.2.11.jar:6.2.11]

at org.springframework.web.servlet.FrameworkServlet.doDelete(FrameworkServlet.java:936) ~[spring-webmvc-6.2.11.jar:6.2.11]

at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:596) ~[tomcat-embed-core-10.1.46.jar:6.0]

at

...
위 예외가 발생하면서
planner_fetch.js의 fetchRemovePlan 함수에서 catch 문에 걸리면서

alert('일정 삭제 중 오류가 발생했습니다.'); 이 실행됩니다.

서비스에서 deleteByPlanNo를 호출할 때 문제가 발생한 것 같은데

찾아보니 레퍼지토리 메서드에 @Modifying을 붙여보라고 해서 해봤는데 해결은 안됐습니다.

어떤 원인으로 위 같은 예외가 발생한거고, 어떻게 해결할 수 있는지 질문드립니다.

 

두번째 질문

 

이전 질문에서
<script src="planner_fetch.js"></script>

<script src="planner.js"></script>

위 코드는 브라우저에서 다음과 같이 처리됩니다.

1. planner_fetch.js를 읽고 실행

2. 그 다음 planner.js를 읽고 실행

3. 이 두 파일은 각각 다른 파일이지만, 브라우저에서는 하나의 JavaScript 실행 공간에서 실행

결과적으로 파일이 나뉘어 있어도 "같은 공간"에서 실행되기 때문에 서로의 전역 변수, 함수 등을 공유할 수 있습니다.

하나 더 중요한 사실이 있는데요, 로딩 순서가 중요합니다.

<script src="planner_fetch.js"></script>

<script src="planner.js"></script>

위와 같은 순서라면, planner_fetch.js가 먼저 실행되고 planner.js에서 planner_fetch.js 안의 함수를 사용할 수 있습니다.

하지만, 로딩 순서를 변경하면

<script src="planner.js"></script>

<script src="planner_fetch.js"></script>

다음과 같은 에러가 발생할 수 있습니다.

fetchPlans(); // 에러 (아직 정의되지 않음)

즉, 먼저 로드 된 파일의 함수만 사용할 수 있습니다.
이렇게 답변을 주셨는데

추가적으로 궁금한 점은

planner.js가 나중에 로드 되어서 planner.js에 있는 함수나 변수를 planner_fetch.js에서 사용하면 정의 되지 않아서 사용을 못해야 할텐데 코드에서는 planner.js에 있는 showDetailPlanView 함수라든지 current_year같은 변수를 사용하고 있는데 에러가 발생하지 않습니다. 이 부분 질문드립니다.

답변 2

0

블스님의 프로필 이미지
블스
지식공유자

시그님 안녕하세요. 메리크리스마스요~~~^^

:: 첫 번째 질문 답변 :::::::::::::::::::::::::::::::

에러 로그를 보면

java.lang.ClassCastException: class com.office.calendar.planner.jpa.PlannerEntity cannot be cast to class java.lang.Integer

이걸로 봐서는 "PlannerEntity를 Integer로 캐스팅"하려다 ClassCastException이 발생한 걸로 보입니다.

즉, deleteByPlanNo()의 실행 결과가 PlannerEntity인데, 메서드 선언은 int(삭제된 행 수) 로 되어 있어 발생한 듯 합니다.

PlannerRepository.java에서 int deleteByPlanNo(int planNo);는 엔티티를 삭제하는데요, 이때 삭제하기 위해서 우선 내부적으로 조회 업무를 먼저 수행합니다.

그리고 JPA(Hibernate)버전에 따라서 반환되는 값을 삭제된 행 수(int)로 반환하는 경우가 있는가 하면, 때로는 삭제하기 위해서 조회 된 PlannerEntity를 반환하기도 합니다.

우선 확인해 볼 수 있는 것은 build.gradle 파일을 열어서 Spring Boot 버전을 확인해 보세요. 저는 3.4.3입니다.

-- build.gradle --------------------------------

id 'org.springframework.boot' version '3.4.3'

------------------------------------------------

이렇게 하면 Hibernate(JPA 구현체) 버전은 자동으로 설정되는데요, 이는 애플리케이션 실행 로그로 확인할 수 있습니다.

-- build.gradle ---------------------------------------------------------

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

--------------------------------------------------------------------------

애플리케이션을 실행 후 로그를 확인하면 다음과 같은 버전 정보가 있을 거에요.

-- 애플리케이션 실행 로그 -----------------------------------------

[Version] - HHH000412: Hibernate ORM core version 6.6.8.Final

----------------------------------------------------------------

Spring Boot 버전에 차이가 있다면 저와 동일하게 3.4.3으로 변경 후 다시 plan을 삭제해 보세요.

(버전을 변경했다면 "Sync All Gradle Projects" 잊지마시구요.)

 

다른 방법은 @Modifying와 JPQL을 이용하는 방법인데요, PlannerRepository의 deleteByPlanNo()메서드를 다음과 같이 수정합니다.

-- PlannerRepository --------------------------------------------

@Modifying

@Query("delete from PlannerEntity p where p.planNo = :planNo")

int deleteByPlanNo(int planNo);

-----------------------------------------------------------------

이 코드는 "JPA에게 삭제 방식을 명확하게 지시하는 코드"로 SQL로 해석하면 다음과 같습니다.

DELETE FROM planner WHERE plan_no = ?

즉, "엔티티를 조회해서 지우지 말고, DB에 바로 delete 쿼리를 날려라!" 정도로 해석할 수 있습니다.

 

:: 두 번째 질문 답변 :::::::::::::::::::::::::::::::

아주 좋은 질문이에요.

이는 시그님이 "개념을 이해하려고" 제대로 고민하고 있다는 증거입니다.

에러가 안 나는 이유는 planner_fetch.js가 실행되는 순간에 planner.js의 함수 또는 변수를 "바로 실행 또는 참조"한 게 아니라, 나중에 실행될 함수 안에서 사용했기 때문입니다.

즉 "정의"와 "실행"은 다릅니다.

planner_fetch.js에서 사용하는 showDetailPlanView함수의 경우

fetchGetPlan(no)함수내에서 사용하고 있기 때문에 planner_fetch.js가 로딩되는 순간 바로 실행되는 것이 아니라 나중에 fetchGetPlan(no)함수가 호출되는 시점에 실행됩니다.

current_year변수도 동일한 이유로 fetchGetCurrentMonthPlans()함수가 호출되는 시점에 참조하게 됩니다.

만약 planner_fetch.js 상단에 다음과 같은 코드를 넣게되면

-- planner_fetch.js --------------------------------------------

console.logshowDetailPlanView: ${showDetailPlanView});

console.logcurrent_year: ${current_year});

----------------------------------------------------------------

이는 에러가 발생합니다.

이유는 planner_fetch.js가 로딩되는 순간 showDetailPlanView와 current_year가 바로 실행되기 때문입니다.

이제 다시 "정의"와 "실행"에 대해서 "정의가 됐다고 바로 실행되는 것은 아니다."를 생각해 보시면 확실하게 이해될 수 있을 거에요.

 

크리스마스 시즌에도 코드를 보고 있다는 것 자체가 한 발 앞서 가고 있다는 증거입니다.

지금은 에러가 나고, 왜 안 되는지 모르겠고, 같은 내용을 몇 번씩 다시 보는 시간이겠지만 이 시간이 쌓여서 "아, 그때 그거였구나" 하는 순간이 반드시 옵니다.

오늘의 한 줄 코드, 오늘 이해한 하나의 개념이 시그님을 전혀 다른 개발자로 만들어 줄 거에요.
지금 이 시간 자체가 크리스마스 선물이라고 생각하고 열코딩하세요.^^ 응원합니다.~

 

0

안녕하세요, 인프런 AI 인턴입니다.
시그님과 함께 코드를 살펴보겠습니다.

첫 번째 질문에 대한 답변

ClassCastException 오류는 자바 클래스 타입 변환에서 문제가 발생했음을 의미합니다. 여기서 오류는 PlannerEntity 클래스 타입 객체를 Integer로 캐스팅하는 과정에서 발생한 것처럼 보입니다.

plannerRepository.deleteByPlanNo(no); 방법이 호출될 때 문제가 될 수 있는 부분은 다음과 같습니다:

  1. 메서드 반환 타입 확인: deleteByPlanNo 메서드가 int 타입을 반환하도록 정의되어 있지만, 이 메서드가 PlannerEntity 타입 객체를 반환하거나 사용하는 리포지토리 메서드 쿼리가 잘못 설정된 것일 수 있습니다. 이는 JPA 리포지토리 인터페이스가 올바르게 정의되지 않거나, JPQL 쿼리 자체가 잘못될 때 발생할 수 있습니다.

  2. 해결 방법:

    • Repository 인터페이스에 @Query를 사용한 JPQL 이나 네이티브 쿼리를 실행하고 있는 경우, 쿼리가 특정 결과 타입을 명시적으로 요구하는지 확인하세요.
    • JPQL 문법 및 SQL/JPQL 쿼리에서 반환되는 값이 명확한지 다시 확인하세요.

이미 @Modifying을 사용해 보셨다고 하셨는데, @Query와 함께 사용하지 않은 경우 확인이 필요합니다.

@Modifying
@Query("DELETE FROM PlannerEntity p WHERE p.planNo = :planNo")
int deleteByPlanNo(@Param("planNo") int planNo);

두 번째 질문에 대한 추가 설명

자바스크립트는 비동기적으로 동작합니다. 스크립트 파일을 로드할 때 실행 순서가 중요하다는 설명이 맞습니다. 그러나 만약 스크립트 로드 자체가 비동기적으로 이루어지거나, 특정 함수가 전역 스코프에 이미 정의된 경우, 변수나 함수의 참조가 가능한 경우가 있습니다.

기본적으로, 브라우저가 스크립트를 로드하고 실행할 때 전역 스코프에 있는 함수나 변수들이 서로 공유됩니다. planner_fetch.js가 먼저 로드되고, planner.js가 그 이후 로드된다면, 전역으로 정의된 함수나 변수들이 planner_fetch.js 내에서 사용될 수 있습니다.

또한, 만약 JavaScript 관련 번들러나 모듈러 시스템(예: Webpack)을 사용하고 있거나, DOMContentLoaded 이벤트 혹은 window.onload 이벤트로 스크립트를 실행하는 경우, 이러한 동작이 다르게 나타날 수 있습니다.

해당 내용을 이해하시는데 참고하시기 바랍니다.

추가 참고

https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.servlet.spring-mvc

질문이 있으시면 언제든지 말씀해 주세요. 도움될 수 있도록 최선을 다하겠습니다.

시그님의 프로필 이미지
시그

작성한 질문수

질문하기