淺出Spring Boot系列(二)程式碼組織及CRUD

weixin_34087301發表於2017-03-25

前言

Spring Boot專案中的程式碼該如何進行有效組織?本文以Bookstore專案為例,進行一個簡易的CRUD系統開發。

目錄

建模

由於是一個簡易的書店系統,建模如下:

4226917-c32a8083f6130104.png
建模

系統中主要存在4個物件,即使用者、訂單、商品、種類。一個使用者對應0個或多個訂單,每個訂單至少包含一件商品。且每個商品都屬於某個種類。

Model

新建model package,並在其中建立上圖中的4個類,另外額外多一個OrderProduct類,該類繼承自Product,增加一個quantity屬性。

User為例:

package com.william.model;

import lombok.Data;
import org.springframework.data.annotation.Id;

import java.util.List;


/**
 * Created by william on 17/3/23.
 */
@Data
public class User {

    @Id
    private String id;
    private String username;
    private String password;
    private String salt;
    private String photo;
    private List<String> roles;
}

一般POJO中每個屬性會建立額外的Getter Setter方法,這裡通過lombok包,引入@Data註解,省略了手動寫這些方法,專案編譯時lombok自動地為我們生成對應方法。

使用時只需在build.gradle檔案中新增對lombok的依賴即可:

compile("org.projectlombok:lombok")

Repository

新建repository package,由於資料我們這裡選用的是mongodb,所以首先引入mongo的依賴

compile("org.springframework.boot:spring-boot-starter-data-mongodb")

這裡需要注意的是,我們選擇建立上述POJO對應的repository 的Interface,而不是Class。這裡以ProductRepositoy為例:

package com.william.repository;

import com.william.model.Product;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.query.Param;

import java.util.List;

/**
 * Created by william on 17/3/24.
 */
public interface ProductRepository extends MongoRepository<Product,String> {

    List<Product> findByCategoryId(@Param("categoryId") String categoryId);
}

這裡我們選擇繼承MongoRepository,且模版列表中第一個引數為POJO的型別,第二個引數為主鍵的型別

為什麼我們在這裡只寫介面而不做實現呢?歸功於Spring強大的依賴注入能力,當專案執行時,Spring會自動為我們注入該介面的實現。如果有使用過Mybatis,它的Mapper實際上也是類似的。

注意到上述還包含一個findByCategoryId的方法,這個也是不需要實現的。

The goal of Spring Data repository abstraction is to significantly reduce the amount of boilerplate code required to implement data access layers for various persistence stores.

由於遵循約定大於配置Spring會自動根據方法名轉換成對應SQL語句。
更多的query method可以檢視官方文件

Service

新建serviceservice.impl package,前者放Interface檔案,後者為對應的實現。由於專案不包含過多的業務邏輯,所以這一層會顯得略有些淡薄,基本只需要呼叫repostory中對應方法即可。
CategoryService的實現為例:

package com.william.service.impl;

import com.william.model.Category;
import com.william.repository.CategoryRepository;
import com.william.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * Created by william on 17/3/25.
 */
@Service
public class MongoCategoryServiceImpl implements CategoryService {

    @Autowired
    private CategoryRepository repository;
    @Override
    public Category create(Category category) {
        return repository.insert(category);
    }

    @Override
    public Category show(String id) {
        return repository.findOne(id);
    }

    @Override
    public Category update(Category category) {
        return repository.save(category);
    }

    @Override
    public List<Category> findAll() {
        Sort sort = new Sort(Sort.Direction.ASC,"order");
        return repository.findAll(sort);
    }

    @Override
    public Category destroy(String id) {
        Category category = repository.findOne(id);
        repository.delete(id);
        return category;
    }
}

實現需要新增@Service的Annotation

Controller

新建controller package,我們依然以資源作為分類標準,建立對應controller。以CategoryController為例:

package com.william.controller;

import com.william.model.Category;
import com.william.model.Product;
import com.william.service.CategoryService;
import com.william.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * Created by william on 17/3/24.
 */
@RestController
@RequestMapping("/categories")
public class CategoryController {

    @Autowired
    private ProductService productService;
    @Autowired
    private CategoryService service;

    @RequestMapping(method = RequestMethod.POST)
    public Category create(@RequestBody Category category)
    {
        return service.create(category);
    }

    @RequestMapping(method = RequestMethod.GET)
    public List<Category> getAllCategories()
    {
        return service.findAll();
    }

    @RequestMapping(value = "/{id}",method = RequestMethod.GET)
    public Category show(@PathVariable String id)
    {
        return service.show(id);
    }

    @RequestMapping(value = "/{id}",method = RequestMethod.PUT)
    public Category update(@PathVariable String id, @RequestBody Category category)
    {
        category.setId(id);
        return service.create(category);
    }

    @RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
    public Category destroy(@PathVariable String id)
    {
        return service.destroy(id);
    }

    @RequestMapping("/{id}/products")
    public List<Product> findAllProducts(@PathVariable String id)
    {
        return productService.findAll(service.show(id));
    }

}

@RestController用於標記這是一個基於Restful API的controller,response將通過response body傳送。

@RequestMapping用於對映對應URL,並且可顯性指定請求的方法。

關於Restful API的設計可以參考阮一峰老師的部落格

至此一個經典的分層架構的API後臺就開發完成了。完整目錄結構如圖:


4226917-49bb7c43e78387d3.png
目錄結構

效果

建立一個Category資源,並新增幾個對應的Product

4226917-7554dc387a4874fd.png
新增商品

GET方式訪問/categories/{category_id},即可看到該類別下的所有商品了。

4226917-c55774ecf1f37c4c.png

相關文章