本课将构建一个基于Spring Boot的应用,它提供对关系型数据库的REST访问接口——通过接口可以对存储在关系型数据库中的User对象进行增删改查操作。应用中我们使用Spring Data REST来创建访问接口。

提示

Spring Data REST不仅支持关系型数据库,还能够支持各类NoSQL数据库——Neo4j, Gemfile和MongoDB。它们不在本课的范围之内,可以参考Spring Data项目。

环境准备

  • 一个称手的文本编辑器(例如Vim、Emacs、Sublime Text)或者IDE(Eclipse、Idea Intellij)
  • Java环境(JDK 1.7或以上版本)
  • 构建工具Gradle 2.3

初始化项目目录

首先创建一个项目目录,在目录中创建一个Gradle项目描述文件build.gradle

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.5.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'spring-boot' 

jar {
    baseName = 'spring-data-rest-demo'
    version = '1.0.0-SNAPSHOT'
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
    jcenter()
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-data-rest")
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    compile("com.h2database:h2")
}

在这个文件中,使用到了Spring Boot Gradle插件来帮助我们简化一些配置工作:

  • 提供了Spring Boot框架的依赖定义,在dependencies标签中直接使用它们即可(不需要声明版本号)
  • 将应用的代码及所有的依赖打包成一个单独的jar文件
  • 自动搜索main函数并作为jar文件的启动函数,jar文件是一个独立可执行的文件

领域对象

在本课中,领域对象是User——它包含两个字段——emailname,依据Gradle的约定,该对象应该位于项目源码文件夹src/main/java下,同时我们将它放在tmy这个package中:

src/main/java/tmy/User.java

@Entity
public class User {

    @Id
    @GeneratedValue
    private long id;

    private String email;

    private String name;

    //构造方法、Getter/Setter方法略
}

@Entity注解表明User是一个JPA实体。每一个User对象有一个唯一的主键标识符id,它是Spring Data JPA自动自增生成的,无需手动处理它。上述领域对象定义了数据存储的格式,Spring Data JPA将其字段映射到关系型数据库表中的列名,假设现在有一个User对象:

User user = new User(1L, "test@test.com", "Test Name");

它在关系型数据库(这里我们用到的是类路径上默认的内存数据库H2Database)中的存储结构是:

idemailname
1test@test.comTest Name

Repository

有了领域对象User后,需要定义一个能对其进行增删改查操作的Repository接口:

src/main/java/tmy/UserRepository.java

import org.springframework.data.repository.PagingAndSortingRepository;

public interface UserRepository extends PagingAndSortingRepository<User, Long> {

}

这个接口中我们没有定义任何操作方法,而是直接继承于PagingAndSortingRepository接口,该接口中已经包含常用的操作方法:

  • findOne(Long id)
  • findAll()
  • save(User user)

在应用启动时,Spring会自动创建这个接口的实现。以调用findOne(1L)为例,实际上Spring Data JPA帮我们生成了一个这样的sql查询:

select * from users where id = 1;

并且将查询结果集按字段名映射为一个User对象并返回。这也就是ORM(Object Relation Mapper)框架帮助我们简化数据访问层开发的基本方法于思路。

同时Spring Data Rest也会在Spring MVC中绑定响应的HTTP路由方法,并创建User对象的RESTful访问端点/users

  • GET /users User列表信息(数据来源于UserRepository对应的关系型数据库,下同)
  • POST /users 创建一个User对象
  • GET /users/{id} 获取单个User对象
  • PUT /users/{id} 更新单个User对象
  • DELETE /users/{id} 删除单个User对象

这也就是对于User资源最基本的增删改查以及列表的RESTful访问接口,在使用Spring Data Rest项目后,我们不再需要自己动手编写这些@Controller,对于大部分资源来说,这些代码都是大同小异的,Spring Data Rest正是把这些通用的代码抽取出来,减轻了我们的编码工作量。

运行应用

运行基于Spring Boot的应用非常简单,无需将代码及依赖打成传统的WAR包放置在应用服务器(Jetty, Tomcat)中,Spring Boot插件在打包的过程中,内嵌了一个应用服务器(默认是tomcat)。所以应用本身是自启动的,我们无需将其部署到外部的应用服务器中:

src/main/java/hello/Application.java

package tmy;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

@SpringBootApplication是Spring Boot提供的注解,他相当于加上如下注解:

  • @Configuration,表明Application是一个Spring的配置对象,用于配置Spring应用上下文。
  • @EnableAutoConfiguration,Spring Boot会根据类路径(classpath)以及一些属性值来自动完成一些配置行为,例如:开发基于Spring MVC的Web应用,需要在配置中加上@EnableWebMvc直接来激活一些默认的Web配置,一旦Spring Boot发现运行时类路径上包含了 spring-webmvc 依赖,它会自动的完成一个Web应用的基本配置——例如配置DispatcherServlet等等。
  • @ComponenScan告知Spring应用从什么位置去发现Spring构件(@Component, @Service, @Configuration)等等

完成上述配置后,运行应用有两种方法:

  • 在IDE中直接运行main方法
  • 通过Gradle打包应用:gradle build,运行:java -jar build/libs/${appname}-{version}.jar

使用REST接口创建一个对象

应用运行后,数据库中并不存在任何数据,可以通过POST /users接口来创建一个User

$ curl -X POST -H "Content-Type:application/json" -d '{  "email" : "test@test.com",  "name" : "Ruici" }' http://localhost:8080/users
HTTP/1.1 201 Created
Server: Apache-Coyote/1.1
Location: http://localhost:8080/users/1
Content-Length: 0
Date: Mon, 20 Jul 2015 07:53:10 GMT

curl命令的参数意义如下:

  • -X 参数指定请求的方法类型——GET, POST, PUT, DELETE
  • -H 参数指定请求的Header,这里因为请求body的内容是json对象,所以必须设置Content-Type:application/json
  • -d 指定请求体内容

如果Windows下使用curl不方便,推荐使用Chrome的插件——图形化界面工具Postman

如果创建成功,服务器会返回201 Created状态码,并在Location头中标明已创建的资源的URL。

获取所有对象

$ curl http://localhost:8080/users
{
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/users{?page,size,sort}",
      "templated" : true
    }
  },
  "_embedded" : {
    "users" : [ {
      "email" : "test@test.com",
      "name" : "Ruici",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/users/1"
        }
      }
    } ]
  },
  "page" : {
    "size" : 20,
    "totalElements" : 1,
    "totalPages" : 1,
    "number" : 0
  }
}

获取单个对象

$ curl http://localhost:8080/users/1
{
  "email" : "test@test.com",
  "name" : "Ruici",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/users/1"
    }
  }
}

嵌套对象

在大多数情况下,领域对象并不是一组简单的对象(例如上例中的User,它只包含两个数据字段emailname),其中最常见的一种情况是组合,例如:

@Entity
public class Address {

    @Id
    @GeneratedValue
    private long id;

    private String city;

    private String street;

    //构造方法和Getter/Setter方法略
}

User类中包含了private Address address;字段用于存储用户的地址。在关系型数据库中通常是再建一张表来进行关联。

Address表:

idcitystreet
1BeijingYiheyuan Road

User表:

idemailnameaddressId
1test@test.comTest Name1

addressId字段表示关联表Addressid为1的记录,SQL查询语句可以这样写:

select u.id as userId, email, name, a.id as addressId, city, street
    from address a inner join user u on a.id =  u.addressId where u.id = 1

Spring Data JPA基于ORM映射原理,可以这样定义关联关系:

@Entity
public class User {

    @Id
    @GeneratedValue
    private long id;

    private String email;

    private String name;

    @OneToOne(cascade = CascadeType.ALL)
    private Address address;

    //构造方法和Getter/Setter方法略

}

添加一个User对象的代码如下:

@Autowired
private UserRepository userRepository;

@Override
public void run(String... args) throws Exception {
    User user = new User("hello@world.com", "Hello World");
    user.setAddress(new Address("Beijing", "Yiheyuan Road 5"));

    userRepository.save(user);

}

也就是说Spring Data JPA会自动的将User对象中嵌套的组合对象Address持久化到相应的关系型数据库表中。对于查询也是如此。

经过以上处理,运行应用后执行curl http://localhost:8080/users,可以看到user字段中已经包含了组合对象address

延伸阅读

登录发表评论 注册

蛮吉

那么问题来了,发布的服务查询出来的JSON不带ID。怎么破?或者出于什么考虑没有ID?

David

这根关系型数据库有什么关系? 参考代码能提供吗?

反馈意见