본문 바로가기
BackEnd/QueryDsl JPA

QueryDsl에서 MySQL Function 사용하기

by 규난 2022. 9. 27.
728x90

부트캠프에서 팀 프로젝트를 진행하면서 QueryDsl을 사용하게 되었다.

마지막 기능 구현 부분인 모임의 일정 기능 구현을 진행하다가

유저가 모임 일정을 생성하려는 경우 일 별로 유저가 모임을 참여했거나, 모임을 생성한 시간들을 데이터 베이스에서 가져와서 일정이 겹치는 경우 생성을 막아줘야하는 부분을 구현해야 할 것 같아서 QueryDsl에서 MySQL Function을 사용 해야 할 일이 생겨버렸다..

처음에는 당연히 MySQL의 함수를 쓸 수 있을거 같아서 바로 쓴 결과는 실패였다.

No data type for node: org.hibernate.hql.internal.ast.tree.MethodNode 

위 내용으로 구글링을 한 결과 dialect를 extends한 CustomDialect를 만들어 줘야 했다.

// CustomMySQL8InnoDBDialect
public class CustomMySQL8InnoDBDialect extends MySQL8Dialect {

    public CustomMySQL8InnoDBDialect() {
        super();

        this.registerFunction("DATE_FORMAT", new StandardSQLFunction("DATE_FORMAT", StandardBasicTypes.STRING));
        this.registerFunction("TIMESTAMPDIFF", new StandardSQLFunction("TIMESTAMPDIFF", StandardBasicTypes.LONG));
    }
}

// yml
jpa:
    open-in-view: false
    properties:
      hibernate:
        format_sql: true
        default_batch_fetch_size: 1000
        jdbc.batch_size: 1000
        dialect: com.workduo.configuration.querydsl.CustomMySQL8InnoDBDialect // 이 부분을 custom dialect으로 변경
    database: mysql

여기서 주의 할 점은 함수를 써서 반환 받을 타입을 StandardBasicTypes.반환타입 으로 잘 정의해줘야 한다 

밑 코드에서 dto의 term이 Long 타입인데 TIMESTAMPDIFF를 StandardBasicType.String 으로 지정을했더니

argument type mismatch 라는 예외가 터진다.

 

// DTO
@Getter
@Setter
@NoArgsConstructor
@Builder
public class MeetingInquireDto {

    private String time;
    private Long term;

    @QueryProjection
    public MeetingInquireDto(String time, Long term) {
        this.time = time;
        this.term = term;
    }
}

// query repository
@Repository
@RequiredArgsConstructor
public class GroupMeetingQueryRepositoryImpl implements GroupMeetingQueryRepository {

    private final JPAQueryFactory jpaQueryFactory;

    @Override
    public List<MeetingInquireDto> meetingInquireList(Long memberId, LocalDateTime startDate, LocalDateTime endDate) {

        return jpaQueryFactory
                .select(
                        new QMeetingInquireDto(
                                startTime(groupMeeting.meetingStartDate),
                                minuteDiff(
                                    groupMeeting.meetingStartDate,
                                    groupMeeting.meetingEndDate
                                )
                        )
                )
                .from(groupMeeting)
                .where(inquireMeeting(memberId, startDate, endDate))
                .fetch();
    }

    private StringTemplate startTime(Expression<? extends LocalDateTime> startDate) {
        return  Expressions.stringTemplate(
                "DATE_FORMAT({0}, {1})",
                groupMeeting.meetingStartDate,
                "%H:%i"
        );
    }

    private NumberExpression<Long> minuteDiff(
            Expression<? extends LocalDateTime> startDate,
            Expression<? extends LocalDateTime> endDate) {
        return MathExpressions.round(
                Expressions.numberTemplate(
                        Long.class,
                        "TIMESTAMPDIFF(MINUTE, {0}, {1})",
                        startDate,
                        endDate
                )
                ,-1
        );
    }

    private BooleanExpression inquireMeeting(
            Long memberId,
            LocalDateTime startDate,
            LocalDateTime endDate) {
        return groupMeeting.id.in(
                select(groupMeetingParticipant.groupMeeting.id)
                        .from(groupMeetingParticipant)
                        .join(groupMeetingParticipant.member, member)
                        .where(
                                groupMeetingParticipant.member.id.eq(memberId),
                                groupMeeting.meetingStartDate.between(
                                        startDate,
                                        endDate
                                )
                        )
        );
    }
}

// test code
@Test
@DisplayName("meetingInquireList")
@Transactional
@Rollback(value = false)
public void meetingInquireList() throws Exception {
    // given
    Long memberId = 3L;
    LocalDateTime startDate = LocalDateTime.of(2022, 9, 26, 0, 0, 0);
    LocalDateTime endDate = LocalDateTime.of(2022, 9, 26, 23, 59, 59, 999999);
    
    // when
    List<MeetingInquireDto> meetingInquireDtos = groupMeetingQueryRepository.meetingInquireList(memberId, startDate, endDate);

    // then 
    for (MeetingInquireDto meetingInquireDto : meetingInquireDtos) {
        System.out.println(meetingInquireDto.getTime() + " " + meetingInquireDto.getTerm());
    }
}

실행 결과를 보니 원하는 대로 쿼리가 잘 나가고 결과도 잘 나온다.

 

2022-09-26의 유저3의 일정

{
    "date": "2022-09-26",
    "times": [
        {
            "time": "00:00",
            "disabled": true
        },
        {
            "time": "01:00",
            "disabled": false
        },
        {
            "time": "02:00",
            "disabled": false
        },
        {
            "time": "03:00",
            "disabled": true
        },
        {
            "time": "04:00",
            "disabled": false
        },
        {
            "time": "05:00",
            "disabled": false
        },
        {
            "time": "06:00",
            "disabled": false
        },
        {
            "time": "07:00",
            "disabled": false
        },
        {
            "time": "08:00",
            "disabled": false
        },
        {
            "time": "09:00",
            "disabled": false
        },
        {
            "time": "10:00",
            "disabled": false
        },
        {
            "time": "11:00",
            "disabled": false
        },
        {
            "time": "12:00",
            "disabled": false
        },
        {
            "time": "13:00",
            "disabled": false
        },
        {
            "time": "14:00",
            "disabled": false
        },
        {
            "time": "15:00",
            "disabled": false
        },
        {
            "time": "16:00",
            "disabled": false
        },
        {
            "time": "17:00",
            "disabled": false
        },
        {
            "time": "18:00",
            "disabled": false
        },
        {
            "time": "19:00",
            "disabled": false
        },
        {
            "time": "20:00",
            "disabled": true
        },
        {
            "time": "21:00",
            "disabled": true
        },
        {
            "time": "22:00",
            "disabled": true
        },
        {
            "time": "23:00",
            "disabled": false
        }
    ]
}

유저3은 00:00 ~ 01:00, 03:00 ~ 04:00, 20:00 ~ 23:00 이렇게 3개의 일정이 있으므로 해당 시간에는 disabled를 true로 보내줌으로써 해당 시간에 모임 생성을 못 하도록 막을 수 있다.

728x90