前言
Spring Boot項(xiàng)目中的代碼該如何進(jìn)行有效組織?本文以Bookstore項(xiàng)目為例,進(jìn)行一個簡易的CRUD系統(tǒng)開發(fā)。
目錄
- Hellowrold及基本概念
- 代碼組織及CRUD
建模
由于是一個簡易的書店系統(tǒng),建模如下:

系統(tǒng)中主要存在4個對象,即用戶、訂單、商品、種類。一個用戶對應(yīng)0個或多個訂單,每個訂單至少包含一件商品。且每個商品都屬于某個種類。
Model
新建model package,并在其中創(chuàng)建上圖中的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中每個屬性會創(chuàng)建額外的Getter Setter方法,這里通過lombok包,引入@Data注解,省略了手動寫這些方法,項(xiàng)目編譯時lombok自動地為我們生成對應(yīng)方法。
使用時只需在build.gradle文件中添加對lombok的依賴即可:
compile("org.projectlombok:lombok")
Repository
新建repository package,由于數(shù)據(jù)我們這里選用的是mongodb,所以首先引入mongo的依賴
compile("org.springframework.boot:spring-boot-starter-data-mongodb")
這里需要注意的是,我們選擇建立上述POJO對應(yīng)的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,且模版列表中第一個參數(shù)為POJO的類型,第二個參數(shù)為主鍵的類型
為什么我們在這里只寫接口而不做實(shí)現(xiàn)呢?歸功于Spring強(qiáng)大的依賴注入能力,當(dāng)項(xiàng)目運(yùn)行時,Spring會自動為我們注入該接口的實(shí)現(xiàn)。如果有使用過Mybatis,它的Mapper實(shí)際上也是類似的。
注意到上述還包含一個findByCategoryId的方法,這個也是不需要實(shí)現(xiàn)的。
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會自動根據(jù)方法名轉(zhuǎn)換成對應(yīng)SQL語句。
更多的query method可以查看官方文檔
Service
新建service、service.impl package,前者放Interface文件,后者為對應(yīng)的實(shí)現(xiàn)。由于項(xiàng)目不包含過多的業(yè)務(wù)邏輯,所以這一層會顯得略有些淡薄,基本只需要調(diào)用repostory中對應(yīng)方法即可。
以CategoryService的實(shí)現(xiàn)為例:
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;
}
}
實(shí)現(xiàn)需要添加@Service的Annotation
Controller
新建controller package,我們依然以資源作為分類標(biāo)準(zhǔn),創(chuàng)建對應(yīng)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用于標(biāo)記這是一個基于Restful API的controller,response將通過response body發(fā)送。
@RequestMapping用于映射對應(yīng)URL,并且可顯性指定請求的方法。
關(guān)于Restful API的設(shè)計(jì)可以參考阮一峰老師的博客
至此一個經(jīng)典的分層架構(gòu)的API后臺就開發(fā)完成了。完整目錄結(jié)構(gòu)如圖:

效果
創(chuàng)建一個Category資源,并添加幾個對應(yīng)的Product。

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