Spring Boot 學習筆記(1.3):RESTful by Spring Boot with MySQL

JacMatamoro 8年前發布 | 24K 次閱讀 REST MySQL Spring JEE框架 Spring Boot

現在的潮流是前端承擔越來越多的責任:MVC中的V和C,后端只需要負責提供數據M,但是后端有更重要的任務:高并發、提供各個維度的擴展能力(負載均衡、數據表切分、服務分離)、更清晰的API設計。Spring Boot框架提供的機制便于工程師實現標準的RESTful接口,本文主要討論如何編寫Controller代碼,另外還涉及了MySQL的數據庫操作, 之前我也寫過一篇關于Mysql的文章 ,但是這篇文章加上了CRUD的操作。

先回顧下之前的文章中我們用到的例子:圖書信息管理系統,主要的領域對象有book、author、publisher和reviewer。

首先我們要在pom文件中添加對應的starter,即 spring-boot-starter-web ,對應的xml代碼示例為:

org.springframework.bootspring-boot-starter-web

然后我們要創建控制器(Controller),先在項目根目錄下創建controller包,一般為每個實體類對象創建一個控制器,例如BookController。

@RestController注解是@Controller和@ResponseBody的合集,表示這是個控制器bean,并且是將函數的返回值直接填入HTTP響應體中,是REST風格的控制器。@RequestMapping(“/books”)表示該控制器處理所有“/books”的URL請求,具體由那個函數處理,要根據HTTP的方法來區分:GET表示查詢、POST表示提交、PUT表示更新、DELETE表示刪除。

  • 查詢所有圖書記錄:利用@Autowired導入BookRepository的Bean,直接調用bookRepository.findAllBooks()即可。我們的返回值形式如下。關于RESTful返回值形式的設計,后續會有專門的文章討論。
{
    "message": "get all books",
    "book": [
        {
            "isbn": "9781-1234-5678",
            "title": "你愁啥",
            "description": "這是一本奇怪的書",
            "author": {
                "firstName": "馮",
                "lastName": "pp"
            },
            "publisher": {
                "name": "大錘出版社"
            },
            "reviewers": []
        },
        {
            "isbn": "9781-1234-1111",
            "title": "別吵吵",
            "description": "哈哈哈",
            "author": {
                "firstName": "杜琪",
                "lastName": "琪"
            },
            "publisher": {
                "name": "大錘出版社"
            },
            "reviewers": []
        }
    ]
}
  • 根據isbn查詢圖書記錄:根據isbn查詢一本書的記錄,調用bookRepository.findBookByIsbn()即可。返回值形式如下:
{
    "message": "get book with isbn(9781-1234-5678)",
    "book": {
        "isbn": "9781-1234-5678",
        "title": "你愁啥",
        "description": "這是一本奇怪的書",
        "author": {
            "firstName": "馮",
            "lastName": "pp"
        },
        "publisher": {
            "name": "大錘出版社"
        },
        "reviewers": []
    }
}
  • 添加圖書記錄,客戶端的圖書信息封裝成json字符串傳遞過來,因此利用@RequestBody獲取POST請求體,由于book記錄中有外鏈記錄,因此要首先解析出author對象和publisher對象,并將它們存入數據庫;然后才生成book對象,并調用bookRepository.save(book)將book記錄存入數據庫。該接口的返回值會把剛添加的圖書信息返回給客戶端,形式類似于getBookByIsbn這個接口。
  • 更新圖書書名,這里簡單以這個接口作為更新的例子。主要步驟是先取出對應isbn的book對象,然后 book.setTitle(title) 更新book信息,然后調用bookRepository.save(book)更新該對象的信息,通過@PathVariable修飾的參數title與URL中用“{title}”的值對應。
  • 刪除圖書記錄;給定圖書的isbn直接刪除即可。

最后,放上完整的Controller代碼:

package com.test.bookpub.controller;

import com.alibaba.fastjson.JSONObject;
import com.test.bookpub.domain.Author;
import com.test.bookpub.domain.Book;
import com.test.bookpub.domain.Publisher;
import com.test.bookpub.repository.AuthorRepository;
import com.test.bookpub.repository.BookRepository;
import com.test.bookpub.repository.PublisherRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.*;

/**
 * @author duqi
 * @create 2015-12-02 18:18
 */
@RestController
@RequestMapping("/books")
public class BookController {
    private static final Logger logger = LoggerFactory.getLogger(BookController.class);

    @Autowired
    private BookRepository bookRepository;
    @Autowired
    public AuthorRepository authorRepository;
    @Autowired
    public PublisherRepository publisherRepository;

    @RequestMapping(method = RequestMethod.GET)
    public Iterable getAllBooks() {
        return bookRepository.findAll();
    }

    @RequestMapping(value = "/{isbn}", method = RequestMethod.GET)
    public Map getBook(@PathVariable String isbn) {
        Book book =  bookRepository.findBookByIsbn(isbn);

        Map response = new LinkedHashMap();
        response.put("message", "get book with isbn(" + isbn +")");
        response.put("book", book);
        return response;
    }

    @RequestMapping(method = RequestMethod.POST)
    public Map addBook(@RequestBody JSONObject bookJson) {
        JSONObject authorJson = bookJson.getJSONObject("author");
        Author author = new Author(authorJson.getString("firstName"), authorJson.getString("lastName"));
        authorRepository.save(author);
        String isbn = bookJson.getString("isbn");
        JSONObject publisherJson = bookJson.getJSONObject("publisher");
        Publisher publisher = new Publisher(publisherJson.getString("name"));
        publisherRepository.save(publisher);
        String title = bookJson.getString("title");
        String desc = bookJson.getString("desc");
        Book book = new Book(author, isbn, publisher, title);
        book.setDescription(desc);
        bookRepository.save(book);

        Map response = new LinkedHashMap();
        response.put("message", "book add successfully");
        response.put("book", book);
        return response;
    }

    @RequestMapping(value = "/{isbn}", method = RequestMethod.DELETE)
    public Map deleteBook(@PathVariable String isbn) {
        Map response = new LinkedHashMap();
        try {
            bookRepository.deleteBookByIsbn(isbn);
        } catch (NullPointerException e) {
            logger.error("the book is not in database");
            response.put("message", "delete failure");
            response.put("code", 0);
        }

        response.put("message", "delete successfully");
        response.put("code", 1);
        return response;
    }

    @RequestMapping(value = "/{isbn}/{title}", method = RequestMethod.PUT)
    public Map updateBookTitle(@PathVariable String isbn, @PathVariable String title) {
        Map response = new LinkedHashMap();
        Book book = null;
        try {
            book = bookRepository.findBookByIsbn(isbn);
            book.setTitle(title);
            bookRepository.save(book);
        } catch (NullPointerException e) {
            response.put("message", "can not find the book");
            return response;
        }

        response.put("message", "book update successfully");
        response.put("book", book);
        return response;
    }
}

有三個問題需要補充探討

現在我要說下Controller的角色,大家可以看到,我這里將很多業務代碼混淆在Controller的代碼中。實際上,根據 程序員必知之前端演進史 一文所述Controller層應該做的事是: 處理請求的參數 渲染和重定向 選擇Model和Service 處理Session和Cookies,我基本上認同這個觀點,最多再加上OAuth驗證(利用攔截器實現即可)。而真正的業務邏輯應該單獨分處一層來處理,即常見的service層;

今天遇到一個類似參考資料2中的錯誤,我經過查找后發現是Jakson解析我的對象的時候出現了無限遞歸解析,究其原因,是因為外鏈:解析book的時候,需要解析author,但是在author中又有books選項,所以造成死循環,解決的辦法就是在author中的books屬性上加上注解:@JsonBackReference;同樣需要在Publisher類中的books屬性加上@JsonBackReference注解。

上述演示的Controller代碼還有兩個問題:返回值形式不統一;并沒有遵循標準的API設計(例如update方法實際上應該由客戶端返回更新過的完整對象,這樣就可以直接調用save方法),后續,我會參考 RESTful API 設計指南 進行學習,對API的設計進行自己的學習總結,讀者朋友,你也需要自己實踐和學習哦,有問題的可以找我討論。

參考資料

  1. repository中的update方法
  2. 使用spring data創建REST應用
  3. 遇到的一個錯誤:at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize
  4. SPRING BOOT: DATA ACCESS WITH JPA, HIBERNATE AND MYSQL

來自: http://blog.jobbole.com/97499/

 本文由用戶 JacMatamoro 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!