조급하면 모래성이 될뿐

JPQL like에 _ 포함시키기 본문

TroubleShooting/데브코스

JPQL like에 _ 포함시키기

Pawer0223 2022. 7. 1. 02:02

상황


  • 설계한 Entity의 id는 2개의 fk 조합으로 {id}_{id} 형태의 String 타입으로 저장된다.
    • {특정 스케줄}_{좌석 번호}를 의미한다.
    • ex) 1_0, 1_1, 1_2...
서비스에서 특정 스케줄에 등록된 좌석번호를 찾기 위해 like {특정 스케줄}_%로 검색하려 한다.

 

AS-IS


  • service에서 {id}_ 를 만들어서 전달했다.
@Service
@Transactional(readOnly = true)
public class ReservedSeatService {	

	...
    
    public List<ResponseFindSeat> findByScheduleId(Long scheduleId) {
		return reservedSeatRepository.searchByScheduleIdStartsWith(makeFindByScheduleParam(scheduleId)).stream()
			.map((reservedSeat -> seatConverter.convertFromSeatToResponseFindSeat(reservedSeat.getSeat())))
			.collect(toList());
	}

	private String makeFindByScheduleParam(Long scheduleId) {
		return new StringBuilder()
			.append(scheduleId)
			.append(ID_SEPARATOR)
			.toString();
	}
}
public interface ReservedSeatRepository extends JpaRepository<ReservedSeat, String> {

	@Query("SELECT rs FROM ReservedSeat rs join fetch rs.seat WHERE rs.id LIKE :scheduleId%")
	List<ReservedSeat> searchByScheduleIdStartsWith(@Param("scheduleId") String scheduleId);
}

 

문제는 외부에서 scheduleId 포맷을 만들어서 전달해야 한다는 것..

 

  • Repository 테스트 코드 작성할 때도 scheduleId를 만들어서 보내는 게 비효율적이라고 느꼈다..
    • 직접 service와 규칙을 맞춰줘야 한다..

 

목표


  • @Query에 _를 포함시키고 싶었다.
  • 아래의 테스트 결과는 1이 나와야 한다.
	@Test
	@DisplayName("만약 1_1과 11_1이 있을 때, 1_로 조회하면 1건만 조회되어야 한다.")
	void testSearchByIdStartsWithBadParam() {
		// 1_1, 2_2, ... , 10_10, 11_11, 12_12  의 데이터가 저장 될 것이다.
		for (int i = 0; i < 12; i++) {
			saveReservedSeatMultiSeat(1);
		}
        
        // 1_0만 조회되기를 기대한다.
		List<ReservedSeat> reservedSeats = reservedSeatRepository.searchByScheduleIdStartsWith(1L);
		assertThat(reservedSeats).hasSize(1);
	}

 

 

실패 Case


public interface ReservedSeatRepository extends JpaRepository<ReservedSeat, String> {
	@Query("SELECT rs FROM ReservedSeat rs join fetch rs.seat WHERE rs.id LIKE CONCAT(:scheduleId, '_', '%')")
	List<ReservedSeat> searchByScheduleIdStartsWith(@Param("scheduleId") Long scheduleId);
}

 

  • 4가 나왔다.

 

  • 실패 원인
    • _가 와일드카드이기 때문에 인식하지 못했다.
    •  %% 는 ‘모든 문자’라는 의미고, _는 ‘한 글자’라는 의미.
    • 참조
  • 따라서 위 쿼리에서 1_%에는 1로 시작하는 id가 모두 포함된 것이었다.
    • 1_1, 10_10, 11_11, 12_12

 

성공


public interface ReservedSeatRepository extends JpaRepository<ReservedSeat, String> {
	@Query("SELECT rs FROM ReservedSeat rs join fetch rs.seat WHERE rs.id LIKE CONCAT(:scheduleId, '\\_', '%')")
	List<ReservedSeat> searchByScheduleIdStartsWith(@Param("scheduleId") Long scheduleId);
}
  • 와일드카드를 검색하기 위해 ESCAPE를 사용해야 한다.
  • _앞에 ESCAPE문자를 적어주면, _가 '한 글자'가 아닌 문자로 해석된다.
  • default ESACPE문자는 역 슬래쉬이다 |
  • java에서도 |를 문자로 인식하기 위해 한번 더 적어주어야 한다. (||로)

 

  • 이런 식으로 ESCAPE문자를 변경해서 사용할 수도 있다.
public interface ReservedSeatRepository extends JpaRepository<ReservedSeat, String> {
	@Query("SELECT rs FROM ReservedSeat rs join fetch rs.seat WHERE rs.id LIKE CONCAT(:scheduleId, 'K_', '%') escape 'K'")
	List<ReservedSeat> searchByScheduleIdStartsWith(@Param("scheduleId") Long scheduleId);
}

 

ESCAPE 관련해서 참고하면 좋을 링크를 첨부한다


반응형