📝 참여자인 경우에 값을 초기화한 후 완주자인 경우 값을 변경
-> 초기화된 값을 유지하는 사람이 완주하지 못한 선수
작성한 코드
public class ARacerWithHashMap {
public String solution(String[] participant, String[] completion){
String whoDidNotFinish= null;
Map<String, Integer> memo = new HashMap<>();
// -------- (1) ----------
for (int i=0; i<participant.length; i++){
String key = participant[i];
memo.put(key, 1);
}
for (int i = 0; i < completion.length; i++) {
String key = completion[i];
memo.put(key, 0);
}
// -------- (2) ----------
for (String key:memo.keySet()) {
if (memo.get(key) == 1){
whoDidNotFinish = key;
}
}
return whoDidNotFinish;
}
}
테스트 코드
class ARacerWithHashMapTest {
@Test
void test(){
ARacerWithHashMap aRacerWithHashMap = new ARacerWithHashMap();
String personWhoDidNotFinish1 = aRacerWithHashMap.solution(new String[]{"leo", "kiki", "eden"}, new String[]{"eden", "kiki"});
String personWhoDidNotFinish2 = aRacerWithHashMap.solution(new String[]{"marina", "josipa", "nikola", "vinko", "filipa"}, new String[]{"josipa", "filipa", "marina", "nikola"});
String personWhoDidNotFinish3 = aRacerWithHashMap.solution(new String[]{"mislav", "stanko", "mislav", "ana"}, new String[]{"stanko", "ana", "mislav"});
assertEquals(personWhoDidNotFinish1, "leo");
assertEquals(personWhoDidNotFinish2, "vinko");
assertEquals(personWhoDidNotFinish3, "mislav");
// 동명이인 mislav가 동일하게 인식되어 null이 반환 -> 테스트 통과 X
}
}
참가자에서 동명이인이 있을 때 (ex. ["mislav", "stanko", "mislav", "ana"]) 동명이인을 동일하게 인식하여 완주자가 ["stanko", "ana", "mislav"]일 때 "mislav"가 반환되는 것이 아니라 null이 반환됩니다.
🔎 동명이인: 무조건 1 또는 0으로 초기화하는 것이 아니라 count를 올리거나 낮추어서 해결!
public String solution(String[] participant, String[] completion){
String whoDidNotFinish= null;
Map<String, Integer> memo = new HashMap<>();
// -------- (1) ----------
for (int i=0; i<participant.length; i++){
String key = participant[i];
// 동명이인 문제를 해결하기 위해 1로 초기화를 하는 것이 아니라 count를 올린다
if(memo.get(key) != 1){// nullpointerException
memo.put(key, memo.get(key)+1);
} else{
memo.put(key, 1);
}
// -------- (2) ----------
for (String key:memo.keySet()) {
if (memo.get(key) == 1){
whoDidNotFinish = key;
}
}
return whoDidNotFinish;
}
getKey()함수에서 초기화 안된 key가 들어가는 경우 nullpoint exception 발생하게 됩니다.
🔎 NullPointerException: 초기화 된 상태인지 확인하는 메소드로 변경
// -------- (1) ----------
for (int i=0; i<participant.length; i++){
String key = participant[i];
if(memo.containsKey(key)){
memo.put(key, memo.get(key)+1);
} else{
memo.put(key, 1);
}
containsKey() 메소드를 통해 초기화 되었는지를 확인합니다.
getOrDefault() 메소드를 사용하여 코드를 더 간단하게 변경
// -------- (1) ----------
for (String com:completion) {
memo.put(com, memo.getOrDefault(com, 0)+1);
}
// getOrDefault(key, defaultValue)
// : key가 존재하면 value를 return, 존재하지 않으면 defaultValue를 return
// -------- (2) ----------
for (String com:completion) {
memo.put(com, memo.get(com)-1);
}
참가자를 확인 할 때 처음인 경우 1, 동명이인인 경우는 2,3,... 로 값을 설정해주기 때문에 getOrDefault에서 default 값을 0으로 설정해서 key가 Map에 존재하지 않으면 value로 0+1=1을 갖고 key가 존재하면 기존의 value에서 1 씩 더하도록 로직을 구현했습니다.
HTTP 메소드에는 GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE 총 8가지 종류가 있다. 이 중 GET, POST, PUT, DELETE를 사용하는 API에 대해서 살펴봅시다.
@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello(){
return "Hello World";
}
RequestMapping은 method를 지정하지 않으면 default로 모든 method(GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE)를 지원하므로 GET API를 구현하고 싶으면 RequestMethod.Get으로 지정해주어야합니다.
스프링 4.3 이후 버전에서는 @GetMapping, @PostMapping, @PutMapping, @DeleteMapping 등과 같은 특정 HTTP 메소드에 맞는 annotation을 지원하기 때문에 이를 사용하여 API를 구현할 수 있습니다.
@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {
@GetMapping(value = "/name") // path
public String getName(){
return "Yeonji";
}
Path Variable: 주소를 통해서 값을 넘길 때 사용합니다
path에 들어가는 파라미터의 이름과 메소드에 들어가는 파라미터의 이름을 동일하게 설정해주어야 합니다.
ex. {variable}과 @PathVariable String variable
@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {
@GetMapping(value = "/variable1/{variable}")
public String getVariable1(@PathVariable String variable){
return variable;
}
@GetMapping(value = "/variable2/{variable}")
public String getVariable2(@PathVariable("variable") String str){
return str;
}
https://api.github.com/repos/Qkite/java-algorithm/commits?since=%222022-10-27T00:00:00Z%22
git commit 기록의 시간을 지정할 수 있는 since는 optional하므로 Query Parameter로 구현했습니다.
@GetMapping(value = "/request1")
public String getRequestParam1(@RequestParam String name, @RequestParam String email, @RequestParam String organization){
return String.format("%s %s %s", name, email, organization);
}
~~~/request1?name=hello&email=hello@gmail.com&organization=Greetings
라고 입력을 하면 name에 hello, email에 hello@gmail.com, organization에 Greetings를 입력받고 http body에 hello hello@gmail.com Greetings를 출력합니다.
class Node{
private String key;
private Integer value;
public Node(String key, Integer value){
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public Integer getValue() {
return value;
}
}
값을 받을 수 있는 getter와 값을 변경할 수 있는 Constructor를 가지는 DTO인 MemberDto 클래스를 정의합니다.
{
private String name;
private String email;
private String organization;
MemberDto(String name, String email, String organization){
this.name = name;
this.email = email;
this.organization = organization;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public String getOrganization() {
return organization;
}
@Override
public String toString(){
return String.format("%s %s %s", this.name, this.email, this.organization);
}
}
메소드의 파라미터를 MemberDto로 설정하여 입력된 값을 받고 toString 메소드를 이용해 http body로 출력합니다.
@GetMapping(value = "/request3")
public String getRequestParam3(MemberDto memberDto){
System.out.println(memberDto);
return memberDto.toString();
}
⭐ POST 메소드와 GET 메소드의 차이점 (면접 단골 문제)
- GET 메소드: 파라미터를 http request message의 header에 path나 쿼리 파라미터로 전달
- POST 메소드: HTTP 요청을 할 때 저장하고자 하는 resource를 http request message의 body에 넣어서 전달
- GET에 비해서 비교적 큰 크기의 정보를 전송할 수 있습니다.
- HTTP 전송 중 노출되면 안되는 정보(회원가입 시 입력하는 정보 - 비밀번호 등)를 보낼 때 암호화를 하여 POST 메소드로 전송합니다.
@RequestMapping(value = "/domain", method = RequestMethod.POST)
public String postExample(){
return "Hello Post API";
}
method를 RequestMethod.POST로 지정하여 POST API를 구현합니다.
POST 요청에서는 HTTP의 body에 json 형식으로 key와 value를 입력하여 정보를 전송할 수 있습니다. 따라서 @RequestBody에 Map<String, Object> 형식의 파라미터를 받습니다.
@PostMapping("/member1")
// request body를 사용함
public String postMember1(@RequestBody Map<String, Object> postData){
StringBuilder sb = new StringBuilder();
postData.entrySet().forEach(map -> {
sb.append(map.getKey() + " : " + map.getValue() + "\n");
});
return sb.toString();
}
Json(JavaScrpit Object Notation): 자바스크립트의 문법을 따르는 데이터 포맷으로 대체로 네트워크를 통해 데이터를 전달할 때 사용하며, 문자열의 형태로 작성되기 때문에 파싱하기 쉽다는 장점이 있습니다.
@ResponseBody에 DTO인 MemberDto 클래스를 파라미터로 받고 return을 ResponseEntity로 구현했습니다. ResponseEntity의 status() 메소드에 HttpStatus.ACCEPTED로 넣어주어 응답 상태를 ACCEPTED로 설정하고, body() 메소드에 memberDto를 넣어주어 응답을 memberDto의 형태로 받게 했습니다.
@RestController
@RequestMapping("/api/v1/put-api")
public class PutController {
@PutMapping(value = "/member3")
public ResponseEntity<MemberDto> postMember3(@RequestBody MemberDto memberDto){
return ResponseEntity
.status(HttpStatus.ACCEPTED)
.body(memberDto);
}
}
따라서 body를 통해 입력 받은 name과 email, organization이 MemberDto에 들어간 후 아래와 같은 json 형태로 출력되고 응답 코드는 202(Accepted)가 뜨게 됩니다.
Path variable의 형태로 입력받은 데이터를 삭제합니다.
@DeleteMapping(value = "/{variable}")
public String deleteVariable(@PathVariable String variable){
return variable;
}
@DeleteMapping(value = "/request-delete")
public String deleteRequestParam(@RequestParam String email, @RequestParam String name){
return name + ":" + email ;
}
Swagger는 OAS(Open API Specification)으로 API의 문서화(명세 - 어떤 로직을 수행하는지 설명하고 이 로직을 수행하기 위해서 어떤 값을 요청해야하고 응답값으로 무엇을 받을 수 있는지를 기록)를 자동화 할 수 있도록 도와주고 파라미터를 넣어서 제대로 응답이 오는지 테스트 할 수 있습니다.
Spring boot 2.7.5, Swagger 3.0.0, Maven 환경에서 진행했습니다.
(1) pom.xml에 의존성 추가
<dependencies>
...
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
(2) configuration 클래스 추가
@Configuration
public class SwaggerConfiguration {
@Bean
public Docket api() {
return new Docket(DocumentationType.OAS_30)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
}
(3) Controller 생성
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BasicController {
@GetMapping("/api/hello1")
public String hello1() {
return "hello";
}
@GetMapping("/api/hello2")
public String hello2(@RequestParam String param) {
return param;
}
}
(4) application.properties를 application.yml로
//application.yml
server:
port: 8081
NullPointerException: SpringApplication.run에서 NullpointerException이 발생하는 경우 아래 코드 추가하기
@SpringBootApplication
public class HelloApplication {
public static void main(String[] args) {
SpringApplication.run(HelloApplication.class, args);
// nullpointerexception 발생
}
}
//application.yml
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
cf) .yml과 .properties
Spring Boot는 .yml과 .properties과 같은 외부의 설정 파일을 통해서 특정 값을 주입받을 수 있습니다.
.yml은 계층 구조로 표현하고 가독성이 좋습니다. @Value 어노테이션을 이용해 값을 주입할 수 있습니다.
// application.yml
external:
record-year: 2021
api:
name: kakao
key: 123123
public class ExternalService{
@Value("${external.record-year}")
private String recordYear;
@Value("${external.api.name}")
private String apiName;
@Value("${external.api.key}")
private Integer apiKey;
}
반면 .properties는 key-value의 형태로 값을 정의할 수 있습니다. .properties에 있는 값도 @Value를 이용해 변수에 주입할 수 있습니다.
이 때 @PropertySource 어노테이션으로 어떤 파일을 사용할 것인지 명시해주고 @Service, @Component, @Configuration과 같은 어노테이션을 통해 클래스를 bean으로 설정해주어야 합니다.
spring.datasource.url=jdbc:mysql://localhost...
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# github token
github.token=ghp_Zwy???...
# slack token
slack.token=xoxb-2402-???...
@PropertySource("classpath:app.properties")
@Component
public class CommitUtil {
private GitHub github;
@Value("${github.token}")
private String token;
}
서버가 요청을 받았으며 서버에 연결된 클라이언트는 작업을 계속 진행하라는 의미입니다.
요청이 성공했음을 나타냅니다.
200 (OK): http 요청이 성공했음을 나타냅니다.
- GET: 리소스를 가져와 http의 message body에 전달했습니다.
- HEAD: 표현 헤더(representation headers)가 reponse에 포함되었습니다.
- PUT 또는 POST: 작업 결과를 설명하는 리소스가 http의 message body에 전송되었습니다.
- TRACE: 서버에서 수신한 요청 메시지를 message body에 전달되었습니다.
201 (Created): 요청이 성공하여 새로운 리소스가 생성되었음을 나타냅니다. 보통 POST 또는 PUT 요청 후에 전송되는 응답입니다.
202 (Accepted): 요청을 받았지만 아직 처리되지 않았습니다. HTTP에는 나중에 요청 결과를 나타내는 비동기 응답을 보낼 수 있는 방법이 없기 때문에 커밋되지 않습니다. 다른 프로세스나 서버가 요청을 처리하는 경우 또는 일괄 처리를 위한 것입니다.
204 (No Content): 요청을 수행했지만 reponse body가 아예 없는 것을 의미합니다. header에는 유의미한 정보가 담겨있을 수도 있습니다.
@GetMapping(value = "/request1")
public void getRequestParam1(@RequestParam String name, @RequestParam String email, @RequestParam String organization){
System.out.println(String.format("%s %s %s", name, email, organization));
}
라고 입력했을 때 200 코드가 뜸을 확인할 수 있습니다. 이는 Body에 들어간 정보는 없지만 body 자체는 존재하기 때문입니다.
요청을 성공적으로 수행하기 위해서 추가적인 조치가 필요함을 의미합니다.
잘못된 요청으로 인해 서버에서 요청을 처리할 수 없음을 의미합니다.
400 (Bad Request): 클라이언트의 오류(잘못된 문법 등) 로 인해 서버가 요청을 처리할 수 없는 경우를 의미합니다.
401 (Unautorized): 클라이언트가 승인되지 않아서 요청을 수행할 수 없는 경우를 의미합니다.
403 (Forbidden): 클라이언트가 contents에 접근할 권한이 없는 경우를 의미합니다.
404 (Not Found): 서버가 요청한 리소스를 찾을 수 없다는 것을 의미합니다. URL이 제대로 되지 않은 경우이거나 API에서 endpoint는 유효하지만 리소스 자체가 존재하지 않는 경우에 발생할 수 있습니다.
405 (Method Not Allowd): 해당하는 리소스에 request method가 적합하지 않은 경우 발생합니다. 예를 들면 @GetMapping를 붙인 메소드를 Post 메소드를 이용해 호출할 때 발생합니다.
408 (Request Timeout): 일정 시간 동안 클라이언트의 요청이 없었지만 서버가 연결되어있는 경우 사용하지 않는 서버의 연결을 종료하려고 함을 의미합니다.
409 (Conflict): 요청이 서버의 현재 상태와 충돌함을 의미합니다.
410 (Gone): 요청한 컨텐츠가 서버에서 영구적으로 삭제된 상태일 때 발생합니다. 클라이언트는 캐시와 리소스 링크를 제거해야합니다.
413 (Payload Too large): 요청한 엔티티가 제한된 범위를 넘어갈 때를 의미합니다. 예를 들어 매우 큰 파일을 업로드 할 때 발생할 수 있습니다.
서버에서 오류가 발생했음을 의미합니다.
그 외의 코드가 궁금한 경우 https://developer.mozilla.org/en-US/docs/Web/HTTP/Status 를 참고해주세요
출처
1. 스프링부트 핵심가이드, 장정우, pp.55-80
2. RequestMapping 어노테이션 공식 문서: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html
3. http method 설명: https://velog.io/@yh20studio/CS-Http-Method-%EB%9E%80-GET-POST-PUT-DELETE
4. DAO, DTO, VO: https://melonicedlatte.com/2021/07/24/231500.html
5. Swagger: https://velog.io/@wotj7687/Spring-Boot-Swagger-3.0.0-%EC%A0%81%EC%9A%A9
6. .yml vs .properties (1): https://velog.io/@tjswlsdl135/application.properties-vs-application.yml
7. .yml vs .properties (2): https://aeliketodo.tistory.com/23
8. http response code 설명(1): https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
9. http response code 설명(2): https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html