네로개발일기

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

'web'에 해당되는 글 82건


반응형

- 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

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

반응형

JSP에서 숫자나 날짜를 정해진 포맷으로 출력할 때, formatNumber, formatDate 확장 태그를 사용한다. 

 

JSTL

이 확장 태그들은 JSTL 라이브러리에 들어있다.

따라서 프로젝트 pom.xml 파일에 JSTL dependency가 들어있어야 한다.

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

위 확장 태그들을 사용하기 위해 필요한 선언이다. 이 선언이 JSP 파일 선두에 있어야 한다.

 

formatNumber

<fmt:formatNumber value="${ weight }" pattern="#.#" />

weight 값이 소숫점 한자리까지 출력된다. 그 아래 자리는 반올림된다.

 

<fmt:formatNumber value="${ weight * 1000 }" pattern="#,###" />

weight * 1000 식의 값이 출력된다. 3자리마다 콤마(,)가 출력된다 (1,234,567)

formatNumber 확장 태그의 pattern은 Java의 DecimalFormat 클래스의 그것과 같다.

 

formatDate

<fmt:formatDate pattern="yyyy-MM-dd" value="${ birthday }" />

birthday 값을 "yyyy-MM-dd" 형태로 출력한다. birthday 값은 Date 타입이어야 한다. formatDate 확장 태그의 pattern은 Java의 SimpleDateFormat 클래스의 그것과 같다. 

 

728x90
반응형
blog image

Written by ner.o

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

반응형

1. 컨트롤러 클래스

HomeController.java

import org.springframework.streotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HomeController {
	
    @RequestMapping("/")
    public String index(Model model) {
    	model.addAttribute("message", "좋은 아침!");
        return "index";
    }
}

컨트롤러 클래스는 웹 브라우저의 URL 요청을 받아서, 웹 서버에서 실행되는 자바 클래스이다. 웹 브라우저가 웹서버에 어떤 URL을 요청을 하면 그 URL에 해당하는 컨트롤러의 메소드가 자동으로 호출되어 실행된다.

 

@Controller 어노테이션

컨트롤러 클래스에 이 어노테이션(Annotation)을 붙여줘야 한다. 

 

액션 메서드

웹 브라우저가 어떤 URL을 웹서버에 요청하면, 그 요청된 URL에 해당하는 컨트롤러의 어떤 메서드가 자동으로 호출된다. 웹 브라우저의 요청에 의해서 자동으로 호출되는 컨트롤러의 메서드를 액션메서드라 한다.

 

@RequestMapping 어노테이션

액션 메서드에 붙은 @RequestMapping("/") 어노테이션의 "/" 부분은 그 액션 메서드를 호출하기 위한 URL이다. 자동으로 호출할 액션 메서드를 찾을 때 컨트롤러 클래스의 이름이나 액션 메서드의 이름은 상관없고 @RequestMapping 어노테이션에 등록되어 있는 URL만 일치하면 된다.

 

컨트롤러 액션 메서드의 리턴값

컨트롤러의 액션 메서드는 문자열을 리턴한다. 컨트롤러의 액션 메서드가 리턴하는 문자열은 View 파일의 이름이다. 컨트롤러의 액션 메서드가 리턴된 후, 뷰 파일이 실행된다. 액션 메서드가 리턴한 이름의 뷰 파일이 실행된다.

 

Model 객체

public String index(Model model)

컨트롤러의 액션 메서드는 Spring Web MVC 엔진에 의해 호출된다. 대부분의 액션 메서드의 파라미터에 Model model 객체가 포함된다. 컨트롤러의 액션 메서드가 뷰 파일에 전달할 데이터를 Model 객체에 넣어 전달한다. 즉, Model 객체는 데이터를 전달하는 객체라고 보면 된다.

 

model의 attribute

model.addAttribute("message", "좋은 아침!");

뷰 파일에 전달할 데이터를 Model 객체에 넣는 코드이다. 전달되는 데이터의 이름은 "message"이고 값은 "좋은 아침!" 문자열이다. 이렇게 모델에 담겨서 뷰에 전달되는 데이터는 model attribute라고 한다. 

뷰에서 model attribute를 출력하는 코드는

${message}

위와 같다. model attribute 이름이 정확히 일치해야 한다.

2. 뷰 (view)

index.jsp

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>

<!DOCTYPE html> 
<html> 
<head>
<meta charset="utf-8"> 
</head>
<body>
	<h1>안녕하세요</h1> 
	<h3>${ message }</h3> 
</body>
</html>

웹 브라우저의 요청(http request)에 대한 응답(http response)으로, 웹 서버에서 웹 브라우저로 전송되는 것들은 http 태그가 대부분이다. 

 

실행 순서

웹 브라우저에서 웹 서버에 요청(http request)이 전달되면, 요청된 URL과 일치하는 컨트롤러의 액션 메서드가 실행된다. 그리고 액션 메서드의 뒤를 이어서 뷰(view) 파일이 실행된다. 뷰 파일의 실행 결과, 출력된 html 태그들이 웹 브라우저로 전송된다.

 

728x90
반응형
blog image

Written by ner.o

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