repository에서 반환되는 값을 class로 받아도 실제로 그리 안 들어가고 interface에 머무는 듯하다.
왜냐면
@JsonFormat부분을 class에 넣으면 날짜 변환이 안된다.
setter 쪽 로직에서 아래와 같은 에러가 난다.
java.lang.UnsupportedOperationException: A TupleBackedMap cannot be modified.
인터페이스 -> 클래스로 매핑하는 로직 따위를 넣으면서까지 이 방식을 고수하고 싶지 않아서 다른 방법을 찾아본다.
(조금만 더 찾아보면 될 것 같기도 한데.... 잘 모르겠다)
2. @NamedNativeQuery를 사용
위와 같이 native query를 사용하되 return 값을 class로 받을 수 있는 방법을 고민하다가 @NatedNativeQuery를 사용하면 result mapping class와 매핑 룰을 지정하여 class로 리턴할 수 있을 것 같아서 시도해보았다. entity클래스 상단에 아래 부분을 추가해보았다.
@NamedNativeQuery(name = "UserLossDaily.getListUserLossDailyWithUserRat2",
query = "select hur.gid, hur.ci, hur.nick, hur.last_logout as lastLogout, " +
"huld.begin_chip as beginChip, huld.last_chip as lastChip, huld.last_chip - huld.begin_chip as changeChip, huld.win_chip as winChip, timestamp(huld.base_date) as baseDateTime, hll.expired_Date as expiredDate, " +
"case when hll.expired_date > now() and huld.base_date = DATE_FORMAT(now(), '%Y-%m-%d') then 'true' else 'false' end as canRelease " +
"from hd_user_loss_daily huld " +
"join hd_user_rat hur on hur.gid = huld.gid " +
"join hd_loss_limit hll on hll.ci = hur.ci " +
"where hur.ci = :#{#req.ci} " + //에러발생
"and huld.base_date between :#{#req.startDate} and :#{#req.endDate} ;"
, resultSetMapping = "Mapping.LossChipDailyRes")
@SqlResultSetMapping(name = "Mapping.LossChipDailyRes",
classes = @ConstructorResult(targetClass = LossChipDailyRes.class, columns = {
@ColumnResult(name = "gid"),
@ColumnResult(name = "ci"),
@ColumnResult(name = "nick"),
@ColumnResult(name = "lastLogout"),
@ColumnResult(name = "beginChip"),
@ColumnResult(name = "lastChip"),
@ColumnResult(name = "changeChip"),
@ColumnResult(name = "winChip"),
@ColumnResult(name = "baseDateTime"),
@ColumnResult(name = "expiredDate"),
@ColumnResult(name = "canRelease"),
}))
@Entity
...
그런데 신박한 에러가 난다. 바로 SpEl을 사용할 수 없는 것이었다.
Space is not allowed after parameter prefix ':'
구글링 해보니 : 앞에 \\를 줘서 escape 하라는 글이 있었는데, 그건 select절 내에서 named parameter를 가져올 때나 가능하고 저렇게 SpEl문법을 escape 할 때 사용할 수 있는 것 같아 보이지는 않았다.
개인적으로
request parameter를 method req변수로 쫙 늘어뜨리고 싶지 않아서(object로 전달하고 그 안에서 꺼내서 매핑하고 싶어서)
entitiy 위에 매핑 룰/쿼리를 쫙 쓰면서 관리하고 싶지 않아서(쿼리는 repository에서 관리하는 게 보기에도 찾기에도 좋다고 생각) 빠르게 포기했다.
native query에서는 dto이름 그대로 매핑하기 때문에 as로 이름을 다시 지정해줬어야 핬지만, jpql query로 바꿀 때는 as를 쓰면 안된다(쿼리 파싱이 안되서 서버가 안 뜸). 순서만 dto constructor랑 맞추줘야 함.
sql function을 사용할 때 function으로 다 바꿔야하고 now()(mysql전용)도 current_date(jpa용) 등으로 바꿔야한다.
true/false도 quotation 처리 확인해야한다. boolean으로 못 가져오고 string으로 인식할 수 있음..
//native query
case when hll.expired_date > now() and huld.base_date = DATE_FORMAT(now(), '%Y-%m-%d') then 'true' else 'false' end as canRelease
//jpql
case when hll.expiredDate > current_timestamp and huld.pk.baseDate = function('date_format', current_date, '%Y-%m-%d') then true else false end
위와 같이 jpql로 바꾸니.. 다행히 서비스 로직을 그대로 사용할 수 있었다.
native query를 자주 사용하지 않아 + 복합적인 조건으로 인해 시간이 걸린 삽질이었지만, native query의 특징을 배울 수 있었던 시간이었다..
이벤트 종료 처리를 위해 날짜 비교 로직을 스크립트에 넣었는데, IE에서만 제대로 실행되지 않는 문제가 있었다.
콘솔에 딱히 에러가 없었는데, 찾아보니 IE에서는 안된다고 잘 알려진 이슈였다.
var nowTime = new Date();
var endTime = new Date("2022-04-26 10:00:00"); //invalid date in IE
if (nowTime > endTime){
//alert("이벤트가 종료되었습니다. 감사합니다.");
location.href = 'naver.com'
}
그러면 어떻게 해야 IE가 알아볼까 싶어서 찾아봤는데, 제일 쉽게 수정할 수 있는 방법은 Date constructor를 사용하는 방법이었다.
var nowTime = new Date();
//var endTime = new Date("2022-04-26 10:00:00"); //invalid date in IE
//var endTime = new Date(2022, 4, 26, 10, 00, 00); //May로 표시됨 0이 1월...
var endTime = new Date(2022, 3, 26, 10, 00, 00);
if (nowTime > endTime){
//alert("이벤트가 종료되었습니다. 감사합니다.");
location.href = 'naver.com'
}
다만 조심해야하는 것은 monthIndex가 0부터 시작하기 때문에 4월은 3으로, 12월은 11로 표시해야 한다. 그리고 시간은 24시간 체계이다.
new Date(year, monthIndex, day, hours, minutes, seconds)
retrieve: http status code가 200일 경우 response body처리 http status code가 다른 경우(400, 500, etc.) WebClientResponseException 떨어짐 에러 처리 커스텀하게 하고 싶으면 onStatus 사용
exchange: any response에서도 사용 가능하나 꼭 response 내용을 사용해야 함(성공이건 실패건 상관없이) 아니면 memory leak이 있을 수 있다고..
응답이 200이고 응답 body에 대한 처리만 하고 싶은 경우 retrieve. 이 외에 응답 코드가 4xx, 5xx 등 특정 코드에 따라 다른 결과를 주고 싶거나 응답 헤더를 사용하고 싶은 경우는 exchange를 사용
에러 처리할 때 doOnNext vs flatMap?
둘 다 작동하긴 하지만 함수의 사상 상 doOnNext가 더 적합한 것 같다. 둘 다 두면 위에서 걸려서 아래로 안 흐른다.
springboot2를 사용하게 되면 서블렛 기반으로 갈지 리엑티브 기반으로 갈지 고민하게 된다. 예전 강의나 자료에서는 둘 중 하나만 골라서 얹어야 한다고 그러는데, 요즘에는 둘 다 얹어서 사용 가능하다(정확히 말하자면 둘 다 있으면 서블랫 기반으로 돌고 webflux의 몇몇 라이브러리를 사용할 수 있다)
새로운 프로젝트를 하기로 했고, 역시나 새로운 기술의 도입의 유혹에서 벗어나지 못하고 있다. spring-web기반으로만 작업해보아서 webflux를 사용해보려고 하는데,, 하면서 이게 맞는지 확신이 안서는데, 빠르게 결정을 해야 하는 상황을 맞닥뜨렸다. 다음은 고민의 일지이다.
1. web/webflux를 동시 사용해보자. 딱히 소스가 달라지는건 없지만 restTemplate가 없어진다는데 그거라도 보완하는 거야..
즉 spring-mvc(톰캣)를 사용하면서 api 요청 부분은 webClient로 비동기 처리, 디비는 동기
spring-webflux가 controller -> service -> repository 구조도 지원하지만(annotation 기반)
router -> handler/service -> repository 구조가 진짜 모습이다.(함수형 프로그래밍 모델 기반)
하지만 진짜 webflux의 기능을 쓰지 못하기 때문에 성능상 이점이 없을 듯하여 굳이 이렇게 사용하지 않아도 될 것 같다.
게다가 webflux 디펜덴시만 있으면 스웨거나 filter 설정 등 기존 로직에 영향이 있을 수 있다.
고생에 비해 성과가 미미할 수 있으니 고민이 필요하다.
3. webflux with r2dbc.. 디비까지 모든 걸 다 리액티브로 바꾸자니 소스에 하나라도 블로킹 걸리면 의미가 없으니 조심해야 하고, 개발 장벽도 있고 무엇보다 혼자 하는 게 아니라 팀으로 개발하니 팀원들의 동의도 구해야 하고.. 개발 속도도 안 날 것 같으니 우선 보류!
디비마저도 비동기로 처리하는 설정, 궁극적으로 지향해야 하는 부분이지만 일반적인 api에는 이렇게까지 사용할 필요는 없을 것 같다. 진짜 콜이 많거나 백그라운드에서 동작하는 것들이 많을 때(배치성 업무) 효력이 좋을 것 같다.
스프링 프레임워크에서는 기본적으로 Unchecked Exception(런타임 예외)이 발생하면 트랜잭션을 롤백합니다.
반면, Checked Exception(체크드 예외)은 기본적으로 트랜잭션을 롤백하지 않습니다. 이는 Checked Exception이 비즈니스 로직 내에서 예외적인 상황으로 처리될 수 있기 때문입니다.
checked exception에서도 롤백이 필요하다면 rollbackFor을 활용하면 된다.
@Transactional(rollbackFor = IOException.class)
public void performTransaction() throws IOException {
repository.save(new Entity());
// Checked Exception 발생
if (true) {
throw new IOException("Checked Exception");
}
repository.save(new Entity());
}
특정 Unchecked Exception에 대해서는 롤백을 원하지 않을 때 noRollbackFor 속성을 사용합니다.
왜 기본적으로 Unchecked Exception에 대해서만 롤백할까?
비즈니스 로직과의 분리: Checked Exception은 비즈니스 로직 내에서 발생할 수 있는 예외적인 상황을 나타내며, 이를 비즈니스 로직의 일부로 처리하는 경우가 많습니다. 반면, Unchecked Exception은 예외적 상황이 아니라 코드의 오류나 논리적 문제를 나타내므로 트랜잭션의 롤백이 필요합니다.
복구 가능성: Checked Exception은 복구 가능한 예외로 간주되어, 개발자가 예외를 처리하여 정상적인 흐름으로 돌아갈 수 있다고 판단할 수 있습니다. Unchecked Exception은 복구가 불가능한 예외로 간주되어 트랜잭션을 롤백하는 것이 합리적입니다.
spring mvc 기반 spring-web의 restTemplate으로 api를 요청하고 있던 와중, restTemplate가 deprecated 된다는 말을 듣고, 대체제인 webClient를 사용해보려고 공부를 하다 보니 webflux의 영역에 들어와 버렸다. 물론 webClient도 sync call을 지원하지만 수많은 api 콜을 비동기로 하면 자연스레 내 api의 속도도 빨라질 것이 아닌가? 위기를 기회로 전환하며 새로운 아키텍처를 익히려고 spring-webflux를 추가하였다.
그런데 spring reactive강의를 듣던 도중, 두 dependency는 spring context의 혼란을 야기하므로 같이 사용하면 안 된다는 말을 들었다. 오래된 강의긴 했지만 나름 스프링 저명인사가 말한 것이기에 안되리라 생각하고 좌절하며 관련 내용을 더 찾아보기로 했다.
1. spring mvc vs spring webflux
spring mvc가 있으면 mvc가 우선순위가 높고 webflux만 있으면 webflux를 쓴다고 한다. 즉, 둘 다 있으면 mvc가 우선 순위다.
근데 애초에 같이 있어도 된다는 전제를 한다면 같이 써도 된다는 것 아닌가?!
2. spring mvc with webclient
스프링 공식문서에 spring mvc + webclient형식으로 같이 써도 된다는 글이 있다.. 하하하하하 살았다.
그리고 스프링에서 같이 쓰는 동영상을 올린 적이 있다(물론 주내용은 springboot2에 대한 데모지만)