入坑Java用Spring boot制作一个Restful后端简记

发布于 2016-07-07  6.55k 次阅读


Spring是一个相当不错的Java框架,完善简洁美好。简单记录一下一个使用了Mysql数据库的Hello World项目搭建流程和我的思考。思考未必完备,参见官方文档:http://spring.io/guides/gs/rest-service/

我们的结果:

我们向以下链接发送HTTP GET请求:

http://localhost:8081

将会得到这样的文本:

Hello, Cheney !

如果向以下链接发送HTTP GETPOST请求,带有一个id参数,内容为一个正整数

http://localhost:8081/verify?id=2

如果能在数据库中找到对应项,将会得到这样的json数据

{"id":0,"message":"Today we eat Potato"}

如果不能在数据库中找到对应项,将会得到如下:

{"id":1,"message":"ID Not Found"}

如果发送的请求中,id不存在或者不是正整数则会得到如下json数据

{"id":2,"message":"ID Illegal"}

一、新建项目

打开start.spring.io

填写包路径,一般为域名倒叙。

Dependences 选择 "Web"、"Rest Repositories"、"MySQL"、"JPA":

然后点击“Generate Project”就会生成一个Demo项目。

直接用Intellij IDE打开这个项目。注意不是导入。

二、配置application.yml

这一步是为了连接数据库、设置持久连接、配置本地调试服务器端口、配置jpa

首先把src/main/resources目录下的application.properties改名为application.yml

然后填入:

server:
  port: 8081

这样这个Demo工程内嵌的Tomcat就会监听8081端口了。不过Tomcat异常的矫情倒是一开始让我浪费了很多时间。

一个PHPer对Tomcat常见的无知

1.在star.spring.io上生成的项目里会自带Tomcat。

2.如果程序出现问题,Tomcat会无法启动。(PHP中不管PHP-fpm还是Apache都很少因为程序问题无法运行)

3.如果数据库无法连接,Tomcat会无法启动。

4.代理软件会影响Tomcat的启动。

接着填:

spring:
  resources:
    chain:
      enabled: true
  datasource:
    url: jdbc:mysql://192.168.207.134:3306/test
    username: root
    password: root
    test-while-idle: true
    validation-query: SELECT 1
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
      naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5Dialect

至于如何在虚拟机或者本地搭建mysql在此就不赘述了,mac下直接brew install mysql

以上就配置好了application.yml

三、写一个Hello World!

由于我们是遵循MVC模式的开发,所以我们进入src/main/java/cn.net.wangchenyu.springapidemo1,新建两个package,一个名字叫controllers另一个叫models

然后我们在controllers包下新建一个Java Class,名字叫IndexController,遵循大驼峰命名法。

在类声明语句的上方我们用@RestController声明这个类是一个RestController,那么它就会返回json数据

然后我们使用@RequestMapping("/")来绑定/这个路径执行下面的indexShow方法,方法名遵循小驼峰命名法。

于是IndexController的内容如下:

package cn.net.wangchenyu.springapidemo1.controllers;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Created by cheneyveron on 7/7/16.
 */
@RestController
public class IndexController {

    @RequestMapping("/")
    public String indexShow(){
        return "Hello Cheney!";
    }
}

现在我们点击上面的绿色运行按钮,等待编译完了以后就可以在浏览器打开localhost:8081了。

四、操作数据库

Models下新建CreateFood类,类声明上方用@Entity表明这是一个实体。然后用@Table(name = "Food")指定对应的表为food

在建表的时候所有的字符都是小写的。

id项前面用@Id @GeneratedValue(strategy = GenerationType.AUTO)声明这是一个主键并且自增。

String声明的name在数据库中会以varchar(255)来储存。

CreateFood完整文件:

package cn.net.wangchenyu.springapidemo1.models;

import javax.persistence.*;

/**
 * Created by cheneyveron on 7/7/16.
 */
@Entity
@Table(name = "Food")
public class CreateFood {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private String name;
}

Mac下按Command + N可以自动补全Getter方法和Setter方法。

为了管理Food表,我们再创建一个名为CreateFoodDaoInterface,继承CrudRepository<T, ID>,这里T就是CreateFoodID就是Integer

这样以后,我们只需要声明一个CreateFoodDao对象,就能直接使用findOne(id)findAllsave这些方法了。

CreateFoodDao.java内容:

package cn.net.wangchenyu.springapidemo1.models;

import org.springframework.data.repository.CrudRepository;

/**
 * Created by cheneyveron on 7/7/16.
 */
public interface CreateFoodDao extends CrudRepository<CreateFood,Long> {
}

为了规范返回值,再在Models下建立一个ReturnMessage类,有两个属性,id和message:

package cn.net.wangchenyu.springapidemo1.models;

/**
 * Created by cheneyveron on 7/7/16.
 */
public class ReturnMessage {
    private int id;//返回代码标示
    private String message;//返回信息
}

五、插入与读取数据库

我们在IndexController中建立一个路径映射到/create,然后建立一个方法create

Create方法中,我们先实例化一个CreateFood对象,然后设置对象的name属性。

如何保存呢?

IndexController中,我们建立一个@AutowiredCreateFoodDao对象createFoodDao。然后调用createFoodDaosave方法,传入上面的createFood对象即可保存。

坑1:注意!

这里的Object不是org.omg.CORBA.Object,而是java.lang.Object包。如果你不小心引入了org.omg.CORBA.Object只需要删除该引用即可,java.lang不需要引入。

如今的IndexController.java如下:

@RestController
public class IndexController {

    @Autowired
    private CreateFoodDao createFoodDao;

    @RequestMapping("/")
    public String indexShow(){
        return "Hello Cheney!";
    }

    @RequestMapping("/create")
    public Object create(){
        CreateFood createFood = new CreateFood();
        createFood.setName("Tomato");
        return createFoodDao.save(createFood);
    }
}

访问/create就会看到创建的对象:

{"id":1,"name":"Tomato"}

更改一下名字,我们再多插入几条:

{"id":2,"name":"Potato"}

{"id":3,"name":"Cucumber"}

如何读取呢?

我们再映射一个路径到/showall,直接返回createFoodDao.findAll()方法即可:

@RequestMapping("/showall")
public Object showAll(){
    return createFoodDao.findAll();
}

访问

http://localhost:8081/showall

就会看到:

[{"id":1,"name":"Tomato"},{"id":2,"name":"Potato"},{"id":3,"name":"Cucumber"}]

我们查看数据库,也会看到数据已经插入进去:

六、传递参数

接下来我们试着通过id来寻找食物。
IndexController中建立一个路径映射到/verify,使用@RequestMapping("/verify")即可。

如何指定只接受POST传值?

只要这样写:@RequestMapping(value = "/verify",method = RequestMethod.POST)就能只接受POST的值了。

默认情况下是GETPOST都接受。

接受参数的方式有三种。

1.直接写在形参位置

    @RequestMapping(value = "/verify")
public Object verify(String stringId){
    return stringId;
}

我们访问

http://localhost:8081/verify?stringId=5

就会返回:

5

注意

传参的时候参数名称区分大小写,必须和形参stringId一模一样才能匹配。

2.自动匹配对象的属性

我们如果在参数的位置上写一个对象,那么传的参数会自动匹配到对象的属性。

    @RequestMapping(value = "/verify")
public Object verify(ReturnMessage returnMessage){
    return returnMessage;
}

我们访问

http://localhost:8081/verify?id=5&Message=ab%22c%20%27s%20%3C?php%20phpinfo();%20?%3E

就会返回:

{"id":5,"message":"ab\"c 's "}

注意

1.传参的时候参数名称仅首字母不区分大小写。

2.私有属性如果没有setter/getter方法则无法设置/获取该属性。

3.使用@RequestParam指定参数

    @RequestMapping(value = "/verify")
public Object verify(@RequestParam(value = "id",defaultValue = "-1") String stringId){
    return stringId;
}

我们访问

http://localhost:8081/verify?id=5

就会返回:

5

注意

必须和value指定的名字id一模一样才能匹配。

七、完成我们的API

Java的强类型性“空指针出错”性让web开发很头疼。如果直接指定int类型参数那么如果空参数就会直接报500错误。

为了让我们的API服务具有更强的容错性,我们可以用String类型来接收参数。整个函数头定义类似于此:

public Object verify(@RequestParam(value = "id",defaultValue = "-1") String stringId){

然后我们在函数内部执行Interger.parseInt(stringId)来强转成Int型。

如果是纯数字,那么parseInt会返回数字。

如果是含有非数字字符,那么parseInt会抛出NumberFormatException异常。

然后我们使用createFoodDao.findOne()方法,传入id来得到一个CreateFood对象createFood

如果数据库能查询到,那么会返回一个CreateFood对象createFood

如果查询不到,会返回null

接下来通过判断id的值来设置returnMessage,最后返回returnMessage即可。

整个verify()函数如下:

@RequestMapping(value = "/verify")
public Object verify(@RequestParam(value = "id",defaultValue = "-1") String stringId){
    long id = -1;
    try{
        id = Integer.parseInt(stringId);
    }catch (Exception e){
        id = -1;
    }
    CreateFood createFood = createFoodDao.findOne(id);
    ReturnMessage returnMessage = new ReturnMessage();
    if(id < 0){
        returnMessage.setId(2);
        returnMessage.setMessage("ID Illegal");
    }else if(createFood == null){
        returnMessage.setId(1);
        returnMessage.setMessage("ID Not Found");
    }else{
        returnMessage.setId(0);
        returnMessage.setMessage("Today we eat"+createFood.getName());
    }
    return returnMessage;
}

至此,一个功能完备,并且具有一定的容错能力的的食物查询API已经做成啦。