개발 일지/주간 개발 일지

[04월 2주차] JPA, Vue.js 3 기본 명령어

계란💕 2023. 4. 16. 13:41

에러 해결


  • 오류: 페이징 에러
  • 원인: 메서드 실행 순서 오류
  • 해결: 실행 순서를 바꾼다. 

 
  cf) BeanUtils.copyProperties(A, B):  값이 같은 필드에 대해서만 A를 B에 복사한다.

  • 문제 코드
    • 페이징 인스턴스를 만들고 조건에 맞는 데이터가 몇 개인지 카운트한다. 
    • 결과값을 page에 세팅한 다음 model 정보를 paging 인스턴스에 카피해준다. 
    •  
    • 그런데, 이렇게 하고나서 검색 조회를 하면 페이지 개수가 totalPage 개수로 설정이 되서 조회하는 데이터를 보여줄 페이지가 2개인 경우에도 페이지가 (전체 목록를 보여줄 수 있는) 4까지 조회된다.  (3, 4를 누르면 데이터가 없다고 나오기는 한다. )
    • 문제점: paging 인스턴스와 model 인스턴스 간에 searchedModelCount 변수의 값이 달라야 정상인데 같으니까 전체 목록의 개수가 들어가버려서 문제가 된다. 
      • 그렇다고해도 검색 결과 카운트가 model, paging에  똑같이 적게 들어가는게 정상 아닌가? 
<hide/>
// Model 목록 조회 (전체 조회, 검색 조회)
public Object getModelList(Model model, HttpServletRequest request) {

    PagingVO page = new PagingVO();
    long totalCount = query.getSearchedModelCount(model);
    page.setTotalListSize(totalCount);
    BeanUtils.copyProperties(model, page);
    ...
    Map<String, Object> resultMap = new HashMap<>();
    ...
    return resultMap;

}

 
 

  • 정상 코드
    • 가장 먼저 카운트를 한다. 
    • 그러고나서 paging 인스턴스를 만들어서 새로운 paging 인스턴스에 model 에 있는 같은 속성에 대해서 정보를 카피한다.
    • 그러면 최종적으로 paging 인스턴스에는 매개변수로 들어온 model에 대한 페이징 정보가 들어간다. 
    • 그러니까 page 인스턴스와 model 인스턴스는 searchedModelCount 에 대한 값이 달라야 정상
<hide/>
// Model 목록 조회 (전체 조회, 검색 조회)
public Object getModelList(Model model, HttpServletRequest request) {

    long totalCount = query.getSearchedModelCount(model);
    PagingVO page = new PagingVO();
    BeanUtils.copyProperties(model, page);
    page.setTotalListSize(totalCount);
    ...
    Map<String, Object> resultMap = new HashMap<>();
    ...
    return resultMap;
}

 


Spring Data JPA 메서드 

  • 특정 조건을 만족하는 repository 안에 모든 엔티티를 지우려면 어떤 방법이 있을까?

 
 Ex) ModelId가 같은 모든 Param을 삭제해보자. 
 
 
  1) @Query를 붙여서 IN 절을 사용하는 방법

  • IN 절을 사용하면 많은 양의 데이터를 조회하는 경우,  OR을 연속해서 사용하는 것보다 훨씬 빠르다. 
  • cf) 쿼리 실행 계획을 보면 OR절 사용과 IN 절 사용은 쿼리 실행 계획에서 차이가 있다. 
    •  OR절 type = ALL, IN절은 type = RANGE
    • type = ALL: 인덱스를 사용하지 않고 테이블을 처음 부터 끝까지 읽는 풀 테이블 스캔 방식을 사용한다. 
    • type  = RANGE: 인덱스 레인지 스캔 방식을 사용하는 접근 방식이다.  
<hide/>
@Modifying
@Query(nativeQuery = true, value = "DELETE FROM MemberParam m WHERE m.member_Id=:memberId")
void deleteAllParamByMemberId(@Param("memberId") String memberId);

 
  Note)

  • member_param(엔티티로 생성된)  테이블 안에서  메서드 안의 매개변수로 들어온 "memberId"에 해당하는 모든 param을 찾아서 삭제하는 메서드이다. 
  • 관련 애너테이션
    • @Query 를 이용하면 안에 직접 쿼리를 작성할 수 있어서 편리하다. 
      • 매개변수로 넣는 문자열 앞에는 @Param을 붙여준다.  
      • param을 붙인 memberId는 쿼리 문에 사용되는 변수명이 일치해야한다. 
      • nativeQuery = true: JPQL과 SQL 구문 차이점 때문에 필요하다. JPQL만으로 복잡한 쿼리를 작성할 수 없는 경우에 네이티브 SQL을 사용해서 DB에 데이터를 보낼 수 있따. 
    • @Modifying: 데이터베이스의 수정 작업을 할 때 쓰인다. 수정, 삭제, 등록할 때 쓰인다. 

참고
인덱스 레인지 스캔: https://oranthy.tistory.com/443
 

 
  2) OR 절을 이용하는 방법: deleteAllInBatch()

  • deleteAllInBatch()를 구현한 메서드 안에 타고타고 들어가보면 다음과 같이 WHERE 절 안에 조건을 "OR"로 이어붙이고 있는 걸 알 수 있다. 
<hide/>

public static <T> Query applyAndBind(String queryString, Iterable<T> entities, EntityManager entityManager) {
	...
    String alias = detectAlias(queryString);
    StringBuilder builder = new StringBuilder(queryString);
    builder.append(" where");
    int i = 0;

    while(iterator.hasNext()) {
        iterator.next();
        Object[] var10002 = new Object[]{alias, null};
        ++i;
        var10002[1] = i;
        builder.append(String.format(" %s = ?%d", var10002));
        if (iterator.hasNext()) {
            builder.append(" or");
        }
    }
	...
    return query;

}
    •  

 
  Note) 일괄 처리를 위한 JPA 메서드 

  • deleteAll()  deleteAllInBatch() 차이점
    • deleteAll(entities) : DELETE  쿼리가 하나씩 날아간다. 따라서, 1000건을 삭제하는 경우 1000개의 DELETE 문이 실행된다. 대량 삭제 시 성능이 떨어진다. 
    • deleteAllInBatch(entities): 여러 엔티티를 한 번에 삭제한다. DELETE 문이 한 번만 실행되므로 효율적이다. 엔티티 개수가 많을 수록 deleteAll()보다 성능 면에서 훨씬 뛰어나다. 내부에 구현된 로직을 들여다 보면 WHERE 절 안에 조건을 "OR"로 이어붙이고 있다. 
      • 엔티티의 전체 집합을 대상으로 삭제를 수행한다. 
      • 모든 엔티티를 삭제한 다음  영속성 컨텍스트를 비운다. (즉, DB와 내용을 일치시키기 위한 작업을 한다. )
    • deleteInBatch(entities) 
      • 매개변수로 전달된 컬렉션 또는 Iterable에 포함된 엔티티만 삭제한다.  
      • 영속성 컨텍스트 비우지 않는다. 따라서 반드시 트랜잭션을 사용해서 엔티티 정보를 갱신해야한다. 

참고) https://velog.io/@hssarah/JPA-deleteAll-vs-deleteAllInBatch-vs-Query-%EC%82%AC%EC%9A%A9

 

 

 

Note  


BooleanBuilder, BooleanExpression 리팩토링

  • 기존에 회사에서 만들었던 코드는 BooleanBuilder를 이용했다. 

 

  • 기존에 Builder를 사용한 코드
    • 매개변수로 유의미한 값(null이거나 "" 가 아닌 값)이 있는 경우에만 if()문 안으로 들어가서 WHERE 절에 AND 조건을 추가한다. 
    • 그러나 딱히 어떤 값이 필요하지 않을 때에는 search.and()문을 추가할 필요가 없다. 
<hide/>
if (StringUtil.isNotEmpty(modelVo.getUseYn())) {
    search.and(model.useYn.equalsIgnoreCase(modelVo.getUseYn()));
}

 
 

  • 리팩토링 시도
    • 세 번째 줄에 return null 라고 넣어줘야 search.and(null) 이 들어가서 where 절은 형식상 있으나 실질적으로 어떤 조건이 들어가지 않는다. 
<hide/>
private BooleanExpression eqUseYn(String useYn){
    if(StringUtil.isEmpty(useYn)){
        return null; 
    }
    return model.useYn.eq(useYn);
}

...
public BooleanBuilder predicate(Model modelVo) {
       BooleanBuilder search = new BooleanBuilder();
       ...
       return search.and(eqUseYn(modelVo.getUseYn()));
       ...
       }
}

 


front-end 전체적인 구조

  • index.js 
    • Spring 으로 따지면 여러 개의 컨트롤러 클래스가 모인 통합 컨트롤러 클래스 느낌이라고 이해했다. 
  • vue 파일: 하나의 기능, 페이지 단위 마다 vue 파일을 만들 수 있다.
    • 각 vue 파일이 하나의 컴포넌트
    • 예를 들어, 상세 조회, 목록 조회, 등록, 수정 기능 등에 대한 기능 단위의 컴포넌트를 만들어서 뷰 파일로 정의한다. 
    • 기능 단위로 vue 파일을 만들거나 화면 단위로 vue를 만들기도 한다.
  • router
    • 각 라우터에  path, name, children 을 설정하고 children 안에는 여러 개의 children이 중첩해서 들어갈 수 있다. 
    • 하나의 라우터는 스프링의 컨트롤러에서 컨트롤러 안에 하나의 메소드와 비슷하다고 생각했다.
    • path에 URL 을 지정해서 해당 주소에 대해 컴포넌트 페이지를 하나씩 매핑한다. 
    • path: 현재 라우터의 경로 
    • name: 현재 라우터의 이름을 정의한다. 중복 불가능
    • children: 현재 라우터의 하위 라우터를 정의하는 영역
  • CommonLayout.vue 파일
    • 전체적인 레이아웃을 지정한다. 
    • 이미 만들어놓은 CommonLayoutFooter, CommonLayoutHeader를 가져와서 전체 레이아웃을 지정 가능한다. 홈페이지의 메뉴 목록 같은 내용이 정의되어 있다. 

 


Vue의 라이프사이클 훅(Lifecycle Hook)

출처 -&amp;nbsp;https://velog.io/@onehousesilver/Vue3-life-cycle-hook

 

  • Vue 객체는 여러 단계를 거쳐서 사용되는데 각 단계별로 hook을 이용해서 부가적으로 처리할 일을 작성할 수 있다. 
  • 라이프 사이클이란 Vue.js 컴포넌트가 생성되고 소멸되기까지의 단계를 말한다.
  • 각 단계의 특정 시점에 실행되는 함수들 라이프사이클 훅이라고한다.
  • 라이프 사이클(lifecycle): create => mount => update => unmount
    • create: 컴포넌트 UI 랜더 이전에 데이터를 구성하는 단계
    • mount: UI 렌더 단계
    • update: Vue의 state [Data()]가 바뀔 때
    • unmount: 컴포넌트 종료 단계
  • beforeCreated() 부터 unmounted() 까지 여러 단계를 거쳐서 Vue 인스턴스가 사용된다.
  • 애플리케이션의 특정 시점에 코드가 실행되도록 하는 기능을 제공한다. 
  • Vue의 라이프사이클 훅은 컴포넌트 혹은 컴포넌트 내부에서 직접 정의한다. 
  • Spring의 @EventListener 애너테이션도 애플리케이션이 시작할 때마다, 어떤 메서드가 실행되도록 정의가능한데 이와 비슷하다고 느낌
  • 생성: beforeCreated(), created(), beforeMount(), mounted()
    • beforeCreated(): 가장 먼저 실행되는 훅이다. 아직 데이터와 이벤트가 설정되지 않은 단계이다. create 단계 직전에 호출된다. 
  • 갱신: beforeUpdate(), update(), beforeUnmount(), unmounted()

 
 

참고) 
https://goodteacher.tistory.com/543
https://velog.io/@yeyo0x0/Vue.js-%EB%9D%BC%EC%9D%B4%ED%94%84%EC%82%AC%EC%9D%B4%ED%81%B4-%ED%9B%85

 


JavaScript 명령어 또는 키워드

  • ref() 
    • ref() 함수는 Vue.js3에서 쓰이는 함수 중 하나이다.
    • ref()를 사용하면 기본값을 가지는 변수를 생성할 수 있다. ref()함수를 통해 생성된 변수는 "value"를 통해 접근할 수 있다. 
    • ref() 함수는 기본적으로 값을 감싸는 객체를 생성한다. 이 객체는 Vue의 반응성 시스템과 함께 사용된다. 
    • 이 변수의 값은 'value' 프로퍼티에 저장된다. 
    • ref()함수로 생성된 변수의 값을 변경하면 Vue.js는 자동으로 컴포넌트의 뷰를 업데이트한다. 
    • DOM요소에 접근하기 위해 사용한다. 
    • ref를 통해 인스턴스 또는 배열에도 접근이 가능하다.
  • <script setup>:  Composition API를 사용하는 컴포넌트에서 간단하고 명확한 구문으로 컴포넌트의 데이터, 메서드 등을 선언할 수 있도록 도와준다. 키워드 "this"를 안 써도 된다

기본 용어

 
 
렌더링(rendering)

  • 브라우저는 HTML을 파싱하고 DOM(문서 객체 모델)을 생성한다. 그러고나서 CSS를 파싱하고 CSSOM을 생성한다. 브라우저는 이 두 개를 결합해서 렌더 트리를 생성한다. 다음으로 렌더링 엔진이 이를 화면에 출력한다 
  • 다시 말해, 렌더링은 html, css, 자바스크립트 코드를 실행하고 이를 화면에 출력하는 과정을 말한다. 

 
라우터(router)

  • 라우터는 SPA(싱글 페이지 애플리케이션)에서 URL을 통해 페이지를 이동할 수 있도록 하는 기능이다. 
  • 일반적인 웹 애플리케이션은 페이지 전환 시에 새로운 HTML파일을 가져오는게 기본이지만 SPA는 클라이언트 측에서 자바스크립트를 이용해서 DOM을 조작하고 필요한 부분만 업데이트해준다. 
  • Vue router를 사용하면 각 페이지마다 컴포넌트를 정의해서 라우팅 매핑을해서 URL에 따라 해당 컴포넌트를 렌더링할 수 있다. 

찾아보기

  • 개발자 도구 창으로 디버깅하는 방법