에러 해결
- 오류: 페이징 에러
- 원인: 메서드 실행 순서 오류
- 해결: 실행 순서를 바꾼다.
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: 데이터베이스의 수정 작업을 할 때 쓰인다. 수정, 삭제, 등록할 때 쓰인다.
- @Query 를 이용하면 안에 직접 쿼리를 작성할 수 있어서 편리하다.
참고
인덱스 레인지 스캔: 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)
- 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에 따라 해당 컴포넌트를 렌더링할 수 있다.
찾아보기
- 개발자 도구 창으로 디버깅하는 방법
'개발 일지 > 주간 개발 일지' 카테고리의 다른 글
[04월 4주차] 프론트엔드 디버깅 방법, PostgreSQL 필드 변경 시 MySQL 과 문법상 차이점 (0) | 2023.04.30 |
---|---|
[04월 3주차] Vue.js 3 과 Spring (0) | 2023.04.22 |
[04월 1주차] 페이징(Paging), QueryDSL, LocalDate와 String 변환 방법 (1) | 2023.04.08 |
[03월 5주차] Thread API 전체적인 흐름 파악하기, 고정 소수점과 부동 소수점 (2) | 2023.04.01 |
[03월 4주차] OkHttp 예제, Thread와 ExecutorService (1) | 2023.03.26 |