vue + jpa project (25) - 공통코드 연관(join)조회 백엔드(1) 본문
vue + jpa project (25) - 공통코드 연관(join)조회 백엔드(1)
- 2023. 12. 1. 16:29
이번 장부터는 알아야할 게 좀 많아서 나 자신도 프로그램을 구현하면서 많이 헤깔리고 어려운 부분이었다.
그래서 이론적인 부분도 잠깐 드려다 보면서 하나씩 진행을 하도록 하겠다.
우선 지난번에 두개 테이블의 관계가 그려진 이미지를 다시 한번 살펴 보겠다.
그룹코드가 부모이고 코드 상세가 그 자식이 된다.
그래서 JPA에서 말하는 연관관계가 있는데 그 연관관계의 종류를 살펴본다면
일대다 단/양방향, 다대일 단/양방향, 일대일 단/양방향, 다대다 단/양방향 같은 것이 존재한다.
여기서 다대다는 실무에서는 거의 사용하고 있지 않는 것으로 알고 있다.
이번 장에서 JPA의 연관관계를 모두 알아보려면 몇일이 걸릴지도 모르기 때문에
일단 이번 장에서는 위의 이미지에 대한 연관관계만 알아보도록 한다.
우선 이미지에서도 볼 수 있듯이 이번 관계는 일대다 또는 다대일 관계이다.
어떻게 사용하느냐에 따라서 일대다도 되고 다대일도 될 수는 있다.
하지만 2개의 테이블에서 관계의 소유자가 어느 것인지를 먼저 파악을 한 뒤에 관계를 정립해야한다.
일반적으로 일대다와 다대일 관계를 연결할 경우, 대부분의 개발자들은 다대일 관계로 처리하는 것이
더 용이하다고 하고 있다. 그리고 나중에 엔티티를 수정하고 실행하면 외부키가 상세쪽에 생긴다.
따라서 소유주는 상세코드 테이블이 된다. 따라서 해당 관계를 다대일의 관계로 진행하려고 한다.
기본적으로는 다대일 단방향의 관계로 진행하면 되나 이후 장에서도 나오겠지만
Querydsl 사용시 처리 방식이 다양하나 두가지 패턴을 가지고 처리하기 위해서는
양방향으로 잡고 진행해야 한다.
일단 지금까지의 결론은 다대일 양방향으로 진행하는 것이다.
(단, 이번 장에서는 다대일 단방향을 먼저 테스트 한 뒤에 진행할 것이다.)
우선 Querydsl 사용 패턴 중 첫번째로 소유자인 상세코드를 기준으로 연관관계인 그룹코드까지 데이터를 가져오는 것을 진행할 것이다. (단방향으로)
1. 상세코드 엔티티 수정
다대일 관계를 위해서 아래를 추가한다.
CodeDetailEntity.java
... 중략
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name="groupCode")
@JsonIgnore
private CodeGroupEntity codeGroupEntity;
... 중략
@ManyToOne 의 FetchType은 기본적으로 EAGER 인데 여기서는 LAZY로 지정하였다. 물론 이후 조회시에 join처리가
아닌 연관관계를 통해서 가져오려고 하기 때문에 사실 FetchType에 영향을 받지는 않지만 일단 LAZY로 하여
해당 키값에 순수하게 맵핑되는 그룹코드만 가져오기 위해서 '지연로딩'을 택했다.
※ EAGER, LAZY 타입도 조금은 이해가 필요하지만 즉시 로딩, 지연 로딩으로 일단 여기서는 이정도로 하겠다.
다른 사이트에서 해당 이론을 좀 더 알아보면 좋을 것이다.
2. 그룹코드 엔티티 수정
일단 여기서는 단방향으로 진행하기 때문에 수정사항은 없다.
엔티티를 수정한 뒤에 서버를 재기동 해보면 테이블의 외부키가 생성이 된걸 볼 수 있다.
만약 연관 관계가 생성이 안되고 오류가 난다면 두개 테이블의 해당 연관관계 필드가 서로 Charset이 다르면 같은 것으로
맞춰야만 오류가 발생이 안된다.
엔티티가 완료되었다면 gradle build를 통해서 QClass를 생성시키자.
3. 상세코드 Dto 수정
CodeDetailDto.java 파일
CodeDetailDto.java
... 중략
private CodeGroupEntity codeGroupEntity;
... 중략
(원래 엔티티 항목을 복사하면서 생성한 것이라 그냥 복사했다)
4. 그룹코드 Dto 수정
일단 여기서는 수정할 건 없다.
5. 공통코드 레파지토리 추가
여기서 공통코드로 정의한 이유는 추후에 다른 패턴도 이 레파지토리를 이용하기 위해서이다.
다른 레파지토리와 같은 패키지에 아래와 같이 추가한다.
CommonCodeRepositoryCondition.java
@RequiredArgsConstructor
@Repository
public class CommonCodeRepositoryCondition {
@PersistenceContext
private EntityManager em;
public Page<CodeDetailEntity> searchCodeDetailCondition(Pageable pageable, CodeDetailDto codeDetailDto) {
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
QCodeGroupEntity a = new QCodeGroupEntity("a");
QCodeDetailEntity b = new QCodeDetailEntity("b");
JPAQuery<CodeDetailEntity> query = queryFactory.selectFrom(b)
.where(b.groupCode.eq("A01"), b.useYn.eq("Y"));
long total = query.stream().count();
List<CodeDetailEntity> results = query
//.where(b.groupCode.eq("A01"), b.useYn.eq("Y"))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
AtomicInteger index = new AtomicInteger(0); // 시작 인덱스
results.forEach((result) -> {
int currentIndex = index.incrementAndGet();
System.out.println("count =============== " + currentIndex);
System.out.println("groupCode : " + result.getGroupCode());
System.out.println("codeValue : " + result.getCodeValue());
System.out.println("codeName : " + result.getCodeName());
System.out.println("groupEntity : " + result.getCodeGroupEntity());
});
System.out.println("last =============== " + index + "/ total = " + total);
return new PageImpl<>(results, pageable, total);
}
}
게시판의 처리 방식과 거의 유사하다. EntityManager를 JPAQueryFactory에 추가하여 생성시키고,
쿼리를 조건을 부여하고 우선 전체 건수를 가져온 다음에 페이지에 맞는 건을 다시 추출한(fetch) 뒤에 상세코드 엔티티의
리스트로 담는다. 엔티티 리스트로 담긴 걸 forEach문을 이용하여 콘솔로 찍어보도록 하자.
여기서 잠깐만 게시판과 비교해보면 뭔가 부족하다. 맞다. 조회조건에 대한 처리가 빠져있다.
이 부분은 잠깐 뒤로 미루자. 지금은 이 부분보다 더 중요한 부분이 우리 앞을 기다리고 있다.
일단 하드 코딩으로 진행하겠다.
그리고 팁하나를 추가한다면
forEach 문 안에서는 (i++) 같은 것을 사용할 수 없다. 그래서 다른 방안으로 처리해야하는데
그 중에 하나가 AtomicInteger 이다. AtomicInteger(0)은 0으로 시작하겠다라는 것이다.
그리고 메소드 중에 incrementAndGet() 이 있는데 이게 뭐냐면 (++i) 와 같다.
즉, 증가를 먼저 시키고 값을 꺼내겠다 라는 것이다.
이것을 통해서 총 몇번을 수행하는 지를 확인 할 수 있다.
참고로 먼저 꺼내어 쓰고 증가시키는 (i++)같은 메소드도 있다.
6. 상세코드 서비스
기존 상세코드 서비스 파일에 아래 내용을 추가하고 Entity -> Dto로 변환하는 메소드도 수정한다.
위에서 만든 레파지토리를 호출하는 부분이다.
...중략
private final CommonCodeRepositoryCondition ccRepository;
...중략
/**
* 공통코드 전체 일괄 조회
*/
public Header<List<CodeDetailDto>> getCommonCodeTotalSearch(CodeDetailDto codeDetailDto, Pageable pageable) {
Page<CodeDetailEntity> codeDetailEntities = ccRepository.searchCodeDetailCondition(pageable, codeDetailDto);
List<CodeDetailDto> dtos = new ArrayList<>();
for (CodeDetailEntity entity : codeDetailEntities) {
dtos.add(entityToDto(entity));
}
log.debug(dtos.toString());
Pagination pagination = new Pagination(
(int)codeDetailEntities.getTotalElements()
, pageable.getPageNumber() + 1
, pageable.getPageSize()
, 10
);
return Header.OK(dtos, pagination);
}
...중략
/* 테이블 데이터 -> 화면쪽으로 */
private CodeDetailDto entityToDto(CodeDetailEntity entity) {
CodeDetailDto dto = CodeDetailDto.builder()
...중략
.useYn(entity.getUseYn())
.codeGroupEntity(entity.getCodeGroupEntity()) <-- 추가된 부분
.regDate((entity.getRegDate() != null)? entity.getRegDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")):null)
...중략
}
7. 상세코드 컨트롤러 수정
상세코드 컨트롤러를 아래와 같이 수정한다. CodeDetailController.java
... 중략
@GetMapping("/detail_join_list")
public Header<List<CodeDetailDto>> detail_join_list(
CodeDetailDto codeDetailDto
, @PageableDefault Pageable pageable) throws Exception {
log.info("detail_join_list");
return service.getCommonCodeTotalSearch(codeDetailDto, pageable);
}
... 중략
작성이 완료되었다면 테스트를 한번 해보자. 프론트로 만들 화면으로 접속해보자.
코드리스트 기본 화면이 해당 처리 방식이다. fnDetailJoinList()
실행을 하면 바로 로그아웃이 된다. 오류가 발생한 것이다.
톰캣 쪽 콘솔 로그를 찾아가 보니 아래와 같이 오류가 발생했다고 한다.
마지막에
org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor] 라고 오류가 발생했다.
왜 그런지 검색을 좀 해보니 위에서 다대일 연관 관계를 구성한 뒤에 FetchType을 LAZY를 준게 이유였다.
LAZY로 처리하니 엔티티가 리턴이 될때 엔티티 그 자체가 리턴이 되는게 아니라 Proxy 처리된 가상의 엔티티로 바뀌어서
Serializer 로 처리되려고 하다보니 오류가 발생한 것이다. 그런데 처리를 위해서 LAZY를 뺄 수는 없다.
그래서 해결 방안을 찾아보니 두가지 방법이 있는데 첫번째 방법은 application.yml 파일에 아래와 같이 추가하는 것이다.
spring:
jackson:
serialization:
fail-on-empty-beans: false
이 방식은 가상의 필드가 생성이 되는 형식이다.
하지만 이 방식은 근본적인 해결 방법이 아니라서 나머지 방법으로 처리하기로 한다.
두번째 방법은 Dto 를 통해서 초기화 시키는 방식으로 처리한다. (이 방식이 제일 적합하다)
8. 그룹코드 Dto 재수정
CodeGroupDto.java를 아래와 같이 추가한다.
... 중략
public static CodeGroupDto init(CodeGroupEntity codeGroupEntity) {
return new CodeGroupDto(
codeGroupEntity.getGroupCode(),
codeGroupEntity.getGroupName(),
codeGroupEntity.getUseYn(),
(codeGroupEntity.getRegDate() != null)? codeGroupEntity.getRegDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")):null,
(codeGroupEntity.getUpdDate() != null)? codeGroupEntity.getUpdDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")):null
);
}
... 중략
9. 상세코드 Dto 재수정
CodeDetailDto.java를 아래와 같이 추가한다.
(CodeDetailDto.java 에서 CodeGroupEntity 필드는 제거해도 그냥 둬도 무방하다. 사용은 X)
... 중략
private CodeGroupDto codeGroupDto; // 추가
... 중략
10. 상세코드 서비스 재수정
다시 아래와 같이 재수정 하자.
... 중략
/* 테이블 데이터 -> 화면쪽으로 */
private CodeDetailDto entityToDto(CodeDetailEntity entity) {
CodeDetailDto dto = CodeDetailDto.builder()
.groupCode(entity.getGroupCode())
.codeValue(entity.getCodeValue())
.codeName(entity.getCodeName())
.sortSeq(entity.getSortSeq())
.useYn(entity.getUseYn())
.codeGroupDto(CodeGroupDto.init(entity.getCodeGroupEntity())) <-- 추가
//.codeGroupEntity(entity.getCodeGroupEntity()) <-- 주석처리
.regDate((entity.getRegDate() != null)? entity.getRegDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")):null)
.updDate((entity.getUpdDate() != null)? entity.getUpdDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")):null)
.build();
return dto;
}
... 중략
서비스 단에서 CodeGroupDto.init 에 그룹엔티티를 넣어서 Dto로 초기화 한 뒤에 넘기겠다는 것이다.
이렇게 수정이 완료되면 한번 실행시켜 보자.
이번엔 아래와 같이 잘 조회가 된다.
얼핏보면 그룹코드를 기준으로 조회되는 것 같지만 실제 데이터는 상세코드를 기준으로 하여 값을 불러온다.
상세코드의 그룹코드 값이 'A01' 인 것만 가져오면서 그에 맞는 그룹코드 값을 가져오는 것이다.
자세히 보면 그룹코드에 대한 값은 모두 동일하다. 상세코드의 부모 값은 하나로 동일하기 때문이다.
json으로 리턴되는 걸 보면 더 이해가 빠를 것이다.
다음 장에서는 그룹코드를 기반으로 한 연관조회를 진행해 볼 것이다.
'프로그램 > Vue.js' 카테고리의 다른 글
vue + jpa project (27) - 공통코드 연관(join)조회 프론트 재수정 (2) | 2023.12.05 |
---|---|
vue + jpa project (26) - 공통코드 연관(join)조회 백엔드(2) (2) | 2023.12.04 |
vue + jpa project (24) - 공통코드 연관(join)조회 프론트 (0) | 2023.12.01 |
vue + jpa project (23) - 공통코드 상세코드관리(2) (0) | 2023.11.23 |
vue + jpa project (22) - 공통코드 상세코드관리(1) (0) | 2023.11.23 |
RECENT COMMENT