Spring Boot2 實戰系列之RESTful Web Service

前言

關於 REST 概念的闡述,是 Roy Fielding 大神在他的 博士論文 中提出的,有興趣的小夥伴可以去看一下,Restful web service是指遵守了 REST 風格的web服務, 可以看下阮一峯老師的 RESTful API 最佳實踐。但要記住的是 REST 是一種設計風格,它背後的理念是使用 HTTP 動詞 GET,POST, PUT, DELETE 來對應服務的 CURD 操作,並且使用 JSON 來請求數據和接收數據。

在設計符合 REST 理念的服務接口時,可以參考以下指導方針:

  • 使用 HTTP 動詞(GET, POST, PUT, DELETE)圍繞服務展開操作
  • 使用 URI 來傳達意圖
  • 請求和響應使用 JSON
  • 使用 HTTP 狀態碼來傳達結果

下面使用一個對員工進行增刪改查的例子來實踐 RESTful 設計。

創建項目

項目結構圖如下:
在這裏插入圖片描述
pom 依賴文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.5.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>top.yekongle</groupId>
	<artifactId>springboot-restful-sample</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>springboot-restful-sample</name>
	<description>RESTful project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>

		<!--In-Memory Database-->
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

代碼編寫

Employee.java, 員工實體類

package com.yekongle.rest.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import lombok.Data;

/**
 * @Description: 持久化實體類
 * @Data: lombok 註解,自動生成 getter, setter方法,重寫equals,hash,toString方法
 * @Entity: 表明該類是持久化實體類(映射到對應table)
 * @Id: 指定 table id
 * @GeneratedValue: 默認採用自增長策略
 * @Author: Yekongle
 * @Date: Apr 7, 2020
 */
@Data
@Entity
public class Employee {
	@Id
	@GeneratedValue
	private Long id;

	private String name;
	private String role;

	public Employee() {
	}

	public Employee(String name, String role) {
		this.name = name;
		this.role = role;
	}
}

EmployeeNotFoundException.java, 自定義 Exception,找不到員工時拋出該 Exception

package top.yekongle.restful.exception;

/**
 * @Description: 自定義employee exception
 * @Author: Yekongle
 * @Date: Apr 7, 2020
 */
public class EmployeeNotFoundException extends RuntimeException {
	private static final long serialVersionUID = 1L;

	public EmployeeNotFoundException(Long id) {
		super("Could not find employee " + id);
	}
}

EmployeeRepository.java, 員工數據操作接口

package top.yekongle.restful.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import top.yekongle.restful.entity.Employee;


/** 
* @Description: 數據操作接口,繼承JpaRepository
* @Author: Yekongle 
* @Date: Apr 7, 2020
*/
public interface EmployeeRepository extends JpaRepository<Employee, Long>{

}

GlobalExceptonConfig.java, 全局 Exception 配置

package top.yekongle.restful.config;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import top.yekongle.restful.exception.EmployeeNotFoundException;

/**
 * @Description: 全局異常配置
 * @Author: Yekongle
 * @Date: Apr 7, 2020
 */
@ControllerAdvice
public class GlobalExceptonConfig {
	
	/**
	 * @ResponseBody 方法返回結果直接寫入到 http reponse中
	 * @ExceptionHandler 捕捉指定的exception
	 * @ResponseStatus 指定response的http status:2xx/4xx/5xx
	 * */
	@ResponseBody
	@ExceptionHandler(EmployeeNotFoundException.class)
	@ResponseStatus(HttpStatus.NOT_FOUND)
	public String employeeNotFoundHandler(EmployeeNotFoundException ex) {
		return ex.getMessage();
	}
	
	@ResponseBody
	@ExceptionHandler(Exception.class)
	@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
	public String systemErrorHandler(Exception ex) {
		return ex.getMessage();
	}
}

DatabaseConfig.java, 數據庫配置,插入兩條初始化數據

package top.yekongle.restful.config;

import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


import lombok.extern.slf4j.Slf4j;
import top.yekongle.restful.entity.Employee;
import top.yekongle.restful.repository.EmployeeRepository;

/** 
* @Description: 數據庫配置
* @Configuration: 表明這個是配置類
* @Sl4j: lombok註解,自動生成Logger
* @Author: Yekongle 
* @Date: Apr 7, 2020
*/

@Configuration
@Slf4j
public class DatabaseConfig {

	/**
	 * 應用上文下加載完後SpringBoot會執行所有註冊到Spring中的CommandLineRunner
	 * */
	@Bean
	public CommandLineRunner initDatabase(EmployeeRepository repository) {
		// 執行CommandLineRunner的回調函數, 往數據庫插入初始數據
		return args -> {
		     log.info("Preloading " + repository.save(new Employee("張三", "初級程序員")));
		     log.info("Preloading " + repository.save(new Employee("李四", "高級程序員")));
		};
	}
}

EmployeeController.java,表現層,處理請求 API

package top.yekongle.restful.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import top.yekongle.restful.entity.Employee;
import top.yekongle.restful.exception.EmployeeNotFoundException;
import top.yekongle.restful.repository.EmployeeRepository;


/**
 * @Description: 員工controller類, 
 * @Author: Yekongle
 * @Date: Apr 7, 2020
 */

// @RestController 註解會使每個方法返回值直接寫入到response body中,而不是去渲染模板
@RestController
public class EmployeeController {
	@Autowired
	EmployeeRepository repository;

    /**
     * 查找所有員工
	 */
	@GetMapping("/employees")
	public ResponseEntity<?> all() {
		List<Employee> employeeList = repository.findAll();
		return ResponseEntity.ok(employeeList);
	}
	
    /**
     * 新建員工
	 */
	@PostMapping("/employees")
	public ResponseEntity<?> newEmployee(@RequestBody Employee newEmployee) {
		repository.save(newEmployee);
		return ResponseEntity.status(HttpStatus.CREATED).body(newEmployee);
	}
	
    /**
     * 根據id查找員工
     * @PathVariable 將參數與請求路徑模板綁定起來,{id}的值就是參數id的值
	 */
	@GetMapping("/employees/{id}")
	public ResponseEntity<?> one(@PathVariable Long id) {
		// 根據id查找員工,找不到拋出一個自定義 exception
		Employee employee = repository.findById(id).orElseThrow(() -> new EmployeeNotFoundException(id));
		return ResponseEntity.ok(employee);
	}

	/**
     * 更新員工信息
     * @PathVariable 將參數與請求路徑模板綁定起來,{id}的值就是參數id的值
	 */
	@PutMapping("/employees/{id}")
	public ResponseEntity<?> replaceEmployee(@RequestBody Employee newEmployee, @PathVariable Long id) {
		// 根據id查找員工,找到就update信息再保存,找不到就直接新建一個員工
		repository.findById(id)
			.map(employee -> {
				employee.setName(newEmployee.getName());
				employee.setRole(newEmployee.getRole());
				return repository.save(employee);
			})
			.orElseGet(() -> {
				newEmployee.setId(id);
				return repository.save(newEmployee);
			});
		
		return ResponseEntity.noContent().build();
	}
	
	/**
     * 刪除員工
     * @PathVariable 將參數與請求路徑模板綁定起來,{id}的值就是參數id的值
	 */
	@DeleteMapping("/employees/{id}")
	public ResponseEntity<?> deleteEmployee(@PathVariable Long id) {
		repository.deleteById(id);
		return ResponseEntity.noContent().build();
	}
}

運行測試

啓動項目,查看控制檯日誌可看到兩條初始化數據插入Database
在這裏插入圖片描述

使用 Postman 對接口進行測試

查詢所有員工http://localhost:8080/employees
在這裏插入圖片描述

查詢某個員工http://localhost:8080/employees/1
在這裏插入圖片描述
查詢某個不存在的員工http://localhost:8080/employees/3
在這裏插入圖片描述

將 id 爲 1 的員工名字改爲趙六http://localhost:8080/employees/1
在這裏插入圖片描述

新增員工http://localhost:8080/employees
在這裏插入圖片描述
再查看所有員工,可見新增了王五的記錄
在這裏插入圖片描述

刪除員工http://localhost:8080/employees/3
在這裏插入圖片描述

項目已上傳至 Github: https://github.com/yekongle/springboot-code-samples/tree/master/springboot-restful-sample , 希望對小夥伴們有幫助哦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章