자원을 이름으로 구분하여 해당 자원의 상태(정보)를 주고 받는 것이다.
HTTP의 URI를 통해 자원(Resource)을 명시하고, HTTP Method(POST, GET, PUT, DELETE)을 통해 해당 자원에 대한 CRUD 작업을 적용하
는 것이다.
자원에 대한 CRUD 작업
* CREATE : 생성(POST)
* READ : 조회(GET)
* UPDATE : 수정(PUT)
* DELETE : 삭제(DELETE)
REST의 필요성
어플리케이션의 분리(백엔드와 프론트엔드의 분리)
다양한 클라이언트의 등장
서버가 다양한 브라우저(javascript), 안드로이드, 아이폰과 같은 다양한 디바이스와 통신 지원
REST의 구성요소
자원(Resource) : URI
* 모든 자원에 대한 고유한 아이디가 존재한다.
* 자원을 구분하는 아이디는 '/users/{userId}'와 같은 URI다.
행위(Verb) : HTTP Method(POST, GET, PUT, DELETE)
* HTTP Method로 자원에 대한 CRUD operation을 정의한다.
표현(Representation of Resource) : JSON, XML, TEXT, RSS 등등
* 클라이언트가 자원에 대한 조작을 요청하면 서버는 적절한 응답(Representation)을 제공한다.
* REST에서 하나의 자원은 JSON, XML, TEXT, RSS 형식으로 표현된다.
* JSON이나 XML로 주고받는 것이 일반적이다.
API(Application Programming Interface)
* 데이터와 기능들의 집합을 제공한다.
* 컴퓨터 프로그램간 상호작용을 촉진하여, 서로 정보를 교환 가능하도록 한다.
REST API
* REST를 기반으로 서비스 API를 제공하는 것.
* Open API : 누구나 사용할 수 있는 공개된 API (다음 지도 API, 다음 REST API, 공공 데이터 API 등)
REST API 설계 기본 규칙
URI는 자원을 표현한다.
자원은 동사보다 명사, 대문자보다 소문자를 사용한다.
예시)
/Students/1 --> /students/1
자원의 document 이름은 단수형으로 표현한다.
자원의 collection이나 store는 복수형으로 표현한다.
자원에 대한 행위는 HTTP 메소드로 표현한다.
예시)
GET /students/delete/1 --> DELETE /students/1
POST /students/insert --> POST /students
경로의 /는 계층관계를 나타낸다.
예시)
GET /teams/{teamId}
GET /teams/{teamId}/playes/{playerId}
파일의 확장자는 포함하지 않는다.
리소스 간에 연관관계가 있는 경우
/리소스/리소스 아이디/관계가 있는 다른 리소스명
예시)
/users/{userId}/orders
모든 팀을 조회 GET /teams/
10개씩 팀을 조회 GET /teams?page=1
팀을 검색 GET /teams?opt=city&keyword=서울
특정팀의 모든 선수 조회 GET /teams/{teamId}/playes
특정팀의 모든 선수를 이름순을 정렬 조회 GET /teams/{teamId}/playes?sort=name
요청 내용 HTTP Method 요청 URI
모든 사용자 정보를 조회 GET /users
한 명의 사용자 정보를 조회 GET /users/{userId}
사용자 정보 추가(생성) POST /users
사용자 정보 수정 PUT /users/{userId}
사용자 정보 삭제 DELETE /users/{userId}
한 사용자의 주문목록 조회 GET /users/{userId}/orders
한 사용자의 주문정보 하나 조회 GET /users/{userId}/orders/{orderId}
최근 일주일 주문목록 조회 GET /users/{userId}/orders?period=1w
REST-API 생성
SpringbootRestApplication.java
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringbootRestApplication {
public static void main(String[] args) {
// SpringApplication.run() 메소드는 스프링 컨테이너 객체를 생성하고 반환한다.
// SpringApplication.run() 메소드의 매개변수로 전달된 클래스에 @SpringBootApplication 어노테이션이 저장되어 있기 때문에
// Spring boot의 자동구성이 수행된다.
SpringApplication.run(SpringbootRestApplication.class, args);
}
}
User.java
package com.example.vo;
import java.sql.Date;
import org.apache.ibatis.type.Alias;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
// mybatis의 어노테이션
@Alias("User")
// lombok의 어노테이션
//@Data = gettter / setter / toString / 메소드가 알아서 생성된다. / 아래와 같은 의미다.
@Getter
@Setter
@ToString
@NoArgsConstructor
public class User {
private String id;
private String password;
private String name;
private String email;
private String tel;
private String photo;
private String deleted;
private Date createdDate;
private Date updatedDate;
}
UserMapper.java
package com.example.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.example.vo.User;
@Mapper
public interface UserMapper {
List<User> getUsers();
User getUserById(String userId);
User getUserByEmail(String email);
void updateUser(User user);
void deleteUser(String userId);
}
users.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.mapper.UserMapper">
<!--
resultMap은
SELECT 실행결과를 지정된 객체와 매핑시킨다.
컬럼 이름 / 멤버변수 이름으로 적는다.
select에 *을 적는 것을 권장하지는 않는다.
* spring_user 테이블에 대한 select문 실행결과를 com.example.vo.User 객체에 매핑시킨다.
* <id />태그와 <result />태그를 사용해서 테이블의 컬럼명(column)과 객체의 멤버변수명(property)을 매핑시킨다.
* <id />태그의 기본키 제약조건이 지정된 컬럼의 값을 객체의 멤버변수와 매핑시킨다.
-->
<resultMap id="UserResultMap" type="User">
<id column="user_id" property="id"/>
<result column="user_password" property="password"/>
<result column="user_name" property="name"/>
<result column="user_email" property="email"/>
<result column="user_tel" property="tel"/>
<result column="user_photo" property="photo"/>
<result column="user_deleted" property="deleted"/>
<result column="user_created_date" property="createdDate"/>
<result column="user_updated_date" property="updatedDate"/>
</resultMap>
<select id="getUsers" resultMap="UserResultMap">
select
*
from
spring_users
</select>
<select id="getUserById" parameterType="string" resultMap="UserResultMap">
select
*
from
spring_users
where
user_id = #{value}
</select>
<select id="getUserByEmail" parameterType="string" resultMap="UserResultMap">
select
*
from
spring_users
where
user_email = #{value}
</select>
<update id="updateUser" parameterType="User">
update
spring_users
set
user_password = #{password},
user_email = #{email},
user_tel = #{tel},
user_deleted = #{deleted},
user_updated_date = sysdate
where
user_id = #{id}
</update>
<delete id="deleteUser" parameterType="string">
delete from
spring_users
where
user_id = #{value}
</delete>
</mapper>
application.yml
# 웹서버 포트 지정
server:
port: 80
# mybatis의 SQL 실행 로그 출력을 위한 설정
logging:
level:
'[com.example.mapper]': trace
spring:
# 데이터베이스와 연결된 Connetion Pool 설정
datasource:
driver-class-name: oracle.jdbc.OracleDriver
url: jdbc:oracle:thin:@localhost:1521:xe
username: hr
password: zxcv1234
# mybatis 환경설정
mybatis:
mapper-locations:
- mybatis/mappers/*.xml
type-aliases-package: com.example.vo
configuration:
jdbc-type-for-null: null
log-impl: org.apache.ibatis.logging.log4j2.Log4j2Impl
UserService.java
package com.example.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.mapper.UserMapper;
import com.example.vo.User;
@Service
//@RequiredArgsConstructor // @Autowired를 붙일 필요 없이 알아서 해준다. (private final UserMapper userMapper;)
public class UserService {
//@Autowired - UserMapper가 필요하니 주입해줘. (아래와 같다.)
//private UserMapper userMapper;
@Autowired //Autowired는 final을 적으면 안된다.
private UserMapper userMapper;
//@Autowired
//public UserService(UserMapper userMapper) {
//this.userMapper = userMapper;
//}
// 모든 사용자 정보를 반환하는 메소드
public List<User> getAllUsers() {
return userMapper.getUsers();
}
public User getUser(String userId) {
return userMapper.getUserById(userId);
}
}
UserController.java
package com.example.web.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import com.example.service.UserService;
import com.example.vo.User;
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/users")
public List<User> users() {
return userService.getAllUsers();
}
@GetMapping("/users/{userId}")
public User user(@PathVariable("userId") String userId) {
return userService.getUser(userId);
}
}
유저 추가
SpringbootRestApplication.java package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringbootRestApplication {
public static void main(String[] args) {
// SpringApplicatin.run() 메소드는 스프링 컨테이너 객체를 생성하고 반환한다.
// SpringApplicatin.run() 메소드의 매개변수로 전달된 클래스에 @SpringBootApplication 어노테이션이 지정되어 있기 때문에
// spring boot의 자동구성이 수행된다.
SpringApplication.run(SpringbootRestApplication.class, args);
}
}
UserMapper.java
package com.example.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.example.vo.User;
@Mapper
public interface UserMapper {
void insertUser(User user);
List<User> getUsers();
User getUserById(String userId);
User getUserByEmail(String email);
void updateUser(User user);
void deleteUser(String userId);
List<String> getUserRoles(String userId);
}
users.xml
insert into spring_users (user_id, user_password, user_name, user_email, user_tel) values (#{id}, #{password}, #{name}, #{email}, #{tel}) select * from spring_users select * from spring_users where user_id = #{value}<select id="getUserByEmail" parameterType="string" resultMap="UserResultMap">
select
*
from
spring_users
where
user_email = #{value}
</select>
<update id="updateUser" parameterType="User">
update
spring_users
set
user_password = #{password},
user_email = #{email},
user_tel = #{tel},
user_deleted = #{deleted},
user_updated_date = sysdate
where
user_id = #{id}
</update>
<delete id="deleteUser" parameterType="string">
delete from
spring_users
where
user_id = #{value}
</delete>
<select id="getUserRoles" parameterType="string" resultType="string">
select
user_role_name
from
spring_user_roles
where
user_id = #{value}
</select>
```
UserService.java
public User saveUser(User user) {
// id, email 중복체크 코드는 생략합니다.
userMapper.insertUser(user);
return userMapper.getUserById(user.getId());
}
public User deleteUser(String userId) {
User user = userMapper.getUserById(userId);
user.setDeleted("Y");
userMapper.updateUser(user);
return userMapper.getUserById(userId);
}
public List<String> getUserRoles(String userId) {
return userMapper.getUserRoles(userId);
}
UserController
package com.example.web.controller;
import java.util.List;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.example.service.UserService;
import com.example.vo.User;
import com.example.web.request.UserRegisterForm;
@RestController
@CrossOrigin("*")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/users")
public List<User> getUsers() {
return userService.getAllUsers();
}
@GetMapping("/users/{userId}")
public User getUser(@PathVariable("userId") String userId) {
return userService.getUser(userId);
}
@PostMapping("/users")
public User saveUser(@RequestBody UserRegisterForm userRegisterForm) {
User user = new User();
BeanUtils.copyProperties(userRegisterForm, user);
user = userService.saveUser(user);
return user;
}
@DeleteMapping("/users/{userId}")
public User deleteUser(@PathVariable("userId") String userId) {
User user = userService.deleteUser(userId);
return user;
}
@GetMapping("/users/{userId}/roles")
public List<String> getUserRoles(@PathVariable("userId") String userId ) {
return userService.getUserRoles(userId);
}
}
User.java
package com.example.vo;
import java.util.Date;
import org.apache.ibatis.type.Alias;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
// mybatis의 어노테이션
@Alias("User")
// lombok의 어노테이션
@Getter
@Setter
@ToString
@NoArgsConstructor
public class User {
private String id;
@JsonIgnore
private String password;
private String name;
private String email;
private String tel;
@JsonIgnore
private String photo;
private String deleted;
@JsonFormat(pattern = "yyyy. M. d.")
private Date createdDate;
@JsonFormat(pattern = "yyyy. M. d.")
private Date updatedDate;
}
실행결과(PostMan)
실행결과(SQL Developer)
유저 삭제
실행결과(PostMan)
실행결과(SQL Developer)
유저 권한 조회
실행결과(PostMan)
crossOrign
유저 조회
user.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"> <title>애플리케이션</title> </head> <body> <div class="container"> <div class="row mb-3"> <div class="col-12"> <h1 class="border p-2 bg-light fs-4 mt-1">사용자 관리</h1> </div> </div> <div class="row mb-3"> <div class="col-6"> <p>사용자 목록을 확인하세요</p> <div class="card"> <div class="card-header">사용자 목록</div> <div class="card-body"> <table class="table" id="table-user-list"> <thead> <tr> <th>순번</th> <th>아이디</th> <th>이름</th> <th>가입일</th> </tr> </thead> <tbody></tbody> </table> <div class="text-end"> <button class="btn btn-primary btn-sm">등록</button> </div> </div> </div> </div> <div class="col-6"> <p>사용자 상세정보를 확인하세요</p> <div class="card"> <div class="card-header">사용자 상세정보</div> <div class="card-body"> <table class="table" id="table-user-info"> <tbody> <tr> <th>아이디</th> <td>hong</td> <th>이름</th> <td>홍길동</td> </tr> <tr> <th>이메일</th> <td>hong@gmail.com</td> <th>전화번호</th> <td>010-1111-1111</td> </tr> <tr> <th>가입일</th> <td>2023-01-01</td> <th>최종수정일</th> <td>2023-01-04</td> </tr> </tbody> </table> <div class="text-end"> <button class="btn btn-danger btn-sm">삭제</button> <button class="btn btn-warning btn-sm">수정</button> </div> </div> </div> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script> <script type="text/javascript"> $(function() { let $usersTbody = $("#table-user-list tbody"); // Rest API 요청 $.getJSON("http://localhost/users", function(users) { users.forEach(function(user, index) { let row = ` <tr> <td>${index + 1}</td> <td>${user.id}</td> <td>${user.name}</td> <td>${user.createdDate}</td> </tr>`; $usersTbody.append(row); }); }); /* $.getJSON(url, data, function(response) { ... }); * url은 ajax 요청을 처리하는 요청 URI다. * data는 서버로 보내는 데이터, 생략가능하다. * function(response) { ... }는 서버로부터 성공적인 응답이 왔을 때 실행되는 함수다. * response에는 서버가 보낸 응답데이터가 들어있다. * response로 전달된 데이터는 json 형식의 텍스트를 자바스크립트 객체/배열로 변환한 것이다. * 응답데이터가 [] 구조일 때-> 자바스크립트 배열 * 응답데이터가 {} 구조일 때-> 자바스크립트 객체 * 위의 예제는 사용자 목록을 응답데이터로 받는다. 응답데이터 : jsonText -> '[{"id":"hong", "name":"김유신"}, {"id":"kang", "name":"강감찬"}]' users : 자바스크립트 배열 -> [{id:"hong", name:"김유신"}, {id:"kang", name:"강감찬"}] users.forEach(function(user, index) { ... }) * users는 배열객체다. * 배열객체는 forEach(함수) 메소드를 가지고 있다. * 배열객체의 forEach(함수) 메소드는 배열이 가지고 있는 데이터의 갯수만큼 함수를 실행시킨다. * 함수 -> function(value, index) { ... } * value에는 함수가 실행될 때 마다 배열의 0번째부터 마지막번째 데이터가 하나씩 전달된다. * index에는 함수가 실행될 때 마다 배열의 index 값이 하났기 전달된다.(0부터 시작하는 값) * user에는 {id:"hong", name:"김유신", createdDate:"2023.1.12"}가 전달된다. * 함수의 내부에서는 user.id로 아이디를, user.name으로 이름을, user.createdDate로 가입일을 조회할 수 있다. */ }); </script> </body> </html>
실행결과