콘텐츠로 이동

2022 10 24

2022-10-24

Spring vs SpringBoot

  • 설명
    • 스프링 부트는 단지 실행만 하면 되는 스프링 기반의 어플리케이션을 쉽게 만들 수 있다
    • 원래 스프링은... xml로 복잡하게 내용들을 다 작성해줘야해
      • 모든 dependency 버전까지 하나하나 정확하게!
  • AutoConfiguration을 활용한 스프링 부트
    • 어플리케이션 개발에 필요한 모든 Dependency를 프레임워크에서 관리
    • jar 파일이 클래스 패스에 있는 경우 스프링 부트는 Dispatcher Servlet으로 자동 구성
    • 스프링 부트는 미리 설정되어있는 starter 프로젝트를 제공
    • SpringBoot-Starter를 제공하여 자동으로 호환되는 버전을 알아서 관리 => 패키지 디펜던시
      • spring-boot-starter-web
      • spring-boot-starter-test
      • spring-boot-starter-data-jpa
      • spring-boot-starter-jdbc
      • spring-boot-starter-security
    • xml 설정 없이 자바 코드를 통해 설정할 수 있음
  • @SpringBootApplication
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
            @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication {
    }
    
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}

SpringApplication.run() => ApplicationContext 생성 및 구동

  1. 일단 psvm에서 본인의 클래스 class를 변수로 두고 정적 메서드로 실행을 시킨다

    • SpringApplication : 자바 메인 메서드에서 부트스트랩 시켜 실행시킬 수 있는 클래스
      • 알맞은 ApplicationContext 객체를 생성
      • CommandLinePropertySource를 통해 CommandLine에 있는 친구들 실행
      • ApplicationContext를 리프레시 하고 모든 Singleton 빈들을 로딩하기
      • 모든 CommandLineRunner 빈들 실행
        public static void main(String[] args) {
            SpringApplication.run(ConnectableApplication.class, args);
        }
        
  2. SpringApplication 클래스 안에서 새로운 SpringApplication 객체 만들고 run!

    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return new SpringApplication(primarySources).run(args);
    }
    

  3. run() 메서드는 다음과 같음

    • ConfigurableApplicationContext 타입의 ApplicationContext 생성하는 과정!
    • AnnotationConfigApplicationContext 타입이 대입되게 됨
    • refreshContext(context)를 통해 각각 구동에 필요한 빈들의 설정들이 적용되고 등록됨
      public ConfigurableApplicationContext run(String... args) {
          long startTime = System.nanoTime();
          DefaultBootstrapContext bootstrapContext = createBootstrapContext();
          ConfigurableApplicationContext context = null;
          configureHeadlessProperty();
          SpringApplicationRunListeners listeners = getRunListeners(args);
          listeners.starting(bootstrapContext, this.mainApplicationClass);
          try {
              ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
              ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
              configureIgnoreBeanInfo(environment);
              Banner printedBanner = printBanner(environment);
              context = createApplicationContext(); // 4번
              context.setApplicationStartup(this.applicationStartup);
              prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
              refreshContext(context); // 5번
              afterRefresh(context, applicationArguments);
              Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
              if (this.logStartupInfo) {
                  new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
              }
              listeners.started(context, timeTakenToStartup);
              callRunners(context, applicationArguments);
          }
          catch (Throwable ex) {
              handleRunFailure(context, ex, listeners);
              throw new IllegalStateException(ex);
          }
          try {
              Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
              listeners.ready(context, timeTakenToReady);
          }
          catch (Throwable ex) {
              handleRunFailure(context, ex, null);
              throw new IllegalStateException(ex);
          }
          return context;
      }
      
  4. createApplicationContext()은 기본으로 AnnotationConfigApplicationContext 방식으로 생성함

    public AnnotationConfigApplicationContext() {
        StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
        this.reader = new AnnotatedBeanDefinitionReader(this);
        createAnnotatedBeanDefReader.end();
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }
    

  5. refreshContext(context)에서 다음 설정들이 다들 적용됨

    • Repository 레이어 초기화 진행
      • Redis, JPA 스캔하면서 설정한 모드에 따라 초기화
    • 톰캣(서블릿 엔진)도 초기화 : 8080 포트로 띄우기
      • 쓰레드 풀도 열어두고!
    • 히카리풀도 초기화 : 데이터베이스 연결하기
    • JPAEntityManagerFactory도 초기화!
    • OSIV도 설정 열어두고...
    • SecurityFilterChain도 등록해주고!
      14:04:44.891 [INFO ] [restartedMain] [o.s.d.r.c.RepositoryConfigurationDelegate] - Multiple Spring Data modules found, entering strict repository configuration mode
      14:04:44.896 [INFO ] [restartedMain] [o.s.d.r.c.RepositoryConfigurationDelegate] - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
      14:04:45.157 [INFO ] [restartedMain] [o.s.d.r.c.RepositoryConfigurationDelegate] - Finished Spring Data repository scanning in 98 ms. Found 0 Redis repository interfaces.
      14:04:50.067 [INFO ] [restartedMain] [o.s.d.r.c.RepositoryConfigurationDelegate] - Multiple Spring Data modules found, entering strict repository configuration mode
      14:04:50.068 [INFO ] [restartedMain] [o.s.d.r.c.RepositoryConfigurationDelegate] - Bootstrapping Spring Data JPA repositories in DEFAULT mode.
      14:04:52.107 [INFO ] [restartedMain] [o.s.d.r.c.RepositoryConfigurationExtensionSupport] - Spring Data JPA - Could not safely identify store assignment for repository candidate interface com.backend.connectable.user.redis.UserTicketEntranceRedisRepository; If you want this repository to be a JPA repository, consider annotating your entities with one of these annotations: javax.persistence.Entity, javax.persistence.MappedSuperclass (preferred), or consider extending one of the following types with your repository: org.springframework.data.jpa.repository.JpaRepository
      14:04:52.764 [INFO ] [restartedMain] [o.s.d.r.c.RepositoryConfigurationDelegate] - Finished Spring Data repository scanning in 2667 ms. Found 7 JPA repository interfaces.
      14:04:52.951 [INFO ] [restartedMain] [o.s.d.r.c.RepositoryConfigurationDelegate] - Multiple Spring Data modules found, entering strict repository configuration mode
      14:04:52.952 [INFO ] [restartedMain] [o.s.d.r.c.RepositoryConfigurationDelegate] - Bootstrapping Spring Data Redis repositories in DEFAULT mode.
      14:05:00.290 [INFO ] [restartedMain] [o.s.cloud.context.scope.GenericScope] - BeanFactory id=2fa6ef28-6c1c-3e6f-a9d4-84a4731e7c62
      14:05:15.633 [INFO ] [restartedMain] [o.s.b.w.e.tomcat.TomcatWebServer] - Tomcat initialized with port(s): 8080 (http)
      14:05:15.811 [INFO ] [restartedMain] [o.a.coyote.http11.Http11NioProtocol] - Initializing ProtocolHandler ["http-nio-8080"]
      14:05:15.813 [INFO ] [restartedMain] [o.a.catalina.core.StandardService] - Starting service [Tomcat]
      14:05:15.814 [INFO ] [restartedMain] [o.a.catalina.core.StandardEngine] - Starting Servlet engine: [Apache Tomcat/9.0.64]
      14:05:18.247 [INFO ] [restartedMain] [o.a.c.c.C.[Tomcat].[localhost].[/]] - Initializing Spring embedded WebApplicationContext
      14:05:18.248 [INFO ] [restartedMain] [o.s.b.w.s.c.ServletWebServerApplicationContext] - Root WebApplicationContext: initialization completed in 54183 ms
      14:05:19.493 [INFO ] [restartedMain] [com.zaxxer.hikari.HikariDataSource] - HikariPool-1 - Starting...
      14:05:21.290 [INFO ] [restartedMain] [com.zaxxer.hikari.HikariDataSource] - HikariPool-1 - Start completed.
      14:05:21.426 [INFO ] [restartedMain] [o.s.b.a.h.H2ConsoleAutoConfiguration] - H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:connectable'
      14:05:25.298 [INFO ] [restartedMain] [o.h.jpa.internal.util.LogHelper] - HHH000204: Processing PersistenceUnitInfo [name: default]
      14:05:25.942 [INFO ] [restartedMain] [org.hibernate.Version] - HHH000412: Hibernate ORM core version 5.6.9.Final
      14:05:27.876 [INFO ] [restartedMain] [o.h.annotations.common.Version] - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
      14:05:29.209 [INFO ] [restartedMain] [org.hibernate.dialect.Dialect] - HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
      
      14:05:38.954 [INFO ] [restartedMain] [o.h.e.t.j.p.i.JtaPlatformInitiator] - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
      14:05:39.037 [INFO ] [restartedMain] [o.s.o.j.LocalContainerEntityManagerFactoryBean] - Initialized JPA EntityManagerFactory for persistence unit 'default'
      14:06:17.568 [WARN ] [restartedMain] [o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration] - spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
      14:06:21.534 [INFO ] [restartedMain] [o.s.b.d.a.OptionalLiveReloadServer] - LiveReload server is running on port 35729
      14:06:37.304 [INFO ] [restartedMain] [o.s.s.web.DefaultSecurityFilterChain] - Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@4f3eb020, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@4eecf606, org.springframework.security.web.context.SecurityContextPersistenceFilter@3a6f3601, org.springframework.security.web.header.HeaderWriterFilter@62216031, org.springframework.security.web.authentication.logout.LogoutFilter@39257525, com.backend.connectable.security.custom.JwtAuthenticationFilter@7235c6ae, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@1493436d, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@1afbaa27, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@7d5440a4, org.springframework.security.web.session.SessionManagementFilter@9e89d5a, org.springframework.security.web.access.ExceptionTranslationFilter@742c305f, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@7214c6c8]
      14:06:46.967 [INFO ] [restartedMain] [o.a.coyote.http11.Http11NioProtocol] - Starting ProtocolHandler ["http-nio-8080"]
      14:06:47.144 [INFO ] [restartedMain] [o.s.b.w.e.tomcat.TomcatWebServer] - Tomcat started on port(s): 8080 (http) with context path ''
      
  6. 다음과 같이 생성된 Context 반환

    context = {AnnotationConfigServletWebServerApplicationContext@6428} "org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@227f5c14, started on Mon Oct 24 14:15:01 KST 2022"
    
    - reader = {AnnotatedBeanDefinitionReader@16464} 
    - scanner = {ClassPathBeanDefinitionScanner@16465} 
    - annotatedClasses = {LinkedHashSet@16466}  size = 0
    - basePackages = null
    - webServer = {TomcatWebServer@16467} 
    - servletConfig = null
    - serverNamespace = null
    - servletContext = {ApplicationContextFacade@16468} 
    - themeSource = {ResourceBundleThemeSource@16469} 
    - beanFactory = {DefaultListableBeanFactory@16470} "org.springframework.beans.factory.support.DefaultListableBeanFactory@d333d97: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalPersistenceAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,connectableApplication,org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory,adminIssueService,adminOrderService,adminService,adminController,artistService,artistController,authService,authController,eventRepositoryImpl,ticketRepositoryImpl,eventService,eventController,globalExceptionHandler,timeCheckAspect,resourceHandlerConfig,swaggerConfig,workaround,restTemplateClient,redisDao,embeddedRedisConfig,redisConfig,kasWebClient,kasWebClientConfigur"
    - resourceLoader = {ClassLoaderFilesResourcePatternResolver@16471} 
    - customClassLoader = false
    - refreshed = {AtomicBoolean@16472} "true"
    - logger = {LogAdapter$Slf4jLocationAwareLog@16473} 
    - id = "application"
    - displayName = "org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@227f5c14"
    - parent = null
    - environment = {ApplicationServletEnvironment@6299} "ApplicationServletEnvironment {activeProfiles=[local, console-logging], defaultProfiles=[default], propertySources=[MapPropertySource {name='server.ports'}, ConfigurationPropertySourcesPropertySource {name='configurationProperties'}, StubPropertySource {name='servletConfigInitParams'}, ServletContextPropertySource {name='servletContextInitParams'}, PropertiesPropertySource {name='systemProperties'}, OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}, RandomValuePropertySource {name='random'}, CachedRandomPropertySource {name='cachedrandom'}, AwsParamStorePropertySource {name='/config/connectable_console-logging/'}, AwsParamStorePropertySource {name='/config/connectable_local/'}, AwsParamStorePropertySource {name='/config/connectable/'}, AwsParamStorePropertySource {name='/config/application_console-logging/'}, AwsParamStorePropertySource {name='/config/application_local/'}, AwsParamStorePropertySource {name='/config/application/'}, OriginTrackedMapPropertySource {nam"
    - beanFactoryPostProcessors = {ArrayList@16476}  size = 3
    - startupDate = 1666588501271
    - active = {AtomicBoolean@16477} "true"
    - closed = {AtomicBoolean@16478} "false"
    - startupShutdownMonitor = {Object@16479} 
    - shutdownHook = null
    - resourcePatternResolver = {ServletContextResourcePatternResolver@16480} 
    - lifecycleProcessor = {DefaultLifecycleProcessor@16481} 
    - messageSource = {DelegatingMessageSource@16482} "Empty MessageSource"
    - applicationEventMulticaster = {SimpleApplicationEventMulticaster@16483} 
    - applicationStartup = {DefaultApplicationStartup@2416} 
    - applicationListeners = {LinkedHashSet@16484}  size = 53
    - earlyApplicationListeners = {LinkedHashSet@16485}  size = 17
    - earlyApplicationEvents = null
    - classLoader = null
    - protocolResolvers = {LinkedHashSet@16486}  size = 0
    - resourceCaches = {ConcurrentHashMap@16487}  size = 0
    

  7. Bean Factory에 등록된 Bean들 (엄청많음...)

    org.springframework.beans.factory.support.DefaultListableBeanFactory@d333d97: defining beans [
    org.springframework.context.annotation.internalConfigurationAnnotationProcessor,
    org.springframework.context.annotation.internalAutowiredAnnotationProcessor,
    org.springframework.context.annotation.internalCommonAnnotationProcessor,
    org.springframework.context.annotation.internalPersistenceAnnotationProcessor,
    org.springframework.context.event.internalEventListenerProcessor,
    org.springframework.context.event.internalEventListenerFactory,
    
    // 어플리케이션
    connectableApplication,
    
    org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory,
    
    // 내가 정의한 빈들
    adminIssueService, adminOrderService, adminService, adminController, artistService, artistController, authService, authController,
    eventRepositoryImpl, ticketRepositoryImpl, eventService, eventController, globalExceptionHandler, timeCheckAspect, resourceHandlerConfig, 
    swaggerConfig, workaround, restTemplateClient, redisDao, embeddedRedisConfig, redisConfig, kasWebClient, kasWebClientConfiguration, 
    kasService, kasEndPointGenerator, kasContractService, transactionOptionManager, kasTokenService, klipService, orderRepositoryImpl, 
    orderService, orderController, s3Service, scheduledTasks, securityConfiguration, webConfiguration, customUserDetailsService, 
    jwtAuthenticationFilter, jwtProvider, smsConfig, smsService, userRepositoryImpl, userService, userTicketService, userController, 
    openApiControllerWebMvc, oasVendorExtensionsMapperImpl, styleEnumMapperImpl, securitySchemeMapperImpl, oasSecurityMapperImpl, 
    
    // 수많은 Swagger 관련 빈들
    schemaMapperImpl, examplesMapperImpl, serviceModelToOpenApiMapperImpl, oasLicenceMapper, apiListingReferenceScanner, apiDocumentationScanner, 
    apiDescriptionReader, apiListingReader, apiModelSpecificationReader, cachingOperationReader, mediaTypeReader, apiListingScanner, 
    apiModelReader, apiDescriptionLookup, operationModelsProvider, operationDeprecatedReader, responseMessagesReader, operationParameterReader, 
    operationTagsReader, apiOperationReader, defaultOperationReader, operationParameterRequestConditionReader, operationParameterHeadersConditionReader, 
    
    // ViewResolver, ObjectMapper, Jackson, Parameter 변환기 등 여러가지 요청/응답에 대한 처리를 위한 빈들
    contentParameterAggregator, operationResponseClassReader, cachingOperationNameGenerator, parameterMultiplesReader, modelAttributeParameterExpander, 
    parameterTypeReader, parameterRequiredReader, parameterDataTypeReader, parameterDefaultReader, parameterNameReader, expandedParameterBuilder, 
    webMvcRequestHandlerProvider, defaultResponseTypeReader, documentationPluginsBootstrapper, documentationPluginsManager, 
    queryStringUriTemplateDecorator, pathMappingDecorator, pathSanitizer, operationPathDecorator, jacksonJsonViewProvider, 
    cachingModelDependencyProvider, typeNameExtractor, propertyDiscriminatorBasedInheritancePlugin, xmlModelPlugin, schemaPluginsManager, 
    jsonIgnorePropertiesModelPlugin, cachingModelPropertiesProvider, objectMapperBeanPropertyNamingStrategy, accessorsProvider, fieldProvider, 
    xmlPropertyPlugin, optimized, factoryMethodProvider, modelSpecificationFactory, cachingModelProvider, defaultModelDependencyProvider, 
    jacksonEnumTypeDeterminer, defaultModelProvider, defaultModelSpecificationProvider, openApiSchemaPropertyBuilder, 
    apiModelPropertyPropertyBuilder, apiModelTypeNameProvider, apiModelBuilder, operationImplicitParameterReader, vendorExtensionsReader, 
    
    // Swagger 설정
    swaggerOperationResponseClassReader, swaggerOperationModelsProvider, openApiOperationAuthReader, 
    swaggerMediaTypeReader, operationHttpMethodReader, operationImplicitParametersReader, operationAuthReader, 
    operationHiddenReader, operationSummaryReader, openApiOperationTagsReader, swaggerResponseMessageReader, 
    operationNicknameIntoUniqueIdReader, operationPositionReader, openApiResponseReader, operationNotesReader, 
    swaggerOperationTagsReader, swaggerParameterDescriptionReader, swaggerExpandedParameterBuilder, openApiParameterBuilder, 
    swaggerApiListingReader, inMemorySwaggerResourcesProvider, apiResourceController, serviceModelToSwagger2MapperImpl, 
    vendorExtensionsMapperImpl, propertyMapperImpl, compatibilityModelMapperImpl, parameterMapperImpl, modelMapperImpl, 
    requestParameterMapperImpl, modelSpecificationMapperImpl, licenseMapperImpl, securityMapperImpl, swagger2ControllerWebMvc, 
    
    // 핸들러 매핑, ViewResolver 등의 Spring MVC flow에 필요한 빈들
    org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration, 
    requestMappingHandlerMapping, mvcPatternParser, mvcUrlPathHelper, mvcPathMatcher, mvcContentNegotiationManager, viewControllerHandlerMapping, 
    beanNameHandlerMapping, routerFunctionMapping, resourceHandlerMapping, mvcResourceUrlProvider, defaultServletHandlerMapping, requestMappingHandlerAdapter, 
    handlerFunctionAdapter, mvcConversionService, mvcValidator, mvcUriComponentsContributor, httpRequestHandlerAdapter, 
    simpleControllerHandlerAdapter, handlerExceptionResolver, mvcViewResolver, mvcHandlerMappingIntrospector, localeResolver, 
    themeResolver, flashMapManager, viewNameTranslator, parseApi, redisConnectionFactory, 
    
    // Redis 관련 설정들
    org.springframework.data.redis.repository.configuration.RedisRepositoryConfigurationExtension#0, keyValueMappingContext, 
    redisCustomConversions, redisReferenceResolver, redisConverter, redisKeyValueAdapter, redisKeyValueTemplate, webClientForKas, redisTemplate,
    
    // 수많은 Configuration들... Jackson, Servlet, WebMvc, Security, Codec, Validation, AOP 등등...
    org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration, objectPostProcessor, 
    org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration, authenticationManagerBuilder, 
    enableGlobalAuthenticationAutowiredConfigurer, initializeUserDetailsBeanManagerConfigurer, initializeAuthenticationProviderBeanManagerConfigurer, 
    org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration, delegatingApplicationListener, 
    webSecurityExpressionHandler, springSecurityFilterChain, privilegeEvaluator, conversionServicePostProcessor, 
    org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration, requestDataValueProcessor, 
    org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration, 
    org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.httpSecurity, initMessageService, 
    org.springframework.scheduling.annotation.SchedulingConfiguration, 
    org.springframework.context.annotation.internalScheduledAnnotationProcessor, 
    org.springframework.context.config.internalBeanConfigurerAspect, jpaAuditingHandler, jpaMappingContext, 
    org.springframework.boot.autoconfigure.AutoConfigurationPackages, 
    org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration, propertySourcesPlaceholderConfigurer, 
    org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration, websocketServletWebServerCustomizer, 
    org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration, 
    org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat, tomcatServletWebServerFactory, 
    org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration, servletWebServerFactoryCustomizer, tomcatServletWebServerFactoryCustomizer, 
    org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor, 
    org.springframework.boot.context.internalConfigurationPropertiesBinderFactory, 
    org.springframework.boot.context.internalConfigurationPropertiesBinder, 
    org.springframework.boot.context.properties.BoundConfigurationProperties, 
    org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar.methodValidationExcludeFilter, 
    server-org.springframework.boot.autoconfigure.web.ServerProperties, webServerFactoryCustomizerBeanPostProcessor, errorPageRegistrarBeanPostProcessor, 
    org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletConfiguration, dispatcherServlet, 
    spring.mvc-org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties, 
    org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration, dispatcherServletRegistration, 
    org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration, 
    org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$Jackson2ObjectMapperBuilderCustomizerConfiguration, 
    standardJacksonObjectMapperBuilderCustomizer, spring.jackson-org.springframework.boot.autoconfigure.jackson.JacksonProperties, 
    org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration, jacksonObjectMapperBuilder, 
    org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$ParameterNamesModuleConfiguration, parameterNamesModule, 
    org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperConfiguration, jacksonObjectMapper, 
    org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration, jsonComponentModule, jsonMixinModule, 
    org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration$DefaultCodecsConfiguration, defaultCodecCustomizer, 
    org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration$JacksonCodecConfiguration, jacksonCodecCustomizer, 
    org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration, spring.codec-org.springframework.boot.autoconfigure.codec.CodecProperties, 
    org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration, defaultValidator, methodValidationPostProcessor, 
    org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration, taskExecutorBuilder, applicationTaskExecutor, 
    spring.task.execution-org.springframework.boot.autoconfigure.task.TaskExecutionProperties, 
    org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration, error, beanNameViewResolver, 
    org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration, conventionErrorViewResolver, 
    spring.web-org.springframework.boot.autoconfigure.web.WebProperties, 
    org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration, errorAttributes, basicErrorController, errorPageCustomizer, preserveErrorControllerTargetClassPostProcessor, 
    org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration, mbeanExporter, objectNamingStrategy, mbeanServer, 
    spring.jmx-org.springframework.boot.autoconfigure.jmx.JmxProperties, 
    org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration, springApplicationAdminRegistrar, 
    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration$CglibAutoProxyConfiguration, 
    org.springframework.aop.config.internalAutoProxyCreator, org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$AspectJAutoProxyingConfiguration, 
    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration, 
    org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration, applicationAvailability, 
    org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration, 
    org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari, dataSource, 
    org.springframework.boot.autoconfigure.jdbc.DataSourceJmxConfiguration$Hikari, 
    org.springframework.boot.autoconfigure.jdbc.DataSourceJmxConfiguration, 
    org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration$PooledDataSourceConfiguration, 
    org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration, hikariPoolDataSourceMetadataProvider, 
    org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration, 
    org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties, 
    org.springframework.cloud.autoconfigure.RefreshAutoConfiguration$RefreshScopeBeanDefinitionEnhancer, 
    org.springframework.cloud.autoconfigure.RefreshAutoConfiguration$JpaInvokerConfiguration, 
    org.springframework.cloud.autoconfigure.RefreshAutoConfiguration, refreshScope, loggingRebinder, configDataContextRefresher, refreshEventListener, 
    spring.cloud.refresh-org.springframework.cloud.autoconfigure.RefreshAutoConfiguration$RefreshProperties, 
    
    // JPA 관련 빈들
    org.springframework.data.jpa.domain.support.AuditingEntityListener, org.springframework.data.jpa.domain.support.AuditingBeanFactoryPostProcessor, 
    org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration$JpaWebConfiguration, openEntityManagerInViewInterceptor, 
    openEntityManagerInViewInterceptorConfigurer, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration, 
    transactionManager, jpaVendorAdapter, entityManagerFactoryBuilder, entityManagerFactory, 
    spring.jpa.hibernate-org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties, 
    spring.jpa-org.springframework.boot.autoconfigure.orm.jpa.JpaProperties, 
    org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, 
    org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration, lettuceClientResources, 
    org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration, stringRedisTemplate, 
    spring.redis-org.springframework.boot.autoconfigure.data.redis.RedisProperties, 
    org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration, 
    org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration, lifecycleProcessor, 
    spring.lifecycle-org.springframework.boot.autoconfigure.context.LifecycleProperties, 
    org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration, persistenceExceptionTranslationPostProcessor, 
    org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, emBeanDefinitionRegistrarPostProcessor, jpaContext, 
    org.springframework.data.jpa.util.JpaMetamodelCacheCleanup, org.springframework.data.jpa.repository.support.JpaEvaluationContextExtension, 
    commentRepositoryImpl, commentRepository, artistRepository, orderRepository, ticketRepository, userRepository, eventRepository, orderDetailRepository, 
    
    // Redis 설정 빈들
    org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration, 
    org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration, userTicketEntranceRedisRepository, 
    
    // Gson, Jackson, HttpMessageConverter, ArgumentResolver
    org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration, gsonBuilder, gson, standardGsonBuilderCustomizer, 
    spring.gson-org.springframework.boot.autoconfigure.gson.GsonProperties, 
    org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration, stringHttpMessageConverter, 
    org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfiguration, 
    mappingJackson2HttpMessageConverter, org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration, 
    org.springframework.boot.autoconfigure.http.GsonHttpMessageConvertersConfiguration, 
    org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration, messageConverters, 
    org.springframework.data.web.config.ProjectingArgumentResolverRegistrar, projectingArgumentResolverBeanPostProcessor, 
    
    // DB 접근 기술
    org.springframework.data.web.config.SpringDataWebConfiguration, pageableResolver, sortResolver, 
    org.springframework.data.web.config.SpringDataJacksonConfiguration, jacksonGeoModule, 
    org.springframework.data.web.config.QuerydslWebConfiguration, querydslPredicateArgumentResolver, querydslBindingsFactory, 
    org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration, pageableCustomizer, sortCustomizer, 
    spring.data.web-org.springframework.boot.autoconfigure.data.web.SpringDataWebProperties, 
    org.springframework.boot.autoconfigure.jdbc.JdbcTemplateConfiguration, jdbcTemplate, 
    org.springframework.boot.autoconfigure.jdbc.NamedParameterJdbcTemplateConfiguration, namedParameterJdbcTemplate, 
    org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, spring.jdbc-org.springframework.boot.autoconfigure.jdbc.JdbcProperties, 
    org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor, 
    org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration, h2Console, spring.h2.console-org.springframework.boot.autoconfigure.h2.H2ConsoleProperties, 
    org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration, spring.info-org.springframework.boot.autoconfigure.info.ProjectInfoProperties, 
    org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, 
    
    //  Netty
    org.springframework.boot.autoconfigure.netty.NettyAutoConfiguration, spring.netty-org.springframework.boot.autoconfigure.netty.NettyProperties, 
    org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration, 
    spring.security-org.springframework.boot.autoconfigure.security.SecurityProperties, 
    org.springframework.boot.autoconfigure.security.servlet.SpringBootWebSecurityConfiguration$ErrorPageSecurityFilterConfiguration, errorPageSecurityFilter, 
    org.springframework.boot.autoconfigure.security.servlet.SpringBootWebSecurityConfiguration, 
    org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, authenticationEventPublisher, 
    org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration, securityFilterChainRegistration, 
    org.springframework.boot.autoconfigure.sql.init.DataSourceInitializationConfiguration, dataSourceScriptDatabaseInitializer, 
    
    // SQL 관련 설정
    org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration, 
    spring.sql.init-org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties, 
    org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration, taskScheduler, scheduledBeanLazyInitializationExcludeFilter, 
    taskSchedulerBuilder, spring.task.scheduling-org.springframework.boot.autoconfigure.task.TaskSchedulingProperties, 
    org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration$JdbcTransactionManagerConfiguration,
    
    // Transaction 담당 빈들
    org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration,
    org.springframework.transaction.config.internalTransactionAdvisor, transactionAttributeSource, transactionInterceptor, 
    org.springframework.transaction.config.internalTransactionalEventListenerFactory, 
    org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration$EnableTransactionManagementConfiguration$CglibAutoProxyConfiguration, 
    org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration$EnableTransactionManagementConfiguration, 
    org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration$TransactionTemplateConfiguration, 
    transactionTemplate, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, platformTransactionManagerCustomizers, 
    spring.transaction-org.springframework.boot.autoconfigure.transaction.TransactionProperties, 
    org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration, restTemplateBuilderConfigurer, restTemplateBuilder, 
    
    // WebServer 관련 빈들
    org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration$NettyWebServerFactoryCustomizerConfiguration, nettyWebServerFactoryCustomizer, 
    org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration$TomcatWebServerFactoryCustomizerConfiguration, tomcatWebServerFactoryCustomizer, 
    org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration, 
    
    // Reactor 관련 빈들
    org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorConfiguration$ReactorNetty, reactorClientResourceFactory, reactorClientHttpConnector, 
    org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration, clientConnectorCustomizer, 
    org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration$WebClientCodecsConfiguration, exchangeStrategiesCustomizer, 
    org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration, webClientBuilder, 
    
    org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration, characterEncodingFilter, localeCharsetMappingsCustomizer, 
    org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration, multipartConfigElement, multipartResolver, 
    spring.servlet.multipart-org.springframework.boot.autoconfigure.web.servlet.MultipartProperties, 
    org.springframework.boot.devtools.autoconfigure.DevToolsDataSourceAutoConfiguration$DatabaseShutdownExecutorEntityManagerFactoryDependsOnPostProcessor, 
    org.springframework.boot.devtools.autoconfigure.DevToolsDataSourceAutoConfiguration, inMemoryDatabaseShutdownExecutor, 
    org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration$RestartConfiguration, restartingClassPathChangedEventListener, 
    classPathFileSystemWatcher, classPathRestartStrategy, fileSystemWatcherFactory, conditionEvaluationDeltaLoggingListener, 
    org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration$LiveReloadConfiguration, liveReloadServer, 
    optionalLiveReloadServer, liveReloadServerEventListener, org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration, 
    spring.devtools-org.springframework.boot.devtools.autoconfigure.DevToolsProperties, 
    
    org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration, configurationPropertiesBeans, configurationPropertiesRebinder, 
    org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration, environmentManager, 
    springfox.documentation.schema.configuration.ModelsConfiguration, typeResolver, modelBuilderPluginRegistry, modelPropertyBuilderPluginRegistry, 
    typeNameProviderPluginRegistry, syntheticModelProviderPluginRegistry, viewProviderPluginRegistry, 
    
    // Swagger 관련 빈들
    springfox.documentation.spring.web.SpringfoxWebConfiguration, defaults, resourceGroupCache, jsonSerializer, descriptionResolver, methodResolver, 
    pathProvider, documentationPluginRegistry, apiListingBuilderPluginRegistry, operationBuilderPluginRegistry, parameterBuilderPluginRegistry, 
    responseBuilderPluginRegistry, expandedParameterBuilderPluginRegistry, operationModelsProviderPluginRegistry, defaultsProviderPluginRegistry, 
    pathDecoratorRegistry, apiListingScannerPluginRegistry, modelNamesRegistryFactoryPluginRegistry, 
    springfox.documentation.spring.web.SpringfoxWebMvcConfiguration, webMvcObjectMapperConfigurer, 
    springfox.documentation.swagger.configuration.SwaggerCommonConfiguration, 
    springfox.documentation.oas.configuration.OpenApiMappingConfiguration, openApiModule, 
    springfox.documentation.oas.configuration.OpenApiWebMvcConfiguration, webMvcOpenApiTransformer, webMvcOpenApiTransformationFilterRegistry, 
    springfox.documentation.oas.configuration.OpenApiDocumentationConfiguration, 
    springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration, expanderMinMax, expanderNotNull, expanderNotBlank, 
    expanderPattern, expanderSize, parameterMinMax, parameterNotNull, parameterNotBlank, parameterPattern, parameterSize, minMaxPlugin, 
    decimalMinMaxPlugin, sizePlugin, isNullPlugin, notNullPlugin, notBlankPlugin, patternPlugin, 
    springfox.documentation.swagger2.configuration.Swagger2WebMvcConfiguration, webMvcSwaggerTransformer, 
    webMvcSwaggerTransformationFilterRegistry, springfox.documentation.swagger2.configuration.Swagger2DocumentationConfiguration, 
    swagger2Module, springfox.boot.starter.autoconfigure.SwaggerUiWebMvcConfiguration, swaggerUiConfigurer, 
    springfox.boot.starter.autoconfigure.OpenApiAutoConfiguration, springfox.documentation-springfox.boot.starter.autoconfigure.SpringfoxConfigurationProperties, 
    org.springframework.orm.jpa.SharedEntityManagerCreator#0
    
    ]; root of factory hierarchy
    

  8. Thread에서 (아마 Main이겠죠?) run() 시켜버림 - Reflection Method invoke로 실행시켜버림! -> 드디어 구동!

    @Override
    public void run() {
        try {
            Class<?> mainClass = Class.forName(this.mainClassName, false, getContextClassLoader());
            Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
            mainMethod.invoke(null, new Object[] { this.args });
        }
        catch (Throwable ex) {
            this.error = ex;
            getUncaughtExceptionHandler().uncaughtException(this, ex);
        }
    }
    

시큐리티 인가 과정 디버거와 함께

엔티티 준영속? 어플리케이션 컨텍스트 새로 생긴다? => 하나의 요청 톺아보기

  • Filter단 준영속 + OSIV
    // HTTP 요청이 들어온다
    2022-10-24 19:45:29.024[DEBUG] : Received [PUT /users HTTP/1.1
    Host: localhost:8080
    Connection: keep-alive
    Content-Length: 55
    sec-ch-ua: "Chromium";v="106", "Google Chrome";v="106", "Not;A=Brand";v="99"
    sec-ch-ua-mobile: ?0
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36
    Authorization: Bearer secret.secret.w-secret
    Content-Type: application/json
    Cache-Control: no-cache
    Postman-Token: 4404766e-5d4c-ab73-d4cb-98faf425b943
    sec-ch-ua-platform: "macOS"
    Accept: */*
    Origin: chrome-extension://fhbjgbiflinjbdggehcddcbncdddomop
    Sec-Fetch-Site: none
    Sec-Fetch-Mode: cors
    Sec-Fetch-Dest: empty
    Accept-Encoding: gzip, deflate, br
    Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6,zh;q=0.5
    
    {
        "nickname":"hello",
        "phoneNumber":"010-0000-0000"
    }]
    2022-10-24 19:45:29.039[DEBUG] : Set query string encoding to UTF-8
    2022-10-24 19:45:29.042[DEBUG] : Security checking request PUT /users
    2022-10-24 19:45:29.043[DEBUG] :   No applicable constraints defined
    2022-10-24 19:45:29.047[DEBUG] : Loading persistent provider registrations from [/private/var/folders/q7/z5mst0411js4_ng0wr6qw9v00000gn/T/tomcat.8080.8416881270018184595/conf/jaspic-providers.xml]
    2022-10-24 19:45:29.047[DEBUG] : Not subject to any constraint
    
    // 최초 요청에 대해서는 DispatcherServlet을 생성한다. 
    2022-10-24 19:45:29.049[INFO ] : Initializing Spring DispatcherServlet 'dispatcherServlet'
    2022-10-24 19:45:29.049[INFO ] : Initializing Servlet 'dispatcherServlet'
    
    // 요청을 처리하는데 필요한 Bean들은 캐싱을 해두게 된다.
    2022-10-24 19:45:29.049[TRACE] : Returning cached instance of singleton bean 'multipartResolver'
    2022-10-24 19:45:29.049[TRACE] : Detected org.springframework.web.multipart.support.StandardServletMultipartResolver@4b7d7c57
    2022-10-24 19:45:29.049[TRACE] : Returning cached instance of singleton bean 'localeResolver'
    2022-10-24 19:45:29.049[TRACE] : Detected org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver@2783508d
    2022-10-24 19:45:29.049[TRACE] : Returning cached instance of singleton bean 'themeResolver'
    2022-10-24 19:45:29.049[TRACE] : Detected org.springframework.web.servlet.theme.FixedThemeResolver@64a43a3c
    2022-10-24 19:45:29.050[TRACE] : Returning cached instance of singleton bean 'requestMappingHandlerMapping'
    2022-10-24 19:45:29.050[TRACE] : Returning cached instance of singleton bean 'viewControllerHandlerMapping'
    2022-10-24 19:45:29.050[TRACE] : Returning cached instance of singleton bean 'beanNameHandlerMapping'
    2022-10-24 19:45:29.050[TRACE] : Returning cached instance of singleton bean 'routerFunctionMapping'
    2022-10-24 19:45:29.050[TRACE] : Returning cached instance of singleton bean 'resourceHandlerMapping'
    2022-10-24 19:45:29.050[TRACE] : Returning cached instance of singleton bean 'defaultServletHandlerMapping'
    2022-10-24 19:45:29.050[TRACE] : Returning cached instance of singleton bean 'requestMappingHandlerAdapter'
    2022-10-24 19:45:29.050[TRACE] : Returning cached instance of singleton bean 'handlerFunctionAdapter'
    2022-10-24 19:45:29.050[TRACE] : Returning cached instance of singleton bean 'httpRequestHandlerAdapter'
    2022-10-24 19:45:29.050[TRACE] : Returning cached instance of singleton bean 'simpleControllerHandlerAdapter'
    2022-10-24 19:45:29.050[TRACE] : Returning cached instance of singleton bean 'handlerExceptionResolver'
    2022-10-24 19:45:29.050[TRACE] : Returning cached instance of singleton bean 'errorAttributes'
    2022-10-24 19:45:29.050[TRACE] : Returning cached instance of singleton bean 'viewNameTranslator'
    2022-10-24 19:45:29.050[TRACE] : Detected DefaultRequestToViewNameTranslator
    2022-10-24 19:45:29.050[TRACE] : Returning cached instance of singleton bean 'mvcViewResolver'
    2022-10-24 19:45:29.050[TRACE] : Returning cached instance of singleton bean 'beanNameViewResolver'
    2022-10-24 19:45:29.050[TRACE] : Returning cached instance of singleton bean 'flashMapManager'
    2022-10-24 19:45:29.050[TRACE] : Detected SessionFlashMapManager
    2022-10-24 19:45:29.050[DEBUG] : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
    2022-10-24 19:45:29.051[INFO ] : Completed initialization in 1 ms
    2022-10-24 19:45:29.051[DEBUG] :   Returning non-STM instance
    
    // SpringSecurity를 사용하기 때문에 DelegatingFilterProxy로 부터 SpringSecurityFilterChain에게 위임해 요청을 타게한다
    2022-10-24 19:45:29.053[TRACE] : Returning cached instance of singleton bean 'springSecurityFilterChain'
    2022-10-24 19:45:29.057[TRACE] : Trying to match request against DefaultSecurityFilterChain [RequestMatcher=any request, Filters=[org.springframework.security.web.session.DisableEncodeUrlFilter@3936f8e0, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@39354298, org.springframework.security.web.context.SecurityContextPersistenceFilter@28e542ce, org.springframework.security.web.header.HeaderWriterFilter@71fcf111, org.springframework.security.web.authentication.logout.LogoutFilter@7e1ffd05, com.backend.connectable.security.JwtAuthenticationFilter@7ed73b15, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@cc0d699, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@7bf7677c, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@6727efeb, org.springframework.security.web.session.SessionManagementFilter@c3bac62, org.springframework.security.web.access.ExceptionTranslationFilter@326c132d, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@10c697d0]] (1/1)
    2022-10-24 19:45:29.057[DEBUG] : Securing PUT /users
    2022-10-24 19:45:29.057[TRACE] : Invoking DisableEncodeUrlFilter (1/12)
    2022-10-24 19:45:29.058[TRACE] : Invoking WebAsyncManagerIntegrationFilter (2/12)
    2022-10-24 19:45:29.059[TRACE] : Invoking SecurityContextPersistenceFilter (3/12)
    2022-10-24 19:45:29.061[DEBUG] : Set SecurityContextHolder to empty SecurityContext
    2022-10-24 19:45:29.061[TRACE] : Invoking HeaderWriterFilter (4/12)
    2022-10-24 19:45:29.061[TRACE] : Invoking LogoutFilter (5/12)
    2022-10-24 19:45:29.062[TRACE] : Did not match request to Or [Ant [pattern='/logout', GET], Ant [pattern='/logout', POST], Ant [pattern='/logout', PUT], Ant [pattern='/logout', DELETE]]
    
    // 내가 정의한 JwtAuthenticationFilter를 탄다
    2022-10-24 19:45:29.062[TRACE] : Invoking JwtAuthenticationFilter (6/12)
    2022-10-24 19:45:29.064[INFO ] : [[Token]] : eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIweDNhYjMxZDIxOWQ0NWNlNDBkNjg2MmQ2OGQzN2RlNmJiNzNlMjFhOGQiLCJleHBpcmUiOjE2ODA5MDgwODZ9.w-atmmKZjGxkn8Zbl850YC4KlyJQ6AyiOpHSyjQzhBE
    2022-10-24 19:45:29.064[INFO ] : [[Path]] : /users
    
    // UserDetailsService에 @Transactional을 붙였기 때문에 트랜잭션이 생성된다 - Hibernate Session이 생성되고 HikariCP의 커넥션이 매칭된다
    2022-10-24 19:45:29.102[DEBUG] : Creating new transaction with name [com.backend.connectable.security.CustomUserDetailsService.loadUserByUsername]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
    2022-10-24 19:45:29.102[TRACE] : Opening Hibernate Session.  tenant=null
    2022-10-24 19:45:29.102[TRACE] : Opened Session [50317ba0-a0b4-49ff-b135-4000cce23439] at timestamp: 1666608329102
    2022-10-24 19:45:29.102[DEBUG] : Opened new EntityManager [SessionImpl(1543251544PersistenceContext[entityKeys=[], collectionKeys=[]];ActionQueue[insertions=ExecutableList{size=0} updates=ExecutableList{size=0} deletions=ExecutableList{size=0} orphanRemovals=ExecutableList{size=0} collectionCreations=ExecutableList{size=0} collectionRemovals=ExecutableList{size=0} collectionUpdates=ExecutableList{size=0} collectionQueuedOps=ExecutableList{size=0} unresolvedInsertDependencies=null])] for JPA transaction
    2022-10-24 19:45:29.102[DEBUG] : On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
    2022-10-24 19:45:29.102[DEBUG] : begin
    2022-10-24 19:45:29.102[TRACE] : Preparing to begin transaction via JDBC Connection.setAutoCommit(false)
    2022-10-24 19:45:29.102[TRACE] : Transaction begun via JDBC Connection.setAutoCommit(false)
    2022-10-24 19:45:29.102[TRACE] : ResourceLocalTransactionCoordinatorImpl#afterBeginCallback
    2022-10-24 19:45:29.102[DEBUG] : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@4380507c]
    2022-10-24 19:45:29.102[TRACE] : Getting transaction for [com.backend.connectable.security.CustomUserDetailsService.loadUserByUsername]
    2022-10-24 19:45:29.110[TRACE] : No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findByKlaytnAddress]: This method is not transactional.
    2022-10-24 19:45:29.116[DEBUG] : Rendered criteria query -> select generatedAlias0 from User as generatedAlias0 where generatedAlias0.klaytnAddress=:param0
    2022-10-24 19:45:29.117[TRACE] : Unable to locate HQL query plan in cache; generating (select generatedAlias0 from User as generatedAlias0 where generatedAlias0.klaytnAddress=:param0)
    2022-10-24 19:45:29.117[DEBUG] : parse() - HQL: select generatedAlias0 from com.backend.connectable.user.domain.User as generatedAlias0 where generatedAlias0.klaytnAddress=:param0
    
    // Statement를 만들어 처리한다. (이하 생략)
    2022-10-24 19:45:29.117[TRACE] : -> statement
    2022-10-24 19:45:29.118[TRACE] : ---> selectStatement
    2022-10-24 19:45:29.118[TRACE] : -----> queryRule
    2022-10-24 19:45:29.118[TRACE] : -------> selectFrom
    2022-10-24 19:45:29.119[TRACE] : <--- selectStatement
    2022-10-24 19:45:29.119[TRACE] : <- statement
    2022-10-24 19:45:29.119[DEBUG] : throwQueryException() : no errors
    2022-10-24 19:45:29.119[DEBUG] : --- HQL AST ---
     \-[QUERY] Node: 'query'
        +-[SELECT_FROM] Node: 'SELECT_FROM'
        |  +-[FROM] Node: 'from'
        |  |  \-[RANGE] Node: 'RANGE'
        |  |     +-[DOT] Node: '.'
        |  |     |  +-[DOT] Node: '.'
        |  |     |  |  +-[DOT] Node: '.'
        |  |     |  |  |  +-[DOT] Node: '.'
        |  |     |  |  |  |  +-[DOT] Node: '.'
        |  |     |  |  |  |  |  +-[IDENT] Node: 'com'
        |  |     |  |  |  |  |  \-[IDENT] Node: 'backend'
        |  |     |  |  |  |  \-[IDENT] Node: 'connectable'
        |  |     |  |  |  \-[IDENT] Node: 'user'
        |  |     |  |  \-[IDENT] Node: 'domain'
        |  |     |  \-[IDENT] Node: 'User'
        |  |     \-[ALIAS] Node: 'generatedAlias0'
        |  \-[SELECT] Node: 'select'
        |     \-[IDENT] Node: 'generatedAlias0'
        \-[WHERE] Node: 'where'
           \-[EQ] Node: '='
              +-[DOT] Node: '.'
              |  +-[IDENT] Node: 'generatedAlias0'
              |  \-[IDENT] Node: 'klaytnAddress'
              \-[COLON] Node: ':'
                 \-[IDENT] Node: 'param0'
    
    2022-10-24 19:45:29.119[DEBUG] : select << begin [level=1, statement=select]
    2022-10-24 19:45:29.121[DEBUG] : FromClause{level=1} : com.backend.connectable.user.domain.User (generatedAlias0) -> user0_
    2022-10-24 19:45:29.122[DEBUG] : Resolved : generatedAlias0 -> user0_.id
    2022-10-24 19:45:29.122[DEBUG] : Resolved : generatedAlias0 -> user0_.id
    2022-10-24 19:45:29.122[TRACE] : Handling property dereference [com.backend.connectable.user.domain.User (generatedAlias0) -> klaytnAddress (class)]
    2022-10-24 19:45:29.122[DEBUG] : getDataType() : klaytnAddress -> org.hibernate.type.StringType@6a59154d
    2022-10-24 19:45:29.122[DEBUG] : Resolved : generatedAlias0.klaytnAddress -> user0_.klaytn_address
    2022-10-24 19:45:29.122[DEBUG] : select : finishing up [level=1, statement=select]
    2022-10-24 19:45:29.122[DEBUG] : processQuery() :  ( SELECT ( {select clause} user0_.id ) ( FromClause{level=1} user user0_ ) ( where ( = ( user0_.klaytn_address user0_.id klaytnAddress ) ? ) ) )
    2022-10-24 19:45:29.122[DEBUG] : Tables referenced from query nodes:
     \-QueryNode
        +-SelectClause
        | referencedTables(entity User): [user]
        |  +-IdentNode
        |  | persister: SingleTableEntityPersister(com.backend.connectable.user.domain.User)
        |  | originalText: generatedAlias0
        |  \-SqlFragment
        +-FromClause
        |  \-FromElement
        \-SqlNode
           \-BinaryLogicOperatorNode
              +-DotNode
              | persister: SingleTableEntityPersister(com.backend.connectable.user.domain.User)
              | path: generatedAlias0.klaytnAddress
              |  +-IdentNode
              |  | persister: SingleTableEntityPersister(com.backend.connectable.user.domain.User)
              |  | originalText: generatedAlias0
              |  \-IdentNode
              |    persister: null
              |    originalText: klaytnAddress
              \-ParameterNode
    
    2022-10-24 19:45:29.122[DEBUG] : Using FROM fragment [user user0_]
    2022-10-24 19:45:29.122[DEBUG] : select >> end [level=1, statement=select]
    2022-10-24 19:45:29.122[DEBUG] : --- SQL AST ---
     \-[SELECT] QueryNode: 'SELECT'  querySpaces (user)
        +-[SELECT_CLAUSE] SelectClause: '{select clause}'
        |  +-[ALIAS_REF] IdentNode: 'user0_.id as id1_5_' {alias=generatedAlias0, className=com.backend.connectable.user.domain.User, tableAlias=user0_}
        |  \-[SQL_TOKEN] SqlFragment: 'user0_.is_active as is_activ2_5_, user0_.klaytn_address as klaytn_a3_5_, user0_.nickname as nickname4_5_, user0_.phone_number as phone_nu5_5_, user0_.privacy_agreement as privacy_6_5_'
        +-[FROM] FromClause: 'from' FromClause{level=1, fromElementCounter=1, fromElements=1, fromElementByClassAlias=[generatedAlias0], fromElementByTableAlias=[user0_], fromElementsByPath=[], collectionJoinFromElementsByPath=[], impliedElements=[]}
        |  \-[FROM_FRAGMENT] FromElement: 'user user0_' FromElement{explicit,not a collection join,not a fetch join,fetch non-lazy properties,classAlias=generatedAlias0,role=null,tableName=user,tableAlias=user0_,origin=null,columns={,className=com.backend.connectable.user.domain.User}}
        \-[WHERE] SqlNode: 'where'
           \-[EQ] BinaryLogicOperatorNode: '='
              +-[DOT] DotNode: 'user0_.klaytn_address' {propertyName=klaytnAddress,dereferenceType=PRIMITIVE,getPropertyPath=klaytnAddress,path=generatedAlias0.klaytnAddress,tableAlias=user0_,className=com.backend.connectable.user.domain.User,classAlias=generatedAlias0}
              |  +-[ALIAS_REF] IdentNode: 'user0_.id' {alias=generatedAlias0, className=com.backend.connectable.user.domain.User, tableAlias=user0_}
              |  \-[IDENT] IdentNode: 'klaytnAddress' {originalText=klaytnAddress}
              \-[NAMED_PARAM] ParameterNode: '?' {name=param0, expectedType=org.hibernate.type.StringType@6a59154d}
    
    2022-10-24 19:45:29.122[DEBUG] : throwQueryException() : no errors
    2022-10-24 19:45:29.122[DEBUG] : HQL: select generatedAlias0 from com.backend.connectable.user.domain.User as generatedAlias0 where generatedAlias0.klaytnAddress=:param0
    2022-10-24 19:45:29.122[DEBUG] : SQL: select user0_.id as id1_5_, user0_.is_active as is_activ2_5_, user0_.klaytn_address as klaytn_a3_5_, user0_.nickname as nickname4_5_, user0_.phone_number as phone_nu5_5_, user0_.privacy_agreement as privacy_6_5_ from user user0_ where user0_.klaytn_address=?
    2022-10-24 19:45:29.122[DEBUG] : throwQueryException() : no errors
    2022-10-24 19:45:29.128[TRACE] : Find: select generatedAlias0 from User as generatedAlias0 where generatedAlias0.klaytnAddress=:param0
    2022-10-24 19:45:29.128[TRACE] : Named parameters: {param0=0x3ab31d219d45ce40d6862d68d37de6bb73e21a8d}
    
    // JPA 내부적으로는 JDBC를 활용하여 pstm, rs, connection을 통한 쿼리 수행을 진행한다
    2022-10-24 19:45:29.129[DEBUG] : 
        select
            user0_.id as id1_5_,
            user0_.is_active as is_activ2_5_,
            user0_.klaytn_address as klaytn_a3_5_,
            user0_.nickname as nickname4_5_,
            user0_.phone_number as phone_nu5_5_,
            user0_.privacy_agreement as privacy_6_5_ 
        from
            user user0_ 
        where
            user0_.klaytn_address=?
    2022-10-24 19:45:29.130[TRACE] : Registering statement [HikariProxyPreparedStatement@526283268 wrapping prep2: select user0_.id as id1_5_, user0_.is_active as is_activ2_5_, user0_.klaytn_address as klaytn_a3_5_, user0_.nickname as nickname4_5_, user0_.phone_number as phone_nu5_5_, user0_.privacy_agreement as privacy_6_5_ from user user0_ where user0_.klaytn_address=?]
    2022-10-24 19:45:29.130[TRACE] : Registering last query statement [HikariProxyPreparedStatement@526283268 wrapping prep2: select user0_.id as id1_5_, user0_.is_active as is_activ2_5_, user0_.klaytn_address as klaytn_a3_5_, user0_.nickname as nickname4_5_, user0_.phone_number as phone_nu5_5_, user0_.privacy_agreement as privacy_6_5_ from user user0_ where user0_.klaytn_address=?]
    2022-10-24 19:45:29.130[TRACE] : binding parameter [1] as [VARCHAR] - [0x3ab31d219d45ce40d6862d68d37de6bb73e21a8d]
    2022-10-24 19:45:29.132[TRACE] : Bound [2] parameters total
    2022-10-24 19:45:29.133[TRACE] : Registering result set [HikariProxyResultSet@1140706281 wrapping rs20: org.h2.result.LocalResultImpl@2fdf4fc0 columns: 6 rows: 1 pos: -1]
    2022-10-24 19:45:29.133[TRACE] : Processing result set
    2022-10-24 19:45:29.133[DEBUG] : Result set row: 0
    2022-10-24 19:45:29.133[TRACE] : extracted value ([id1_5_] : [BIGINT]) - [1]
    2022-10-24 19:45:29.133[DEBUG] : Result row: EntityKey[com.backend.connectable.user.domain.User#1]
    2022-10-24 19:45:29.134[TRACE] : Initializing object from ResultSet: [com.backend.connectable.user.domain.User#1]
    2022-10-24 19:45:29.135[TRACE] : Hydrating entity: [com.backend.connectable.user.domain.User#1]
    2022-10-24 19:45:29.136[TRACE] : extracted value ([is_activ2_5_] : [BOOLEAN]) - [true]
    2022-10-24 19:45:29.136[TRACE] : extracted value ([klaytn_a3_5_] : [VARCHAR]) - [0x3ab31d219d45ce40d6862d68d37de6bb73e21a8d]
    2022-10-24 19:45:29.136[TRACE] : extracted value ([nickname4_5_] : [VARCHAR]) - [joel]
    2022-10-24 19:45:29.136[TRACE] : extracted value ([phone_nu5_5_] : [VARCHAR]) - [010-1111-1111]
    2022-10-24 19:45:29.136[TRACE] : extracted value ([privacy_6_5_] : [BOOLEAN]) - [true]
    2022-10-24 19:45:29.136[TRACE] : Done processing result set (1 rows)
    2022-10-24 19:45:29.136[TRACE] : Total objects hydrated: 1
    2022-10-24 19:45:29.137[DEBUG] : Resolving attributes for [com.backend.connectable.user.domain.User#1]
    2022-10-24 19:45:29.137[DEBUG] : Processing attribute `isActive` : value = true
    2022-10-24 19:45:29.137[DEBUG] : Attribute (`isActive`)  - enhanced for lazy-loading? - false
    2022-10-24 19:45:29.137[DEBUG] : Processing attribute `klaytnAddress` : value = 0x3ab31d219d45ce40d6862d68d37de6bb73e21a8d
    2022-10-24 19:45:29.137[DEBUG] : Attribute (`klaytnAddress`)  - enhanced for lazy-loading? - false
    2022-10-24 19:45:29.137[DEBUG] : Processing attribute `nickname` : value = joel
    2022-10-24 19:45:29.137[DEBUG] : Attribute (`nickname`)  - enhanced for lazy-loading? - false
    2022-10-24 19:45:29.137[DEBUG] : Processing attribute `phoneNumber` : value = 010-1111-1111
    2022-10-24 19:45:29.137[DEBUG] : Attribute (`phoneNumber`)  - enhanced for lazy-loading? - false
    2022-10-24 19:45:29.137[DEBUG] : Processing attribute `privacyAgreement` : value = true
    2022-10-24 19:45:29.137[DEBUG] : Attribute (`privacyAgreement`)  - enhanced for lazy-loading? - false
    2022-10-24 19:45:29.137[DEBUG] : Done materializing entity [com.backend.connectable.user.domain.User#1]
    
    // 해당 쿼리를 수행할 때 만들어둔 pstm, rs, connection을 닫는다
    2022-10-24 19:45:29.137[TRACE] : Releasing statement [HikariProxyPreparedStatement@526283268 wrapping prep2: select user0_.id as id1_5_, user0_.is_active as is_activ2_5_, user0_.klaytn_address as klaytn_a3_5_, user0_.nickname as nickname4_5_, user0_.phone_number as phone_nu5_5_, user0_.privacy_agreement as privacy_6_5_ from user user0_ where user0_.klaytn_address=? {1: '0x3ab31d219d45ce40d6862d68d37de6bb73e21a8d'}]
    2022-10-24 19:45:29.137[TRACE] : Closing result set [HikariProxyResultSet@1140706281 wrapping rs20: org.h2.result.LocalResultImpl@2fdf4fc0 columns: 6 rows: 1 pos: 1]
    2022-10-24 19:45:29.137[TRACE] : Closing prepared statement [HikariProxyPreparedStatement@526283268 wrapping prep2: select user0_.id as id1_5_, user0_.is_active as is_activ2_5_, user0_.klaytn_address as klaytn_a3_5_, user0_.nickname as nickname4_5_, user0_.phone_number as phone_nu5_5_, user0_.privacy_agreement as privacy_6_5_ from user user0_ where user0_.klaytn_address=? {1: '0x3ab31d219d45ce40d6862d68d37de6bb73e21a8d'}]
    2022-10-24 19:45:29.137[TRACE] : Starting after statement execution processing [ON_CLOSE]
    2022-10-24 19:45:29.138[TRACE] : Initializing non-lazy collections
    2022-10-24 19:45:29.140[TRACE] : Completing transaction for [com.backend.connectable.security.CustomUserDetailsService.loadUserByUsername]
    2022-10-24 19:45:29.140[DEBUG] : Initiating transaction commit
    
    // 트랜잭션이 종료되는 시점에 DB에 커밋한다
    2022-10-24 19:45:29.140[DEBUG] : Committing JPA transaction on EntityManager [SessionImpl(1543251544PersistenceContext[entityKeys=[EntityKey[com.backend.connectable.user.domain.User#1]], collectionKeys=[]];ActionQueue[insertions=ExecutableList{size=0} updates=ExecutableList{size=0} deletions=ExecutableList{size=0} orphanRemovals=ExecutableList{size=0} collectionCreations=ExecutableList{size=0} collectionRemovals=ExecutableList{size=0} collectionUpdates=ExecutableList{size=0} collectionQueuedOps=ExecutableList{size=0} unresolvedInsertDependencies=null])]
    2022-10-24 19:45:29.140[DEBUG] : committing
    2022-10-24 19:45:29.140[TRACE] : ResourceLocalTransactionCoordinatorImpl#beforeCompletionCallback
    2022-10-24 19:45:29.140[TRACE] : SessionImpl#beforeTransactionCompletion()
    2022-10-24 19:45:29.140[TRACE] : Automatically flushing session
    2022-10-24 19:45:29.140[TRACE] : Flushing session
    2022-10-24 19:45:29.140[DEBUG] : Processing flush-time cascades
    2022-10-24 19:45:29.140[TRACE] : Processing cascade ACTION_PERSIST_ON_FLUSH for: com.backend.connectable.user.domain.User
    2022-10-24 19:45:29.140[TRACE] : Done processing cascade ACTION_PERSIST_ON_FLUSH for: com.backend.connectable.user.domain.User
    
    // Dirty Checking을 하고 Flush를 진행한다
    2022-10-24 19:45:29.140[DEBUG] : Dirty checking collections
    2022-10-24 19:45:29.140[TRACE] : Flushing entities and processing referenced collections
    2022-10-24 19:45:29.140[TRACE] : Processing unreferenced collections
    2022-10-24 19:45:29.140[TRACE] : Scheduling collection removes/(re)creates/updates
    2022-10-24 19:45:29.140[DEBUG] : Flushed: 0 insertions, 0 updates, 0 deletions to 1 objects
    2022-10-24 19:45:29.140[DEBUG] : Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections
    2022-10-24 19:45:29.140[DEBUG] : Listing entities:
    2022-10-24 19:45:29.140[DEBUG] : com.backend.connectable.user.domain.User{phoneNumber=010-1111-1111, klaytnAddress=0x3ab31d219d45ce40d6862d68d37de6bb73e21a8d, privacyAgreement=true, nickname=joel, id=1, isActive=true}
    2022-10-24 19:45:29.140[TRACE] : Executing flush
    2022-10-24 19:45:29.140[TRACE] : Starting after statement execution processing [ON_CLOSE]
    2022-10-24 19:45:29.140[TRACE] : Post flush
    2022-10-24 19:45:29.140[TRACE] : LogicalConnection#beforeTransactionCompletion
    2022-10-24 19:45:29.140[TRACE] : SynchronizationRegistryStandardImpl.notifySynchronizationsBeforeTransactionCompletion
    
    // Connection Commit을 진행한다
    2022-10-24 19:45:29.140[TRACE] : Preparing to commit transaction via JDBC Connection.commit()
    2022-10-24 19:45:29.140[TRACE] : Transaction committed via JDBC Connection.commit()
    2022-10-24 19:45:29.140[TRACE] : re-enabling auto-commit on JDBC Connection after completion of JDBC-based transaction
    2022-10-24 19:45:29.140[TRACE] : LogicalConnection#afterTransaction
    
    // JDBC 리소스 릴리즈 
    2022-10-24 19:45:29.140[TRACE] : Releasing JDBC resources
    2022-10-24 19:45:29.140[TRACE] : ResourceLocalTransactionCoordinatorImpl#afterCompletionCallback(true)
    2022-10-24 19:45:29.140[TRACE] : SynchronizationRegistryStandardImpl.notifySynchronizationsAfterTransactionCompletion(3)
    2022-10-24 19:45:29.140[TRACE] : SessionImpl#afterTransactionCompletion(successful=true, delayed=false)
    
    // JPA 엔티티 매니저 닫기 -> 영속성 컨텍스트도 닫히면서 엔티티는 준영속 상태로 넘어감
    2022-10-24 19:45:29.140[DEBUG] : Closing JPA EntityManager [SessionImpl(1543251544PersistenceContext[entityKeys=[EntityKey[com.backend.connectable.user.domain.User#1]], collectionKeys=[]];ActionQueue[insertions=ExecutableList{size=0} updates=ExecutableList{size=0} deletions=ExecutableList{size=0} orphanRemovals=ExecutableList{size=0} collectionCreations=ExecutableList{size=0} collectionRemovals=ExecutableList{size=0} collectionUpdates=ExecutableList{size=0} collectionQueuedOps=ExecutableList{size=0} unresolvedInsertDependencies=null])] after transaction
    2022-10-24 19:45:29.140[TRACE] : Closing session [50317ba0-a0b4-49ff-b135-4000cce23439]
    2022-10-24 19:45:29.140[TRACE] : Closing JDBC container [org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl@47a52022]
    2022-10-24 19:45:29.140[TRACE] : Releasing JDBC resources
    2022-10-24 19:45:29.140[TRACE] : Closing logical connection
    2022-10-24 19:45:29.140[TRACE] : Releasing JDBC resources
    2022-10-24 19:45:29.140[TRACE] : Logical connection closed
    
    // 그 다음 필터들이 수행된다
    2022-10-24 19:45:29.141[TRACE] : Invoking RequestCacheAwareFilter (7/12)
    2022-10-24 19:45:29.141[TRACE] : Invoking SecurityContextHolderAwareRequestFilter (8/12)
    2022-10-24 19:45:29.141[TRACE] : Invoking AnonymousAuthenticationFilter (9/12)
    
    // SpringSecurity 측에서 Authentication 객체가 있는 경우, 따로 또 만들어 저장하진 않음
    2022-10-24 19:45:29.141[TRACE] : Did not set SecurityContextHolder since already authenticated UsernamePasswordAuthenticationToken [Principal=com.backend.connectable.security.ConnectableUserDetails@67531e47, Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[]]
    2022-10-24 19:45:29.141[TRACE] : Invoking SessionManagementFilter (10/12)
    2022-10-24 19:45:29.141[TRACE] : Preparing session with ChangeSessionIdAuthenticationStrategy (1/1)
    2022-10-24 19:45:29.141[TRACE] : Invoking ExceptionTranslationFilter (11/12)
    2022-10-24 19:45:29.141[TRACE] : Invoking FilterSecurityInterceptor (12/12)
    2022-10-24 19:45:29.142[TRACE] : Did not match request to Ant [pattern='/**/*', OPTIONS] - [permitAll] (1/9)
    2022-10-24 19:45:29.142[TRACE] : Did not match request to Ant [pattern='/users/login'] - [permitAll] (2/9)
    2022-10-24 19:45:29.142[TRACE] : Did not match request to Ant [pattern='/events'] - [permitAll] (3/9)
    2022-10-24 19:45:29.142[TRACE] : Did not match request to Ant [pattern='/events/**'] - [permitAll] (4/9)
    2022-10-24 19:45:29.142[TRACE] : Did not re-authenticate UsernamePasswordAuthenticationToken [Principal=com.backend.connectable.security.ConnectableUserDetails@67531e47, Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[]] before authorizing
    2022-10-24 19:45:29.142[TRACE] : Authorizing filter invocation [PUT /users] with attributes [authenticated]
    2022-10-24 19:45:29.147[DEBUG] : Authorized filter invocation [PUT /users] with attributes [authenticated]
    2022-10-24 19:45:29.147[TRACE] : Did not switch RunAs authentication since RunAsManager returned null
    2022-10-24 19:45:29.147[DEBUG] : Secured PUT /users
    2022-10-24 19:45:29.150[DEBUG] : Set encoding to UTF-8
    
    // DispatcherServlet의 핸들러 매핑을 통해 요청을 처리할 핸들러(컨트롤러) 를 가져온다
    // 찾아낸 핸들러를 처리할 핸들러 어댑터를 찾아, 핸들러 어댑터로 핸들러를 처리한다
    2022-10-24 19:45:29.150[TRACE] : PUT "/users", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet'
    2022-10-24 19:45:29.153[TRACE] : Returning cached instance of singleton bean 'userController'
    2022-10-24 19:45:29.153[TRACE] : Mapped to com.backend.connectable.user.ui.UserController#modifyUser(ConnectableUserDetails, UserModifyRequest)
    
    // OSIV를 활짝 연다. 이제 인터셉터단 까지 영속성 컨텍스트는 살아있게 된다. 
    2022-10-24 19:45:29.158[DEBUG] : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
    2022-10-24 19:45:29.158[TRACE] : Opening Hibernate Session.  tenant=null
    2022-10-24 19:45:29.158[TRACE] : Opened Session [b88524e4-3fd7-4db1-b935-7b521c4b58fd] at timestamp: 1666608329158
    2022-10-24 19:45:29.194[TRACE] : Read "application/json;charset=UTF-8" to [com.backend.connectable.user.ui.dto.UserModifyRequest@40401b01]
    2022-10-24 19:45:29.232[TRACE] : Calling isReachable on object com.backend.connectable.user.ui.dto.UserModifyRequest@40401b01 with node name nickname.
    2022-10-24 19:45:29.240[TRACE] : Validating value hello against constraint defined by ConstraintDescriptorImpl{annotation=j.v.c.NotBlank, payloads=[], hasComposingConstraints=true, isReportAsSingleInvalidConstraint=false, constraintLocationKind=FIELD, definedOn=DEFINED_LOCALLY, groups=[interface com.backend.connectable.exception.sequence.ValidationGroups$NotEmptyGroup], attributes={groups=[Ljava.lang.Class;@68274b46, message=닉네임은 필수 입력값 입니다., payload=[Ljava.lang.Class;@5cec7483}, constraintType=GENERIC, valueUnwrapping=DEFAULT}.
    2022-10-24 19:45:29.240[TRACE] : Creating instance of bean 'org.hibernate.validator.internal.constraintvalidators.bv.NotBlankValidator'
    2022-10-24 19:45:29.240[TRACE] : Returning cached instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
    2022-10-24 19:45:29.244[TRACE] : Returning cached instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
    2022-10-24 19:45:29.244[TRACE] : Returning cached instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
    
    // Bean Validation이 진행된다. 
    2022-10-24 19:45:29.245[TRACE] : No @Scheduled annotations found on bean class: class org.hibernate.validator.internal.constraintvalidators.bv.NotBlankValidator
    2022-10-24 19:45:29.245[TRACE] : Finished creating instance of bean 'org.hibernate.validator.internal.constraintvalidators.bv.NotBlankValidator'
    2022-10-24 19:45:29.247[TRACE] : Calling isReachable on object com.backend.connectable.user.ui.dto.UserModifyRequest@40401b01 with node name phoneNumber.
    2022-10-24 19:45:29.247[TRACE] : Validating value 010-0000-0000 against constraint defined by ConstraintDescriptorImpl{annotation=j.v.c.NotBlank, payloads=[], hasComposingConstraints=true, isReportAsSingleInvalidConstraint=false, constraintLocationKind=FIELD, definedOn=DEFINED_LOCALLY, groups=[interface com.backend.connectable.exception.sequence.ValidationGroups$NotEmptyGroup], attributes={groups=[Ljava.lang.Class;@5dd1427f, message=연락처는 필수 입력값 입니다., payload=[Ljava.lang.Class;@5cec7483}, constraintType=GENERIC, valueUnwrapping=DEFAULT}.
    2022-10-24 19:45:29.248[TRACE] : Creating instance of bean 'org.hibernate.validator.internal.constraintvalidators.bv.NotBlankValidator'
    2022-10-24 19:45:29.249[TRACE] : Finished creating instance of bean 'org.hibernate.validator.internal.constraintvalidators.bv.NotBlankValidator'
    2022-10-24 19:45:29.249[TRACE] : Validating value 010-0000-0000 against constraint defined by ConstraintDescriptorImpl{annotation=j.v.c.Pattern, payloads=[], hasComposingConstraints=true, isReportAsSingleInvalidConstraint=false, constraintLocationKind=FIELD, definedOn=DEFINED_LOCALLY, groups=[interface com.backend.connectable.exception.sequence.ValidationGroups$PatternCheckGroup], attributes={flags=[Ljavax.validation.constraints.Pattern$Flag;@7fa06575, regexp=^\d{2,3}-\d{3,4}-\d{4}$, groups=[Ljava.lang.Class;@10c95502, message=연락처는 xxx-xxxx-xxxx 양식으로 구성되어야 합니다., payload=[Ljava.lang.Class;@424e80cd}, constraintType=GENERIC, valueUnwrapping=DEFAULT}.
    2022-10-24 19:45:29.249[TRACE] : Creating instance of bean 'org.hibernate.validator.internal.constraintvalidators.bv.PatternValidator'
    2022-10-24 19:45:29.250[TRACE] : Returning cached instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
    2022-10-24 19:45:29.252[TRACE] : Returning cached instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
    2022-10-24 19:45:29.252[TRACE] : Returning cached instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
    2022-10-24 19:45:29.252[TRACE] : No @Scheduled annotations found on bean class: class org.hibernate.validator.internal.constraintvalidators.bv.PatternValidator
    2022-10-24 19:45:29.252[TRACE] : Finished creating instance of bean 'org.hibernate.validator.internal.constraintvalidators.bv.PatternValidator'
    2022-10-24 19:45:29.254[TRACE] : Validating value hello against constraint defined by ConstraintDescriptorImpl{annotation=j.v.c.Pattern, payloads=[], hasComposingConstraints=true, isReportAsSingleInvalidConstraint=false, constraintLocationKind=FIELD, definedOn=DEFINED_LOCALLY, groups=[interface com.backend.connectable.exception.sequence.ValidationGroups$PatternCheckGroup], attributes={flags=[Ljavax.validation.constraints.Pattern$Flag;@7fa06575, regexp=^(?=.*[a-zA-Z0-9가-힣])[a-zA-Z0-9가-힣]{2,10}$, groups=[Ljava.lang.Class;@4f7a0a73, message=닉네임은 2자 이상 10자 이하, 영어 또는 숫자 또는 한글로 구성된 닉네임이여야 합니다., payload=[Ljava.lang.Class;@424e80cd}, constraintType=GENERIC, valueUnwrapping=DEFAULT}.
    2022-10-24 19:45:29.254[TRACE] : Creating instance of bean 'org.hibernate.validator.internal.constraintvalidators.bv.PatternValidator'
    2022-10-24 19:45:29.255[TRACE] : Finished creating instance of bean 'org.hibernate.validator.internal.constraintvalidators.bv.PatternValidator'
    2022-10-24 19:45:29.255[TRACE] : Arguments: [com.backend.connectable.security.ConnectableUserDetails@67531e47, com.backend.connectable.user.ui.dto.UserModifyRequest@40401b01]
    
    // 컨트롤러 메서드가 실행된다. 
    2022-10-24 19:45:29.255[INFO ] : $$ Controller METHOD START $$
    
    // 서비스에 CGLIB 프록시가 적용되어, 해당 Service 메서드 실행전에 트랜잭션이 먼저 생성이 된다
    // EntityManager는 Thread에 하나 넣는다
    2022-10-24 19:45:29.255[DEBUG] : Found thread-bound EntityManager [SessionImpl(1131948410PersistenceContext[entityKeys=[], collectionKeys=[]];ActionQueue[insertions=ExecutableList{size=0} updates=ExecutableList{size=0} deletions=ExecutableList{size=0} orphanRemovals=ExecutableList{size=0} collectionCreations=ExecutableList{size=0} collectionRemovals=ExecutableList{size=0} collectionUpdates=ExecutableList{size=0} collectionQueuedOps=ExecutableList{size=0} unresolvedInsertDependencies=null])] for JPA transaction
    2022-10-24 19:45:29.255[DEBUG] : Creating new transaction with name [com.backend.connectable.user.service.UserService.modifyUserByUserDetails]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
    2022-10-24 19:45:29.255[DEBUG] : On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
    2022-10-24 19:45:29.255[DEBUG] : begin
    2022-10-24 19:45:29.256[TRACE] : Preparing to begin transaction via JDBC Connection.setAutoCommit(false)
    2022-10-24 19:45:29.256[TRACE] : Transaction begun via JDBC Connection.setAutoCommit(false)
    2022-10-24 19:45:29.256[TRACE] : ResourceLocalTransactionCoordinatorImpl#afterBeginCallback
    2022-10-24 19:45:29.256[DEBUG] : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@4c09c2b8]
    2022-10-24 19:45:29.256[TRACE] : Getting transaction for [com.backend.connectable.user.service.UserService.modifyUserByUserDetails]
    
    // CGLIB 프록시가 target 서비스를 직접 호출한다
    2022-10-24 19:45:29.260[INFO ] : $$ SERVICE METHOD START $$
    
    // 어플리케이션 단에서 트랜잭션이 필요없다면, 생성하지 않는다. 
    // 하지만 조회된 엔티티의 경우 영속성 컨텍스트의 혜택을 받을 수 있다. 우리에겐 엔티티 매니저 있으니까
    // 열심히 쿼리 날린다
    2022-10-24 19:45:29.260[TRACE] : No need to create transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findByKlaytnAddress]: This method is not transactional.
    2022-10-24 19:45:29.261[DEBUG] : Rendered criteria query -> select generatedAlias0 from User as generatedAlias0 where generatedAlias0.klaytnAddress=:param0
    2022-10-24 19:45:29.261[TRACE] : Located HQL query plan in cache (select generatedAlias0 from User as generatedAlias0 where generatedAlias0.klaytnAddress=:param0)
    2022-10-24 19:45:29.261[TRACE] : Find: select generatedAlias0 from User as generatedAlias0 where generatedAlias0.klaytnAddress=:param0
    2022-10-24 19:45:29.261[TRACE] : Named parameters: {param0=0x3ab31d219d45ce40d6862d68d37de6bb73e21a8d}
    2022-10-24 19:45:29.261[DEBUG] : 
        select
            user0_.id as id1_5_,
            user0_.is_active as is_activ2_5_,
            user0_.klaytn_address as klaytn_a3_5_,
            user0_.nickname as nickname4_5_,
            user0_.phone_number as phone_nu5_5_,
            user0_.privacy_agreement as privacy_6_5_ 
        from
            user user0_ 
        where
            user0_.klaytn_address=?
    2022-10-24 19:45:29.261[TRACE] : Registering statement [HikariProxyPreparedStatement@502302921 wrapping prep3: select user0_.id as id1_5_, user0_.is_active as is_activ2_5_, user0_.klaytn_address as klaytn_a3_5_, user0_.nickname as nickname4_5_, user0_.phone_number as phone_nu5_5_, user0_.privacy_agreement as privacy_6_5_ from user user0_ where user0_.klaytn_address=?]
    2022-10-24 19:45:29.261[TRACE] : Registering last query statement [HikariProxyPreparedStatement@502302921 wrapping prep3: select user0_.id as id1_5_, user0_.is_active as is_activ2_5_, user0_.klaytn_address as klaytn_a3_5_, user0_.nickname as nickname4_5_, user0_.phone_number as phone_nu5_5_, user0_.privacy_agreement as privacy_6_5_ from user user0_ where user0_.klaytn_address=?]
    2022-10-24 19:45:29.261[TRACE] : binding parameter [1] as [VARCHAR] - [0x3ab31d219d45ce40d6862d68d37de6bb73e21a8d]
    2022-10-24 19:45:29.261[TRACE] : Bound [2] parameters total
    2022-10-24 19:45:29.261[TRACE] : Registering result set [HikariProxyResultSet@527341005 wrapping rs21: org.h2.result.LocalResultImpl@2074a17f columns: 6 rows: 1 pos: -1]
    2022-10-24 19:45:29.261[TRACE] : Processing result set
    2022-10-24 19:45:29.261[DEBUG] : Result set row: 0
    2022-10-24 19:45:29.261[TRACE] : extracted value ([id1_5_] : [BIGINT]) - [1]
    2022-10-24 19:45:29.261[DEBUG] : Result row: EntityKey[com.backend.connectable.user.domain.User#1]
    2022-10-24 19:45:29.262[TRACE] : Initializing object from ResultSet: [com.backend.connectable.user.domain.User#1]
    2022-10-24 19:45:29.262[TRACE] : Hydrating entity: [com.backend.connectable.user.domain.User#1]
    2022-10-24 19:45:29.262[TRACE] : extracted value ([is_activ2_5_] : [BOOLEAN]) - [true]
    2022-10-24 19:45:29.262[TRACE] : extracted value ([klaytn_a3_5_] : [VARCHAR]) - [0x3ab31d219d45ce40d6862d68d37de6bb73e21a8d]
    2022-10-24 19:45:29.262[TRACE] : extracted value ([nickname4_5_] : [VARCHAR]) - [joel]
    2022-10-24 19:45:29.262[TRACE] : extracted value ([phone_nu5_5_] : [VARCHAR]) - [010-1111-1111]
    2022-10-24 19:45:29.262[TRACE] : extracted value ([privacy_6_5_] : [BOOLEAN]) - [true]
    2022-10-24 19:45:29.262[TRACE] : Done processing result set (1 rows)
    2022-10-24 19:45:29.262[TRACE] : Total objects hydrated: 1
    2022-10-24 19:45:29.262[DEBUG] : Resolving attributes for [com.backend.connectable.user.domain.User#1]
    2022-10-24 19:45:29.262[DEBUG] : Processing attribute `isActive` : value = true
    2022-10-24 19:45:29.262[DEBUG] : Attribute (`isActive`)  - enhanced for lazy-loading? - false
    2022-10-24 19:45:29.262[DEBUG] : Processing attribute `klaytnAddress` : value = 0x3ab31d219d45ce40d6862d68d37de6bb73e21a8d
    2022-10-24 19:45:29.262[DEBUG] : Attribute (`klaytnAddress`)  - enhanced for lazy-loading? - false
    2022-10-24 19:45:29.262[DEBUG] : Processing attribute `nickname` : value = joel
    2022-10-24 19:45:29.262[DEBUG] : Attribute (`nickname`)  - enhanced for lazy-loading? - false
    2022-10-24 19:45:29.262[DEBUG] : Processing attribute `phoneNumber` : value = 010-1111-1111
    2022-10-24 19:45:29.262[DEBUG] : Attribute (`phoneNumber`)  - enhanced for lazy-loading? - false
    2022-10-24 19:45:29.262[DEBUG] : Processing attribute `privacyAgreement` : value = true
    2022-10-24 19:45:29.262[DEBUG] : Attribute (`privacyAgreement`)  - enhanced for lazy-loading? - false
    2022-10-24 19:45:29.262[DEBUG] : Done materializing entity [com.backend.connectable.user.domain.User#1]
    2022-10-24 19:45:29.262[TRACE] : Releasing statement [HikariProxyPreparedStatement@502302921 wrapping prep3: select user0_.id as id1_5_, user0_.is_active as is_activ2_5_, user0_.klaytn_address as klaytn_a3_5_, user0_.nickname as nickname4_5_, user0_.phone_number as phone_nu5_5_, user0_.privacy_agreement as privacy_6_5_ from user user0_ where user0_.klaytn_address=? {1: '0x3ab31d219d45ce40d6862d68d37de6bb73e21a8d'}]
    2022-10-24 19:45:29.262[TRACE] : Closing result set [HikariProxyResultSet@527341005 wrapping rs21: org.h2.result.LocalResultImpl@2074a17f columns: 6 rows: 1 pos: 1]
    2022-10-24 19:45:29.262[TRACE] : Closing prepared statement [HikariProxyPreparedStatement@502302921 wrapping prep3: select user0_.id as id1_5_, user0_.is_active as is_activ2_5_, user0_.klaytn_address as klaytn_a3_5_, user0_.nickname as nickname4_5_, user0_.phone_number as phone_nu5_5_, user0_.privacy_agreement as privacy_6_5_ from user user0_ where user0_.klaytn_address=? {1: '0x3ab31d219d45ce40d6862d68d37de6bb73e21a8d'}]
    2022-10-24 19:45:29.262[TRACE] : Starting after statement execution processing [ON_CLOSE]
    2022-10-24 19:45:29.262[TRACE] : Initializing non-lazy collections
    2022-10-24 19:45:29.262[INFO ] : @@USER_DETAILS_USER_OBJECT::com.backend.connectable.user.domain.User@20
    
    // 서비스 메서드가 끝났다. 트랜잭션을 종료하고, commit을 한다
    2022-10-24 19:45:29.262[INFO ] : $$ SERVICE METHOD DONE $$
    2022-10-24 19:45:29.262[TRACE] : Completing transaction for [com.backend.connectable.user.service.UserService.modifyUserByUserDetails]
    2022-10-24 19:45:29.262[DEBUG] : Initiating transaction commit
    2022-10-24 19:45:29.262[DEBUG] : Committing JPA transaction on EntityManager [SessionImpl(1131948410PersistenceContext[entityKeys=[EntityKey[com.backend.connectable.user.domain.User#1]], collectionKeys=[]];ActionQueue[insertions=ExecutableList{size=0} updates=ExecutableList{size=0} deletions=ExecutableList{size=0} orphanRemovals=ExecutableList{size=0} collectionCreations=ExecutableList{size=0} collectionRemovals=ExecutableList{size=0} collectionUpdates=ExecutableList{size=0} collectionQueuedOps=ExecutableList{size=0} unresolvedInsertDependencies=null])]
    2022-10-24 19:45:29.262[DEBUG] : committing
    2022-10-24 19:45:29.262[TRACE] : ResourceLocalTransactionCoordinatorImpl#beforeCompletionCallback
    2022-10-24 19:45:29.262[TRACE] : SessionImpl#beforeTransactionCompletion()
    2022-10-24 19:45:29.262[TRACE] : Automatically flushing session
    2022-10-24 19:45:29.262[TRACE] : Flushing session
    2022-10-24 19:45:29.262[DEBUG] : Processing flush-time cascades
    2022-10-24 19:45:29.262[TRACE] : Processing cascade ACTION_PERSIST_ON_FLUSH for: com.backend.connectable.user.domain.User
    2022-10-24 19:45:29.262[TRACE] : Done processing cascade ACTION_PERSIST_ON_FLUSH for: com.backend.connectable.user.domain.User
    
    // Dirty Checking을 진행한다. 엔티티의 스냅샷과 다르니, update 쿼리 날려보도록 하자
    2022-10-24 19:45:29.262[DEBUG] : Dirty checking collections
    2022-10-24 19:45:29.262[TRACE] : Flushing entities and processing referenced collections
    2022-10-24 19:45:29.262[TRACE] : com.backend.connectable.user.domain.User.nickname is dirty
    2022-10-24 19:45:29.262[TRACE] : com.backend.connectable.user.domain.User.phoneNumber is dirty
    2022-10-24 19:45:29.262[TRACE] : Found dirty properties [[com.backend.connectable.user.domain.User#1]] : [nickname, phoneNumber]
    2022-10-24 19:45:29.262[TRACE] : Updating entity: [com.backend.connectable.user.domain.User#1]
    2022-10-24 19:45:29.263[TRACE] : Processing unreferenced collections
    2022-10-24 19:45:29.263[TRACE] : Scheduling collection removes/(re)creates/updates
    2022-10-24 19:45:29.263[DEBUG] : Flushed: 0 insertions, 1 updates, 0 deletions to 1 objects
    2022-10-24 19:45:29.263[DEBUG] : Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections
    2022-10-24 19:45:29.263[DEBUG] : Listing entities:
    2022-10-24 19:45:29.263[DEBUG] : com.backend.connectable.user.domain.User{phoneNumber=010-0000-0000, klaytnAddress=0x3ab31d219d45ce40d6862d68d37de6bb73e21a8d, privacyAgreement=true, nickname=hello, id=1, isActive=true}
    2022-10-24 19:45:29.263[TRACE] : Executing flush
    2022-10-24 19:45:29.265[TRACE] : Updating entity: [com.backend.connectable.user.domain.User#1]
    2022-10-24 19:45:29.265[TRACE] : Initializing service [role=org.hibernate.engine.jdbc.batch.spi.BatchBuilder]
    2022-10-24 19:45:29.267[TRACE] : Starting after statement execution processing [ON_CLOSE]
    2022-10-24 19:45:29.267[DEBUG] : 
        update
            user 
        set
            is_active=?,
            klaytn_address=?,
            nickname=?,
            phone_number=?,
            privacy_agreement=? 
        where
            id=?
    2022-10-24 19:45:29.268[TRACE] : Registering statement [HikariProxyPreparedStatement@227393620 wrapping prep4: update user set is_active=?, klaytn_address=?, nickname=?, phone_number=?, privacy_agreement=? where id=?]
    2022-10-24 19:45:29.268[TRACE] : Dehydrating entity: [com.backend.connectable.user.domain.User#1]
    2022-10-24 19:45:29.268[TRACE] : binding parameter [1] as [BOOLEAN] - [true]
    2022-10-24 19:45:29.268[TRACE] : binding parameter [2] as [VARCHAR] - [0x3ab31d219d45ce40d6862d68d37de6bb73e21a8d]
    2022-10-24 19:45:29.268[TRACE] : binding parameter [3] as [VARCHAR] - [hello]
    2022-10-24 19:45:29.268[TRACE] : binding parameter [4] as [VARCHAR] - [010-0000-0000]
    2022-10-24 19:45:29.268[TRACE] : binding parameter [5] as [BOOLEAN] - [true]
    2022-10-24 19:45:29.268[TRACE] : binding parameter [6] as [BIGINT] - [1]
    
    // 쿼리 끝났으면 또 pstm, conn, rs 릴리즈
    2022-10-24 19:45:29.270[TRACE] : Releasing statement [HikariProxyPreparedStatement@227393620 wrapping prep4: update user set is_active=?, klaytn_address=?, nickname=?, phone_number=?, privacy_agreement=? where id=? {1: TRUE, 2: '0x3ab31d219d45ce40d6862d68d37de6bb73e21a8d', 3: 'hello', 4: '010-0000-0000', 5: TRUE, 6: 1}]
    2022-10-24 19:45:29.270[TRACE] : Closing prepared statement [HikariProxyPreparedStatement@227393620 wrapping prep4: update user set is_active=?, klaytn_address=?, nickname=?, phone_number=?, privacy_agreement=? where id=? {1: TRUE, 2: '0x3ab31d219d45ce40d6862d68d37de6bb73e21a8d', 3: 'hello', 4: '010-0000-0000', 5: TRUE, 6: 1}]
    2022-10-24 19:45:29.270[TRACE] : Starting after statement execution processing [ON_CLOSE]
    2022-10-24 19:45:29.270[TRACE] : Starting after statement execution processing [ON_CLOSE]
    2022-10-24 19:45:29.271[TRACE] : Starting after statement execution processing [ON_CLOSE]
    2022-10-24 19:45:29.271[TRACE] : Post flush
    2022-10-24 19:45:29.271[TRACE] : LogicalConnection#beforeTransactionCompletion
    2022-10-24 19:45:29.271[TRACE] : SynchronizationRegistryStandardImpl.notifySynchronizationsBeforeTransactionCompletion
    2022-10-24 19:45:29.271[TRACE] : Preparing to commit transaction via JDBC Connection.commit()
    2022-10-24 19:45:29.271[TRACE] : Transaction committed via JDBC Connection.commit()
    2022-10-24 19:45:29.271[TRACE] : re-enabling auto-commit on JDBC Connection after completion of JDBC-based transaction
    2022-10-24 19:45:29.271[TRACE] : LogicalConnection#afterTransaction
    2022-10-24 19:45:29.271[TRACE] : Releasing JDBC resources
    2022-10-24 19:45:29.271[TRACE] : Starting after statement execution processing [ON_CLOSE]
    2022-10-24 19:45:29.271[TRACE] : ResourceLocalTransactionCoordinatorImpl#afterCompletionCallback(true)
    2022-10-24 19:45:29.271[TRACE] : SynchronizationRegistryStandardImpl.notifySynchronizationsAfterTransactionCompletion(3)
    2022-10-24 19:45:29.271[TRACE] : SessionImpl#afterTransactionCompletion(successful=true, delayed=false)
    
    // OSIV가 인터셉터 단까지 걸려있기 때문에, 엔티티 매니저는 트랜잭션이 끝나도 살아있다.
    2022-10-24 19:45:29.271[DEBUG] : Not closing pre-bound JPA EntityManager after transaction
    2022-10-24 19:45:29.271[INFO ] : $$ Controller METHOD DONE $$
    
    // 뷰리졸버에서 이제 DTO를 JSON으로 바꿔주자
    2022-10-24 19:45:29.280[DEBUG] : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/cbor]
    2022-10-24 19:45:29.280[TRACE] : Writing [com.backend.connectable.user.ui.dto.UserModifyResponse@6398b197]
    2022-10-24 19:45:29.284[TRACE] : Not injecting HSTS header since it did not match request to [Is Secure]
    2022-10-24 19:45:29.287[TRACE] : No view rendering, null ModelAndView returned.
    
    // 인터셉터 끝났나보다. 이제 OSIV 닫자
    // 닫는거 보니까 필터에서 닫는거랑 똑같이 생겼다! 영속성 컨텍스트 이런거 싹다 파기시켜버려!
    2022-10-24 19:45:29.287[DEBUG] : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
    2022-10-24 19:45:29.287[TRACE] : Closing session [b88524e4-3fd7-4db1-b935-7b521c4b58fd]
    2022-10-24 19:45:29.287[TRACE] : Closing JDBC container [org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl@7e415931]
    2022-10-24 19:45:29.287[DEBUG] : HHH000420: Closing un-released batch
    2022-10-24 19:45:29.287[TRACE] : Starting after statement execution processing [ON_CLOSE]
    2022-10-24 19:45:29.287[TRACE] : Releasing JDBC resources
    2022-10-24 19:45:29.287[TRACE] : Starting after statement execution processing [ON_CLOSE]
    2022-10-24 19:45:29.287[TRACE] : Closing logical connection
    2022-10-24 19:45:29.287[TRACE] : Releasing JDBC resources
    2022-10-24 19:45:29.287[TRACE] : Starting after statement execution processing [ON_CLOSE]
    2022-10-24 19:45:29.287[TRACE] : Logical connection closed
    
    // 200 OK를 반환한다. 
    2022-10-24 19:45:29.287[DEBUG] : Completed 200 OK, headers={masked}
    

Transactional Readonly True?

  • 참고: https://willseungh0.tistory.com/75
  • 성능 향상의 이유
    • 강제로 플러시 호출하지 않으면 플러시 호출 안됨
    • 등록/수정/삭제 동작 안함
    • 영속성 컨텍스트 변경 감지 스냅샷 보관하지 않아 성능 향상

트랜잭션 수행 디버거와 함께

Spring은 항상 CGLIB을 쓸까?

  • 참고: https://docs.spring.io/spring-framework/docs/3.0.0.M3/reference/html/ch08s06.html
  • 참고: https://stackoverflow.com/questions/68084650/why-does-spring-inject-some-of-my-dependencies-as-cglib-proxies-and-why-are-the
  • Spring AOP는 JDK dynamic proxy 혹은 CGLIB 프록시를 통해 프록시를 생성한다.
    • JDK dynamic proxy를 통해 프록시를 만들 수 있다면 이걸로 우선적으로 만드려 한다.
    • 타겟 객체가 하나의 인터페이스라도 구현하고 있다면, JDK dynamic proxy가 생성될 것
    • 만약 인터페이스로 구현을 해둔게 없다면 CGLIB으로 프록시화 할 것
  • 물론 CGLIB으로 싹다 프록시화 시킬 수 있음. 하지만 다음을 유념할 것
    • final은 재정의 할 수 없기에 AOP 도입 못함
    • CGLIB 라이브러리가 꼭 필요합니다
    • 프록시 객체의 생성자는 2번 호출됨. 프록시화된 객체에 대한 서브클래스가 생성되는 과정에서 어쩔 수 없음
      • 프록시화 된 객체는 2개가 각각 생성됨.
      • JDK는 이게 2번 호출 안됨.
        • 생성자에 로직 없으면 큰 문제 안됨

JDK Dynamic Proxy

  • 공통으로 처리할 전/후처리 로직을 InvocationHandler로 구현하여 인터페이스를 구현할 때 사용하도록 하자
    AInterface target = new AImpl();
    // JDK 동적 프록시의 InvocationHandler를 구현한 구현체로, 
    // target 객체의 메서드 수행 전/후처리에 대한 로직을 담아둔다 
    TimeInvocationHandler handler = new TimeInvocationHandler(target);
    // AInterface 타입으로 프록시를 만든다
    // 따라서 실 구현체와는 아무런 연관이 없는 프록시가 생성이 된다
    // 그저 각 메서드 호출시 필요한 전/후처리를 담은 핸들러만 전달
    AInterface proxy = (AInterface) Proxy.newProxyInstance(AInterface.class.getClassLoader(), new Class[]{AInterface.class}, handler);
    proxy.call();
    
  • 스프링에서 사용하는 jdk - getProxy() 메서드
    public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
        Assert.notNull(config, "AdvisedSupport must not be null");
        if (config.getAdvisorCount() == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
            throw new AopConfigException("No advisors and no TargetSource specified");
        }
        this.advised = config;
        this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
        findDefinedEqualsAndHashCodeMethods(this.proxiedInterfaces);
    }
    
    @Override
    public Object getProxy() {
        return getProxy(ClassUtils.getDefaultClassLoader());
    }
    
    @Override
    public Object getProxy(@Nullable ClassLoader classLoader) {
        if (logger.isTraceEnabled()) {
            logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
        }
        return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
    }
    

CGLIB

  • 공통으로 처리할 전/후처리 로직을 MethodInterceptor로 구현하여 프록시 메서드 수행시 사용하도록 하자
    ConcreteService target = new ConcreteService();
    // CGLIB는 enhancer를 통한 프록시를 생성한다
    Enhancer enhancer = new Enhancer();
    // 상속받을 구체 클래스를 지정한다. 
    enhancer.setSuperclass(ConcreteService.class); 
    // CGLIB은 MethodInterceptor를 통해 프록시에 적용할 실행 로직을 할당
    enhancer.setCallback(new TimeMethodInterceptor());
    // 지정한 콘크리트 클래스를 상속받아 프록시를 생성한다. 
    ConcreteService proxy = (ConcreteService) enhancer.create();
    proxy.call();
    
  • 스프링에서 사용하는 cglib - getProxy() 메서드
    @Override
    public Object getProxy(@Nullable ClassLoader classLoader) {
        if (logger.isTraceEnabled()) {
            logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
        }
    
        try {
            Class<?> rootClass = this.advised.getTargetClass();
            Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");
    
            Class<?> proxySuperClass = rootClass;
            if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
                proxySuperClass = rootClass.getSuperclass();
                Class<?>[] additionalInterfaces = rootClass.getInterfaces();
                for (Class<?> additionalInterface : additionalInterfaces) {
                    this.advised.addInterface(additionalInterface);
                }
            }
    
            validateClassIfNecessary(proxySuperClass, classLoader);
    
            // Configure CGLIB Enhancer...
            Enhancer enhancer = createEnhancer();
            if (classLoader != null) {
                enhancer.setClassLoader(classLoader);
                if (classLoader instanceof SmartClassLoader &&
                        ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                    enhancer.setUseCache(false);
                }
            }
            enhancer.setSuperclass(proxySuperClass);
            enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
            enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
            enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
    
            // Callback 구현을 통한 인터셉터 등록
            Callback[] callbacks = getCallbacks(rootClass);
            Class<?>[] types = new Class<?>[callbacks.length];
            for (int x = 0; x < types.length; x++) {
                types[x] = callbacks[x].getClass();
            }
            enhancer.setCallbackFilter(new ProxyCallbackFilter(
                    this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
            enhancer.setCallbackTypes(types);
    
            return createProxyClassAndInstance(enhancer, callbacks);
        }
        catch (CodeGenerationException | IllegalArgumentException ex) {
            throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
                    ": Common causes of this problem include using a final class or a non-visible class",
                    ex);
        }
        catch (Throwable ex) {
            // TargetSource.getTarget() failed
            throw new AopConfigException("Unexpected AOP exception", ex);
        }
    }
    

DefaultAopProxyFactory

  • DefaultAopProxyFactory로 기본적으로 프록시화를 시키네!
    public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
    
        private static final long serialVersionUID = 7930414337282325166L;
    
        @Override
        public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
            if (!NativeDetector.inNativeImage() &&
                    (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
                Class<?> targetClass = config.getTargetClass();
                if (targetClass == null) {
                    throw new AopConfigException("TargetSource cannot determine target class: " +
                            "Either an interface or a target is required for proxy creation.");
                }
                // 타겟 클래스가 인터페이스 있으면 => Jdk
                if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
                    return new JdkDynamicAopProxy(config);
                }
                // 없으면 CGLIB
                return new ObjenesisCglibAopProxy(config);
            }
            else {
                return new JdkDynamicAopProxy(config);
            }
        }
    
        private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
            Class<?>[] ifcs = config.getProxiedInterfaces();
            return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
        }
    }