네로개발일기

개발자 네로의 개발 일기, 자바를 좋아합니다 !

'web/Spring'에 해당되는 글 67건


반응형

Client에서 받은 요청을 객체로 바인딩하기 위해 사용하는 방법에는 총 @RequestBody, @RequestParam, @ModelAttribute 총 3가지가 있다. 

🥑 @RequestParam

@RequestParam은 1개의 HTTP 요청 파라미터를 받기 위해서 사용한다.(HTTP 요청 파라미터를 @RequestParam이 쓰고있는 메서드의 변수로 Mapping해준다.) @RequestParam은 필수여부가 true이기 때문에 기본적으로  해당 파라미터가 전송되어야 한다. (전송되지 않으면 400 Error가 발생한다) 반드시 필요한 변수가 아니라면 required의 값을 false로 설정할 수 있으며 해당 Parameter를 사용하지 않고 요청을 보낼 경우 default로 defaultValue 옵션을 통해 값을 설정할 수 있다.

- value

- required

- defaultValue

 

🥑 @RequestBody

클라이언트가 전송하는 Json(application/json) 형태의 HTTP Body 내용을 Java 객체로 변환시켜주는 역할을 한다. 그렇기 때문에 Body가 존재하지 않는 HTTP GET 메서드에 @RequestBody를 사용할 경우 에러가 발생하게 된다. @RequestBody로 받은 데이터는 Spring에서 관리하는 MessageConverter들 중 하나인 MappingJackson2HttpMessageConverter를 통해 Java 객체로 변환된다.

Spring은 메시지를 변환하는 과정에서 객체의 기본 생성자를 통해 객체를 생성하고 내부적으로 Reflection을 사용해 값을 할당하므로 @RequestBody에는 값을 주입하기 위한 생성자나 Setter가 필요없다.

Json 데이터를 변환하기 위해서는 Jackson 라이브러리가 사용되는데, Jackson 라이브러리 내부적으로는 Getter나 Setter, @JsonInclude 등을 통해 필드에 있는 변수들의 이름을 찾고, Reflection을 이용해 값을 할당한다.

 

* Reflection

inspect or/and modify runtime attributes of classes, interfaces, fields, and methods.

자바에서 기본적으로 제공하는 API, 구체적인 클래스 타입을 알지 못해도 그 클래스의 정보(메서드, 변수 등등)에 접근이 가능하다.

 

🥑 @ModelAttribute

@ModelAttribute는 Client가 전송하는 multipart/form-data 형태의 HTTP Body 내용과 HTTP 파라미터의 값들을 생성자나 Setter를 통해 주입하기 위해 사용된다. @ModelAttribute에는 매핑시키는 파라미터의 타입이 객체의 타입과 일피하는지를 포함한 다양한 검증(Validation) 작업이 추가적으로 진행된다. 예를 들어 게시물의 번호를 저장하는 int형 index 변수에 1번이라는 String형을 넣으려고 한다면 BindException이 발생하게 된다. 즉, Json이나 XML과 같은 형태의 데이터를 MessageConverter를 통해 변환시키는 @RequestBody와 달리, @ModelAttribute는 multipart/form-data 형태의 HTTP Body와 HTTP 파라미터들을 생성자나 수정자로 주입시킨다는 차이가 있다.

 

> @ModelAttribut와 @RequestBody를 보다 극단적으로 설명하자면, @ModelAttribute는 바인딩하는 값들을 주입해주는 생성자나 Setter가 없다면 매핑이 되지 않는다. 하지만 @RequestBody는 요청받은 데이터를 변환시키는 것이기 때문에, 생성자나 Setter함수가 없어도 값이 매핑된다.

 

@ModelAttribut 어노테이션을 활용해서 특정 Parameter만 받아올 수도 있다. 

 

 

[출처]

https://mangkyu.tistory.com/72

 

[Spring] @RequestBody, @ModelAttribute, @RequestParam의 차이

이번에는 Spring에서 Client로 받은 요청을 객체로 바인딩하기 위해 사용하는 방법들에 대해서 알아보도록 하겠습니다. 1. RequestBody, ModelAttribute, RequestParam이란? [ @RequestParam ] @RequestParam은..

mangkyu.tistory.com

https://www.baeldung.com/java-reflection

https://maivve.tistory.com/298

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

레거시 프로젝트가 Spring Mybatis로 되어있었고 DBMS는 Cubrid로 되어있었다. 이 프로젝트를 Spring JPA로 변경하기 위해 설정을 바꾸어 보았다.

 

🥑 Database: Cubrid 10.1.5.7809

🍒 Spring Boot version 2.6

🍇 Spring JPA, Spring Boot

🫐 Maven

 

pom.xml

    <repositories>
        <repository>
            <id>cubrid.release.repository</id>
            <url>http://maven.cubrid.org</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <dependencies>
    	...
        <dependency>
            <groupId>cubrid</groupId>
            <artifactId>cubrid-jdbc</artifactId>
            <version>10.1.5.7809</version>
        </dependency>
        ...
    </dependencies>

pom.xml파일에 repository와 dependency를 추가해준다. version을 잘 고려해서 넣으면 된다. (10.1.3.7765 버전 이상이어야 Spring JPA가 오류없이 작동하는 것 같다.) 

 

application.yml

spring:
  datasource:
    driver-class-name: cubrid.jdbc.driver.CUBRIDDriver
    url: jdbc:cubrid:[host 주소]:[port 번호]:[DB 이름]:public::?charSet=utf-8
    username: 
    password: 
  jpa:
    database-platform: org.hibernate.dialect.CUBRIDDialect

url, username, password만 수정해서 작성하면 된다.

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

- spring boot가 지원하는 DB migration tool은 Flyway와 Liquibase가 대표적

Flyway

- 오픈소스 마이그레이션 툴
- SQL 스크립트의 변화를 추적하면서 자동적으로 테이블의 스키마나 데이터의 변경 이력을 관리

스프링 부트에서 Flyway 사용
🌱 spring boot 2.6.0
🐥 mysql 5.7
🍿 jdk 8

- 의존성 추가
1) maven

# pom.xml 

<dependency> 
    <groupId>org.flywaydb</groupId> 
    <artifactId>flyway-core</artifactId> 
    <version>5.2.4</version> 
</dependency>

참고
- 공식문서에는 MySQL 5.7버전은 flyway 유료버전인 Flyway Teams을 사용해야 한다고 되어있다. version을 5.2.4로 정해주었더니 되어 version을 따로 기입하였다.

2) gradle

# build.gradle

dependencies {
	implementation: 'org.flywaydb:flyway-core:5.2.4'
}


- 프로젝트 구조

resource 디렉터리에 db.migration 디렉터리를 생성해서 V0__init.sql 파일을 만든다.
- 경로를 변경하고 싶을 때는 application.yml 파일에서 아래와 같이 변경해주면 된다.

spring: flyway: location: [파일위치]

sql 파일을 생성할 때 파일 이름을 정해진 기준에 따라 생성해야 한다.
=> File Naming

  • prefix: default로 V 는 버전 마이그레이션, R은 반복 마이그레이션용 접두사이다. 반드시 V또는 R로 시작해야만 flyway가 인식.
  • version: version은 버전 마이레이션에서만 사용되며 숫자와 Dots(점)이나 underscore(언더바) 조합으로 구성한다. (반복 마이그레이션에서 (version을 명시하면 filename제약 위반으로 에러 발생)
  • separator: 설명부분을 구분하기 위한 구분자이며 반드시 undersocre(언더바)를 2개( __ ) 써야한다.
  • description: 이 부분은 schema_version테이블에 저장시 설명으로 사용된다.
  • suffix: 확장자 기본은 .sql

- application.yml 파일 내용 추가

spring: datasource: url: jdbc:mysql://localhost:[port]/[schema명]?characterEncoding=UTF-8 username: [username] password: [password] driver-class-name: com.mysql.jdbc.Driver flyway: enabled: true baseline-on-migrate: true

- spring.flyway.baseline-on-migrate

  • 기본값: false
  • false: flyway_schema_history 테이블이 있는 경우
  • true: flyway_schema_history 테이블이 없는 경우 생성

이외에도 설정 정보들을 변경할 수 있다. 참고

- Spring boot Run
spring boot를 실행하면 DB schema에 flyway_schema_history 테이블이 생성되면서 migration이 자동으로 실행됩니다.
아래는 관련 로그입니다. migration이 잘 실행되었는지 확인할 수 있습니다.

INFO 49320 --- [ restartedMain] o.f.c.internal.database.DatabaseFactory : Database: jdbc:mysql://localhost:3306/[schema명] (MySQL 5.7) INFO 49320 --- [ restartedMain] o.f.core.internal.command.DbValidate : Successfully validated 1 migration (execution time 00:00.139s) INFO 49320 --- [ restartedMain] o.f.core.internal.command.DbMigrate : Current version of schema `[schema명]`: 0 INFO 49320 --- [ restartedMain] o.f.core.internal.command.DbMigrate : Schema `[schema명]` is up to date. No migration necessary.

- flyway_schema_history 테이블
flyway_scheme_history 테이블은 flyway에서 형상관리를 위하여 자동으로 생성되는 테이블입니다.

  • version: 파일의 V 뒤에 붙어있던 숫자로 낮은 순서부터 실행되며 실행 순서대로 테이블에 쌓이는 구조를 가집니다.
  • checksum: 파일의 내용을 hashing한 것입니다. 만약 파일의 내용이 달라지면 이 체크섬이 달라지게 됩니다. 한번 체크섬을 만들어 둔 후 파일을 수정한다면 그렇게 되면 누군가에 의해서 형상관리에 문제가 생겼다고 판단하기 때문에 flyway는 에러처리를 하게됩니다. 이럴 경우 해당 파일에 대한 체크섬을 repair한 후 success를 0으로 돌리는 등의 작업이 필요합니다.
  • success: 파일 실행에 성공했는지 여부를 나타내는 값입니다. 이 값에 따라서 flyway에서 해당 파일의 내용을 실행할지 말지 결정합니다.














참고
https://flywaydb.org/documentation

 

Homepage - Flyway

Version control for your database. Robust schema evolution across all your environments. With ease, pleasure, and plain SQL.

flywaydb.org

https://sabarada.tistory.com/193

 

[flyway] flyway를 통해 DDL 형상관리를 하자 - Spring Boot (Java API) 편

안녕하세요. 오늘은 flyway를 이용하여 로컬 환경에서 DDL의 형상관리를 하는 방법을 알아보도록 하겠습니다. flyway flyway는 데이터베이스의 형상관리를 목적으로 하는 툴입니다. 데이터베이스의

sabarada.tistory.com

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

🐝  Rest 서비스를 호출하는 방법

- RestTemplate 

Spring 3부터 지원, REST API 호출 이후 응답을 받을 때까지 기다리는 동기 방식

- AsyncRestTemplate

Spring 4에 추가된 비동기 RestTemplate

- WebClient

Spring 5에 추가된 논블록, 리액티브 웹 클라이언트로 동기, 비동기 방식을 지원

 

🐝  RestTemplate이란?

- spring 3.0 부터 지원

- 스프링에서 제공하는 http 통신에 사용할 수 있는 템플릿

- HTTP 서버와의 통신을 단순화하고 RESTful 원칙을 지킨다. (json, xml을 응답받을 수 있음)

 

🐝  RestTemplate 메서드

메서드 HTTP 설명
getForObject GET 주어진 URL 주소로 HTTP GET 메서드로 객체로 결과를 반환받는다.
getForEntity GET 주어진 URL 주소로 HTTP GET 메서드로 ReponseEntity로 결과를 반환받는다.
postForLocation POST POST 요청을 보내 헤더에 저장된 URI를 결과로 반환받는다.
postForObject POST POST 요청을 보내 객체로 결과를 반환받는다.
postForEntity POST POST 요청을 보내 ResponseEntity로 결과를 반환받는다.
delete DELETE 주어진 URL 주소로 HTTP DELETE 메서드를 실행한다.
headForHeaders HEADER 헤더의 모든 정보를 얻을 수 있으면 HTTP HEAD 메서드를 사용한다.
put PUT 주어진 URL 주소로 HTTP PUT 메서드를 실행한다.
patchForObject PATCH 주어진 URL 주소로 HTTP PATCH 메서드를 실행한다.
optionsForAllow OPTIONS 주어진 URL 주소에서 지원하는 HTTP 메서드를 조회한다.
exchange any HTTP 헤더를 새로 만들 수 있고 어떤 HTTP 메서드도 사용가능하다.
execute any Request/Response 콜백을 수정할 수 있다.

 

🐝  초기화

기본 생성자는 java.net.HttpURLConnection 라이브러리를 사용한다. ClientHttpRequestFactory를 사용하여 다른 HTTP 라이브러리로 바꿀 수 있다. 스프링은 Apache HttpComponents, Netty, OkHttp를 지원한다.

예)

RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());

각 ClientHttpRequestFactory는 기본 HTTP 클라이언트 라이브러리에 대한 구성 옵션을 제공한다. 주의할 점은 java.net 패키지의 HTTP 구현체를 사용할 경우 401 상태를 가진 응답 객체에 접근할 때 Exception이 발생할 수 있으므로, 이럴 경우 다른 HTTP 라이브러리를 사용해야 한다.

 

🐝  URIs

대부분의 RestTemplate 메서드는 String vararg 또는 Map<String, String>을 통해 URI 템플릿과 URI 템플릿 변수를 허용한다.

// String vararg
String result = restTemplate.getForObject("http:example.com/hotels/{hotel}/bookings/{booking}", String.class, "102", "1204");

// Map<String, String>
Map<String, String> vars = Collections.singletonMap("hotel", "102");
String result = restTemplate.getForObject("http://example.com/hotels/{hotel}", String.class, vars);

URI 템플릿은 자동으로 인코딩된다.

restTemplate.getForObject("http://example.com/hotel list", String.class);
// 요청 url: "http://example.com/hotel%20list"

RestTemeplate의 uriTemplateHandler 속성을 사용해 URI 인코딩 방법을 지정할 수 있다. 또는 java.net.URI를 사용하여 RestTemplate 메서드에 인자로 전달할 수 있다.

 

🐝  Headers

exchange() 메서드를 사용해 요청 헤더를 지정할 수 있다.

String uriTemplate = "http://example.com/hotels/{hotel}"
URI uri = UriComponentsBuilder.fromUriString(uriTemplate).build(102);

RequestEntity<Void> requestEntity = RequestEntity.get(uri)
    .header("MyRequestHeader", "MyValue")
    .build();
    
ResponseEntity<String> response = template.exchange(requestEntity, String.class);

String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();

ResponseEntity를 반환하는 RestTemplate의 여러 메서드를 통해 응답 헤더를 얻을 수 있다.

 

🐝  RestTemplate 동작 원리

- HTTP Client는 HTTP를 사용하여 통신하는 범용 라이브러리

- RestTemplate는 HTTPClient를 추상화(HttpEntity의 json, xml 등)해서 제공

 

  1. 어플리케이션이 RestTemplate를 생성하고, URI, HTTP 메소드 등의 헤더를 담아 요청
  2. RestTemplate는 HttpMessageConverter를 사용하여 requestEntity를 요청 메세지로 변환
  3. RestTemplate는 ClientHttpRequestFactory로 부터 ClientHttpRequest를 가져와서 요청을 보냄
  4. ClientHttpRequest 는 요청메세지를 만들어 HTTP 프로토콜을 통해 서버와 통신
  5. RestTemplate 는 ResponseErrorHandler 로 오류를 확인하고 있다면 처리로직을 태움
  6. ResponseErrorHandler 는 오류가 있다면 ClientHttpResponse 에서 응답데이터를 가져와서 처리
  7. RestTemplate 는 HttpMessageConverter 를 이용해서 응답메세지를 java object(Class responseType) 로 변환
  8. 어플리케이션에 반환

🐝  RestTemplate 사용 예제

조건

- 비동기 방식으로 POST 메서드를 사용해 log를 저장해야 함. => AsyncRestTemplate JSONObject로 값 전달

- 다른 API 사용 => url 사용

    public static void insertLog(HttpServletRequest request, String memberId, String publicDataPk) {
        String url = "http://localhost:8080/logs";
        
        AsyncRestTemplate restTemplate = new AsyncRestTemplate(); // 비동기 전달
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        
        JSONObject jsonObject = new JSONObject();

        jsonObject.put("memberId", memberId);
        jsonObject.put("sessionId", request.getRequestedSessionId());
        jsonObject.put("publicDataId", publicDataId);

        HttpEntity<String> logRequest = new HttpEntity<>(jsonObject.toString(), httpHeaders);
        restTemplate.postForEntity(url, logRequest, String.class);
        
        ... 생략
    }

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

1. Department DTO

Department 테이블에서 조회한 데이터를 채울 DTO(Data Transfer Object)이다.

 

dto/Department.java

package dto;

public class Department {
	
    int id;
    String name;
    
    public int getId() {
    	return id;
    }
    
    public void setId(int id) {
    	this.id = id;
    }
    
    public String getName() {
    	return name;
    }
    
    public void setName(String name) {
    	this.name = name;
    }
}

2. Department mapper 구현

mapper/DepartmentMapper.java

package mapper;

import java.util.List;

import org.apache.ibatis.annotations.Delete; 
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import dto.Department;

@Mapper 
public interface DepartmentMapper { 
	
    @Select("SELECT * FROM department")
    List<Department> findAll();
    
    @Select("SELECT * FROM department WHERE id = #{id}") 
    Department findOne(int id); 
    
    @Insert("INSERT department (name) VALUES (#{name})") 
    @Options(useGeneratedKeys=true, keyProperty="id") 
    void insert(Department department); 
    
    @Update("UPDATE department SET name = #{name} WHERE id = #{id}") 
    void update(Department department); 
    
    @Delete("DELETE FROM department WHERE id = #{id}") 
    void delete(int id); 
}

@Mapper

mybatis mapper에는 @Mapper 어노테이션이 붙어있어야 한다.

 

findAll

@Select("SELECT * FROM Department")
List<Department> findAll();

findAll 메서드는 department 테이블의 모든 레코드를 조회해서 리턴한다. findAll 메서드를 호출하면, "SELECT * FROM Department" SQL 명령이 MySQL 엔진에서 실행된다. 이 메서드의 조회 결과 레코드 한 개당 Department 객체 한 개가 생성되어 조회 결과 레코드의 칼럼들이 Department 객체의 속성에 채워진다. 이 때 컬럼명과 객체의 속성명이 일치하는 것만 채워지고 일치하지 않는 것은 무시된다. 이렇게 생성된 Department 객체들이 List 타입의 객체에 채워져 리턴된다.

 

- 조회 결과 칼럼명 일치

SELECT SQL 명령의 조회 결과가 Java 객체에 자동으로 채워질 때 조회 결과 칼럼명과 Java 객체의 setter 이름이 일치해야 한다.

 

findOne

@Select("SELECT * FROM department WHERE id = #{id}")
Department findOne(int id);

findOne 메서드는 department 테이블의 레코드 한개를 조회해서 리턴한다. findOne 메서드를 호출하면 "SELECT * FROM department WHERE id = #{id}" 명령이 MySQL 엔진에서 실행된다. 이때, 이 메소드의 파라미터 id 변수의 값이, #{id} 부분에 채워진 후 실행된다. Department 객체를 한 개 생성해서, 조회 결과 레코드의 각 컬럼의 값을, 이 Department 객체에 채워서 리턴한다. 이때 컬럼명과 객체의 속성명이 일치하는 것만 채워지고, 일치하지 않는 것은 무시된다. 조회 결과가 없을 경우에, 이 메소드는 null을 리턴한다.

 

insert

@Insert("INSERT department (name) VALUES (#{name})")
@Options(useGeneratedKeys=true, keyProperty="id")
void insert(Department department);

insert 메서드의 파라미터 변수가 Department 객체이다. 이 Java 객체의 속성값이 INSERT SQL 명령의 #{...} 부분에 채워져서 실행된다. Java 객체의 속성명과 #{...} 부분의 이름이 일치해야 속성값이 채워질 수 있다.

 

@Options(useGeneratedKeys=true, keyProperty="id")

INSERT할 테이블의 기본키(primary key) 필드 이름이 "id"이고, 이 필드의 값은 자동으로 생성된다는 선언이다. (Auto Increment 필드)

 

update

@Update("UPDATE department SET name = #{name} WHERE id = #{id}")
void update(Department department);

 

delete

@Delete("DELETE FROM department WHERE id = #{id}")
void delete(int id);

 

3. Department Controller

controller/DepartmentController.java

package controller;

import java.util.List; 

import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.stereotype.Controller; 
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import dto.Department; 
import mapper.DepartmentMapper;

@Controller
@RequestMapping("department")
public class DepartmentController {

    @Autowired
    DepartmentMapper departmentMapper; 
    
    @RequestMapping("list")
    public String list(Model model) { 
    	List<Department> departments = departmentMapper.findAll();
        model.addAttribute("departments", departments); 
        return "department/list";
    }
    
    @GetMapping("create")
    public String create(Model model) { 
    	model.addAttribute("department", new Department()); 
    	return "department/edit";
    } 
    
    @PostMapping("create") 
    public String create(Model model, Department department) {
    	departmentMapper.insert(department); 
    	return "redirect:list";
    } 
    
    @GetMapping("edit")
    public String edit(Model model, @RequestParam("id") int id) { 
    	Department department = departmentMapper.findOne(id); 
    	model.addAttribute("department", department); 
    	return "department/edit";
    } 
    
    @PostMapping("edit")
    public String edit(Model model, Department department) {
    	departmentMapper.update(department);
    	return "redirect:list";
    } 
    
    @RequestMapping("delete")
    public String delete(Model model, @RequestParam("id") int id) { 
	    departmentMapper.delete(id);
    	return "redirect:list";
    } 
}

 

@Autowired

@Autowired
DepartmentMapper departmentMapper;

DepartmentMapper 인터페이스를 구현한 Java 클래스를 spring mybatis가 자동으로 구현해주고, 그 클래스의 객체를 하나 생성하여 departmentMapper 멤버 변수에 자동으로 대입해준다.

 

4. request parameter 전달

액션 메서드의 파라미터에 request parameter 값이 채워져서 액션 메서드에 전달된다.

 

값을 하나씩 전달 받을 때 - @RequestParam

@GetMapping("edit")
public String edit(Model model, @RequestParam("id") int id)

값을 여러 개 전달 받을 때 - 파라미터 객체

@PostMapping("edit")
public String edit(Model model, Department department)

request parameter 데이터가 Department 객체에 자동으로 채워진다. request parameter 데이터의 이름과 액션 메서드의 파라미터 객체의 속성이름이 일치할 경우에 (setter 메서드 이름이 일치할 경우에) request parameter 데이터가 그 속성에 자동으로 채워진다.

 

 

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !