1. Error 발생
프로젝트에서 매칭을 위해 Post 약속 장소 주변에 거주 중인 User 중 한 명을 추천해주는 쿼리를 작성했다.
코드는 다음과 같았다.
User 클래스
@Getter
@Entity
@NoArgsConstructor
@Table(name="User")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="user_id")
private int userIdx;
@Column(name="Keyword_keyword_id")
private Integer keywordIdx;
private String name;
private String role;
private String email;
private String password;
private String phone;
private String image;
private String status; //일반 사용자 or 음식점 사장님
private Timestamp created_at;
private Timestamp updated_at;
private double latitude;
private double longitude;
private String jwt;
}
UserResDto 클래스
@Data
@NoArgsConstructor
public class UserResDto {
private int userIdx;
private int keywordIdx;
private String name;
private String role;
private String email;
private String password;
private String phone;
private String image;
private String status; //일반 사용자 or 음식점 사장님
private Timestamp created_at;
private Timestamp updated_at;
private double latitude;
private double longitude;
private String jwt;
@Builder
public UserResDto(User user) {
this.userIdx = user.getUserIdx();
this.keywordIdx = user.getKeywordIdx();
this.name = user.getName();
this.role = user.getRole();
this.email = user.getEmail();
this.password = user.getPassword();
this.phone = user.getPhone();
this.image = user.getImage();
this.status = user.getStatus();
this.created_at = user.getCreated_at();
this.updated_at = user.getUpdated_at();
this.latitude = user.getLatitude();
this.longitude = user.getLongitude();
this.jwt = null;
}
}
MatchingService 클래스
@Service
@RequiredArgsConstructor
public class MatchingService {
private final MatchingRepository matchingRepository;
private final PostRepository postRepository;
private final ApplicationRepository applicationRepository;
private final UserRepository userRepository;
// 매칭 신청이 처음인 경우
@Transactional(rollbackFor = Exception.class)
public UserResDto getFirstMatching(int postIdx) throws BaseException {
Post post = postRepository.findById(postIdx).orElseThrow(IllegalStateException::new);
User userResult = matchingRepository.findUserLivingNearByFirst(post.getLatitude(), post.getLongitude());
// 조건에 맞는 유저가 1명도 없는 경우
if(userResult == null)
throw new BaseException(NO_USERS_MATCHING_CONDITION);
Matching newMatching = Matching.builder()
.userFirstIdx(userResult.getUserIdx())
.count(1)
.postIdx(postIdx)
.build();
matchingRepository.save(newMatching);
return new UserResDto(userResult);
}
}
MatchingRepository 인터페이스
@Repository
public interface MatchingRepository extends JpaRepository<Matching, Integer> {
@Query(value = "SELECT *, (6371*acos(cos(radians( :lat ))*cos(radians(latitude))*cos(radians(longitude)-radians( :lon ))+sin(radians( :lat ))*sin(radians(latitude)))) as distance\n" +
"FROM User u \n" +
"HAVING distance <= 0.5 \n" +
"ORDER BY distance asc limit 1", nativeQuery = true)
User findUserLivingNearByFirst(@Param("lat") Double lat, @Param("lon") Double lon);
}
그런데 해당 기능을 테스트하면 결과 value가 중괄호에 둘러쌓여 출력되면서 ConverterNotFoundException이 발생했다.
에러 메시지는 Integer 타입의 결과값을 User로 변환할 수 있는 converter를 찾지 못했다는 의미로
2개 이상의 Object 배열을 클래스에서 뽑아오는 경우 Mapping에 대한 정보가 없을 때 변환이 되지 않아 발생할 수 있다.
2. 해결 방법
이런 경우 Spring JPA Projection을 활용해
내가 정의한 인터페이스를 기준으로 적절한 데이터만 가져올 수 있도록 Repository에 인터페이스 객체를 반환값으로 매핑시켜주는것이다.
코드는 다음과 같다.
MatchingResDto 클래스
@Data
@NoArgsConstructor
public class MatchingResDto {
private int userIdx;
private int keywordIdx;
private String name;
private String role;
private String email;
private String password;
private String phone;
private String image;
private String status; //일반 사용자 or 음식점 사장님
private Timestamp created_at;
private Timestamp updated_at;
private double latitude;
private double longitude;
private String jwt;
private String matchingResult;
public MatchingResDto(UserRepository.UserInfo user){
this.userIdx = user.getUser_id();
this.keywordIdx = user.getKeyword_keyword_id();
this.name = user.getName();
this.role = user.getRole();
this.email = user.getEmail();
this.password = user.getPassword();
this.phone = user.getPhone();
this.image = user.getImage();
this.status = user.getStatus();
this.created_at = user.getCreated_at();
this.updated_at = user.getUpdated_at();
this.latitude = user.getLatitude();
this.longitude = user.getLongitude();
this.jwt = null;
}
}
MatchingService 클래스
@Service
@RequiredArgsConstructor
public class MatchingService {
private final MatchingRepository matchingRepository;
private final PostRepository postRepository;
private final ApplicationRepository applicationRepository;
private final UserRepository userRepository;
// 매칭을 처음 신청한 경우
@Transactional(rollbackFor = Exception.class)
public MatchingResDto getFirstMatching(int postIdx) throws BaseException {
Post post = postRepository.findById(postIdx).orElseThrow(IllegalStateException::new);
MatchingRepository.MatchingUser userResult = matchingRepository.findUserLivingNearByFirst(post.getLatitude(), post.getLongitude());
// 조건에 맞는 유저가 1명도 없는 경우
if(userResult == null)
throw new BaseException(NO_USERS_MATCHING_CONDITION);
Matching newMatching = Matching.builder()
.userFirstIdx(userResult.getUser_id())
.count(1)
.postIdx(postIdx)
.build();
matchingRepository.save(newMatching);
MatchingResDto matchingRes = new MatchingResDto(userRepository.findByUserIdx(userResult.getUser_id()));
matchingRes.setMatchingResult("매칭 1회 신청");
return matchingRes;
}
}
★ MatchingRepository 인터페이스 ★
@Repository
public interface MatchingRepository extends JpaRepository<Matching, Integer> {
@Query(value = "SELECT *, (6371*acos(cos(radians( :lat ))*cos(radians(latitude))*cos(radians(longitude)-radians( :lon ))+sin(radians( :lat ))*sin(radians(latitude)))) as distance\n" +
"FROM User u \n" +
"HAVING distance <= 0.5 \n" +
"ORDER BY distance asc limit 1", nativeQuery = true)
MatchingUser findUserLivingNearByFirst(@Param("lat") Double lat, @Param("lon") Double lon);
interface MatchingUser {
int getUser_id();
String getDistance();
Integer getKeyword_keyword_id();
String getName();
String getRole();
String getEmail();
String getPassword();
String getPhone();
String getImage();
String getStatus();
Timestamp getCreated_at();
Timestamp getUpdated_at();
Double getLatitude();
Double getLongitude();
}
}
MatchingRepository에서 interface를 선언하고 해당 interface를 함수의 반환값으로 사용하고 있다.
interface 내부에 getter를 활용해서 속성을 정의하면, 해당 interface는 class처럼 사용이 가능해진다.
또한 MatchingService에서도 User와 UserResDto가 아닌 MatchingRepository.MatchingUser와 MatchingResDto를 활용해주면 매핑이 정상적으로 이루어진 것을 확인할 수 있다.
{
"isSuccess": true,
"code": 1000,
"message": "요청에 성공하였습니다.",
"result": {
"userIdx": 40,
"keywordIdx": 161,
"name": "testUser",
"role": "user",
"email": "testUser@naver.com",
"password": "bkfFWxmK9S5N+pqwEmckrA==",
"phone": "01011111111",
"image": null,
"status": "active",
"created_at": "2023-02-11 07:22:55",
"updated_at": null,
"latitude": 67.1234567,
"longitude": 127.3012345,
"jwt": null,
"matchingResult": "매칭 1회 신청"
}
}
'Framework > Spring' 카테고리의 다른 글
[SpringBoot] Swagger 3.0 + 전역적 Bearer 토큰 적용 (0) | 2023.10.31 |
---|---|
[SpringBoot] Naver CLOVA Sentiment를 활용한 감정분석 (0) | 2023.08.30 |
[SpringBoot] ChatGPT를 활용한 API 작성 (0) | 2023.08.21 |
[SpringBoot] JPA NoViableAltException: unexpected token: value 에러 (0) | 2023.04.02 |
[SpringBoot] IntelliJ 2022.3 Gradle로 실행돼서 너무 느린 경우 (0) | 2023.03.30 |