Day 96

ChangWoo·2023년 1월 18일
0

중앙 HTA

목록 보기
40/51
post-thumbnail

Spring Boot

  • 프로젝트마다 DAO가 달라질 수 있다.
  • Spring boot로 다양한 DB엑세스와 연동이 가능해진다.
  • 뷰가 JSP가 Thmeleaf 템플릿 엔진인 경우도 있다.

  • REST API 어플리케이션 방식으로도 사용할 수 있다.
  • Controller에 @RestController 어노테이션을 붙여줘야 한다.

  • spring boot REST API 애플리케이션은 데이터를 제공할 뿐, 프론트엔드 쪽 개발을 하지 않는 것.

REST (REpresentational State Transfer)

  • 자원을 이름으로 구분하여 해당 자원의 상태(정보)를 주고 받는 것이다.

  • 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로 주고받는 것이 일반적이다.

REST API(Application Programming Interface)

  • 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

  • REST API 설계 예시

요청 내용 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);
	}
}
  • @RestController는 JSON만 보낼 수 있다. (그래서 @ResponseBody를 붙이지 않아도 된다.)

유저 추가

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>

실행결과

profile
한 걸음 한 걸음 나아가는 개발자

0개의 댓글