vue + jpa project (26) - 공통코드 연관(join)조회 백엔드(2) 본문

프로그램/Vue.js

vue + jpa project (26) - 공통코드 연관(join)조회 백엔드(2)

반응형

 

이번 장에서는 Querydsl 의 두번째 방식인 그룹코드를 기반으로 한 조회를 진행해보도록 하겠다.

 

Querydsl 사용 패턴 중 두번째로 그룹코드를 기준으로 연관관계인 그룹코드를 left/right outer join을 이용하여 조회하는 것이다.

 

1. 그룹코드 엔티티 수정

   그룹코드에서 상세코드 쪽으로 연관관계를 맺는다. 아래를 추가하면서 양방향이 된다.

CodeGroupEntity.java
... 중략
    @JsonIgnore
    @OneToMany
    @JoinColumn(name="groupCode")
    private List<CodeDetailEntity> codeDetailEntity;
... 중략

@OneToMany 의 FetchType은 기본적으로 LAZY이기 때문에 생략했다.

 

엔티티가 완료되었다면 gradle build를 통해서 QClass를 갱신시키자. 

 

 

2. 그룹코드 Dto 수정

   그룹코드 이외에 상세코드의 정보도 가져오기 위해서 아래 항목을 추가한다.

CodeGroupDto.java
... 중략
    private String codeValue;
    private String codeName;
    private int sortSeq;
    private String detailRegDate;
    private String detailUpdDate;
... 중략

@OneToMany 의 FetchType은 기본적으로 LAZY 여서 여기서는 생략을 했다.

사실 이 부분도 실제 FetchType의 영향을 받지는 않을 것이다. 

 

그런데 항목을 추가하자마자 아래 쪽에 init 메소드가 오류가 난다.

그것은 @AllArgsConstructor 때문인데 기존에는 5개의 항목의 최대여서 자동으로 생성이 되었었는데

이제 항목이 늘어나는 바람에 @AllArgsConstructor 로 인해서 파라미터가 10개인 생성자가 암묵적으로 생성이 되었기 

때문이다. 그래서 별도의 생성자를 추가한다. 

 

3. 그룹코드 Dto 재수정

    추가한 필드 아래에 생성자를 추가한다.

CodeGroupDto.java
... 중략
    public CodeGroupDto(String groupCode, String groupName, String useYn, String regDate, String updDate) {
        this.groupCode = groupCode;
        this.groupName = groupName;
        this.useYn = useYn;
        this.regDate = regDate;
        this.updDate = updDate;
    }
... 중략

 

 

 

4. 공통코드 레파지토리 수정

  기존 공통코드 레파지토리로 추가한 클래스 파일에서 아래와 같이 그룹코드 기준의 join하는 메소드를 추가한다.

CommonCodeRepositoryCondition.java

@RequiredArgsConstructor
@Repository
public class CommonCodeRepositoryCondition {
    
    @PersistenceContext
    private EntityManager em;

    public Page<CodeDetailEntity> searchCodeDetailCondition(Pageable pageable, CodeDetailDto codeDetailDto) {
        ...중략
    }

    // 추가 메소드
    public Page<CodeGroupDto> searchCodeGroupCondition(Pageable pageable, CodeDetailDto codeDetailDto) {
        JPAQueryFactory queryFactory = new JPAQueryFactory(em);
        
        QCodeGroupEntity a = new QCodeGroupEntity("a");
        QCodeDetailEntity b = new QCodeDetailEntity("b");

        JPAQuery<CodeGroupEntity> query = queryFactory.selectFrom(a)
                .leftJoin(a.codeDetailEntity, b);
        
        long total = query.stream().count();

        List<CodeGroupDto> results = query
        		.select(Projections.bean(CodeGroupDto.class, a.groupCode, a.groupName, b.codeValue, b.codeName, b.sortSeq, a.regDate, a.updDate, a.useYn, b.regDate, b.updDate))
                .where(a.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("groupName : " + result.getGroupName());
            System.out.println("codeValue : " + result.getCodeValue());
            System.out.println("codeName : " + result.getCodeName());
            System.out.println("sortSeq : " + result.getSortSeq());
            System.out.println("regDate : " + result.getRegDate());
            System.out.println("updDate : " + result.getUpdDate());
            System.out.println("detailRegDate : " + result.getDetailRegDate());
            System.out.println("detailUpdDate : " + result.getDetailUpdDate());
        });
        
        System.out.println("last =============== " + index + "/ total = " + total);

        return new PageImpl<>(results, pageable, total);
    }
}

해당 소스에서 눈여겨 볼만한 부분은 leftjoin 부분이다. 두번째 패턴의 핵심인 부분이다. 

앞장에서는 연관관계를 이용하여 처리를 한 부분이라면 이번 장에서는 join을 이용하여 fetch처리를 한 부분이 핵심이다.

 

 

여기서 잠깐 쿼리 작성용 Methods 를 확인해보겠다.

JPAQuery 메소드들은 모두 자신이 속한 객체를 리턴하므로 메소드 체이닝기법으로 "."을 찍고 연속해서 메소드를 사용할 수 있다. JPAQuery가 구현한 JPQLQuery 인터페이스의 cascading 메소드들은 다음과 같다.

메소드 기능
from 쿼리 대상을 설정한다.
innerJoin, join,
leftJoin, fullJoin, on
조인 부분을 추가한다. 조인 메소드에서 첫 번째 인자는 조인 소스이고, 두 번재 인자는 대상(별칭으로 지정 가능)이다.
where 쿼리에 필터를 추가한다.
가변인자나 and, or 메소드를 이용해서 필터를 추가한다.
groupBy 가변인자 형식의 인자를 기준으로 그룹을 추가한다.
having Predicate 표현식을 이용해서 "group by" 그룹핑의 필터를 추가한다.
orderBy 정렬 표현식을 이용해서 정렬 순서를 지정한다. 숫자나 문자열에 대해서는 asc()나 desc()를 사용하고, OrderSpecifier에 접근하기 위해 다른 비교 표현식을 사용한다.
limit, offset, restrict 결과의 페이징을 설정한다. limit은 최대 결과 개수, offset은 앞에서부터 건너 뛸 로우의 개수, restrict는 limit과 offset을 함께 정의한다.

 

 

5. 그룹코드 서비스 수정

   그룹코드 서비스에 공통함수를 호출하는 메소드를 추가한다.

CodeGroupService.java

... 중략
    private final CommonCodeRepositoryCondition ccRepository;

    /**
     * 공통코드 전체 일괄 조회(그룹코드 기준)
     */
    public Header<List<CodeGroupDto>> getCommonCodeTotalSearch(CodeDetailDto codeDetailDto, Pageable pageable) {

        ModelMapper modelMapper = new ModelMapper();
        
        Page<CodeGroupDto> codeGroupEntities = ccRepository.searchCodeGroupCondition(pageable, codeDetailDto);
        List<CodeGroupDto> dtos = new ArrayList<>();

        for (CodeGroupDto entity : codeGroupEntities) {
            dtos.add(entity);
        }
        
        log.debug(dtos.toString());
        
        Pagination pagination = new Pagination(
                (int)codeGroupEntities.getTotalElements()
                , pageable.getPageNumber() + 1
                , pageable.getPageSize()
                , 10
        );

        return Header.OK(dtos, pagination);
    }
... 중략

상세코드 서비스와 좀 다른 점은 엔티티를 Dto로 바꾸는 메소드를 사용하지 않았다. 

그 이유는 이미 쿼리 단계에서부터 Dto로 변환이 되어 넘어오기 때문이다. 그냥 단순히 Page리스트를 List로 변환하는 

것이다.

 

 

6. 그룹코드 컨트롤러 수정

    공통코드 컨트롤러에 아래와 같이 메소드를 추가한다.

CodeGroupController.java

... 중략
    @GetMapping("/group_join_list")
    public Header<List<CodeGroupDto>> group_join_list(
            CodeDetailDto codeDetailDto
            , @PageableDefault Pageable pageable) throws Exception {
        log.info("group_join_list");
        
        return service.getCommonCodeTotalSearch(codeDetailDto, pageable);
    }
... 중략

다른 점은 없고, 그냥 위에서 추가된 서비스를 호출한다.

 

일단 소스가 완료되면 톰캣을 재기동 한 다음에 한번 실행시켜보자. 실행시키면 아래와 같은 오류가 발생한다.

LocalDateTime 을 String으로 변환할 수 없다고 한다. 말 그대로 그럴 수 밖에 없다. 

select 절에서 regDate나 updDate 필드를 조회하여 반환하면서 그것을 CodeGroupDto.java 로 입력하려고 하는 것인데 
CodeGroupDto.java 의 Date 필들의 속성은 String이기 때문이다. 그래서 LocalDateTime 으로 넘어오는 필드를 

select 절에서 변환시켜서 넘겨야만 한다.

 

아래는 조회된 값을 Date 값을 String으로 변환(mariadb기준)하여 주는 샘플이다. 

	StringTemplate changeDate = Expressions.stringTemplate(
			"DATE_FORMAT({0}, {1})"
			, 엔티티 항목
			, ConstantImpl.create("%Y-%m-%d %T"));

 

이것을 하나하나 변수로 정의하기는 그래서 공통 함수로 만들자.

 

 

7. 공통함수 클래스 추가

    원래는 공통함수로 사용하는 클래스인데, 지금은 공통으로 쓸만한게 위에 것 밖에 없어서 일단 이것만 추가한다.

   패키지는  com.example.vueJpaProject.util 로 가져간다. 파라미터는 엔티티의 필드이다. 

StringUtils.java
... 중략
public class StringUtils {
    
    public static StringTemplate getEntityDateTimeFormat(Object entityField) {
        
        StringTemplate changeDate = Expressions.stringTemplate(
                "DATE_FORMAT({0}, {1})"
                , entityField
                , ConstantImpl.create("%Y-%m-%d %T"));
        
        return changeDate;
    }

}
... 중략

 

 

8. 공통코드 레파지토리  재수정

    공통 레파지토리를 재수정하자. DateTime의 형태를 String으로 바꾸는 것이다.

CommonCodeRepositoryCondition.java

... 중략
    
	List<CodeGroupDto> results = query
			.select(Projections.bean(CodeGroupDto.class, a.groupCode, a.groupName, b.codeValue, b.codeName, b.sortSeq, StringUtils.getEntityDateTimeFormat(a.regDate), StringUtils.getEntityDateTimeFormat(a.updDate), a.useYn, StringUtils.getEntityDateTimeFormat(b.regDate)))
			.where(a.useYn.eq("Y"))
			.offset(pageable.getOffset())
			.limit(pageable.getPageSize())
			.fetch();
... 중략

 

이렇게 수정하고 다시 실행시켜 보자. 

이런 다시 오류가 난다. 

무엇 때문에 발생하는지 확인을 해보니 위의 메소드를 처리한 부분에서 오류가 난다. 오류가 난 이유는 alias가 없어서 

나는 오류였다. 그래서 alias를 부여를 해보자

 

9. 공통코드 레파지토리  재재수정

 

CommonCodeRepositoryCondition.java

... 중략
	List<CodeGroupDto> results = query
			.select(Projections.bean(CodeGroupDto.class, a.groupCode, a.groupName, b.codeValue, b.codeName, b.sortSeq, 
					StringUtils.getEntityDateTimeFormat(a.regDate).as("regDate"), StringUtils.getEntityDateTimeFormat(a.updDate).as("updDate"), a.useYn, 
					StringUtils.getEntityDateTimeFormat(b.regDate).as("detailRegDate"), StringUtils.getEntityDateTimeFormat(b.updDate).as("detailUpdDate")))
			.where(a.useYn.eq("Y"))
			.offset(pageable.getOffset())
			.limit(pageable.getPageSize())
			.fetch();
... 중략

 

이렇게 수정하고 다시 실행시켜 보자

 

이번엔 아래와 같이 잘 조회가 된다. 

잘보면 상세코드가 없는 그룹코드도 모두 조회가 되어서 나온다. 

응답 받은 데이티를 확인하면 더 잘 이해가 될 것이다.

 

일단 어려운 연관관계 처리를 마무리 하였다.

다음 장에서는 검색 등을 감안하여  소스를 좀 더 개선시키도록 하겠다. 

 

반응형

프로그램/Vue.js Related Articles

MORE

Comments