# RestTemplate - exchange (POST, PUT, DELETE)
RestTemplate에서 HttpMethod와 관계없이 전체적으로 사용할 수 있는 exchange 를 사용하여 POST, PUT, DELETE 요청하기!
[통신 프로세스]
View ↔ [ ajax ] ↔ Controller ↔ [ RestTemplate ] ↔ RestController ↔ Service ↔ Mapper ↔ DataBase
1. exchange POST 통신, ContentType: application/x-www-form-urlencoded
2. exchange PUT 통신
3. exchange DELETE 통신
# 환경
Tool : STS 4.13.0
Ver : 2.7.5 [GA]
java : 11
Repo : MAVEN
DB : ORACLE XE (11g)
View : Thymeleaf
jQuery: 3.6.0
Type : Client(WEB), Server(API)
# Page
# View
<body>
<h1>REST TEMPLATE V2 테스트 페이지 입니다.</h1>
<div>
<h3>SAMPLE REST CODE</h3>
<input type="button" onclick="call('get')" value="GET" style="display: inline;"/>
<input type="button" onclick="call('post-json')" value="POST-JSON" style="display: inline;"/>
<input type="button" onclick="call('post-form')" value="POST-FROM" style="display: inline;"/>
<br>
<input type="button" onclick="call('exchange-post')" value="POST" style="display: inline;"/>
<input type="button" onclick="call('exchange-put')" value="PUT" style="display: inline;"/>
<input type="button" onclick="call('exchange-delete')" value="DELETE" style="display: inline;"/>
<br>
</div>
</body>
<script type="text/javascript" th:inline="javascript">
function call(path) {
const target = `/test/rest-v2/${path}`;
$.ajax(target,
{
dataType: "json",
}).done(function(output) {
console.log(`OUTPUT[${path}]:`, JSON.stringify(output.result));
}).fail(function(jqXHR) {
console.log("ERROR:", jqXHR);
}
);
}
</script>
# Controller - Client(WEB)
@Controller
@RequestMapping("/test/rest-v2")
public class TestRestV2Controller {
@Value("${api.url.api}")
private String apiUrlApi;
@Autowired
private RestTemplate template;
@RequestMapping("/view")
public ModelAndView view(ModelAndView mav) {
mav.setViewName("test/restView2");
return mav;
}
/**
* RestTemplate exchange( URI, method, requestEntity, responseType, [uriVariables] )
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@RequestMapping("/exchange-post")
public ModelAndView exchangePost() {
// 요청하려는 URL 설정.
String target = apiUrlApi.concat("/api-v1/test/rest/exchange-post");
// 요청하려는 헤더 ContentType 설정.
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// 요청 시 본문에 담을 데이터 설정.
MultiValueMap<String, Object> body = new LinkedMultiValueMap<String, Object>();
body.add("data", "테스트");
body.add("data", "TEST");
// Body와 Header 결합한 Entity 설정.
HttpEntity requestEntity = new HttpEntity(body, header);
// 전송 및 응답 데이터 설정.
ResponseEntity<RestMessage> responseEntity = template.exchange(target, HttpMethod.POST, requestEntity, RestMessage.class);
RestMessage message = responseEntity.getBody();
// 응답 데이터 설정.
ModelAndView mav = new ModelAndView("jsonView");
mav.addObject("result", message.getData());
return mav;
}
/**
* RestTemplate exchange( URI, method, requestEntity, responseType, [uriVariables] )
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@RequestMapping("/exchange-put")
public ModelAndView exchangePut() {
// 요청하려는 URL 설정.
String target = apiUrlApi.concat("/api-v1/test/rest/exchange-put");
// 요청하려는 헤더 ContentType 설정.
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.APPLICATION_JSON);
// 요청 시 본문에 담을 데이터 설정.
List<String> data = new ArrayList<String>();
data.add("테스트");
data.add("TEST");
Map<String, Object> body = new HashMap<String, Object>();
body.put("krName", "구글");
body.put("data", data);
// Body와 Header 결합한 Entity 설정.
HttpEntity requestEntity = new HttpEntity(body, header);
// 전송 및 응답 데이터 설정.
ResponseEntity<RestMessage> responseEntity = template.exchange(target, HttpMethod.PUT, requestEntity, RestMessage.class);
RestMessage message = responseEntity.getBody();
// 응답 데이터 설정.
ModelAndView mav = new ModelAndView("jsonView");
mav.addObject("result", message.getData());
return mav;
}
/**
* RestTemplate exchange( URI, method, requestEntity, responseType, [uriVariables] )
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@RequestMapping("/exchange-delete")
public ModelAndView exchangeDelete() {
// 요청하려는 URL 설정.
String target = apiUrlApi.concat("/api-v1/test/rest/exchange-delete");
// 요청하려는 헤더 ContentType 설정.
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.APPLICATION_JSON);
// 요청 시 본문에 담을 데이터 설정.
List<String> data = new ArrayList<String>();
data.add("테스트");
data.add("TEST");
Map<String, Object> body = new HashMap<String, Object>();
body.put("krName", "네이버");
body.put("data", data);
// Body와 Header 결합한 Entity 설정.
HttpEntity requestEntity = new HttpEntity(body, header);
// 전송 및 응답 데이터 설정.
ResponseEntity<RestMessage> responseEntity = template.exchange(target, HttpMethod.DELETE, requestEntity, RestMessage.class);
RestMessage message = responseEntity.getBody();
// 응답 데이터 설정.
ModelAndView mav = new ModelAndView("jsonView");
mav.addObject("result", message.getData());
return mav;
}
}
# RestController - Server(API)
@Slf4j
@RestController
@RequestMapping("/api-v1/test/rest")
public class TestRestController {
@Autowired
private TestService testService;
/**
* application/x-www-form-urlencoded;
*/
@PostMapping("/exchange-post")
public RestMessage post(@ModelAttribute TestPVO testPVO) {
log.debug("[PARAMETER] [exchange-post] TestPVO: {}", testPVO);
List<TestRVO> listTestRVO = testService.domains(testPVO);
RestMessage message = new RestMessage();
message.setOk();
message.setData(listTestRVO);
return message;
}
/**
* 클라이언트가 기존 리소스를 완전히 교체해야 하는 경우 PUT을 사용할 수 있습니다.
* 부분 업데이트를 수행할 때 HTTP PATCH를 사용할 수 있습니다.
* PutMapping 사용.
*/
@PutMapping("/exchange-put")
public RestMessage put(@RequestBody TestPVO testPVO) {
log.debug("[PARAMETER] [exchange-put] TestPVO: {}", testPVO);
List<TestRVO> listTestRVO = testService.domains(testPVO);
RestMessage message = new RestMessage();
message.setOk();
message.setData(listTestRVO);
return message;
}
/**
* DeleteMapping 사용.
*/
@DeleteMapping("/exchange-delete")
public RestMessage delete(@RequestBody TestPVO testPVO) {
log.debug("[PARAMETER] [exchange-delete] TestPVO: {}", testPVO);
List<TestRVO> listTestRVO = testService.domains(testPVO);
RestMessage message = new RestMessage();
message.setOk();
message.setData(listTestRVO);
return message;
}
}
# Service
@Service
public class TestService {
@Autowired
private TestMapper testMapper;
public String time(String pattern) {
return testMapper.time(pattern);
}
public List<TestRVO> domains(TestPVO testPVO) {
return testMapper.domains(testPVO);
}
}
# Mapper - Interface
@Mapper
public interface TestMapper {
String time(String pattern);
List<TestRVO> domains(TestPVO testPVO);
}
# Mapper - xml
<mapper namespace="com.prjt.blog.test.mapper.TestMapper">
<select id="time" parameterType="string" resultType="string">
/** 현재시간 **/ SELECT TO_CHAR(SYSDATE, #{pattern, jdbcType=VARCHAR}) as time FROM DUAL
</select>
<select id="domains" parameterType="com.prjt.blog.test.model.TestPVO" resultType="com.prjt.blog.test.model.TestRVO">
/** 도메인 **/
WITH domain_table AS (
SELECT null AS kr_name, null AS en_name, null AS addr_url, null AS delegator FROM dual
UNION ALL
SELECT '네이버', 'NAVER', 'https://www.naver.com', '최수연' FROM dual
UNION ALL
SELECT '구글', 'GOOGLE', 'https://www.google.com', '선다 피차이' FROM dual
UNION ALL
SELECT '카카오', 'KAKAO', 'https://www.kakocorp.com', '홍은택' FROM dual
)
SELECT *
FROM domain_table
<where>
AND kr_name is not null
<if test="krName != null and krName != ''">
AND kr_name = #{krName, jdbcType=VARCHAR}
</if>
</where>
</select>
</mapper>
# TestPVO
@Data
public class TestPVO {
private String krName;
private List<String> data;
}
# 테스트 로그
/* exchange(): POST 시간 순으로 나열 */
Client - [WEB] [REQ]: null
Client - [WEB] [URL]: (GET) http://localhost:8888/test/rest-v2/exchange-post
Client - [REST] [CALL]: data=%ED%85%8C%EC%8A%A4%ED%8A%B8&data=TEST
Client - [REST] [EXEC]: (POST) http://localhost:9999/api-v1/test/rest/exchange-post
Server - [WEB] [REQ]: [{"data":["테스트","TEST"]}]
Server - [WEB] [URL]: (POST) http://localhost:9999/api-v1/test/rest/exchange-post
Server - [PARAMETER] [exchange-post] TestPVO: TestPVO(krName=null, data=[테스트, TEST])
DB SQL - ==> Preparing: /** 도메인 **/ WITH domain_table AS ( SELECT null AS kr_name, null AS en_name, null AS addr_url, null AS delegator FROM dual UNION ALL SELECT '네이버', 'NAVER', 'https://www.naver.com', '최수연' FROM dual UNION ALL SELECT '구글', 'GOOGLE', 'https://www.google.com', '선다 피차이' FROM dual UNION ALL SELECT '카카오', 'KAKAO', 'https://www.kakocorp.com', '홍은택' FROM dual ) SELECT * FROM domain_table WHERE kr_name is not null
DB SQL - ==> Parameters:
DB SQL - <== Total: 3
Server - [WEB] [RES]: {"code":"0000","data":[{"krName":"네이버","enName":"NAVER","addrUrl":"https://www.naver.com","delegator":"최수연"},{"krName":"구글","enName":"GOOGLE","addrUrl":"https://www.google.com","delegator":"선다 피차이"},{"krName":"카카오","enName":"KAKAO","addrUrl":"https://www.kakocorp.com","delegator":"홍은택"}]}
Client - [REST] [BACK]: (200) {"code":"0000","message":"정상적으로 처리 되었습니다.","data":[{"krName":"네이버","enName":"NAVER","addrUrl":"https://www.naver.com","delegator":"최수연"},{"krName":"구글","enName":"GOOGLE","addrUrl":"https://www.google.com","delegator":"선다 피차이"},{"krName":"카카오","enName":"KAKAO","addrUrl":"https://www.kakocorp.com","delegator":"홍은택"}],"args":null,"success":true}
Client - [WEB] [RES]: {"view":"jsonView","model":{"result":[{"krName":"네이버","enName":"NAVER","addrUrl":"https://www.naver.com","delegator":"최수연"},{"krName":"구글","enName":"GOOGLE","addrUrl":"https://www.google.com","delegator":"선다 피차이"},{"krName":"카카오","enName":"KAKAO","addrUrl":"https://www.kakocorp.com","delegator":"홍은택"}]},"cleared":false}
View - OUTPUT[exchange-post]: [{"krName":"네이버","enName":"NAVER","addrUrl":"https://www.naver.com","delegator":"최수연"},{"krName":"구글","enName":"GOOGLE","addrUrl":"https://www.google.com","delegator":"선다 피차이"},{"krName":"카카오","enName":"KAKAO","addrUrl":"https://www.kakocorp.com","delegator":"홍은택"}]
/* exchange(): PUT 시간 순으로 나열 */
Client - [WEB] [REQ]: null
Client - [WEB] [URL]: (GET) http://localhost:8888/test/rest-v2/exchange-put
Client - [REST] [CALL]: {krName=구글, data=[테스트, TEST]}
Client - [REST] [EXEC]: (PUT) http://localhost:9999/api-v1/test/rest/exchange-put
Server - [WEB] [REQ]: [{"krName":"구글","data":["테스트","TEST"]}]
Server - [WEB] [URL]: (PUT) http://localhost:9999/api-v1/test/rest/exchange-put
Server - [PARAMETER] [exchange-put] TestPVO: TestPVO(krName=구글, data=[테스트, TEST])
DB SQL - ==> Preparing: /** 도메인 **/ WITH domain_table AS ( SELECT null AS kr_name, null AS en_name, null AS addr_url, null AS delegator FROM dual UNION ALL SELECT '네이버', 'NAVER', 'https://www.naver.com', '최수연' FROM dual UNION ALL SELECT '구글', 'GOOGLE', 'https://www.google.com', '선다 피차이' FROM dual UNION ALL SELECT '카카오', 'KAKAO', 'https://www.kakocorp.com', '홍은택' FROM dual ) SELECT * FROM domain_table WHERE kr_name is not null AND kr_name = ?
DB SQL - ==> Parameters: 구글(String)
DB SQL - <== Total: 1
Server - [WEB] [RES]: {"code":"0000","data":[{"krName":"구글","enName":"GOOGLE","addrUrl":"https://www.google.com","delegator":"선다 피차이"}]}
Client - [REST] [BACK]: (200) {"code":"0000","message":"정상적으로 처리 되었습니다.","data":[{"krName":"구글","enName":"GOOGLE","addrUrl":"https://www.google.com","delegator":"선다 피차이"}],"args":null,"success":true}
Client - [WEB] [RES]: {"view":"jsonView","model":{"result":[{"krName":"구글","enName":"GOOGLE","addrUrl":"https://www.google.com","delegator":"선다 피차이"}]},"cleared":false}
View - OUTPUT[exchange-put]: [{"krName":"구글","enName":"GOOGLE","addrUrl":"https://www.google.com","delegator":"선다 피차이"}]
/* exchange(): DELETE 시간 순으로 나열 */
Client - [WEB] [REQ]: null
Client - [WEB] [URL]: (GET) http://localhost:8888/test/rest-v2/exchange-delete
Client - [REST] [CALL]: {krName=네이버, data=[테스트, TEST]}
Client - [REST] [EXEC]: (DELETE) http://localhost:9999/api-v1/test/rest/exchange-delete
Server - [WEB] [REQ]: [{"krName":"네이버","data":["테스트","TEST"]}]
Server - [WEB] [URL]: (DELETE) http://localhost:9999/api-v1/test/rest/exchange-delete
Server - [PARAMETER] [exchange-delete] TestPVO: TestPVO(krName=네이버, data=[테스트, TEST])
DB SQL - ==> Preparing: /** 도메인 **/ WITH domain_table AS ( SELECT null AS kr_name, null AS en_name, null AS addr_url, null AS delegator FROM dual UNION ALL SELECT '네이버', 'NAVER', 'https://www.naver.com', '최수연' FROM dual UNION ALL SELECT '구글', 'GOOGLE', 'https://www.google.com', '선다 피차이' FROM dual UNION ALL SELECT '카카오', 'KAKAO', 'https://www.kakocorp.com', '홍은택' FROM dual ) SELECT * FROM domain_table WHERE kr_name is not null AND kerver - r_name = ?
DB SQL - ==> Parameters: 네이버(String)
DB SQL - <== Total: 1
Server - [WEB] [RES]: {"code":"0000","data":[{"krName":"네이버","enName":"NAVER","addrUrl":"https://www.naver.com","delegator":"최수연"}]}
Client - [REST] [BACK]: (200) {"code":"0000","message":"정상적으로 처리 되었습니다.","data":[{"krName":"네이버","enName":"NAVER","addrUrl":"https://www.naver.com","delegator":"최수연"}],"args":null,"success":true}
Client - [WEB] [RES]: {"view":"jsonView","model":{"result":[{"krName":"네이버","enName":"NAVER","addrUrl":"https://www.naver.com","delegator":"최수연"}]},"cleared":false}
View - OUTPUT[exchange-delete]: [{"krName":"네이버","enName":"NAVER","addrUrl":"https://www.naver.com","delegator":"최수연"}]
# 내용
3가지 방식으로 나누어 테스트 해보았고, 설정된 부분을 보면 조금 씩 다르다는 걸 알 수 있다.
1. exchange - POST
- HttpMethod: POST
- ContentType: application/x-www-form-urlencoded;charset=UTF-8
- URI: /api-v1/test/rest/exchange-post
- 파라미터 맵핑: @ModelAttribute
- 파라미터 타입: TestPVO
2. exchange - PUT
- HttpMethod: PUT
- ContentType: application/json
- URI: /api-v1/test/rest/exchange-put
- 파라미터 맵핑: @RequestBody
- 파라미터 타입: TestPVO
3. exchange - DELETE
- HttpMethod: DELETE
- ContentType: application/json
- URI: /api-v1/test/rest/exchange-delete
- 파라미터 맵핑: @RequestBody
- 파라미터 타입: TestPVO
exchange의 가장 큰 특징은 HttpMethod를 자유롭게 설정할 수 있다는 것이다. exchange를 사용한다면, 공통 함수를 만들어 공통적으로 들어가는 헤더나 설정을 조작 하기에 좋을 것 같다.
Controller 코드에선 header를 직접 설정하여 ContentType을 설정했지만, 설정하지 않아도 기본적으로 json으로 설정이 되고, 전송 데이터 타입이 MultiValueMap이라면 application/x-www-form-urlencoded 으로 자동 설정 된다.
json 방식으로 요청을 받는다면, @RequestBody를 사용하여 데이터를 맵핑하고,
form 방식으로 요청을 받는다면, @RequestParam를 사용하여 원시형타입이나 MultiValueMap으로 맵핑하고, @ModelAttribute를 사용하여 VO로 맵핑할 수 있다.
# RestTemplate 시리즈
https://hjho95.tistory.com/35 getForObject, postForObject
https://hjho95.tistory.com/36 exchange (POST, PUT, DELETE)
https://hjho95.tistory.com/37 Configuration
https://hjho95.tistory.com/38 ClientHttpRequestInterceptor, ResponseErrorHandler
# RestTemplate 시리즈 참조 페이지
RestTemplate: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html
RestTemplate: https://www.baeldung.com/rest-template
Interceptor: https://www.baeldung.com/spring-rest-template-interceptor
ErrorHandling: https://www.baeldung.com/spring-rest-template-error-handling
'스프링 부트' 카테고리의 다른 글
[SpringBoot] OpenAPI 3.0 설정하기! (swagger-ui) (0) | 2023.01.14 |
---|---|
[SpringBoot] RestTemplate 구성하기! (0) | 2022.11.27 |
[SpringBoot] RestTemplate - getForObject, postForObject 로 통신하기! (0) | 2022.11.19 |
[SpringBoot] 스프링 부트 프로젝트 생성하기 (0) | 2022.11.06 |
[SpringBoot] 필터를 이용하여 요청 로그 남기기! (CommonsRequestLoggingFilter) (0) | 2022.10.22 |