前言
Spring Boot
项目中的代码该如何进行有效组织?本文以Bookstore
项目为例,进行一个简易的CRUD系统开发。
目录
- Hellowrold及基本概念
- 代码组织及CRUD
建模
由于是一个简易的书店系统,建模如下:
系统中主要存在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
新建service
、service.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后台就开发完成了。完整目录结构如图:
效果
创建一个Category
资源,并添加几个对应的Product
。
以GET
方式访问/categories/{category_id}
,即可看到该类别下的所有商品了。