[Routine] 15 주차 시작!

[Routine] 15 주차 시작!

2023년 4월 24일 부터 4월 30일 까지의 나의 루틴.

#목차

2023-04-24

  • 오늘도 어김없이 영한 님의 인강을 들으면서 출근하였다.
  • 근데 영한 님의 마지막 말이 너무 마음에 와닿다.
  • 개발이 재미있고 적성에 맞는 거 같은데, 매일 야근 또는 공부하는 것들이 지칠 때도 있다.
  • 이게 나의 길이 맞나?.. 뭐 등등의 고민들…
  • 결국 답은 꾸준함이라고 한다. 꾸준함을 가지고 흔들리지 말자!라는 생각이 들었다.
  • 뭔가.. 이런 생각이 들 때마다 아.. 나도 이제 늙었구나..라고 생각이 든다..ㅠㅠ

2023-04-25

2023-04-26 스터디 발표

  • 오늘도 어김없이 영한 님의 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 인강을 들으며 출근하였다.
  • 어제 개발팀 전체 회식이어서 늦게까지 회식하고 나니 피곤해서 오늘은 좀 늦게 출근하였다.😝
  • 오늘은 원래 매주 목요일이던 스터디를 이번 주만 하루 앞당겨 진행하기로 하였다.
  • 직원 중 한 명이 예비군으로 인해 참석을 못 할 수 있어 하루 앞당겨 스터디에 참여하여 같이 진행하려고 하루 앞당겼다.

스터디 발표

  1. 코드 지우개 - 람다식2
  2. 승연씨 - ChatGPT3
    • Ximillar : 이미지 및 동영상 인식 및 분석 도구로, 머신러닝을 사용하여 이미지와 동영상에서 사물과 장면을 식별, 저작권 무료
    • MoodBit : 기업
    • Knoyd : 비지니스 솔루션. 머신 러닝을 사용하여 비즈니스에 데이터 기반 인사이트와 예측을 제공하는 예측 분석 플랫폼
    • Chorus.AI : 자연어 처리를 사용하여 영업 통화를 분석하고 고객 감정, 제품 피드백 및 영업 서오가에 대한 인사이트를 제공하는 도구
    • Receptiviti : 자연어 처리를 사용하여 언어 패턴을 분석하고 성격 특성과 감정 상태에 대한 인사이트를 제공하는 성격 분석 도구
    • Paragon : 자연어 처리를 사용하여 대량의 비정형 텍스트 데이터에서 인사이트와 트렌드를 추출
    • Arria NLG : 머신 러닝을 사용하여 데이터에서 사람과 유사한 언어를 생성하는 자연어 생성 도구, 기업 보고서 및 서면 커뮤니티케이션을 자동화
    • 코그니티브스케일 : 비즈니스 프로세스 자동화.
    • Hootsutie: 소셜 미디어 모니터링
    • Phind : ChatGPT.Phind
    • app.Code2Flow: 코드 로직을 flowchart로 그려주는 도구
    • Auto GPT : GPT-4 모델을 기반. 인간의 개입 없이 스스로 쓰기 작업을 통해 퍼포먼스를 달성함. 오픈소스. Python, AGI - artificial general intelligence

2023-04-27

  • 오늘도 어김없이 영한 님의 인강을 들으며 출근하였다.
  • 요새 퇴근하고 계속 JUnit5 테스트 관련해서 작업 중인데 오류가 생각보다 잡히지 않아 시간이 오래 걸리고 있다.
  • 얼른 처리해서 다음 test로 진행하고 싶은데… 어렵다..

2023-04-28

  • 오늘도 어김없이 영한 님의 인강을 들으면서 출근하였다.
  • 어제 드디어! 동욱 님의 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 책을 토대로 작업했던 오류를 모두 찾아내어 수정하여 커밋 하였다.
  • 오류의 원인을 블로그로 포스팅할 예정이다.
  • 간단하게 오류의 원인을 설명하자면 우선 오타!… assertThat(body).contains("스프링 부트로 시작하는 웹 서비스")로 html body 안에 contains()를 통해서 String 타입 스프링 부트로 시작하는 웹 서비스를 찾고 있었는데 내가 테스트를 스프링부트로 시작하는 웹 서비스로 스프링 띄고 부트가 아니라 스트링부트라고 붙여서 테스트를 하고 있었다… 하…
  • 그리고 oauth2를 통해 with(oauth2Login()))를 추가했어야 했는데 기존 mvc.perform(get("/hello").andExpect(status().isOk()).andExpect(content().string(hello)); 만 사용하고 있던 것.
  • 즉, mvc.perform(get("/hello").with(oauth2Login())).andExpect(status().isOk()).andExpect(content().string(hello)); 라고 수정을 하여 정상적인 테스트를 진행하였다.
  • 또한 test 쪽의 application.yml파일에서 hibernate를 책의 내용과 똑같이 hibernate.dialect=MySQL5InnoDBDialect라고 해놓았던것..
  • H2DB를 사용했기 때문에 hibernate.dialect=H2Dialect가 맞았다. 역시.. 실패를 통해 많은 것들을 배우게 되는거 같다.
  • 제일 어이없었던건… 오타…!!!

Back to [Routine] 14 주차 시작!

Continue with [Routine] 16 주차 시작!

[Routine] 14 주차 시작!

[Routine] 14 주차 시작!

2023년 4월 17일 부터 4월 23일 까지의 나의 루틴.

#목차

2023-04-17

  • 저번 주 금요일에 동욱 님의 책을 다 읽어서 새로운 책과 함께 출근하였다.
  • 인강은 영한 님의 인강을 계속 듣고, 구글 엔지니어는 이렇게 일한다를 읽으면서 출근하였다.

2023-04-18

  • 오늘은 인강을 듣지 않고 내가 여태까지 배운 거와 기본 지식이 얼마나 쌓여있는지 확인하면서 오게 되었는데.. 한참 모자란 거 같다는 생각이 든다..
  • 막상 내가 사용하고 있는 기술이고 인터넷이나 문서를 보게 되면 이해가 가지만, 막상 보지 않고 생각해 보려고 하면 꽤 시간이 걸리고 까먹은 지식들도 많다고 느끼게 된다.
  • 역시 답은 꾸준히 하는 수밖에 없는듯하다.

2023-04-19

  • 오늘도 영한 님의 인강을 듣지 않고 출근하였다.
  • 이유는 Spring Batch에 대해 궁금한 점이 생겨 이것저것 자료를 찾아보며 출근하였다.
  • 아직 까진 부족하지만 Spring Batch를 사용하는 이유는 가장 큰 이유는 대용량 DB의 작업을 위한다는 것이다.
  • 또한 작업의 방식이 다르다.
  • Spring Web MVC는 요청 -> 응답 -> 요청 식으로 계속해서 인터렉션이 발생한다.
  • Spring Batch의 경우 인터렉션이 발생하지 않는다. 요청하면 끝!이라는 점이다. 요청하게 되면 Batch Application에서 완결이 나는 작업들을 하게 되는데 이것이 Spring Web MVC의 차이다.
  • 위 정보들은 [우아한테크세미나] 190926 우아한스프링배치 by 우아한형제들 이동욱님의 영상에 나오는 정보들이며, 해당 영상과 또다른 자료들을 참고하여 공부해야겠다는 생각을 한다.
  • 대용량의 트래픽을 상당히 경험해보고 싶은 나로썬 꼭 공부해야 겠다는 생각이 든다!.
  • Spring Batch뿐만 아니라 TDD의 경우 Batch Application을 작업하는대 무조건 선행되어야 하는 작업이라고 한다.
  • 또한 SaaS의 기술도 공부해서 무중단 배포의 환경을 만들어 보고 싶다는 생각도 한다.
  • 다 해봐야지!

2023-04-20 스터디

  • 오늘은 영한 님의 인강을 들으면서 출근하였다.
  • 어제 Spring Batch의 테스트 작업이 선행되어야 한다고 했는데, 이것 또한 방법론이라 어떤 방법이 더 좋은지 조금 더 고민해 볼 필요가 있다고 생각한다.

스터디

  1. asd - 컬레션 프레임워크3
    1. HashSet
      • HashSet은 Set인터페이스를 구현한 가장 대표적인 클래스이며 Set 인터페이스의 특징대로 HashSet은 중복된 요소를 저장하지 않는다.
      • HashSet에 새로운 요소를 저장할때는 add 메서드나 addAll 메서드를 사용하는데 만일 HashSet에 이미 저장되어 있는 요소와 중복된 요소를 추가하려고 한다면 이 메소드들은 false를 반환함으로서 중복된 요소리기 때문에 추가에 실패했다는 것을 알린다.
      • 이러한 HashSet의 특징을 이용한다면 컬렉션내의 중복된 요소를 쉽게 제거할 수 있다.
      • AraayList와 달리 HashSet은 저장순서를 유지하지 않음으로 저장순서를 유지하고자 한다면 LinkedList를 이용해야 한다.
    2. TreeSet
      • TreeSet은 이진 탐색 트리라는 자료구조 형태로 데이터를 저장하는 컬렉션 클래스이다. 이진 탐색 트리는 정렬, 검색, 범위 검색에 높은 성능을 보이는 자료구조이며 TreeSet 이진 탐색 트리의 성능을 향상 시킨 레드-블랙 트리 형태로 구현되어 있다.
      • Set 인터페이스를 구현했으므로 중복된 데이터의 저장을 허용하지 않으며 정렬된 위치에 저장하므로 저장순서를 유지하지도 않는다.
    3. 이진 트리
      • 이진 트리는 링크드 리스트처럼 여러개의 노드가 서로 연결되어 있는 구조로, 각 노드에 최대 2개의 노드를 연결할 수 있으며, 루트라고 불리는 하나의 노드에서부터 시작해서 계속 확장되어 나갈 수 있다. 이진 탐색 트리는 이진 트리의 한 종류이다.
      • 위 아래로 연결된 두 노드를 부모, 자식 관계에 있다고 하며 위의 노드를 부모, 아래의 노드를 자식 노드라고 한다. 부모 - 자식 관계는 상대적인 것이며 하나의 부모노드 최대 두개의 자식 노드와 연결될 수 있다.
    4. 이진 탐색 트리
      • 이진 탐색 트리는 부모노드의 왼쪽에는 부모노드의 값보다 작은 값의 자식노드를 오른쪽에는 큰 값의 자식노드를 저장하는 이진 트리이다.
      • 왼쪽 마지막 값에서부터 오른쪽 값까지 값을 왼쪽 노드 -> 부모 노드 -> 오른쪽 노드 순으로 읽어오면 오름차순으로 정렬된 순서를 얻을 수 있다. TreeSet은 이처럼 정렬된 상태를 유지하기 때문에 단일 값 검색과 범위검색이 매우 빠르다. 저장된 값의 개수에 비례해서 검색시간이 증가하긴 하지만 값의 개수가 10배 증가해도 특정값을 찾는데 필요한 비교 횟수가 3~4번 증가할 정도로 검색 효율이 뛰어난 자료구조이다. 트리는 데이터를 순차적으로 저장하는 것이 아니라 저장하는 위치를 찾아서 저장해야하고 삭제하는 경우에는 트리의 일부를 재구성해야하기때문에 링크드리스트보다 데이터의 추가/삭제 시간은 더 걸린다. 대신 배열이나 링크드 리스트에 비해 검색과 정렬기능이 더 뛰어나다.
      • 즉, 이진 탐색 트리는
        • 모든 노드는 최대 두 개의 자식 노드를 가질 수 있다.
        • 왼쪽 자식노드의 값은 부모 노드의 값보다 작고 오른쪽 자식 노드의 값은 부모노드의 값보다 커야한다.
        • 노딍 추가 삭제에 시간이 걸린다. (반복 비교로 자리를 찾아 저장)
        • 검색(범위검색)과 정렬에 유리하다.
        • 중복된 값을 저장하지 못한다.
  2. 왕돼지티라노의 기록 - 연관관계 매핑 기초
  3. 슈팅이가키우는강아지 - docker

2023-04-21

  • 어김없이 영한님의 인강과 함께 출근하였다.

2023-04-22

Commit with 게시글 수정/삭제 구현

2023-04-23

Commit with spring security oauth2 설정

Back to [Routine] 13 주차 시작!

Continue with [Routine] 15 주차 시작!

[Routine] 13 주차 시작!

[Routine] 13 주차 시작!

2023년 4월 10일 부터 4월 16일 까지의 나의 루틴.

#목차

2023-04-10

  • 오늘도 어김없이 영한 님과, 동욱 님과 함께 출근하였다.
  • 요새 동욱 님의 책을 보는데, TDDSpring Security, GoogleNaverID, PW를 활용한 인증 절차인 OAuth 사용 방법을 읽고 있다.
  • 내가 회사에서 할 땐 엄청 어려웠던 거 같은데, 역시 고수분들이 하는 건 엄청 쉽게 하는 거 같은 느낌이 든다.
  • 결국 반복의 중요성 같다.
  • 해당 내용을 나의 사이드 프로젝트에 적용시켜야겠다.

2023-04-11

  • 오늘도 어김없이 영한 님과, 향로 님과 함께!

HelloControllerTest로 알아보는 Test 어노테이션

//@RunWith(StringRunner.class)  // JUnit4 버전 <-> JUnit5 버전 @ExtendWith(SpringExtension.class)
@ExtendWith(SpringExtension.class) // 1) JUnit5 버전 <-> JUnit4 버전 //@RunWith(StringRunner.class) 
@WebMvcTest(controllers = HelloController.class) // 2)
public class HelloControllerTest {
    @Autowired // 3)
    private MockMvc mvc; // 4)
    
    @Test
    public void hello가_리턴된다() throws Exception {
        String hello = "hello";
        mvc.perform(get("/hello"))  // 5)
                .andExpect(status().isOk()) // 6)
                .andExpect(content().string(hello)); // 7)
    }
}
  1. @ExtendWith(SpringExtension.class) : JUnit5에서 @RunWith(StringRunner.class)@ExtendWith(SpringExtension.class)로 바뀌었다.
    • @RunWith(StringRunner.class) : JUnit4에서 사용
      • 테스트를 진행할 때 JUnit에 내장된 실행자 외에 다른 실행자를 실행
      • 여기서는 SpringRunner라는 스프링 실행자를 사용
      • 즉, 스프링 부트 테스트JUnit 사이에 연결자 역할
  2. @WebMvcTest
    • 여러 스프랭 테스트 어노테이션 중, Web(Spring MVC)에 집중할 수 있는 어노테이션.
    • 선언할 경우 @Controller, @ControllerAdvice등을 사용할 수 있다.
    • 단, @Service, @Component, @Respository등은 사용할 수 없다.
    • 여기서는 Controller만 사용하기 때문에 선언
  3. @Autowired : 스프링이 관리하는 빈(Bean)을 주입
  4. private MockMvc mvc;
    • Web API를 테스트 할 때 사용
    • 스프링 MVC 테스트의 시작점
    • 이 클래스를 통해 HTTP GET, POST등에 대한 API테스트
  5. mvc.perform(get("/hello"))
    • MockMvc를 통해 /hello 주소로 HTTP GET 요청
    • 체이닝이 지원되어 아래와 같이 여러 검증 기능을 이어서 선언
  6. .adnExpect(status().isOk())
    • mvc.perform의 결과를 검증
    • HTTP HeaderStatus를 검증
    • 우리가 흔히 알고 있는 200, 404, 500등의 상태를 검증
    • 여기선 OK 즉, 200인지 아닌지를 검증
  7. .adnExpect(content().string(hello))
    • mvc.perform의 결과를 검증
    • 응답 본문의 내용을 검증
    • Controller에서 "hello"를 리턴하기 때문에 이 값이 맞는지 검증

Commit with 60~65p test 코드의 어노테이션 설명 및 테스트

2023-04-12

  • 오늘도 어김없이 영한 님과, 향로 님과 함께!
  • 오늘 향로님의 책을 읽고 테스트하는 도중 gradle 버전에 따른 lombok 이슈를 발견하였다.

// file: "build.gradle"
dependencies {
   // Gradle 5.x 미만
   // implementation 'org.projectlombok:lombok'
   // Gradle 5.x 이상
   compileOnly 'org.projectlombok:lombok'
   annotationProcessor 'org.projectlombok:lombok'
}
  • 위 코드 처럼 gradle 버전에 따른 Lombok 설정이 다르다.
  • gradle 버전 확인은 터미널에서 ./gradlew --version로 확인 가능하다.

Commit with 66~77p 롬복설정 및 dto 테스트

2023-04-13 스터디 발표

  • 오늘도 어김없이! 영한 님, 동욱 님과 함께!

spring boot 프로젝트에 인메모리형 데이터베이스 h2 추가


// file: "build.gradle"
dependencies {
   // JPA 설정
   implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

   // h2 database
   runtimeOnly 'com.h2database:h2'
}
  • 위 스프링 버전과 gradle 버전에 따른 jpa 설정 방법과 h2 database 설정 방법이 다를 수 있다.

자바빈 규약의 Getter/Setter

  • 자바빈 규약을 생각하면서 getter/setter를 무작정 생성하는 경우가 있는데, 이렇게 되면 해당 클래스의 인스턴스 값들이 언제 어디서 변화하는지 코드상으로 명확하게 구분할 수가 없어, 차후 기능 변경 시 정말 복잡해진다.
  • 그래서 Entity 클래스에서는 절대 Setter 메서드를 만들지 않는다.
  • 대신, 해당 필드의 값 변경이 필요하면 명확히 그 목적과 의도를 나타낼 수 있는 메서드를 추가해야만 한다.
// file: "자바빈 규약의 잘못된 예.java"
public class Order {
    public void setStatus(boolean status) {
        this.status = status;
    }
}

public void 주문서비서의_취소이벤트() {
    order.setStatus(false);
}
  • 위 코드는 setter 메서드를 이용하여 status의 값을 직접 변경하고 있다.
  • 이러한 코드는 추후 값들이 변경 될 때마다 다른 쪽에서 사용할때 엉뚱한 값이 나타낼 수 있는 이슈가 발생하게 된다.
  • 특히 중요한 결제와 같은 돈에 관련된 문제가 발생할 수 있기 때문에 주의해야 한다.
// file: "자바빈 규약의 올바른 사용 예.java"
public class Order {
   public void cancelOrder() {
       this.status = false;
    }
}

public void 주문서비서의_취소이벤트() {
   order.cancelOrder();
}
  • 그렇다면 위 문제 Setter가 없는 상황에서 어떻게 값을 채워 DB에 삽입(insert)을 해야 할까?
  • 기본적인 구조는 생성자를 통해 최종값을 채운 후 DB에 삽입하는 것이며, 값 변경이 필요한 경우 해당 이벤트에 맞는 public 메서드를 호출 하여 변경하는 것을 전제로 한다.
  • 또 다른 방법은 @Builder를 통해 제공되는 빌터 클래스를 사용하게 된다.
  • 생성자나 빌더나 생성 시점에서 값을 채워주는 역할은 똑같지만, 생성자의 경우 지금 채워야 할 필드가 무엇인지 명확히 지정할 수 가 없다.
// file: "생성자의 경우 지금 채워야할 필드가 무엇인지 명확히 지정할 수 없는 문제.java"
public class ExampleCode {
    public Example(String a, String b) {
        this.a = a;
        this.b = b;
    }
}
// file: "@Builer를 사용하여 어떤 값을 채워야 할지 명확하게 인지.java"
public class ExampleCode {
    public Example ex() {
        return Example.builder()
                .a(a)
                .b(b)
                .build();
    }
}
  • JpaRepository<Entity 클래스, PK 타입>를 상속하면 기본적인 CRUD 메서드가 자동으로 생성
  • @Repository를 추가할 필요도 없다.
  • 주의!
  • Entity 클래스와 기본 Entity Repository는 함께 위치해야 하는 점.
  • 둘은 아주 밀접한 관계이고, Entity 클래스는 기본 Repository 없이는 제대로 역할을 할 수 가 없다.
  • 나중에 프로젝트 규모가 커져 도메인별로 프로젝트를 분리해야 한다면 이때 Entity 클래스와 기본 Repository는 함께 움직여야 하므로 도메인 패키지에 함께 관리하게 된다.

  • 예를 들어 new Example(b, a)처럼 ab의 위치를 변경해도 코드를 실행하기 전까지는 문제를 찾을 수 없는 문제가 발생한다.
  • 하지만 @Builder를 사용하게 되면 어느 필드에 어떤 값을 채워야 할지 명확하게 인지 할 수 있다.


스터디 발표

  1. 승연씨 - OSI 7 계층 TCP/IP
    • 4계층 : Application Layer
    • 3계층 : Transport Layer
      • LISTEN : 열려 있음(=/= 통신중) 즉, 응답 대기 상태
      • ESTABLISHED : 통신중 즉, 상호 연결상태
      • CLOSED : 연결이 끊어짐
      • SYS-SENT : 연결을 요청한 상태
      • SYN_RECEIVED : 연결 요청에 응답후 확인을 기다리고 있음
      • TIME_WAIT : 연결은 종료되었으나 원격의 수신 보장을 위해 기다리고 있는 상태
      • netstat –an | grep LISTEN (현재 열려있는 모든 포트)
      • netstat –an | grep ESTABLISHED | wc –l (모든 서비스 동시 접속자 수)
      • netstat –an | grep :80 | ESTABLISHED | wc –l (웹 동시 접속자 수)
      • netstat –an | grep –E “포트번호|포트번호|….” (원하는 포트의 네트워크 상태만 보고 싶을 때 사용)
    • 2계층 : 인터넷 계층
    • 1계층 : 네트워크 계층(링크)
      • NIC 장치를 통해 이더넷의 주소를 뽑아낸 것이 MAC 주소이다.
  2. 코드 지우개 - 람다식
    • @FunctionalInterface -> @FunctionalInterface 어노테이션을 사용하면 하나의 추상 메소드만 가지는지 컴파일러가 체크하도록 하여 두 개 이상의 추상 메소드가 선언되어 있으면 컴파일 오류를 발생시킨다.
    • jquery 이벤트 함수 연속 호출

2023-04-14

  • 오늘도 어김없이! 영한 님, 동욱 님과 함께!

@Transactional 이란

  • @Transactional이 붙은 메서드는 메서드가 포함하고 있는 작업 중에 하나라도 실패할 경우 전체 작업을 취소한다.
  • Test 환경에서의 (TestTransaction.제공메서드)는 메서드가 종료될 때 자동으로 RollBack 된다.

2023-04-15


JPA Auditing이란

  • JPA가 createDate, modifyDate를 자동으로 설정해주게 된다.

2023-04-16

스프링 부트와 AWS로 혼자 구현하는 웹 서비스

  • 해당 책에서는 mustache를 이용하여 view를 구성하지만 thymeleaf를 사용하여 view를 구성 할 예정.

Back to [Routine] 12 주차 시작!

Continue with [Routine] 14 주차 시작!

[Programming] 스프링 부트와 AWS로 혼자 구현하는 웹 서비스

[Programming] 스프링 부트와 AWS로 혼자 구현하는 웹 서비스

“읽은 기간 : 2023-04-03 ~ 2023-04-14”

#목차

스프링 부트와 AWS로 혼자 구현하는 웹 서비스

책 정보

  • 제목 : 스프링 부트와 AWS로 혼자 구현하는 웹 서비스
  • 부제 : 웹 서비스의 모든 과정을 경험한다. 경험이 실력이 되는 순간!
  • 저자 : 이동욱
  • 쪽수 : 416쪽
  • 발행(출시)일자 : 2019년 11월 29일

후기

[Gradle] Gradle 버전에 따른 Lombok 설정 방법

[Gradle] Gradle 버전에 따른 Lombok 설정 방법

“Gradle 버전에 따른 Lombok 설정 방법”

#목차

문제

문제 상황 재현 코드

테스트 코드

// file: "HelloResponseDTOTest.java"
public class HelloResponseDTOTest {
    @Test
    public void 롬복_기능_테스트() {
        // given
        String name = "test";
        int amount = 1000;

        // when
        HelloResponseDTO dto = new HelloResponseDTO(name, amount);

        // then
        assertThat(dto.getName()).isEqualTo(name);
        assertThat(dto.getAmount()).isEqualTo(amount);
    }
}
  • 코드에 이상은 없었다.
  • 문제는 gradle의 버전에 따라 lombok 설정이 다른 것이었다.

문제의 build.gradle 설정

// file: "build.gradle"
dependencies {
   // Gradle 5.x 미만
   implementation 'org.projectlombok:lombok'
}
  • 위 코드처럼 내가 설정을 해놓았다.
  • 문제는 프로젝트의 Gradle 버전과 lombok 버전이 맞지 않아 생기는 오류라는 것을 확인하였다.
  • 나의 gradle 버전을 확인하지 않고 설정 해버렸던 것이었다.

나의 gradle 버전 확인

./gradlew --version
  • 나의 Gradle Version7.6.1
  • 내가 build.gradle 설정에서 설정해 준 버전은 Gradle 5.x 미만의 설정이어서 variable '변수명' not initialized in the default constructor라는 오류가 발생했던 것이었다.

해결

해결 build.gradle 재설정

// file: "build.gradle"
dependencies {
   // Gradle 5.x 이상
   compileOnly 'org.projectlombok:lombok'
   annotationProcessor 'org.projectlombok:lombok'
}
  • 5.x대의 설정 방법으로 설정해주니 해결 되었다.

결론

  • 테스트 작업 시 variable '변수명' not initialized in the default constructor라는 해당 오류 발생 시 해당 프로젝트의 Gradle 버전 확인
  • Gradle 버전(5.x 미만, 5.x 이상)에 따라 lombok 설정 후 테스트 재진행

Reference

[Routine] 12 주차 시작!

[Routine] 12 주차 시작!

2023년 4월 03일 부터 4월 09일 까지의 나의 루틴.

#목차

2023-04-03

  • 그리고… 역시나 월요일 병이 심한지 피곤하다…ㅠㅠ
  • 오늘도 어김없이 영한 님의 인강과, 오늘은 재성 님의 책을 다 읽어서 동욱 님의 스프링 부트와 AWS로 혼자 구현하는 웹 서비스를 읽기 시작하였다.
  • intelliJ의 사용법과 test code, 그리고 AWS를 활용한 작업이 가장 기대된다.

2023-04-04

  • 오늘도 영한 님과, 향로 님의 책과 함께 출근하였다.

2023-04-05

  • 오늘도 영한 님과, 향로 님의 책과 함께 출근하였다.
  • 요새 계속 TDD에 관심이 많아져 공부하고 있다.
  • 이중석 님의 실전! 스프링부트 상품-주문 API 개발로 알아보는 TDD을 보는데 아주 간단한 테스트라고 말씀하시지만 내가 보기엔 엄청난 고수처럼 느껴졌다.
  • intelliJ 단축키도 굉장히 잘 사용하셔서 굉장히 부러웠다.
  • 나도 연습해야겠다!

2023-04-06 스터디

  • 오늘도 어김없다. 역시나 영한 님과, 향로 님과 함께 출근!

왕돼지티라노의 기록 - 연관관계 매핑 기초


2023-04-07

  • 오늘도 역시나 영한 님과, 향로 님과 함께!
  • 어제 알게 된 사실이지만 나의 블로그 왼쪽 사이드바 tree에서 daily log를 클릭하게 되면 books의 데이터도 같이 나오는 현상을 발견하였다.
  • 결국 잘 해결했다.
  • 확인을 해보니까 tag를 설정하는 부분에서 category 부분이 모두 daily로 되어있어야 하는데 routine으로 되어있었던 것이다.
  • 이것을 수정해 주니 바로 적용되었다.
  • ctrl+c,v를 할 때 항상 잘 보고 해야겠다ㅠㅠ

2023-04-08

  • 오늘은 정말 오랜만에 정말 푹 잤다.
  • 머리도 하고, 밥 먹고 오후에 이제 인강 들으면서 공부와 요새 TDD에 관심이 많아져 TDD에 대해 공부 좀 해야겠다.

2023-04-09

  • 오늘도 푹 잤다.
  • 요새 잠을 계속 잘 못 자서 컨디션이 안 좋았는데, 이번 주는 푹 자고 컨디션을 회복하는 데 신경을 썼다.

Back to [Routine] 11 주차 시작!

Continue with [Routine] 13 주차 시작!

[Routine] 11 주차 시작!

[Routine] 11 주차 시작!

2023년 3월 27일 부터 4월 02일 까지의 나의 루틴.

#목차

2023-3-27

2023-03-27

  • 오늘도 어김없이 영한 님과 재성 님과 함께!😎
  • 오늘 출근하자 마자 어제 작업한 문제해결 첫 포스트를 일부 수정하였다.
  • cover img와 내용들을 수정 후 나의 블로그 소개 글 또한 수정할 예정이다.

2023-3-28

2023-03-28

  • 오늘도 영한 님, 재성 님과 함께!
  • 어제 퇴근 후 이번 주 있을 발표자료 미비된 준비하고 아침에 출근 해서 마지막 미비된 부분을 수정하였다.

2023-3-29

2023-03-29

  • 오늘도 역시 영한 님, 재성 님과 함께 출근하였다.
  • 어제 퇴근 후 이번 주 있을 스터디 발표 자료를 만드느라 1시쯤 잔거 같다😂

2023-3-30 스터디 발표

2023-03-30

  • 늦잠을 자버렸다…😇
  • 오늘도 어김없이 영한 님의 인강을 들으면서 출근을…
  • 어제부터 스프링 부트 - 핵심 원리와 활용 강의를 들으면서 출근 중이다.
  • 아무래도 지금 회사에서도 spring boot를 사용하여 프로젝트를 진행하고 있어서 더 재미있을거 같다.

스터디 발표

  1. asd - 컬레션 프레임워크2

  2. 승연씨 - ChapGPT4.0
    • OpenAI
      • 챗봇 심심이와 같음
      • 수천조의 텍스트 단위로 학습
      • 딥러닝 3.5버전 까지는 딥러닝 방법의 code가 오픈되어 있다. 4.0은 업계 비밀.
  3. 코드는 지우개로 지우게 - 인터페이스 상속

2023-3-31

2023-03-31

  • 오늘도 영한 님과 재성 님과 함께 출근하였다.
  • 계속 인강을 들으면서 드는 생각이 활용 편이긴 한데 아는 것도 있고 모르는 내용도 있다는 게 아직 배울게 엄청 많이 남았구나…라고 생각이 든다.
  • 차근차근 하나씩 하나씩… 도 좋지만 모르겠다 그냥 부딪쳐서 배워봐야겠다. 직접 코드 짜보고 이것저것 해보면서 조금 더 지식을 쌓고 싶다.

2023-4-01

  • 오늘은 학점은행제 시험 기간이라 시험을 얼른 다 보고 공부를 해야겠다!

2023-4-02

  • 어제 시험을 하나도 보지 못하고 놀러 다녀서 오늘 시험을 모두 치러야겠다ㅠㅠ

Back to [Routine] 10 주차 시작!

Continue with [Routine] 12 주차 시작!

[성능개선] 불필요한 정보 노출로 인한 성능 저하

[성능개선] 불필요한 정보 노출로 인한 성능 저하

#목차

문제

  • jsp 프로젝트에서 vue 프로젝트로 마이그레이션 작업 중 사용되지 않는 컬럼들 까지 요청하여 성능상의 문제 소지를 발견하였다.
  • 총 7건의 요청사항에 문제를 발견하였는데, 모두 불필요한 컬럼들까지 요청하고 있었다.

문제 상황 재현 코드

// file: "재현 코드 = 리스트 가져오는 서비스 재현 코드.java"
@Service
@RequiredArgsConstrutor
public class ExampleService {
    private final ExUser3JoinTableRepository  exUser3JoinTableRepository; 
    
    public List<ExUser3JoinTable> getList(ReqDTO reqDto) {
        List<ExUser3JoinTable> list = exUser3JoinTableRepository.findAll(
                Specification.joinAndJoinAndJoinAll(
                        reqDto.getStartRegistDate(),
                        reqDto.getEndRegistDate(),
                        reqDto.getStartModifyDate(),
                        reqDto.getEndModifyDate(),
                        reqDto.getSearchKeyword(),
                        reqDto.getClientComponySn(),
                        reqDto.getAuthoritySn(),
                        reqDto.getUseStatus()
                ), Sort.by(Sort.Direction.DESC, "등록일자")
        );
        return list;
    }
}
  • Service 로직을 보면 Specification을 이용하여 요청DTO를 통해 검색조건을 만족하는(없으면 Object.isEmptyif문 패스) 3개의 join된 테이블로 list를 조회해한다.
  • 하지만 여기서 문제는 바로 List<ExUser3JoinTable> 부분이다.

문제의 조인 테이블

// file: "재현 코드 = 3개의 조인 테이블 재현 코드.java"
import java.io.Serializable;

@Entity
@Table(name = "고객정보테이블")
public class ExUser3JoinTable extends SuperUser implements Serializable {
    @NotFound(action = NotFoundAction.IGNORE)
    @JoinColumn(name = "authority_sn", insertable = false, update = false, referenceColumnName = "authority_sn")
    @ManyToOne(fetch = FetchType.EAGER)
    public Authority AuthorityInfo;

    @NotFound(action = NotFoundAction.IGNORE)
    @JoinColumn(name = "client_company_sn", insertable = false, update = false, referenceColumnName = "client_company_sn")
    @ManyToOne(fetch = FetchType.EAGER)
    public ClientCompony ClientComponyInfo;
}

클라이언트에 전송되는 과도한 데이터들

원인 테이블

  • 무려 33개의 컬럼의 데이터가 요청되어있던 것이다.
  • 내가 필요한 데이터는 13개의 컬럼 데이터만 필요하다.
  • 무려 20개의 사용되지 않는 컬럼들(물론 조인한 컬럼2개 제외하면 18개이다.)을 DB에서 가져와 Client쪽으로 데이터를 보내주고 있었다.

해결

  • 기존 JPAfindAll키워드를 사용해서 모든 컬럼을 찾아오는 대신에 QueryDSL로 필요한 컬럼만 조인하여 응답 DTO를 만들어 해결하였다.

데이터 조회를 QueryDSL로

데이터 조회를 QueryDSL로 - BooleanBuilder 사용

// file: "재현 코드 = queryDSL - BooleanBuilder를 사용한 예제.java"
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

@Repository
@RequiredArgsConstructor
class QClassUserRepository implements ExUserRepository {
    private final JPAQueryFactory jPAQueryFactory;
    @Override
    public List<UserListDTO> getList(SearchVO searchVO) {
        QUser qUser = QUser.user;
        QAuthority qAuthority = QAuthority.authority;
        QClientCompony qClientCompony = QClientCompony.clientCompony;

        BooleanBuilder builder = new BooleanBuilder();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYY-MM-dd");
        if (ObjectUtils.isNotEmpty(searchVO.getRegStrDate()) && ObjectUtils.isNotEmpty(searchVO.getRegEndDate())) {
            LocalDateTime parseStrDate = LocalDateTime.of(LocalDate.parse(searchVO.getStrDate(), formatter), LocalTime.of(0, 0, 0));
            LocalDateTime parseEndDate = LocalDateTime.of(LocalDate.parse(searchVO.getEndDate(), formatter), LocalTime.of(23, 59, 59));
            builder.and(qUser.regDt.between(parseStrDate, parseEndDate));
        }
        if (StringUtils.isNotBlank(searchVO.getSearchKeyword())) {
            builder.or(qUser.nm.like("%" + searchVO.getSearchKeyword() + "%"));
            builder.or(qAuthority.authNm.like("%" + searchVO.getSearchKeyword() + "%"));
            builder.or(qClientCompony.coNm.like("%" + searchVO.getSearchKeyword() + "%"));
        }
        // 등등... 
        return jPAQueryFactory
                .select(
                        Projections.filds(
                                UserListDTO.class,
                                qUser.id,
                                qAuthority.auth,
                                qClientCompony.nm
                                // 조회 컬럼..
                        )
                )
                .from(qUser)
                .leftjoin(qAuthority)
                .on(qUser.userSn.eq(qAuthority.userSn))
                .leftjoin(qClientCompony)
                .on(qUser.coSn.eq(qClientCompony.coSn))
                .where(builder)
                .orderBy(qUser.regDt.desc())
                .fetch();
    }
}
  • 위 예제 코드를 보면 그럭저럭 나쁘지 않은 거 같지만..
  • 사실 해당 QClassUserRepository에 구현 클래스가 하나이면 상관없지만 여러 구현 클래스가 추가로 생기면 문제가 발생하기 시작한다.
  • 예를 들어 매번 검색 조건에 날짜가 들어가게 되면 BooleanBuilderDateTimeFormatter을 구현 클래스 안에다가 작성해야 하는 번거로움이 발생하게 된다.
  • 위 방법을 좀 더 편리하게 바꾸려면 BooleanExpression을 사용하여 메서드를 만들어 사용하는 방법으로 리팩토링하면 코드가 훨씬 깔끔해진다.

데이터 조회를 QueryDSL로 - BooleanExpression 사용하여 리팩토링

// file: "재현 코드 = queryDSL - BooleanExpression으로 리팩토링 사용한 예제.java"
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

@Repository
@RequiredArgsConstructor
class QClassUserRepository implements ExUserRepository {
    private final JPAQueryFactory jPAQueryFactory;
    @Override
    public List<UserListDTO> getList(SearchVO searchVO) {
        QUser qUser = QUser.user;
        QAuthority qAuthority = QAuthority.authority;
        QClientCompony qClientCompony = QClientCompony.clientCompony;
        
        return jPAQueryFactory
                .select(
                        Projections.filds(
                                UserListDTO.class,
                                qUser.id,
                                qAuthority.auth,
                                qClientCompony.nm
                                // 조회 컬럼
                        )
                )
                .from(qUser)
                .leftjoin(qAuthority)
                .on(qUser.userSn.eq(qAuthority.userSn))
                .leftjoin(qClientCompony)
                .on(qUser.coSn.eq(qClientCompony.coSn))
                .where(
                        betWeenRegDt(searchVO.getRegStrDate(), searchVO.getRegEndDate(), EnDateTy.SVC_STR_DT),
                        betWeenModDt(searchVO.getModStrDate(), searchVO.getModEndDate(), EnDateTy.SVC_END_DT),
                        likeSearchKeyword(searchVO.getSearchKeyword())
                        // 등등...
                )
                .orderBy(qUser.regDt.desc())
                .fetch();
    }
    
    // 검색조건 등록 날짜
    private BooleanExpression betWeenRegDt(String schStrRegDt, String schEndRegDt, EnDateTy schDateType) {
        return getBooleanExpression(schStrRegDt, schEndRegDt, schDateType);
    }
    // 검색조건 수정 날짜
    private BooleanExpression betWeenModDt(String schStrModDt, String schEndModDt, EnDateTy schDateType) {
        return getBooleanExpression(schStrModDt, schEndModDt, schDateType);
    }
    // 날짜 parsing 
    private BooleanExpression getBooleanExpression(String strDt, String endDt, EnDateTy schDateType) {
        QUser qUser = QUser.user;
        if (StringUtils.isBlank(strDt) || StringUtils.isBlank(endDt)) return null;
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYY-MM-dd");
        LocalDateTime parseStrDate = LocalDateTime.of(LocalDate.parse(searchVO.getStrDate(), formatter), LocalTime.of(0, 0, 0));
        LocalDateTime parseEndDate = LocalDateTime.of(LocalDate.parse(searchVO.getEndDate(), formatter), LocalTime.of(23, 59, 59));
        return schDateType.equals(EnDateTy.SVC_STR_DT) ? qUser.regDt.between(parseStrDate, parseEndDate) : qUser.modDt.between(parseStrDate, parseEndDate);
    }
    // 검색어
    private BooleanExpression likeSearchKeyword(String keyword) {
        QUser qUser = QUser.user;
        QAuthority qAuthority = QAuthority.authority;
        QClientCompony qClientCompony = QClientCompony.clientCompony;
        return keyword != null ? qUser.nm.like("%" + searchVO.getSearchKeyword() + "%")
                .or(qAuthority.authNm.like("%" + searchVO.getSearchKeyword() + "%"))
                .or(qClientCompony.coNm.like("%" + searchVO.getSearchKeyword() + "%")) : null;
    }
    // 등등 ...
}
  • 위 예시 코드처럼 BooleanExpression을 활용하여 공통적으로 사용하거나 사용할 법한 메서드들을 만들어 사용하였다.
  • 이렇게 되면 매번 구현 클래스를 만들때 일일이 조건문을 주지 않고 사용할 수 있는 편리한 장점이 있다.

JPA findAll 대신 QueryDSL을 활용한 필요한 컬럼 조회

QueryDSL

  • 위의 코드로 인해 코드 량도 좀 더 깔끔하게 줄었다.

수정한 List

// file: "재현 코드 = 수정한 테이블의 리스트 재현 코드.java"
@Service
@RequiredArgsConstrutor
public class ExampleService {
    private final ExUserRepository  exUserRepository;

    public List<UserListDTO> getList(SearchVO searchVO) {
        return exUserRepository.getList();
    }
}
  • 해당 코드를 통해 좀 더 코드가 간편해진 듯하다.

수정한 List Debugging

수정한 테이블의 컬럼

  • 수정한 코드를 통해 필요한 13개의 컬럼 데이터만 보내주었다.
  • 기존의 데이터(33개의 컬럼 데이터)와 약 20개의 차이가 난다.

기존 vs 수정한 코드의 실행 시간

1번째 페이지 - 약 61% 개선

  • 기존 List 호출 시간 : 0.018sec
  • 수정 List 호출 시간 : 0.007sec
  • 61%의 성능 개선

2번째 페이지 - 약 31%, 81% 개선

  • 기존 List 호출 시간 : 0.077sec
  • 수정 List 호출 시간 : 0.053sec
  • 31%의 성능 개선

  • 기존 Detail 호출 시간 : 0.027sec
  • 수정 Detail 호출 시간 : 0.005sec
  • 81%의 성능 개선

3번째 페이지 - 약 74%, 39% 개선

  • 기존 List 호출 시간 : 0.027sec
  • 수정 List 호출 시간 : 0.007sec
  • 74%의 성능 개선

  • 기존 Detail 호출 시간 : 0.023sec
  • 수정 Detail 호출 시간 : 0.014sec
  • 39%의 성능 개선

4번째 페이지 - 약 78%, 93% 개선

  • 기존 List 호출 시간 : 0.033sec
  • 수정 List 호출 시간 : 0.007sec
  • 78%의 성능 개선

  • 기존 Detail 호출 시간 : 0.105sec
  • 수정 Detail 호출 시간 : 0.009sec
  • 93%의 성능 개선

결론

  • 컴퓨터의 성능에 따라 성능 차이는 좀 달라질 수 있다. 하지만, 기존보다 성능이 좋아진 건 사실이다!.
  • 그리고, DB 컬럼의 용량이나 부하가 증가하게 되면 성능 면에선 더욱 큰 차이가 날 수 있다.
  • 위 문제 개선을 통해 전체적으로 약 65%의 성능을 개선하게 되었다.

[Routine] 10 주차 시작!

[Routine] 10 주차 시작!

2023년 3월 20일 부터 3월 26일 까지의 나의 루틴.

#목차

2023-03-20

2023-03-20

2023-03-21

2023-03-21

  • 오늘도 영한 님과 재성님의 책과 함께…🤔

2023-03-22

2023-03-22

  • 오늘도 어김없이 영한 님, 재성 님과 함께 출근하였다.
  • 요새 블로그에 대해 생각이 많아졌다.
  • 원래 계획은 공부를 많이 하는 것이었다.
  • 하지만, 공부는 기본이고 회사에서 일하면서 겪는 문제 해결에 대한 포스팅이나 내가 공부한 글을 테스트하면서 직접 눈으로 확인할 수 있는 것이 좋겠다는 결론을 내렸다.
  • 그래서 앞으로 블로그를 대폭 수정할 계획이다.
  • 블로그 주도 개발을 위하여…!

2023-03-23 스터디

2023-03-23

  • 오늘도 어김없이 영한 님, 재성 님과 함께 출근!

왕돼지티라노의 기록 - 엔티티 매핑

  • @NonNull : Null 체크하여 null로 넘어온 경우, NullPointException 예외를 발생
  • @NotNull : Null만 허용하지 않는다. ("" 또는 " " 허용)
  • @Nullable : Null을 허용
  • @NotEmpty : null"" 둘 다 허용하지 않는다. (" " 허용)
  • @NotBlank : null, "", " " 모두 허용하지 않는다.
  • @BLOB : 바이너리 데이터를 DB 외부에 저장하기 위한 타입, 즉, 컴퓨터가 인식하는 모든 파일(이진 데이터)을 저장하는 타입
  • @CLOB : 문자열 데이터를 DB 외부에 저장하기 위한 데이터 타입

docker : BY

  • 프로젝트 시작 시, 환경 설정부터 기본 세팅을 해야 하지만, 도커를 사용하면 프로젝트마다 반복 되는 세팅을 할 필요가 없다.
  • 도커가 없는 시기 세팅
    • 용량 선정, 서버 구매, IDC 입고, Network 연결, OS 설치, SW 설치, 운영 관리, 실시간 감시

^.^ - var, let, const 비교


2023-03-24

2023-03-24

  • 늦잠을 자버렸다…🤣
  • 오늘도 영한 님의 인강과, 재범 님의 책과 함께 출근하였다.

2023-03-25

  • 오늘은 늦잠을 자서 8시에 일어났다.
  • 어제 vue 프로젝트로 마이그레이션 작업 중 성능 개선에 의문이 들어 테스트를 하던 중 조금 더 나은 방법에 대해 고민하고 실행해 보았다.
  • 그 문제 발생과 해결 그리고 느낀점을 적기 위해 새롭게 사이드 탭을 만들어 관리할 예정이다.

2023-03-26

  • 어제와 마찬가지로 문재 발생에 대한 포스트를 작성하였다.

Back to [Routine] 9 주차 시작!

Continue with [Routine] 11 주차 시작!

Pagination