Home

0

Spring 핵심원리 고급편 - 동시성 이슈

목차 Post not found: springboot/spring-aop/threadlocal/threadlocal-01 Post not found: springboot/spring-aop/threadlocal/threadlocal-02 Post not found: springboot/spring-aop/threadlocal/threadlocal-03 Post not found: springboot/spring-aop/threadlocal/threadlocal-04 Post not found: springboot/spring-aop/threadlocal/threadlocal-05 Post not found: springboot/spring-aop/threadlocal/threadlocal-06 동시성 이슈 발생 여러 Thread가 동시에 Application 로직을 호출하게 되면서 Level 상태값이 꼬여서 보이게 된다. Thread 로 구분해서 로그 확인2021-12-06 02:18:26.028 INFO 22044 --- [nio-8080-exec-1] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] OrderController.request()2021-12-06 02:18:26.029 INFO 22044 --- [nio-8080-exec-1] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] |-->OrderService.orderItem()2021-12-06 02:18:26.029 INFO 22044 --- [nio-8080-exec-1] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | |-->OrderRepository.save()2021-12-06 02:18:27.033 INFO 22044 --- [nio-8080-exec-1] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | |<--OrderRepository.save() time=1004ms2021-12-06 02:18:27.033 INFO 22044 --- [nio-8080-exec-1] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] |<--OrderService.orderItem() time=1004ms2021-12-06 02:18:27.033 INFO 22044 --- [nio-8080-exec-1] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] OrderController.request() time=1005ms 2021-12-06 02:18:26.114 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | |-->OrderController.request()2021-12-06 02:18:26.115 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | | |-->OrderService.orderItem()2021-12-06 02:18:26.115 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | | | |-->OrderRepository.save()2021-12-06 02:18:27.119 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | | | |<--OrderRepository.save() time=1004ms2021-12-06 02:18:27.120 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | | |<--OrderService.orderItem() time=1005ms2021-12-06 02:18:27.120 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | |<--OrderController.request() time=1006ms Thread 를 이용한 테스트 코드 작성@Slf4jpublic class FieldService { private String nameStore; public String logic(String name){ log.info("저장 name={} -> nameStore = {}", name, nameStore); nameStore = name; sleep(1000); log.info("조회 nameStore = {}", nameStore); return nameStore; } private void sleep(int millis) { try { Thread.sleep(millis); }catch (InterruptedException e){ e.printStackTrace(); } }}

0

Spring 핵심원리 고급편 - 필드 동기화 적용

목차 Post not found: springboot/spring-aop/threadlocal/threadlocal-01 Post not found: springboot/spring-aop/threadlocal/threadlocal-02 Post not found: springboot/spring-aop/threadlocal/threadlocal-03 Post not found: springboot/spring-aop/threadlocal/threadlocal-04 Post not found: springboot/spring-aop/threadlocal/threadlocal-05 Post not found: springboot/spring-aop/threadlocal/threadlocal-06 필드 동기화 적용Bean 으로 등록@Configuration public class LogTraceConfig { @Bean public LogTrace logTrace() { return new FieldLogTrace(); } } @RestController@RequiredArgsConstructorpublic class OrderControllerV3 { private final OrderServiceV3 orderService; private final LogTrace trace; @GetMapping("/v3/request") public String request(String itemId){ System.out.println(itemId); TraceStatus status = null; try { status = trace.begin("OrderController.request()"); orderService.orderItem(itemId); trace.end(status); return "ok"; }catch (Exception e){ trace.exception(status, e); throw e; // 예외를 반드시 던져 줘야 한다. } }} @Service@RequiredArgsConstructorpublic class OrderServiceV3 { private final OrderRepositoryV3 orderRepository; private final LogTrace trace; public void orderItem (String itemId){ TraceStatus status = null; try { status = trace.begin("OrderService.orderItem()"); orderRepository.save(itemId); trace.end(status); }catch (Exception e){ trace.exception(status, e); throw e; // 예외를 반드시 던져 줘야 한다. } }} @Repository@RequiredArgsConstructorpublic class OrderRepositoryV3 { private final LogTrace trace; public void save(String itemId){ TraceStatus status = null; try { status = trace.begin("OrderRepository.save()"); // 저장 로직 if(itemId.equals("ex")){ throw new IllegalStateException("예외 발생!"); } sleep(1000); trace.end(status); }catch (Exception e){ trace.exception(status, e); throw e; // 예외를 반드시 던져 줘야 한다. } } private void sleep(int millis) { try{ Thread.sleep(millis); }catch (InterruptedException e){ e.printStackTrace(); } }}

0

Spring 핵심원리 고급편 - 필드 동기화

목차 Post not found: springboot/spring-aop/threadlocal/threadlocal-01 Post not found: springboot/spring-aop/threadlocal/threadlocal-02 Post not found: springboot/spring-aop/threadlocal/threadlocal-03 Post not found: springboot/spring-aop/threadlocal/threadlocal-04 Post not found: springboot/spring-aop/threadlocal/threadlocal-05 Post not found: springboot/spring-aop/threadlocal/threadlocal-06 필드 동기화public interface LogTrace { TraceStatus begin(String message); void end(TraceStatus status); void exception(TraceStatus status, Exception e);} 필드 동기화를 위한 로그 분석기 만들기 TraceId 객체가 없을 경우 새로 생성한다. TraceId 객체가 있을 경우 Level 을 증가 시킨다. private void syncTraceId(){ if(traceIdHolder == null){ traceIdHolder = new TraceId(); }else{ traceIdHolder = traceIdHolder.createNextId(); }} TraceId 내 Level 이 0일 경우 해당 객체를 null 로 초기화 한다. TraceId 내 Level 이 0이 아닐 경우 Level을 1 감소시킨다. private void releaseTraceId() { if(traceIdHolder.isFirstLevel()){ traceIdHolder = null; // destroy }else{ traceIdHolder = traceIdHolder.createPreviousId(); }} TraceId 객체는 파라미터로 전달되는 것이 아니라 traceIdHolder 필드에 저장된다.

0

Spring 핵심원리 고급편 - Logging Filter 파라미터 동기화 개발

목차 Post not found: springboot/spring-aop/log-trace/log-trace-01 Post not found: springboot/spring-aop/log-trace/log-trace-02 Post not found: springboot/spring-aop/log-trace/log-trace-03 Spring 핵심원리 고급편 - Logging Filter 파라미터 동기화 개발Logging Filter 파라미터 동기화 개발@Slf4j@Componentpublic class HelloTraceV2 { private static final String START_PREFIX = "-->"; private static final String COMPLETE_PREFIX = "<--"; private static final String EX_PREFIX = "<X-"; public TraceStatus begin(String message) { TraceId traceId = new TraceId(); Long startTimeMs = System.currentTimeMillis(); log.info("[{}] {}{}", traceId.getId(), addSpace(START_PREFIX, traceId.getLevel()), message); return new TraceStatus(traceId, startTimeMs, message); } public TraceStatus beginSync(TraceId beforeTraceId, String message) { TraceId nextId = beforeTraceId.createNextId(); Long startTimeMs = System.currentTimeMillis(); log.info("[{}] {}{}", nextId.getId(), addSpace(START_PREFIX, nextId.getLevel()), message); return new TraceStatus(nextId, startTimeMs, message); } public void end(TraceStatus status) { complete(status, null); } public void exception(TraceStatus status, Exception e) { complete(status, e); } private void complete(TraceStatus status, Exception e) { Long stopTimeMs = System.currentTimeMillis(); long resultTimeMs = stopTimeMs - status.getStartTimeMs(); TraceId traceId = status.getTraceId(); if (e == null) { log.info("[{}] {}{} time={}ms", traceId.getId(), addSpace(COMPLETE_PREFIX, traceId.getLevel()), status.getMessage(), resultTimeMs); } else { log.info("[{}] {}{} time={}ms ex={}", traceId.getId(), addSpace(EX_PREFIX, traceId.getLevel()), status.getMessage(), resultTimeMs, e.toString()); } } private static String addSpace(String prefix, int level) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < level; i++) { sb.append((i == level - 1) ? "|" + prefix : "| "); } return sb.toString(); }} @RestController@RequiredArgsConstructorpublic class OrderControllerV2 { private final OrderServiceV2 orderService; private final HelloTraceV2 trace; @GetMapping("/v2/request") public String request(String itemId){ System.out.println(itemId); TraceStatus status = null; try { status = trace.begin("OrderController.request()"); orderService.orderItem(status.getTraceId(), itemId); trace.end(status); return "ok"; }catch (Exception e){ trace.exception(status, e); throw e; // 예외를 반드시 던져 줘야 한다. } }} @Service@RequiredArgsConstructorpublic class OrderServiceV2 { private final OrderRepositoryV2 orderRepository; private final HelloTraceV2 trace; public void orderItem(TraceId traceId, String itemId){ TraceStatus status = null; try { status = trace.beginSync(traceId, "OrderService.orderItem()"); orderRepository.save(status.getTraceId(),itemId); trace.end(status); }catch (Exception e){ trace.exception(status, e); throw e; // 예외를 반드시 던져 줘야 한다. } }} @Repository@RequiredArgsConstructorpublic class OrderRepositoryV2 { private final HelloTraceV2 trace; public void save(TraceId traceId, String itemId){ TraceStatus status = null; try { status = trace.beginSync(traceId,"OrderRepository.save()"); // 저장 로직 if(itemId.equals("ex")){ throw new IllegalStateException("예외 발생!"); } sleep(1000); trace.end(status); }catch (Exception e){ trace.exception(status, e); throw e; // 예외를 반드시 던져 줘야 한다. } } private void sleep(int millis) { try{ Thread.sleep(millis); }catch (InterruptedException e){ e.printStackTrace(); } }}

0

Spring 핵심원리 고급편 - Logging Filter

목차 Post not found: springboot/spring-aop/log-trace/log-trace-01 Post not found: springboot/spring-aop/log-trace/log-trace-02 Post not found: springboot/spring-aop/log-trace/log-trace-03 Spring 핵심원리 고급편 - Logging Filter@RestController@RequiredArgsConstructorpublic class OrderControllerV1 { private final OrderServiceV1 orderService; private final HelloTraceV1 trace; @GetMapping("/v1/request") public String request(String itemId){ TraceStatus status = null; try { status = trace.begin("OrderController.request()"); orderService.orderItem(itemId); trace.end(status); return "ok"; }catch (Exception e){ trace.exception(status, e); throw e; // 예외를 반드시 던져 줘야 한다. } }} @Service@RequiredArgsConstructorpublic class OrderServiceV1 { private final OrderRepositoryV1 orderRepository; private final HelloTraceV1 trace; public void orderItem(String itemId){ TraceStatus status = null; try { status = trace.begin("OrderService.orderItem()"); orderRepository.save(itemId); trace.end(status); }catch (Exception e){ trace.exception(status, e); throw e; // 예외를 반드시 던져 줘야 한다. } }} @Repository@RequiredArgsConstructorpublic class OrderRepositoryV1 { private final HelloTraceV1 trace; public void save(String itemId){ TraceStatus status = null; try { status = trace.begin("OrderRepository.save()"); // 저장 로직 if(itemId.equals("ex")){ throw new IllegalStateException("예외 발생!"); } sleep(1000); trace.end(status); }catch (Exception e){ trace.exception(status, e); throw e; // 예외를 반드시 던져 줘야 한다. } } private void sleep(int millis) { try{ Thread.sleep(millis); }catch (InterruptedException e){ e.printStackTrace(); } }}

0

Spring 핵심원리 고급편 - Logging Filter

목차 Post not found: springboot/spring-aop/log-trace/log-trace-01 Post not found: springboot/spring-aop/log-trace/log-trace-02 Post not found: springboot/spring-aop/log-trace/log-trace-03 Spring 핵심원리 고급편 - Logging FilterTraceId - 트랜잭션 Id와 level을 관리public class TraceId { private String id; // 트랜잭션 ID private int level; // 깊이를 표현할 수 있는 Level public TraceId() { this.id = creatId(); this.level = 0; } private TraceId(String id, int level){ this.id = id; this.level = level; } public String creatId() { return UUID.randomUUID().toString().substring(0, 8); } public TraceId createNextId(){ return new TraceId(id, level+1); } // 트랜잭션 ID 는 기존과 같고 Level 은 하나 감소시킨다. public TraceId createPreviousId(){ return new TraceId(id, level-1); } public boolean isFirstLevel(){ return level == 0; }} TraceStatus - 로그 상태 정보를 나타낸다. traceId 내부에 트랜잭션 Id 와 Level 을 가지고 있다. startTimeMs 로그 시작 시간이다. 로그 종료시 이 시작 시간을 기준으로 시작 ~ 종료까지 수행시간을 구할 수 있다. message 시작시 사용한 메시지다. 로그 종료시에 사용해 출력한다. public class TraceStatus { private TraceId traceId; private Long startTimeMs; private String message; public TraceStatus(TraceId traceId, Long startTimeMs, String message) { this.traceId = traceId; this.startTimeMs = startTimeMs; this.message = message; } public TraceId getTraceId() { return traceId; } public Long getStartTimeMs() { return startTimeMs; } public String getMessage() { return message; }} @Slf4j@Componentpublic class HelloTraceV1 { private static final String START_PREFIX = "-->"; private static final String COMPLETE_PREFIX = "<--"; private static final String EX_PREFIX = "<X-"; public TraceStatus begin(String message) { TraceId traceId = new TraceId(); Long startTimeMs = System.currentTimeMillis(); log.info("[{}] {}{}", traceId.getId(), addSpace(START_PREFIX, traceId.getLevel()), message); return new TraceStatus(traceId, startTimeMs, message); } public void end(TraceStatus status) { complete(status, null); } public void exception(TraceStatus status, Exception e) { complete(status, e); } private void complete(TraceStatus status, Exception e) { Long stopTimeMs = System.currentTimeMillis(); long resultTimeMs = stopTimeMs - status.getStartTimeMs(); TraceId traceId = status.getTraceId(); if (e == null) { log.info("[{}] {}{} time={}ms", traceId.getId(), addSpace(COMPLETE_PREFIX, traceId.getLevel()), status.getMessage(), resultTimeMs); } else { log.info("[{}] {}{} time={}ms ex={}", traceId.getId(), addSpace(EX_PREFIX, traceId.getLevel()), status.getMessage(), resultTimeMs, e.toString()); } } private static String addSpace(String prefix, int level) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < level; i++) { sb.append((i == level - 1) ? "|" + prefix : "| "); } return sb.toString(); }} 테스트 코드 작성

0

STL - map 컨테니어

map 연관 컨테이너 에서 원소로 Key와 Value의 쌍으로 저장되는 컨테이너[] 연산자를 이용해 value 에 접근 할 수 있다. (value = m[key] 형태로 사용) key 는 중복해서 저장될 수 없다. 노드 기반 컨테이너 균현 이진 트리로 구현돼 있다. Map Member 함수 함수 설명 반환 값 begin() 첫번째 원소를 가르키는 iterator 를 가져온다 iterator end() 마지막 원소를 가르키는 다음 iterator 를 가져온다 iterator clear() map 내 모든 원소를 삭제한다 empty() map 이 비었는지 확인한다 bool size() map 에 저장된 원소의 개수를 가져온다 int find(key) map 내 key 가 저장된 iterator 를 가져온다. 없으면 end iterator를 반환한다 iterator count(key) map 내 key 의 개수를 가져온다 int erase(key) map 내 key 원소를 삭제한후 다음 원소를 가르키는 itrator를 반환한다 iterator erase(iterator) iterator 가 가르키는 원소를 삭제한 후 다음 원소를 가르키는 iterator를 반환한다 iterator erase(begin, end) iterator begin에서 end까지 원소를 삭제한 후 다음 원소를 가르키는 iterator를 반환한다 iterator insert(key) map 에 key 를 삽입한 후 저장된 iterator와 성공 여부를 담는 Pair 객체를 반환한다 Pair<iterator, bool> insert(begin, end) iterator begin에서 end까지 원소를 map 에 삽입한다. lower_bound(key) key 값 보다 같거나 큰 값이 처음으로 나타나는 iterator 를 반환한다. (이상) iterator upper_bound(key) key 값 보다 큰 값이 처음으로 나타나는 iterator 를 반환한다. (초과) iterator

0

STL - set 컨테이너

set 연관 컨테이너 에서 key 라 불리는 원소의 집합으로 이뤄진 컨테이너 key 는 중복해서 저장될 수 없다. 노드 기반 컨테이너 균현 이진 트리로 구현돼 있다. 시퀸스 컨테이너로 배열과 비슷하지만 동적으로 데이터를 추가할 수 있고, 크기가 자동으로 늘어난다는 장점이 있다. Set Member 함수 함수 설명 반환 값 begin() 첫번째 원소를 가르키는 iterator 를 가져온다 iterator end() 마지막 원소를 가르키는 다음 iterator 를 가져온다 iterator clear() set 내 모든 원소를 삭제한다 empty() set 이 비었는지 확인한다 bool size() set 에 저장된 원소의 개수를 가져온다 int find(key) set 내 key 가 저장된 iterator 를 가져온다. 없으면 end iterator를 반환한다 iterator count(key) set 내 key 의 개수를 가져온다 int erase(key) set 내 key 원소를 삭제한후 다음 원소를 가르키는 itrator를 반환한다 iterator erase(iterator) iterator 가 가르키는 원소를 삭제한 후 다음 원소를 가르키는 iterator를 반환한다 iterator erase(begin, end) iterator begin에서 end까지 원소를 삭제한 후 다음 원소를 가르키는 iterator를 반환한다 iterator insert(key) set 에 key 를 삽입한 후 저장된 iterator와 성공 여부를 담는 Pair 객체를 반환한다 Pair<iterator, bool> insert(begin, end) iterator begin에서 end까지 원소를 set 에 삽입한다. lower_bound(key) key 값 보다 같거나 큰 값이 처음으로 나타나는 iterator 를 반환한다. (이상) iterator upper_bound(key) key 값 보다 큰 값이 처음으로 나타나는 iterator 를 반환한다. (초과) iterator insert(begin, end) 다른 컨테이너어 저장된 데이터를 set 에 데이터를 넣을때 사용하면 유용한 함수다.

0

STL - vector 컨테이너

vector 시퀸스 컨테이너로 배열과 비슷하지만 동적으로 데이터를 추가할 수 있고, 크기가 자동으로 늘어난다는 장점이 있다. Vector Member 함수 함수 설명 반환 값 push_back(value) vector 끝에 value 를 넣는다 pop_back() vector 의 마지막 원소를 삭제한다 begin() vector 의 첫번째 원소를 가르키는 iterator를 가져온다 Iterator end() vector 의 마지막 원소 다음을 가르키는 iterator를 가져온다. Iterator clear() vector 내 모든 원소를 삭제한다. size() vector 에 저장된 원소의 개수를 반환한다. int empty() vector 가 비었는지 확인한다. bool insert(iterator, value) 해당 iterator가 가르키는 위치에 value를 삽입한다. insert(iterator, begin, end) 해당 iterator가 가르키는 위치에 특정 iterator 가르키는 begin 에서 end 까지 원소를 삽입한다. erase(iterator) 해당 iterator가 가르키는 원소를 삭제한다. 반환 값으로 다음 iterator 를 반환한다. Iterator erase(begin, end) begin 에서 end 범위까지 원소를 삭제한다. 반환 값으로 삭제한 마지막 원소 다음 iterator를 가져온다. Iterator assign(n, value) vector 에 value를 n 개 만큼 할당한다. 이전 값이 있더라도 초기화 되고 재할당된다. assign(begin, end) vector 값을 특정 iterator 가르키는 begin 에서 end 범위 값으로 할당한다 이전 값이 있더라도 초기화되고 재할당된다. at(index) index가 가르키는 value를 가져온다. 데이터 삽입 함수push_back 함수 push_back 함수는 vector 마지막에 원소를 넣어준다. #include <iostream>#include <vector>using namespace std;int main(void) { vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); for (int a : v) { cout << a << " "; } cout << endl; return 0;} 1 2 3

0

JPA 연관 관계 - @MappedSuperclass

목차 JPA 연관 관계 - 고아 객체 JPA 연관 관계 - 즉시로딩과 지연로딩 JPA 연관 관계 - 프록시 객체 JPA 연관 관계 - @MappedSuperclass JPA 연관 관계 - 상속 관계 Mapping JPA 연관 관계 - 영속성 전이 Cascade JPA 연관 관계 - 양방향 연관관계와 연관과계의 주인 JPA 연관 관계 - 양방향 연관관계 JPA 연관 관계 - 객체 지향 스럽게 모델링 하기 JPA 연관 관계 - 객체 관계 모델링하기 JPA 연관 관계 공통 Mapping 정보를 관리하기 위한 MappedSuperclass Entity 에 공통적으로 들어가는 Mapping 정보를 관리해 코드가 중복해서 작성하는 것을 방지하기 위해 사용하는 어노테이션 상속관계 Mapping 이 아니다. 부모 클래스를 상속 받는 자식 클래스에 Mapping 정보만 을 제공한다. 직접 생성해서 사용할 일이 없으므로 추상클래스(abstract) 를 사용하는 것이 좋다. MappedSuperclass 클래스를 상속 받기 위해서는 같은 @MappedSuperclass 를 사용한 클래스나 @Entity 을 이용한 클래스만 상속이 가능하다. 요구 사항 모든 Entity 에는 생성자, 생성일, 수정자, 수정일 정보가 들어가야 한다. 모든 Entity 에 공통적으로 들어가는 Mapping 정보 를 관리하기 위한 BaseEntity 클래스를 생성한다. @MappedSuperclasspublic abstract class BaseEntity { private String createdBy; private LocalDateTime createdDate; private String lastModifiedBy; private LocalDateTime lastModifiedDate;}