跳到主要內容

Spring boot v1.5 (三) magic RESTful and some swagger

    會用Magic來形容Spring boot的RESTful,一方面是微服務的架構,另一方面則是透過Spring boot真得很容易完成,本篇開始也就會一些專案的經驗帶進來。
    簡單介紹一下REST(Representational State Transfer),具象狀態的傳輸,Wiki是這樣說明的,其實它的出現,主要是因為原本的Web Service太過複雜太謹慎了,SOAP跟XML-RPC不容易讓人一下子就了解,在這惟快不破的時代,時間就是金錢,所以REST的出現,讓原本比較難的Web Service變得簡單易懂,而且也容易上手,有些要點可以參這裡都有說明可以看看,對於微服務來說,無狀態很重要,未來架構設計也應該朝向無狀態,不然很難處理日益暴漲的量,符合REST設計風格的Web API稱為RESTful API,這就是我們常說的RESTful,比Web Service多了幾種操作方式,分別為get、post、put、delete、patch及head等(當然還有其他的),差別在這裡可以參考看看,Web Service有wsdl,那RESTful有嗎?答案就是Swagger,已經可以算是RESTful的標準了,Spring boot整合swagger也是很無縫地喔。



學習目的:建立一個REST的Controller,並包含了單元測試(Mockito),以及Swagger的設定。
學習時數:2.5 hr
教學影片:
 


pom.xml 說明
spring-boot-starter-web:配置 Web Project所需的函式庫
spring-boot-starter-test:配置 unit or mock test 所需的函式庫
spring-boot-starter-actuator:配置監控spring boot所需的函式庫

springfox-swagger2:配置swagger 2 api所需的函式庫
springfox-swagger-ui:配置swagger ui所需的函式庫
程式碼說明
  • Example3.java

package louisz.springboot.example3;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Louisz Spring boot introduce ex.3
 *
 */

@SpringBootApplication
public class Example3 {
 /**
  * 程式執行起點
  * 
  * @param args
  */
 public static void main(String[] args) {
  SpringApplication.run(Example3.class, args);

 }

}

  • Ex3Controller.java

package louisz.springboot.example3;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/user") // 上層路徑配置
public class Ex3Controller {
 //單一儲存庫
 static Map users = Collections.synchronizedMap(new HashMap());

 /**
  * 取得所有User清單
  * 
  * @return
  */
 @RequestMapping(value = "/all", method = RequestMethod.POST)
 public List getUserList() {
  // 取得所有User List
  return new ArrayList(users.values());
 }

 /**
  * 新增User資料,傳入User JSON資料進行新增,成功回傳success
  * 
  * @param user
  * @return
  */
 @RequestMapping(value = "/add", method = RequestMethod.POST)
 public String addUser(@ModelAttribute User user) {
  // 傳入Input JSON會自動轉換為User物件
  users.put(user.getId(), user);
  return "add success";
 }

 /**
  * 傳入id進行查詢,回傳User JSON
  * 
  * @param id
  * @return
  */
 @RequestMapping(value = "/find/{id}", method = RequestMethod.POST)
 public User findUser(@PathVariable Long id) {
  // Output User會自動轉換為JSON物件
  return users.get(id);
 }

 /**
  * 傳入id及JSON User進行資料更新
  * 
  * @param id
  * @param user
  * @return
  */
 @RequestMapping(value = "/update/{id}", method = RequestMethod.POST)
 public String updateUser(@PathVariable Long id, @ModelAttribute User user) {
  // 更新User資料
  User u = users.get(id);
//  System.out.println(u.getName()+"=>"+user.getMobileNumber());
  // 使用了BeanUtils
  BeanUtils.copyProperties(user, u);
  users.put(id, u);
  return "update success";
 }

 /**
  * 傳入id進行資料刪除,回傳success
  * 
  * @param id
  * @return
  */
 @RequestMapping(value = "/remove/{id}", method = RequestMethod.DELETE)
 public String removeUser(@PathVariable Long id) {
  //刪除user資料
  users.remove(id);
  return "delete success";
 }

}


}

  • User.java
package louisz.springboot.example3;

public class User {
 private Long id;
 private String name;
 private String email;
 private String mobileNumber;
 public Long getId() {
  return id;
 }
 public void setId(Long id) {
  this.id = id;
 }
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public String getEmail() {
  return email;
 }
 public void setEmail(String email) {
  this.email = email;
 }
 public String getMobileNumber() {
  return mobileNumber;
 }
 public void setMobileNumber(String mobileNumber) {
  this.mobileNumber = mobileNumber;
 }

}
  • SwaggerConfiguration.java
package louisz.springboot.example3;

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

import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import static springfox.documentation.builders.PathSelectors.regex;

@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
 @Bean
 public Docket usersApi() {
  return new Docket(DocumentationType.SWAGGER_2).select()
    .apis(RequestHandlerSelectors.basePackage("louisz.springboot.example3")).paths(regex("/user.*"))
    .build();

 }
}


  • TestEx3Controller.java(單元測試,請注意註解部分說明)
package louisz.springboot.example3;

//這邊要注意import的方式採用static
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

//spring boot 1.4以後的方式
@RunWith(SpringRunner.class)
@WebMvcTest(Ex3Controller.class)
public class TestEx3Controller {
 @Autowired
 private MockMvc mvc;

 /**
  * 單元測試測試前前置設定
  * 
  * @throws Exception
  */
 @Before
 public void setUp() throws Exception {
  mvc = MockMvcBuilders.standaloneSetup(new Ex3Controller()).build();
 }

 /**
  * 測試範例三的RestController各項method
  * 
  * @throws Exception
  */
 @Test
 public void testEx3Controller() throws Exception {
  /*
   * 撰寫單元測試的時候,先想清楚要測試的流程,例如本範例為測試新增修改刪除功能,
   * 你可以一個一個method測試,你也可以使用先新增一筆,然後進行修改,再查詢後刪 除等,把單元測試想做一個情境會比較好進行撰寫。
   */

  RequestBuilder request = null;
  // 我這裡設定使用post進行查詢
  // 1.測試是否回覆200的訊息,並且確認是否尚無資料
  request = post("/users/all/");
  mvc.perform(request).andExpect(status().isOk()).andExpect(content().string(equalTo("[]")));
  // 2.新增一筆資料,新增成功後須回覆success的資訊
  request = post("/users/add/").param("id", "1").param("name", "Louisz").param("email", "louisz6ster@gmail.com")
    .param("mobileNumber", "0912345678");
  mvc.perform(request).andExpect(content().string(equalTo("success")));
  // 3.再進行查詢,因為只有一筆不用特別指定,確認回覆的資訊是否與預期相同
  request = post("/users/all/");
  mvc.perform(request).andExpect(status().isOk()).andExpect(content().string(equalTo(
    "[{\"id\":1,\"name\":\"Louisz\",\"email\":\"louisz6ster@gmail.com\",\"mobileNumber\":\"0912345678\"}]")));
  // 4.修改原有資訊資料,修改成功後須回覆success的資訊
  request = post("/users/update/1").param("name", "Jessie").param("email", "louisz6ster@gmail.com")
    .param("mobileNumber", "0923456789");
  mvc.perform(request).andExpect(content().string(equalTo("success")));
  // 5.使用指定查詢進行該資料比對,確認修改的資訊是否正確
  request = post("/users/find/1");
  mvc.perform(request).andExpect(content().string(equalTo(
    "{\"id\":1,\"name\":\"Jessie\",\"email\":\"louisz6ster@gmail.com\",\"mobileNumber\":\"0923456789\"}")));

  // 6.刪除指定的資料,刪除成功後須回覆success
  request = delete("/users/remove/1");
  mvc.perform(request).andExpect(content().string(equalTo("success")));

  // 7.最後再查詢一次全部資料,須回覆空值,表示單元測試完成
  request = post("/users/all/");
  mvc.perform(request).andExpect(status().isOk()).andExpect(content().string(equalTo("[]")));
 }
}

先進行單元測試,然後執行程式使用 POSTMAN 測試
測試網址
http://localhost:8090/user
Swagger-ui網址
http://localhost:8090/swagger-ui.html
Swagger-api網址
http://localhost:8090/v2/api-docs

Github code(louisz.springboot.example3)

swagger待續....

參考連結:
https://springframework.guru/spring-boot-restful-api-documentation-with-swagger-2/
http://blog.didispace.com/springbootswagger2/

https://zh.wikipedia.org/wiki/REST
https://data-sci.info/2015/10/24/%E5%B8%B8%E8%A6%8B%E7%9A%84http-method%E7%9A%84%E4%B8%8D%E5%90%8C%E6%80%A7%E8%B3%AA%E5%88%86%E6%9E%90%EF%BC%9Agetpost%E5%92%8C%E5%85%B6%E4%BB%964%E7%A8%AEmethod%E7%9A%84%E5%B7%AE%E5%88%A5/

留言

這個網誌中的熱門文章

Pentaho kettle取指定目錄下的所有檔案

最近開始玩 BI的東西,而之前專案有使用到Pentaho Open Source這個好物.... Pentaho裡面很多東西,跟 Jasperreport一樣東西很多,我最先接觸到的是kettle ETL的工具,玩了一陣子,開始有空就把它寫下來,以免忘記。 Scan一個目錄下所有檔案,然後塞進去資料庫 1.先拉兩個 Input,一個Get File Names,一個是CSV file input,再拉一個 output 中的 Table output,然後把他連起來。 2.點開 Get File Names,File or directory設定你的指定目錄,Regular Expression則是輸入.*\.*$則是所有檔案,若是CSV則可.*\.torrent$這可以了,可以按一下 Preview rows看看是否正確。

Spring boot v1.5 (六) spring data jpa 基本操作

最近天氣好熱,做甚麼事都覺得很懶,想要寫個spring data jpa也是懶懶的,不過這部分卻也是滿重要的一部分,前一篇介紹 JDBCTemplate ,已經覺得跟以前寫SQL方式有所差異了,JPA帶來的是物件導向的設計面思考,說到JPA不得不提提 ORM ,Object-relational mapping主要想法為簡化及物件導向的設計,讓RDB更貼近Object,在設計上可以更加便利,甚至透過一些設計可以讓Table具有物件導向的特性如繼承等等,以往要使用ORM的框架,都會先以 Hibernate 進行,不過近來慢慢地轉向JPA,主要還是在減少程式碼、增加彈性等等,大體的功能沒有差異很大,所以從Hibernate轉到JPA問題不大,JPA要介紹的東西還滿多的,所以我這裡會再分成三個章節來介紹。 SPRING DATA JPA基本操作 JPQL & Named SQL & Native SQL Cache & DB Design Pattern SPRING DATA JPA更加簡化的程式撰寫,只需要一個 Interface內寫一些查詢 method就可以操作JPA,因為利用 method 組合查詢條件,確實很方便也很容易理解,若是都沒有辦法符合需求當然也可以自己實作一個來用當然沒有問題。 學習目的 :SPRING DATA JPA基本操作。 學習時數 :3.5hr 教學影片: pom.xml 說明 spring-boot-starter-web:配置 Web Project所需的函式庫。 spring-boot-starter-test:配置 unit or mock test 所需的函式庫。 spring-boot-starter-actuator:配置監控spring boot所需的函式庫,後續spring cloud會使用到,所以一開就導入。 spring-boot-starter-jdbc:配置使用jdbc所需的函式庫。 postgresql:配置postgresql連接Driver所需的函式庫。 jasypt-spring-boot-starter:加解密所需的函式庫。 spring-boot-starter-data-jpa:配置Spring data jpa所需的函式庫。

IReport字型下拉選單中文亂碼

這個問題其實也不是很大啦,不過當你有很多的中文字型檔的時候可能就不知道要選哪一個,啟動IReport後,開啟報表後會發現左邊下拉選單中,最下面的字型清單中有出現方框,顯示不出該字型的名稱,這幾個字型應該是判斷新細明體,標楷體及細明體,如下圖 下載IReport的Source Code來檢查一下,it.businesslogic.ireport.gui.MainFrame發現這個JComboBox有特別設定Arial字型,當然只要是中文的都顯示不出來ㄚ,所以點掉這一行後重新編譯,嘿嘿就可以了。 jComboBoxFont.setFont(new java.awt.Font("Arial", 0, 11)); 我目前使用的版本為 IReport-3.0.0-src