728x90

아이템1에서 적정 팩토리 메소드/팩토리 메소드, 선택적 매개변수가 많을 경우 적절히 대응하기 어렵다. 

 

해당 챕터에서 예시로 나오는 

점층적 생성자 패턴(telescoping constructor pattern)

위 패턴의 최악의 단점은  계속해서 내가 필요한 만큼 인스턴스를 늘려가야하는게 최악의 단점이다.

그리고 매개변수가 늘어가면 코드 가독성이 굉장히 떨어지게된다.

 

자바빈즈 패턴

객체 하나를 만들려면 메서드를 여러개 호출해야 하고, 개체가 완전히 생성되기 전까지는 일관성(consistency)이 무너진다.

또한 불변으로 만들 수 없다. 이 단점 보완을 위해 생성이 끝난 객체를 수동으로 얼리고(freezing) 얼리기 전에는 사용 할 수 없도록 해야한다.

 

 

위 2가지 패턴의 장점을 합친 방식이 빌더 패턴(Builder pattern)이다.

객체를 직접 만드는 대신 필수 매개변수만으로 생성자를 호출해 빌더 객체를 얻는다. 그리고 매개변수 없이 build 매서드를 통해 불변 객체를 얻을 수 있다.

빌더패턴은  명명된 선택적 매개변수(named optional paraemters)를 흉내 낸것이다.

이 패턴은  계측적으로 설계된 클래스와 쓰기 좋고 매개변수가 많을때 쓰면 좋다.

 

 

 

 

728x90
728x90

현재 스웨거 문서로만 적용이 되어 있는데 아무래도 문서 명세서로 보기에는 restdocs이 편할 듯하여 내가 주로 담당하는 리소스에 적용할려고 한다.

일단 스웨거와 restDocs 의 차이

 

  • Spring Rest Dcos
    • 장점
      • 실제 코드에 영향이 없음
      • 테스트코드 성공시에만 문서작성이 완료됨.
    • 단점
      • 적용하기 어려움
  • Swagger
    • 장점
      • API 테스트화면 제공
      • 적용이 매우 쉬운편
    • 단점
      • 실제 코드(프로덕션)에 어노테이션 추가
      • 실제코드와 동기화 안될 수 있음

 

와 같은 특징이 있지만 실제로 써본 결과 프론트 엔드 쪽 작업자가 있는 경우 스웨거가 있는것이 좀더 편하다고 하는사람이 많았다.
그러나 퇴사자 혹은 입사자가 많이 생기는 경우 restDoc 문서를 만들어 두는것이  파악하기에는 훨씬 좋다고 느껴져
2가지 다 적용이 되면 좋을 것 같다.

 

 

 

restDocs 작성 방법

작성에 앞서 몇까지 선택 해야하는 것들이 있는데
1. AsciiDoc VS Markdown => 일단 가이드 문서에 있는 adoc을 사용 했는데 markdown도 사용이 가능 한걸로 확인했다.
장점은 작성이 쉽다는데 공식문서를 따르기로 했다.

2. MockMvc(@WebMvcTest) VS Rest Assured(@SpringBootTest) => 문서 작성시 Mocking을 사용하여 작성
Rest Assured 는 BDD 스타일로 직관적이지만 별도의 구성없이는 @SpringBootTest 로 수행
전체 컨테스트를 로드하여 빈을 주입하기에 속도가 많이 느림.
MockMvc 는 @WebMvcTest 수행  controller Layer만 테스트하여 속도가 빠름 
단순 컨트롤러 테스트만 할 경우 MockMvc가 좋고 아닌 경우 는 Rest Assured가 좋음

JUNIT 외에  Spock라는게 있는데 작업을 할 프로젝트에는 Jnut 이기때문에 고려하지 않기로 한다.

 

 

적용 Project 버전 확인

Sprting boot  2.6.7
java 17
gradle 7

 

 

build.gradle 설정 추가

실제 적용된 코드 

id "org.asciidoctor.jvm.convert" version "3.3.2" //asciidoc 파일 변환 및  복사 플러그인 

configurations {
    asciidoctorExt 
}

ext {// snippets 파일이 저장될 경로 변수 설정
    snippetsDir = file('build/generated-snippets')
}


//dependency 추가
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor:2.0.6.RELEASE'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc:2.0.6.RELEASE'

test {// 테스트 실행 시 snippets를 저장할 경로로 snippetsDir 사용
	outputs.dir snippetsDir
}

// API 문서 생성
asciidoctor {
    dependsOn test
    inputs.dir snippetsDir
    attributes 'snippets': snippetsDir
}

tasks.register('copyDocument', Copy) {
    dependsOn asciidoctor
    from file("build/docs/asciidoc")
    into file("src/main/resources/static/docs")
}

build {
    dependsOn copyDocument
}
bootJar {
    dependsOn asciidoctor
    copy {
        from "${asciidoctor.outputDir}"
        into 'src/main/resources/static/docs'
    }

}

 

 

TestCode 작성

//실제 작성된 프로젝트 적용됨으로 컨트롤러와DTO 등 추가로 작성이 필요함

@AutoConfigureMockMvc // -> webAppContextSetup(webApplicationContext)
@AutoConfigureRestDocs // -> apply(documentationConfiguration(restDocumentation))
@SpringBootTest // @WebMvcTest(NaverOrderController.class) 사용하여도 무관
class NaverOrderControllerTest {
		@Autowired
    private MockMvc mockMvc;
		@MockBean
    private NaverOrderService naverOrderService;
		
		@Test
    @DisplayName("메뉴얼 네이버 상세 호출 RestDoc")
    void callManualNaverProductOrderChangedRestDoc() throws Exception {
			mockMvc.perform(patch("/api/partner/v1/naver/order/hope-delivery")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(arrivalDateRequest))
                        .accept(MediaType.APPLICATION_JSON)
                )
                .andDo(print())
                .andExpect(status().isOk())
                .andDo(document("hopeDeliveryRequest"
                        , preprocessRequest(prettyPrint())
                        , preprocessResponse(prettyPrint())
                        , requestFields(
                                fieldWithPath("data").type(JsonFieldType.ARRAY).description("data List")
                                , fieldWithPath("data[].orderSrl").type(JsonFieldType.STRING).description("의뢰번호")
                                , fieldWithPath("data[].productOrderId").type(JsonFieldType.STRING).description("상품주문번호")
                                , fieldWithPath("data[].hopeDeliveryYmd").type(JsonFieldType.STRING).description("희망일자")
                                , fieldWithPath("data[].hopDeliveryHm").type(JsonFieldType.STRING).description("희망시간").ignored()
                                , fieldWithPath("data[].region").type(JsonFieldType.STRING).description("지역").ignored()
                                , fieldWithPath("data[].changeReason").type(JsonFieldType.STRING).description("변경사유")
                                , fieldWithPath("data[].accountId").type(JsonFieldType.STRING).description("판매사계정")
                                )
                        , responseFields(
                                fieldWithPath("successList").type(JsonFieldType.ARRAY).description("success data")
                                , fieldWithPath("[]").description("성공주문번호").optional().type(JsonFieldType.STRING)
                                , fieldWithPath("failList").type(JsonFieldType.ARRAY).description("fail data")
                                ,fieldWithPath("failList[].productOrderId").type(JsonFieldType.STRING).description("실패 상품 주문 번호")
                                ,fieldWithPath("failList[].code").type(JsonFieldType.STRING).description("실패 코드")
                                ,fieldWithPath("failList[].desc").type(JsonFieldType.STRING).description("실패 설명")
                                )
                        )
                );
		}

//파라메터와 response 객체 및 호출 mocking이 필요함

}

 

위 테스트코드 실행 시 아래와 같은 파일들이 생성된다.

 

이후 해당 파일을 추출해서 만들려면 adoc 파일이 필요하다.

 

 

참고문서

https://spring.io/guides/gs/testing-restdocs/

728x90
728x90

현재 작업중인 리소스 리팩토링 하는 과정에서 monggoDB 트랜잭션이 작동하지 않는다는 사실을 전달받았다.

이에 실제로 지원을 하지 않는 것인지 추가로 지원이 되는지 부터 확인했다.

아래는 참조 문서이다.

 

https://www.baeldung.com/spring-data-mongodb-transactions

 

문서를 보니 spring boot transaction 이 적용될려면 몇가지 조건이 필요하다.

 

  1. MongoDB 버전이 4.0 이상
  2. 레플리카 셋 적용

 

회사 몽고디비를 확인하니 4.0 버전이 설치가 되어 있지만 레플리카 셋이 적용 되지 않았다.

직접 작업에 참여한것도 아니고 이전에 일하는 사람이 하고 퇴사했다는 애기만 들었다.

회사에서는 AWS documentDB를 이용하기 떄문에 아마 비용문제로 하나만 했을거로 예상이 된다.

그래도 권장 사양이 있는데 좀 의아하긴하다.

 

일단 몽고디비의 레플리카 셋구조를 보면

 

 

첫번째는 하나의 primary DB에 2개의 복제본인 secondary DB 가 붙는 구조

 

 

 

두번 째는 하나의 secondary에 arbiter 가 붙는 구조

 

 

 

  1. primary - 단 하나만 존재 가능하며 실제 클라이언트와 정보를 주고 받음.
  2. secondary - 장애시 프라이머리로 전환이 가능하며 클라이언트 일기 작업 분담 가능 (slave db 처럼)
  3. arbiter - 레플리카셋을 3대 이상의 홀수로 구성할 수 없을시, 투표권만을 가지고 레플리카 셋을 모니터링 역할.

개념적으로 이해 했을때는 위와같이 복제 디비가 존재해야지만 트랜잭션 롤백이 가능한것 으로 보인다.

실제 설치형 DB 의 경우 추가 설정을 할게 추가로 존재하지만 AWS document DB 의 경우 추가로 생성만 해주면 된다.

아래는 실제 적용 코드이다.

 

 

@Configuration
@EnableTransactionManagement
@EnableMongoRepositories(basePackages = "kr.howser.partner.repository.mongo")
public class MongoConfig extends AbstractMongoClientConfiguration {

    @Value("${spring.data.mongodb.uri}")
    private String uri;

    @Value("${spring.data.mongodb.data-base}")
    private String dataBaseName;

    @Override
    @NonNull
    protected String getDatabaseName() {
        return this.dataBaseName;
    }

    @Override
    @NonNull
    public MongoClient mongoClient() {
        final ConnectionString connectionString = new ConnectionString(this.uri);
        final MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
                .applyConnectionString(connectionString)
                .build();
        return MongoClients.create(mongoClientSettings);
    }

    @Primary
    @Bean(name = "mongoTransaction")
    public MongoTransactionManager transactionManager(MongoDatabaseFactory dbFactory) {
        return new MongoTransactionManager(dbFactory);
    }

    @Primary
    @Bean
    public MongoTemplate mongoTemplate() {
        MongoDatabaseFactory mongoDbFactory = new SimpleMongoClientDatabaseFactory(mongoClient(), this.dataBaseName);
        DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory);
        MongoMappingContext mongoMappingContext = new MongoMappingContext();
        mongoMappingContext.setFieldNamingStrategy(new SnakeCaseFieldNamingStrategy());
        MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
        converter.setTypeMapper(new DefaultMongoTypeMapper(null));//_class delete
        return new MongoTemplate(mongoDbFactory, converter);
    }

}

 

 

위와같이 트랜잭션을 걸어주면 된다. 트랜잭션 빈 설정 오류시는 이름을 꼭 추가로 해주어야한다.

MongoDatabaseFactory mongoDbFactory = new SimpleMongoClientDatabaseFactory(mongoClient(), this.dataBaseName);
        DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory);
        MongoMappingContext mongoMappingContext = new MongoMappingContext();
        mongoMappingContext.setFieldNamingStrategy(new SnakeCaseFieldNamingStrategy());
        MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
        converter.setTypeMapper(new DefaultMongoTypeMapper(null));//_class delete

 

 

 

이 부분의 경우 _class 를 지워줄때 사용한다. 필요한 부분이라 굳이 지워나 싶기는 하나 일단은 삭제 요청을 받아서 삭제가 되도록 ..

이번에 작업을 하면서 느끼지만 추가로 작업을 한다면 이론적인 부분 부터 꼭 하는 습관을 가지도록 더 정진해야겠다..

728x90
728x90

 

현재 재직중인 회사에서는 레거시 프로젝트의 기술부채가 심각하여 로그를 쌓고 있지만 해당 로그를 잘 활용하여 찾기는 어렵게되어 있다.

 

이에 내가 주로 작업하는 파트너 리소스에 traceId 를 생성하여 빠르게 로그를 추적하고자 한다.

 

이전에 다녔던 회사중 한곳은 쿠버네티스 환경의 모든 리소스의 로그가 트레이스 단위로 추적이 가능한 환경이 였는데 현재 회사에서는 기술부채로 인하여 새로운 시스템 도입이 어려운 상황이다.

이에 현재 내가 담당하고 주로 작업을 진행하는 파트너리소스에 접근하는 호출들에 대하여 요청 아이디를 생성하기로 했다. 내가 담당하는 부분말고도 다른부분도 모두 적용하여 trace를 생성하면 좋겠지만 일단 해당 리소스에만 작업하기로 한다.

일단 내가 원하는이상적인 방법은 spring cloud sleuth와 zipkin을 통하여 흩어 져 있는 리소스를 모두 취합하고

trace추적 및 span 단위 작업 등을 확인하는 이상적인 구조..

아래는 2가지 기능들의 참조 사이트 이다.

 

https://docs.spring.io/spring-cloud-sleuth/docs/2.2.x-SNAPSHOT/reference/html/

 

Spring Cloud Sleuth

Spring Cloud Sleuth automatically instruments all your Spring applications, so you should not have to do anything to activate it. The instrumentation is added by using a variety of technologies according to the stack that is available. For example, for a s

docs.spring.io

 

https://zipkin.io/

 

OpenZipkin · A distributed tracing system

Zipkin Zipkin is a distributed tracing system. It helps gather timing data needed to troubleshoot latency problems in service architectures. Features include both the collection and lookup of this data. If you have a trace ID in a log file, you can jump di

zipkin.io

 

일단 급한대로 다른 방법이 없는지 찾아보니 MDC((Mapped Diagnostic Contex) 라는 게 있어 이를 활용하기로 했다.

 

 

MDC는 로깅 프레임워크에서 사용되는 기능이며 현재 실행중인 컨텍스트(혹은 쓰레드)에 메타 정보를 넣고 관리할 수 있는 공간입니다.

각각의 로그 메시지에 대한 컨텍스트 정보를 추가하여 제공하고, 로그 메시지를 보다 구체적이고 유용하게 만들어줍니다.

내부적으로 쓰레드 로컬을 사용하며 Map 형태로 되어있어, Key Value 형태로 값을 보관하고 꺼낼 수 있어 관리에 용이합니다.

  • 즉 스레드 단위로 관리되는 Map을 사용합니다.

MDC를 사용하면 로깅 프레임워크에서 제공하는 로그 출력 형식에 특정 데이터를 쉽게 추가할 수 있습니다.

주로 다음과 같은 정보를 MDC에 설정하여 사용합니다:

  • 요청 ID: 각각의 웹 요청이나 작업에 대해 고유한 식별자를 설정
  • 세션 ID: 사용자 세션을 식별하기 위한 정보를 설정
  • 사용자 정보: 현재 사용자의 ID, 이름 등과 같은 정보를 설정
  • 트랜잭션 ID: 트랜잭션 처리에 대한 고유한 식별자를 설정
  • 로깅 수준: 특정 로그 메시지에 대한 로깅 수준을 설정

MDC는 주로 다중 스레드 환경에서 스레드 로컬 (ThreadLocal) 변수를 사용하여 각각의 스레드에 대해 독립적인 MDC 값을 설정하고 사용할 수 있습니다. 이를 통해 멀티스레드 환경에서도 로그 메시지에 대한 정확한 컨텍스트 정보를 유지할 수 있습니다.

예를 들어, 웹 애플리케이션에서 각각의 HTTP 요청에 대해 고유한 요청 ID를 MDC에 설정하면, 해당 요청과 관련된 모든 로그 메시지에 자동으로 요청 ID가 포함됩니다. 이를 통해 로그 메시지를 필터링하거나 추적하는 등의 작업을 보다 쉽게 수행할 수 있습니다.

MDC는 Java에서는 로깅 프레임워크인 SLF4J (Simple Logging Facade for Java)와 함께 사용됩니다.

 

 

실제 적용 코드

 

일단 요청이 들어오는 시점에 최초로 MDC 요청아이디와 스레드 아이디를 달아주고자 doFilter를 활용했다.

 

 

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MDCRequestFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        final UUID uuid = UUID.randomUUID();
        MDC.put("request_id", uuid.toString());
        MDC.put("thread_id", Thread.currentThread().getName());
        chain.doFilter(request, response);
        MDC.clear();

    }
}

 

 

logback 에서는 추가된 요청 아이디와 스레드 아이드를 추가해준다.

 

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
		<layout class="ch.qos.logback.classic.PatternLayout">
			<pattern>[%d{yyyy.MM.dd HH:mm:ss.SSS}] - [%-5level] - [%X{request_id}] - [%logger{5}] - [%X{thread_id}] [%method:%line] - %msg%n</pattern>
		</layout>
	</appender>

 

 

실제 생성된 로그

 

 

 

생각보다 간단하게 적용 되었다. 현재 내용에는 빠져 있지만 completeFuture 비동기 처리 시 요청 아이디를 찾지 못하여 해당 값은 추가 처리를 통하여 처리하였다.

 

추가로 멀티 쓰레드 처리 시 추가 설정및 확인이 필요하다.

728x90

'코드 > dev' 카테고리의 다른 글

spring Rest Docs  (0) 2024.03.03
Spring boot MongoDB transaction  (0) 2024.02.28
NoHttpResponseException 해결 방법  (3) 2024.02.26
Hikari pool맥스 사이즈 문제  (1) 2024.02.26
form 요청 PATCH , PUT, DELETE는 불가능한 이유  (2) 2024.02.26
728x90

외부 연동 리소스 작업중 간헐적으로 443 NoHttpResponseException 에러가 떨어졌다.

타겟 서버에서 유효하지 않은 HTTP 응답으로 제대로 응답하기를 실패했다는 신호라고 나오는데

더 알아보면 HTTP 1.1 / KEEP -ALIVE가 보장될떄 나올 수 있는 오류이다.

즉 , 커넥션이 유지될떄 호출이 되면 해당 타겟 서버에서는 이미 응답을 줬다는 의미로 해당 exception 이 떨어진다.

최초에는 커넥션 을 빠르게 끊고 다시 맺는 옵션을 추가하였습니다.

 

 

final int INVALID_CONNECTION_CLOSE_TIME = 5 * 1000;
        manager.setValidateAfterInactivity(INVALID_CONNECTION_CLOSE_TIME);
//위 옵션을 restTemplate bean 생성이 추가

 

 

위와 같은 조치를 했지만 여전히 발생을 했습니다.

여러 레퍼런스와 스택오버플로우를 보니 위와 같은 문제 발생시 자제 재 호출하는 방법 밖에 없다는 결론에 도달했습니다.

 

 

 private static HttpRequestRetryHandler retryHandler() {
        return (exception, executionCount, context) -> {
            if (executionCount > 10) {//5
                return false;
            }

            HttpClientContext clientContext = HttpClientContext.adapt(context);
            HttpRequest request = clientContext.getRequest();

            if (exception instanceof java.net.SocketException && exception.getMessage()
                    .equals("Connection reset") || exception instanceof NoHttpResponseException) {
                log.info(
                        "RetryHandler Connection reset executionCount - {}, totalCount - {}, request - {}",
                        executionCount, 10,
                        request);

                // 2번 요청 이후부터는 2초 후 재요청
                if (executionCount >= 2) {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        log.warn("Interrupted {} ", e.getMessage(), e);
//                        LOGGER.log(Level.WARN, "Interrupted!", e);
                        /* Clean up whatever needs to be handled before interrupting  */
                        Thread.currentThread().interrupt();
                    }
                }
                return true;
            }

            return false;
        };
    }

 

 

위와 같이 retryHandler에 NoHttpResponseException 재호출 되도록 설정 했습니다.

설정 이후에는 해당 오류는 발생하지 않았습니다.

 

참고 레퍼런스

https://stackoverflow.com/questions/26111331/org-apache-http-nohttpresponseexception-xx-xx-xx-xx443-failed-to-respond

 

org.apache.http.NoHttpResponseException: XX.XX.XX.XX:443 failed to respond

Currently I am using Apache http components client V4.3.5. In my case, I can upload small file(1kb), but it is not working on large file(100kb) when I run the code and get the exception "org.apache...

stackoverflow.com

 

https://hc.apache.org/httpclient-legacy/exception-handling.html

728x90

'코드 > dev' 카테고리의 다른 글

Spring boot MongoDB transaction  (0) 2024.02.28
traceId 로그 생성  (0) 2024.02.28
Hikari pool맥스 사이즈 문제  (1) 2024.02.26
form 요청 PATCH , PUT, DELETE는 불가능한 이유  (2) 2024.02.26
Swagger UI  (2) 2023.11.27
728x90

API 구현시 적은 트래픽에서는 문제가 없지만 트래픽이 늘어나면 hikari pool connection timeout 발생으로 요청을 처리하지 못하는 문제를 겪은 적이 있다.

모니터링을 하다보면 hikariPool 에서 connection을 얻을 수 없다고 한다.

이런 경우 나를 포함해서 통상 이렇게 해결을 했을 것이다.

  • pool 사이즈를 늘려서 해결

사이즈를 늘려서 해결 할 수 있지만 이게 과연 정답인지에 대하여 생각을 해볼 필요가 있다.

Deadlock 발생으로 인하여 생기는 문제

트래픽이 몰리는 상황에서는 Threand간 커넥션을 차지 하기위해서 레이스 커넥션이 발생

한 트랜잭션에 2개의 커넥션이? 필요

우리가 생각할때 하나의 트랜잭션당 하나의 풀만 있으면 될것 같지만 그렇지 않기떄문에 풀사이즈를 일단 늘리면 해결이 되지만 정확한 숫자는 하단의 공식을 참조하는게 좋다.

 

 

 

우리가 생각할때 하나의 트랜잭션당 하나의 풀만 있으면 될것 같지만 그렇지 않기떄문에 풀사이즈를 일단 늘리면 해결이 되지만 정확한 숫자는 하단의 공식을 참조하는게 좋다

 

 

같이 일을 했었던 분이 알려준 참고하라는 공식


우형에서 사용하는 hikaricp 풀사이즈 공식
Tn : cpu 쓰레드 갯수
cm : 하나의 task에서 동시에 필요한 커넥션 갯수

pool size = Tm * (Cm-1) + (Tn/2)

히카리에서 제안하는 계산공식
Tm * (Cm-1) + 1

 

 

일을 하고 문제를 확인하고 해결을 할떄마다 항상 새로운걸 배우고 공부하게되는데 이런걸 먼저 확인하고 해결하는사람들은 참 존경스럽다..

 

참고했던 포스팅 

https://techblog.woowahan.com/2664/

 

HikariCP Dead lock에서 벗어나기 (이론편) | 우아한형제들 기술블로그

{{item.name}} 안녕하세요! 공통시스템개발팀에서 메세지 플랫폼 개발을 하고 있는 이재훈입니다. 메세지 플랫폼 운영 장애를 바탕으로 HikariCP에서 Dead lock이 발생할 수 있는 case와 Dead lock을 회피할

techblog.woowahan.com

 

https://soongjamm.tistory.com/156

 

(성능테스트) Hikari pool connection 데드락 해결

안녕하세요. 최근 진행중인 프로젝트에서 최근 주문 API를 구현하고, JMeter를 이용해 부하 테스트를 진행한 적이 있습니다. 적은 트래픽에서는 문제가 없었는데, 부하를 일으키니 Hikari pool connectio

soongjamm.tistory.com

 

728x90

'코드 > dev' 카테고리의 다른 글

traceId 로그 생성  (0) 2024.02.28
NoHttpResponseException 해결 방법  (3) 2024.02.26
form 요청 PATCH , PUT, DELETE는 불가능한 이유  (2) 2024.02.26
Swagger UI  (2) 2023.11.27
슬로우 쿼리 문제 (mariadb boolean column index)  (0) 2023.11.24

+ Recent posts