매번 MyBatis, JPA 만 쓰다가 이번에 Servlet&JSP로 구현하면서 JDBCTemplate을 처음 사용해봤다.
크게 어려운점도 없었고 문제도 없었지만 동적쿼리를 사용하는 점에서 고민이 있었다.
MyBatis나 JPA에서 동적쿼리를 사용하는데에 있어서는 처리 방안이 따로 마련되어 있다.
하지만 JDBCTempate의 경우 String 타입의 문자열로 쿼리를 작성해두고 사용하기 때문에 막상 동적쿼리를 사용하려고 하니 어? 하는 부분이 생겼다.
일단 사용하는 부분들은 insert 처리와 delete 처리였다.
INSERT INTO board(boardNo, boardTitle, boardContent)
VALUES(?, ?, ?)
,(?, ?, ?);
DELETE FROM board WHERE boardNo IN (?, ?);
동적쿼리가 필요한 만큼 value의 개수는 매번 달라질 수 있었기 때문에 방법이 필요했다.
일단 insert의 경우 List<DTO> 타입으로 받아와서 처리를 했고,
delete의 경우 List<Long> 타입으로 받아오도록 처리를 했다.
그럼 이 List의 size에 따라 value 부분이 조정되도록 처리해야 했다.
가장 먼저 든 생각은 반복문으로 필요한 부분을 추가해서 만들자는 것이었다.
JDBCTemplate은 문자열로 쿼리를 먼저 작성하니 원하는 만큼 반복해서 붙여넣으면 되지 않나? 라는 생각이었다.
근데 효율성에서 고민이 들었다.
그렇게 처리하고자 한다면 크게 봤을 때, 쿼리문을 만드는데 반복문 한번, PreparedStatment를 통해 값을 넣을 때 또 반복문 한번.
이렇게 두번의 반복문을 거쳐 처리할 필요가 있었다.
그리고 세부적으로 본다면 쿼리문을 만드는 반복문 안에서 , 의 위치를 맞춰서 넣어줄 수 있도록 조건문 역시 필요하겠다는 생각이 들었다.
그럼 반복문을 돌리면서 조건문도 체크하고 이거 끝나면 반복문이 또 돌아가야된다.
그래서 String 클래스에서 뭔가 방법이 있지 않을까 하고 찾아봤다.
역시 방법은 있었다.
찾은 방법은 String.join() 이었다.
전체적인 코드를 먼저 보면 아래와 같다.
//insert sql
//List<DTO> insertDTO size = 3
String insertSQL = "INSERT INTO board(boardNo, boardTitle, boardContent) VALUES";
String valueSQL = String.join(",", Collections.nCopies(insertDTO.size(), "(?, ?, ?)"));
String insertSQL = insertSQL.concat(valueSQL);
/*
insertSQL = "INSERT INTO board(boardNo, boardTitle, boardContent)
VALUES(?, ?, ?)
,(?, ?, ?)
,(?, ?, ?)"
*/
//delete sql
//list<Long> deleteNoList size = 3
String deleteSQL = "DELETE FROM board WHERE boardNo IN (%s)";
String valueSQL = String.join(",", Collections.nCopies(deleteNoList.size(), "?"));
String deleteSQL = String.format(deleteSQL, valueSQL);
/*
deleteSQL = "DELETE FROM board WEHRE boardNo IN (?, ?, ?)"
*/
String.join은 (추가 문자열 사이에 들어갈 문자열, 추가하고자 하는 문자열의 리스트 혹은 배열) 형태로 처리한다.
insert 처리의 valueSQL을 나눠서 보면 ","과 Collections~~~로 나눠서 볼 수 있다.
그럼 추가 문자열 사이에 ' , '를 넣겠다는 것이고, 추가 문자열은 insertDTO.size 만큼의 "(?, ?, ?)"가 들어있는 리스트라는 것이다.
그럼 insertDTO의 size를 3으로 가정한 만큼 (?, ?, ?) (?, ?, ?) (?, ?, ?) 이렇게 세개의 값을 가진 리스트가 생성이 되는것이다.
그리고 join을 통해 저 값 사이사이에 ' , '가 들어가게 되어 (?, ?, ?), (?, ?, ?), (?, ?, ?)라는 문자열이 완성이 된다.
delete의 경우도 마찬가지이다. insert와 구조적인 차이는 있지만 ? ? ? 라는 세개의 값을 가진 리스트를 만든 것이고,
이 세개의 값 사이사이에 ' , '를 넣어 문자열을 만들었기 때문에 ?,?,?라는 문자열이 만들어지게 된다.
이후 처리에서는 insert와 delete가 다른 방법으로 처리된다.
concat()과 format()으로 처리한 경우인데 일단 format부터 보자면 printf와 같은 원리다.
deleteSQL을 보면 IN 절 뒤에 (%s)가 들어가게 된다.
그래서 format을 통해 deleteSQL의 %s 부분에 valueSQL이 들어가게 되고
IN (?,?,?) 형태로 문자열이 완성될 수 있는것이다.
printf 로 보자면 System.out.printf("~~~IN (%s)", valueSQL); 이렇게 볼 수 있다.
concat은 문자열 뒤에 문자열을 추가해준다.
그래서 VALUES 까지만 작성된 insertSQL뒤에 필요했던 (?, ?, ?), (?, ?, ?), (?, ?, ?) 형태로 문자열을 완성할 수 있는것이다.
concat의 경우는 조금 더 알아야 할 점이 있다.
언뜻 보기에는 StringBuilder에서 append와 동일해 보일 수 있다.
하지만 차이는 있다.
가장 먼저 concat()에서 기존 문자열에 null은 들어갈 수 없다.
기존 문자열이 null인 경우에는 NullPointerException이 발생하게 된다.
그리고 기존 문자열과 concat 수행 후 문자열의 주소값이 달라진다.
Hello와 World를 concat으로 연결해준다고 가정했을 때
예를 들어 Hello의 주소값이 100이었다면 HelloWorld로 concat을 수행한 뒤에는 200으로 변경된다.
반면 StringBuilder는 기존값이 null이어도 정상적으로 수행한다.
단, null뒤에 append를 통해 World를 붙이게 된다면 World가 되는것이 아닌 nullWorld가 된다.
이점은 주의해야 한다.
그리고 StringBuilder의 경우는 주소값이 변경되지 않는다.
문자열을 합칠 때 + 연산자도 많이 사용하게 되는데 이 + 연산자는 자바 버전에 따라 다르다고 한다.
1.5버전을 기준으로 하는데 1.5 이전에는 concat과 같은 처리였지만 1.5 이후에는 StringBuilder와 같은 처리가 된다고 한다.
Reference
[JAVA] 문자열 붙이는 방식의 차이(concat, +, StringBuilder)
자바에서 String타입을 붙일 때 사용하는 방법은 다양하다. 기본 연산자인 +를 비롯하여 String Builder, concat 모두 들어보거나 써본 용어일 것이다. 근데 동작 방식에 어떤 차이가 있을까? 먼저 결과
devdy.tistory.com
'Project&문제해결' 카테고리의 다른 글
React 권한 페이지 접근 렌더링 문제 해결 (0) | 2024.04.12 |
---|---|
JWT&Redis 로그인에 대한 고민 (0) | 2023.10.25 |
Servlet&JSP에서 Ajax Response (0) | 2023.10.20 |
@Transactional의 rollback처리 문제 해결 (0) | 2023.09.29 |
REST 프로젝트에 JWT 적용 (0) | 2023.06.12 |