浅出Spring Boot系列(二)代码组织及CRUD

前言

Spring Boot项目中的代码该如何进行有效组织?本文以Bookstore项目为例,进行一个简易的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

新建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后台就开发完成了。完整目录结构如图:


目录结构

效果

创建一个Category资源,并添加几个对应的Product

添加商品

GET方式访问/categories/{category_id},即可看到该类别下的所有商品了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,248评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,681评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,443评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,475评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,458评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,185评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,451评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,112评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,609评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,083评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,163评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,803评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,357评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,357评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,590评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,636评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,925评论 2 344

推荐阅读更多精彩内容