본문 바로가기
Spring

Spring 기초3 (데이터 받는 method) (Json 변환 String&Object) (@RequestParam @RequestBody) (HTTP 를 객체로)

by sehunbang 2024. 1. 19.

1. 데이터를 client 에 반환 하는 방법. 

그러면 서버는 요청을 받아 html/css/js 파일을 반환해주는게 주 업무일까요?

당연히 정답은 없지만.

최근의 경향으로는 그렇지는 않다.( 전에는 조금 더 그랬었던 편)

웹 생태계가 고도화 되는 과정중에 상대적으로 프론트엔드와 백엔드가 각각 따로 발전하게 되면서

느슨하게 결합하는 방식을 더 많이 채택하게 되었고, 최근에는 서버가 직접 뷰(html/css/js)를 반환하기 보다는 요청에 맞는 특정한 정보만 반환하는 것을 조금 더 선호하기도 합니다. 그래서 요즘에는 주로 서버에서는 데이터 교환 포맷 중 JSON 형태로 데이터를 반환하기도 하는데, 보통 이렇게 생겼습니다!

 

 

따라서 강의에서도 기본적인 html 파일 요청을 제외하고는 JSON 데이터를 요청하는 API를 통해 브라우저에서 html을 조작하여 반영하는 식으로 강의가 진행될 예정입니다.

JS와 html 파일은 전부 완성이된 상태로 제공되기 때문에 우리는 서버 제작에만 집중하면 됩니다.

 

1. Json 데이터 변환하는 방법.

템플릿 엔진에 적용된 SpringBoot 는 Controller 에서 문자열을 반환하면 template 에서 해당 문자열 html 을 가져옴.

따라서 html 파일이 아닌 json 데이터를 반환 하고 싶으면  @ResponseBody 애너테이션을 추가해줘야함.

 

2. JSON 데이터 반환 방법

http://localhost:8080/response/json/string 에

@GetMapping("/response/json/string")
@ResponseBody
public String helloStringJson() {
    return "{\"name\":\"Robbie\",\"age\":95}";
}

 

Java 는 JSON 타읍을 지원하지 않아서 Json 형태의 String 으로 반환 해야합니다.

// Content-type 이 applicatation/json
//response body
@GetMapping("/json/class")
@ResponseBody
public Star helloStringclass() { // Spring 은 객체의 를 json 을로 변환해줌
    return new Star("Robit",98);
}

객체로 반환 하는 방법.

@ResponseBody 는 html 파일이 아닌 데이터 보낼때 사용.

@Controller
@RestController

RestController = Controller + Response Body.

@ResponseBody 하구 돌려도됨.

 

2. JackSon

Jackson Library 에서 

2.1 Object 에서 json 으로

public class JacksonTest {
    @Test
    @DisplayName("Object To JSON : get Method 필요")
    void test1() throws JsonProcessingException {
        Star star = new Star("Robbie", 95);

        ObjectMapper objectMapper = new ObjectMapper(); // Jackson 라이브러리의 ObjectMapper
        String json = objectMapper.writeValueAsString(star);

        System.out.println("json = " + json);
    }
}

Jackson Lib 에서 가져온 ObjectMatter 객에의, 안에 writeValueAsString() 을 상용 하여 Object 안에 있는 것들을 Json 방식의 String 으로 바환 합니다.

// Getter 메소드를 사용 해서 가져 옵니다.

 

2.2 json 에사 Object 로 바꾸는 방법.

@Test
@DisplayName("JSON To Object : 기본 생성자 & (get OR set) Method 필요")
void test2() throws JsonProcessingException {
    String json = "{\"name\":\"Robbie\",\"age\":95}"; // JSON 타입의 String

    ObjectMapper objectMapper = new ObjectMapper(); // Jackson 라이브러리의 ObjectMapper

    Star star = objectMapper.readValue(json, Star.class);
    System.out.println("star.getName() = " + star.getName());
}

어떤객체로 말들건지 정해야합니다.

Star star = objectMapper.readValue(json, Star.class)

// 기본생성자 메소드가 필요하고, Setter 메소드를 사용 해서 object 를 만듭니다..

// 필드 이름 변수명과, json 에 있는 이름들이 같아야 합니다.

 

3. Path Variable 과 Request Param

클라가 브라우저로 HTTP 요청을 보낼때 데이터를 함께 보낼수 있습니다.

서버는 이런 데이타를 받아 쓰는데. 

데이터를 보내는 방식이 한가지가 아니라 많이 있기 때문에, 모든 방식의 방법을 학습해야 합니다.

1. path variable

GET /star/{name}/age/{age} 

 @PathVariable
// [Request sample]
// GET http://localhost:8080/hello/request/star/Robbie/age/95
@GetMapping("/star/{name}/age/{age}")
@ResponseBody
public String helloRequestPath(@PathVariable String name, @PathVariable int age)
{
    return String.format("Hello, @PathVariable.<br> name = %s, age = %d", name, age);
}

 

http://localhost:8080//hello/request/star/방세훈/age/12

(API 테이블에 방식으로 프엔 개발자들과 어떤 방식으로 할지 소통을 합니다).

 

2.RequestParam

quary string 방식 

param?key=value&key=value

 @RequestParam 

 

// [Request sample]
// GET http://localhost:8080/hello/request/form/param?name=Robbie&age=95
@GetMapping("/form/param")
@ResponseBody
public String helloGetRequestParam(@RequestParam String name, @RequestParam int age) {
    return String.format("Hello, @RequestParam.<br> name = %s, age = %d", name, age);
}

 

http://localhost:8080/hello/request/form/param?name=방세훈&age=87

 

3. PostRequestParam

Post 는 Get 과 다르게 body 부분들 받고 있습니

@RequestParam
// [Request sample]
// POST http://localhost:8080/hello/request/form/param
// Header
//  Content type: application/x-www-form-urlencoded
// Body
//  name=Robbie&age=95
@PostMapping("/form/param")
@ResponseBody
public String helloPostRequestParam(@RequestParam String name, @RequestParam int age) {
    return String.format("Hello, @RequestParam.<br> name = %s, age = %d", name, age);
}

http://localhost:8080/hello/request/form/param

정보가 payload(body) 안에 넣어서 옵니다.

 

(@RequestParam 은 생략이 가능합니다.)

하지만 만약 필요한 데이터가 안온다면 bad request error 가 뜹니다.

(@RequestParam(required = false) 하면 해당 값이 호환되어 있지 않아 오류가 발생하지 않음 null 이 들어옴).

PathVariable( required = false ) 도 있음.

 

 

3. HTTP 를 객체로 처리하는법

@ModelAttribute

body 부분에 들어온 quary String 방식의 데이터를 객체에 mapping 해서 가지고옴.

// [Request sample]
// POST http://localhost:8080/hello/request/form/model
// Header
//  Content type: application/x-www-form-urlencoded
// Body
//  name=Robbie&age=95
@PostMapping("/form/model")
@ResponseBody
public String helloRequestBodyForm(@ModelAttribute Star star) {
    return String.format("Hello, @ModelAttribute.<br> (name = %s, age = %d) ", star.name, star.age);
}

Jackson Lib 에서 봤듯이 json -> Object 처럼 quary String 으로 되어있는것을  Object 로 Spring 내부에서 바꿔 준다

그래서 

필드 이름을 (e.g. String name) 을 클라이언트 에서 랑 동일한 이름으로 해야함.

 

helloRequestParam 방법

// [Request sample]
// GET http://localhost:8080/hello/request/form/param/model?name=Robbie&age=95
@GetMapping("/form/param/model")
@ResponseBody
public String helloRequestParam(@ModelAttribute Star star) {
    return String.format("Hello, @ModelAttribute.<br> (name = %s, age = %d) ", star.name, star.age);
}

만약에 String name & int age 만 있는게 아니라 엄청 많음 변수들이 있다면. 전부다 Request param 해주긴엔 너무 힘들다.

그래서 겍체로 한번에 받을수 있게 Spring 에서 기능해줍니다,

 

참고로 Request param & ModelAttribute  생략이 가능합니다.

Spring은 해당 파라미터가 simple value type(int , String) = Request param

아니면 ModelAttribute  으로 간주합니다.

/////

Json 형식으로 들어온것을 객체로.

   // [Request sample]
    // POST http://localhost:8080/hello/request/form/json
    // Header
    //  Content type: application/json
    // Body
    //  {"name":"Robbie","age":"95"}
    @PostMapping("/form/json")
    @ResponseBody
    public String helloPostRequestJson(@RequestBody Star star) {
        return String.format("Hello, @RequestBody.<br> (name = %s, age = %d) ", star.name, star.age);
    }
}

json to Object 처럼 jackson lib 가 해줌.

@RequestBody

이걸 꼭 알려 줘야 합니다.

 

4. 미니 프로젝트

 간단한 index.html 을 만들어서 resources / static 에 넣어둡니다.

localhost:{port} 가 자동으로 static 안에 있는 index 을 찾아 넣습니다.

 

2. 서버쪽 개발

전체 목록조회는 GET 으로

신규 생성은 POST

메모 변경은 PUT

메모 삭제는 DELETE 로 할겁니다.

//

메모 생성하기 POST /api/memos MemoResponseDto
메모 조회하기 GET /api/memos List<MemoResponseDto>
메모 변경하기 PUT /api/memos/{id} Long
메모 삭제하기 DELETE /api/memos/{id} Long

 

5. Create( 생성 ), Read ( 조회 )구현하기

DTO란 무엇일까? 

(Data Transfer Object)는 데이터 전송 및 이동을 위해 생성되는 객체를 의미합니다.

Client에서 보내오는 데이터를 객체로 처리할 때 사용됩니다

또한 서버의 계층간의 이동에도 사용됩니다

 

그리고 DB와의 소통을 담당하는 Java 클래스를 그대로 Client에 반환하는 것이 아니라 DTO로 한번 변환한 후 반환할 때도 사용됩니다.

@RestController
@RequestMapping("/api")

Post 방식으로 올리기.

@PostMapping("/memos")
public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
    // RequestDto -> Entity
    Memo memo = new Memo(requestDto);

    // Memo Max ID Check
    Long maxId = memoList.size() > 0 ? Collections.max(memoList.keySet()) + 1 : 1;
    memo.setId(maxId);

    // DB 저장
    memoList.put(memo.getId(), memo);

    // Entity -> ResponseDto
    MemoResponseDto memoResponseDto = new MemoResponseDto(memo);

    return memoResponseDto;
}

get 방식으로 가져오기

@GetMapping("/memos")
public List<MemoResponseDto> getMemo() {
    // Map To List
    List<MemoResponseDto> responseList = memoList.values().stream()
            .map(MemoResponseDto::new).toList();
    return responseList;
}

put 방식으로 업데이트

// 업데이트
@PutMapping("/memos/{id}")
// json 받아올때는 @RequestBody
public long Updatamemp(@PathVariable long id, @RequestBody MemoRequestDto requestDto) {
    // 해당 메모가 존제 하는지.
    if (memoList.containsKey(id)) {
        Memo memo = memoList.get(id);
        // 메모 수정
        memo.update(requestDto);
        return memo.getId();
    } else {
        throw new IllegalArgumentException("선택한 메모는 존재 하지 않아요");
    }
}

 

delete 방식으로 삭제

// delete 지우는거
@DeleteMapping("/memos/{id}")
public long deleteMemo(@PathVariable long id) {
    // 해당 메모가 db 에 존샣라는지
    if (memoList.containsKey(id)) {
        // 메모 삭제
        memoList.remove(id);
        return id;
    } else {
        throw new IllegalArgumentException("선택한 메모는 존재 하지 않아요");
    }
}

 

5. 실제 데이터 베이스로.

1. 용어 설명

Database : 데이터 집합/저장소DBMS (Database Management System) : Database 를 관리하는 시스템.

RDBMS (Relational DBMS)( 관계형 데이터베이스 ) : 테이블(table)이라는 최소 단위로 구성되며, 이 테이블은 열(column)과 행(row)으로 이루어져 있습니다.

테이블간 FK(Foreign Key)를 통해 다른 데이터를 조합해서 함께 볼수 있다라는 장점이 있습니다

RDBMS 종류:

SQL

MySQL

...

DDL : SQL  커맨드들 CREATE (생성), ALTER(수정), DROP(DB/테이블 삭제), TRUNCATE ( 컬럼값만 남기면서 DB/테이블 삭제) etc....

DCL : GRANT( 사용자 또는 ROLE에 대해 권한을 부여할 수 있습니다. ), REVOKE ( 사용자 또는 ROLE에 부여한 권한을 회수할 수 있습니다. )

DML :

INSERT : 테이블에 새로운 row를 추가할 수 있습니다.  

SELECT : 테이블의 row를 선택할 수 있습니다.

UPDATE : 테이블의 row의 내용을 수정할 수 있습니다.

DELETE : 테이블의 row를 삭제할 수 있습니다.

 

Intellij Database 랑 연결 (JDBC) 해보기

//

6. 실제 데이터 베이스로.

java 에서 sql 에 연결 + 커맨드를 쓰는 방법(library) 중 하나입니다,

php 같은 겨우에 mysqli($servername, $username, $password); 같은 거라고 보심 됨.

 

JDBC template.

 

resources -> application.properties.

에서 데이타 베이스 정보.

spring.datasource.url=jdbc:mysql://localhost:3306/memo
spring.datasource.username=root
spring.datasource.password={비번!}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

build.gradle 에 jdbc 를 사용 하기 위한 dependncy 추가

dependencies {
    // MySQL
    implementation 'mysql:mysql-connector-java:8.0.28'
    implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'

    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

 

new cotroller 

package com.sparta.memo.controller;

import com.sparta.memo.dto.MemoRequestDto;
import com.sparta.memo.dto.MemoResponseDto;
import com.sparta.memo.entity.Memo;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.web.bind.annotation.*;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

@RestController
@RequestMapping("/api")
public class MemoController {

    private final JdbcTemplate jdbcTemplate;

    public MemoController(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @PostMapping("/memos")
    public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
        // RequestDto -> Entity
        Memo memo = new Memo(requestDto);

        // DB 저장
        KeyHolder keyHolder = new GeneratedKeyHolder(); // 기본 키를 반환받기 위한 객체

        String sql = "INSERT INTO memo (username, contents) VALUES (?, ?)";
        jdbcTemplate.update( con -> {
                    PreparedStatement preparedStatement = con.prepareStatement(sql,
                            Statement.RETURN_GENERATED_KEYS);

                    preparedStatement.setString(1, memo.getUsername());
                    preparedStatement.setString(2, memo.getContents());
                    return preparedStatement;
                },
                keyHolder);

        // DB Insert 후 받아온 기본키 확인
        Long id = keyHolder.getKey().longValue();
        memo.setId(id);

        // Entity -> ResponseDto
        MemoResponseDto memoResponseDto = new MemoResponseDto(memo);

        return memoResponseDto;
    }

    @GetMapping("/memos")
    public List<MemoResponseDto> getMemos() {
        // DB 조회
        String sql = "SELECT * FROM memo";

        return jdbcTemplate.query(sql, new RowMapper<MemoResponseDto>() {
            @Override
            public MemoResponseDto mapRow(ResultSet rs, int rowNum) throws SQLException {
                // SQL 의 결과로 받아온 Memo 데이터들을 MemoResponseDto 타입으로 변환해줄 메서드
                Long id = rs.getLong("id");
                String username = rs.getString("username");
                String contents = rs.getString("contents");
                return new MemoResponseDto(id, username, contents);
            }
        });
    }

    @PutMapping("/memos/{id}")
    public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
        // 해당 메모가 DB에 존재하는지 확인
        Memo memo = findById(id);
        if(memo != null) {
            // memo 내용 수정
            String sql = "UPDATE memo SET username = ?, contents = ? WHERE id = ?";
            jdbcTemplate.update(sql, requestDto.getUsername(), requestDto.getContents(), id);

            return id;
        } else {
            throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
        }
    }

    @DeleteMapping("/memos/{id}")
    public Long deleteMemo(@PathVariable Long id) {
        // 해당 메모가 DB에 존재하는지 확인
        Memo memo = findById(id);
        if(memo != null) {
            // memo 삭제
            String sql = "DELETE FROM memo WHERE id = ?";
            jdbcTemplate.update(sql, id);

            return id;
        } else {
            throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
        }
    }

    private Memo findById(Long id) {
        // DB 조회
        String sql = "SELECT * FROM memo WHERE id = ?";

        return jdbcTemplate.query(sql, resultSet -> {
            if(resultSet.next()) {
                Memo memo = new Memo();
                memo.setUsername(resultSet.getString("username"));
                memo.setContents(resultSet.getString("contents"));
                return memo;
            } else {
                return null;
            }
        }, id);
    }
}