본문 바로가기
포스코x코딩온

[포스코x코딩온] 풀스택 부트캠프 18주차 정리 Java(API(Get, Post), SQL Mapper(MyBatis))

by 김선지 2024. 2. 24.

Get method

controller에서 @GetMapping(url) 형식으로 get 요청(request)에 대해서 response를 해준다.

 

	@GetMapping("/")
    public String getReq() {
        return "_02_restapi/req";
        // templates/ _02_restapi 패키지에 있는 req.html 파일을 return (render)한다는 뜻
    }


    @GetMapping("/get/res1")
    public String getRes1(@RequestParam(value = "name") String name, @RequestParam(value = "age") int age, Model model) {
        // @RequestParam 어노테이션
        // - string query 중에서 name key 에 대한 value 를 String name 에 매핑 (?key=value)
        // - required=true 가 기본 값이므로 요청 URL 에서 name key 를 필수로 보내야 함.
        // ex. GET /get/res1?name=someone&age=someage
        model.addAttribute("name", name);
        model.addAttribute("age", age);
        // name 이라는 key 값에 name이라는 value를 할당해서 return 값에서 쓸 수 있게 함
        return "_02_restapi/res";
    }


    @GetMapping("/get/res2")
    public String getRes2(@RequestParam(value = "name", required = false) String name,
                          @RequestParam(value = "age", required = false) Integer age, Model model) {
        // required = false 옵션
        // - query string 에서 특정 key 를 optional하게 받아야 하는 경우
        // ex. 검색어 (필수) 해시태그 (선택)
        // - ?search=바나나
        // - ?search=바나나&hashtag=과일
        model.addAttribute("name", name);
        model.addAttribute("age", age); // 만약 인풋이 비어있으면 ""으로 와서 String으로 받았음,
        // 지금은 인풋 없애버리고 null값으로 들어올 거기 때문에 Integer로 바깠음
        return "_02_restapi/res";
    }

    @GetMapping("/get/res3/{param1}/{param2}")
    public String getRes3(@PathVariable(value = "param1") String param1, @PathVariable(value = "param2") int age, Model model) {
        // @PathVariable 어노테이션
        // - test/{id} 형식의 URL 경로로 넘어오는 변수로 값을 받을 때 사용
        // - 기본적으로 경로 변수는 값을 가져야 함 (값 없는 경우는 404 에러가 발생)
        // - 참고, uri 에 기입한 변수명과 다른 매개변수 이름을 사용하고 싶다면
        // - @PathVariable int age
        // - @PathVariable(value="param2") int age <= 이런 식으로 사용해야 함 (uri 랑 동일한 매개변수 사용 시 생략 가능)

        model.addAttribute("name", param1);
        model.addAttribute("age", age);
        return "_02_restapi/res";
    } // O

    @GetMapping({"/get/res4/{name}", "get/res4/{name}/{age}"}) // 선택적으로 받아오는 PathVariable이 있다면 경로를 여러개 설정
    public String getRes4(@PathVariable(value = "name") String name, @PathVariable(value ="age", required = false) Integer age, Model model) {
        // required = false 옵션
        // - name (필수), age (선택)
        // - optional 한 parameter 가 있다면 경로의 맨 뒤에 오도록 설정
        // 참고. Integer age 라고 한 이유
        // - age 는 optional 한 값. 즉 null 이 될 수 있기 때문에 primitive type 가 아닌 reference type 인 래퍼 객체 사용
        model.addAttribute("name", name);
        model.addAttribute("age", age);
        return "_02_restapi/res";
    }

 

 


 

Post method

Post의 경우는 @PostMapping(url)이다.

body의 경우도 @RequestParam으로 받아준다.

@PostMapping("/post/res2")
    public String postRes2(@RequestParam(required = false) String name, @RequestParam(required = false) Integer age, Model model) {
        model.addAttribute("name", name);
        model.addAttribute("age", age);
        return "_02_restapi/res";
    } // O

    // ㄴ 여기까지 코드는 return 이 항상 Template View! 하지만 API 에서 데이터 자체를 응답하고 싶다면?
    // => @ResponseBody 어노테이션 사용
    @PostMapping("/post/res3")
    @ResponseBody // restAPI로 응답
    public String postRes3(@RequestParam String name, @RequestParam int age, Model model) {
        // @ResponseBody 어노테이션
        // - 응답시 객체를 JSON 으로 리턴할 때 사용 (직렬화, serialize)
        // - 즉, 응답 객체를 전달 (express의 res.json 과 유사)
        model.addAttribute("name", name);
        model.addAttribute("age", age);
//        return "_02_restapi/res"; // @ResponseBody가 붙으면 템플릿 엔진 리턴이 아닌 res.send() 처럼 문자열 그 자체 응답
        return name + " " + age;
        // json 형식으로 보내려면 그냥 객체로 묶어서 return 해줘야함. 
    } // O

 

 

 


Get 요청 with DTO 이용(urlencoded)

setter가 정의되어있어야 한다.

// ======================= DTO 이용 ==============================
// 일반 폼을 이용할 땐 DTO를 이용해야 한다.
// 1. Get 요청
@GetMapping("/dto/res1")
@ResponseBody
public String dtoRes1(@ModelAttribute UserDTO userDTO) {
    // 변수로 값을 하나씩 가져오는 것이 아니라 DTO 객체에 값을 담아서 가져오기
    // @ModelAttribute: HTML 폼 데이터를 컨트롤러로 전달할 때 객체 매핑하는 어노테이션
    // -> 매핑: setter 함수 실행
    // ex. ?name=홍길동&age=20 -> setName(), setAge() 실행해서 값 매핑
    // **************** Lombok plugin 설치해야 에러 안남 **********
    // - 왜 에러? 롬복은 애플리케이션 실행 후에 getter, setter 를 생성해 주기 때문에 즉, 이 시점에는 getter가 없다고 판단해서 에러가 남
    return userDTO.getName() + " " + userDTO.getAge();
} // (O)


// 2. Post 요청
@PostMapping("/dto/res2")
@ResponseBody
public String dtoRes2(UserDTO userDTO) {
    // @ModelAttribute 어노테이션이 없을 떄에는 자동 추가됨, (생략 가능)
    return userDTO.getName() + " " + userDTO.getAge();
} // (O)

 

Get 요청 with Vo (Urlencoded)

 

setter가 정의되어있어야 하지만 Vo의 경우 setter는 없고, getter만 존재한다. 고로 VO의 경우 일반 폼 전송이 불가하다.

 


동적 폼 전송의 경우 (Axios)

쉽게 말하자면 axios post 요청의 경우 @RequestBody를 이용해야한다.

// @RequestBody 로 값을 전달할 때 userVO 에 setter 함수가 없어도 값이 들어간다.
// 일반: multipart urlencoded
// setter 함수 실행(ModelAttribute)이 아니라 각각의 필드(변수)에 직접적으로 값을 주입(RequestBody)하면서 매핑
// @ModelAttribute 가 setter 함수를 실행해 값을 넣어준다면
// @RequestBody 는 각각의 필드에 직접 주입. private이어도 넣어준다.

 

// DTO 이용 With axios
@GetMapping("/axios/res1")
@ResponseBody
public String axiosRes1(@RequestParam String name, @RequestParam String age) {
    return "이름: " + name + ", 나이: " + age;
} // o

@GetMapping("/axios/res2")
@ResponseBody
public String axiosRes2(UserDTO userDTO) {
    // @modelAttribute 생략
    // @ModelAttribute: HTML 폼 데이터를 컨트롤러로 전달할 때 객체 매핑하는 어노테이션
    return "이름: " + userDTO.getName() + ", 나이: " + userDTO.getAge();
}  // o

// post
@PostMapping("/axios/res3")
@ResponseBody
public String axiosRes3(@RequestParam String name, @RequestParam String age) {
    // @modelAttribute 생략
    // @RequestParam required 기본값이 true
    // axios의 경우 post로 값을 전달하게 될 경우 파라미터로 값이 들어오지 않는다.
    // 값이 들어오지 않는데 기본 값이 true이기 때문에 오류 발생
    return "이름: " + name + ", 나이: " + age;
} // X (error 400)

@PostMapping("/axios/res4")
@ResponseBody
public String axiosRes4(UserDTO userDTO) {
    // @modelAttribute 생략
    // @ModelAttribute: HTML 폼 데이터를 컨트롤러로 전달할 때 객체 매핑하는 어노테이션
    // axios 로 값을 전달하게 될 경우 파라미터로 값이 들어오지 않는다. (Post 로 보냈을 때)

    //문제는 HTTP POST 요청을 처리할 때 Spring이 요청 바디의 데이터를 UserDTO 객체로 변환하지 못하고 있다는 것이다.
    // 이 문제는 HTTP POST 요청의 바디를 읽어 UserDTO 객체로 변환하는 방법을 정확하게 설정하지 않았기 때문에 발생한다.
    //@RequestBody 애노테이션을 사용하여 HTTP 요청의 바디를 읽어와서 UserDTO 객체로 변환할 수 있도록 설정해야 합니다.
    return "이름: " + userDTO.getName() + ", 나이: " + userDTO.getAge();
} // o (null)

@PostMapping("/axios/res5")
@ResponseBody
public String axiosRes5(@RequestBody UserDTO userDTO) {
    // @modelAttribute 생략
    return "이름: " + userDTO.getName() + ", 나이: " + userDTO.getAge();
} // o, axios 할떄는 post의 경우 RequestBody로 받아야함

 

 


SQL Mapper

주요목적: SQL 쿼리와Java 메서드/ 객체간의 매핑을 중심으로
SQL 쿼리를직접 작성, 해당쿼리의결과를Java 객체(domain)에 매핑
동적SQL 생성, 조건문, 반복문등의SQL 작성에있어서유연
SQL 쿼리의세밀한튜닝이필요할주로사용
: MyBatis, JDBCTemplate

 

MyBatis

SQL Mapper 로써, 코드와 파라미터설정 결과 핑을 대신해준다.
자바와 RDBS 프로그래밍을 쉽게 있도록 도와주는 프레임워크

 

 

구현은 이런식으로 해주면 된다. (이전에 mysql을 통해서 테이블을 만들어줘야 한다.)

 

폴더 구조

 

만들어보면서 받은 느낌을 표현하자면 

 

    1. 컨트롤러는 다 만들어진 로직을 이용만 하는 곳

    2. 도메인은 db와 소통하기 위한 비즈니스 모델이 있는 곳

    3. dto는 dto형태 (데이터 전송을 위한 container class)의 클래스들이 있는 곳

    4. mapper는 db와 소통하는 로직(메소드)을 정의하는 곳

    5. service는 비즈니스 로직들을 이용해서 controller에서 이용하는 메소드를 정의하는 곳

이라는 느낌인 것 같다.

그래서 이런 순서로 작업하는 게 편하다고 말씀하신 듯..?

// 작업 순서 : sql 이용해 table 생성 -> 도메인 생성 -> dto -> mapper -> service -> controller

 

또한 만들다 보니까 dto와 domain의 역할이 다르기는 하지만 굉장히 비슷하다는 생각이 들었다. 그래서 gpt한테 질문해봤다.

 

이해해보면 DB와 연결되는 부분에서는 Domain을 이용하고 (return도 Domain class의 형태로 받는다.)

DB로부터 받은 데이터를 전송하기 위해서는 DTO를 이용하는 것 같다.

 

 


My Batis의 작동원리는 이렇다고 한다.

 

1. 애플리케이션 시작  SqlSessionFactoryBuilder 설정 파일을 참고해 SqlSessionFactory 생성
   •  SqlSessionFactory : 데이터베이스와의 연결과 SQL 실행에 대한 모든 것 을 가진 객체


2.  DB 작업SqlSessionFactory SqlSession 생성


  • SqlSession : Connection 생성하거나 원하는 SQL 전달하고, 결과를 Return 해주는 객체

    (express에서 connect를 통해서 커넥션을 해주고 에 쿼리문을 적었던 것과 동일한 것 같다.)


3. 생성된 SqlSession 참고해 mapper 인터페이스 호출 (mapper 인터페이스에는 쿼리문이 있다.)

4. Mapper  SqlSession 호출해 SQL 실행  결과 Return

 

즉, express에서 직접 sql을 이용했을 때 커넥트와 쿼리문을 이용하는 메소드를 직접 정의하고 이를 통해서 처리해줬었던 기억이 있는데 이걸 미리 만들어주는 기능을 하는 것 같다.