用Spring Boot & Cloud,Angular2快速搭建微服务web应用 - 实现RESTful CRUD
用Spring Boot & Cloud和Angular2快速搭建微服务web应用
·
序
打算就”用Spring Boot & Cloud和Angular2快速搭建微服务web应用“这个题目写一系列文章,作为自己学习的一个记录。在参加完读脉组织的一个Java培训活动后,发现自己在这方面的知识已经落后好多年了。讲师Josh的激情演讲,也让我看到了自己和顶级程序员的差距。在此感谢读脉组织的这次活动,感谢Josh的激情演讲,给了我写这个系列文章的动力。
Josh:https://github.com/joshlong
一切从“Spring Initializr”开始
为了快速地搭建和运行Spring Boot项目,Pivotal提供了称之为“Spring Initializr”的web界面,用于下载预先定义好的Maven或Gradle构建配置。为实现RESTful CRUD,访问
http://start.spring.io/,创建一个新的项目叫做user-service,为health-travel提供用户管理服务,并且选择如下依赖:Rest Repositories,JPA,MySQL。然后点击“Generate Project”会产生一个名为user-service的zip包,里面包含了项目需要的Maven工程文件。
打开pom.xml,可以看到项目的依赖已经生成好了:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.healtrav</groupId>
<artifactId>user-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>user-service</name>
<description>user management service for healtrav</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
同时注意到以下几个依赖:
- spring-boot-starter-data-jpa:使用Spring Data JPA 读写数据。Spring Data JPA是Spring Data项目的一部分,用于简化持久层的业务逻辑。它会根据方法的名字来确定方法要实现什么样的业务逻辑,所以developer只要按照规范的名字去命名方法,声明接口。参考链接:http://projects.spring.io/spring-data-jpa/
- spring-boot-starter-data-rest:用RESTful的方式访问Spring Data数据仓库,提供了CRUD。参考链接:http://projects.spring.io/spring-data-rest/
Spring Initializr还生成了user-service的主程序文件UserServiceApplication.java
package com.healtrav;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
现在我们只需要添加一些业务代码,就可以直接实现一个Restful,支持CRUD的web应用。
实现
添加domain/User.java
package com.healtrav.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@NotNull
private String username;
@NotNull
private String password;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
User对应数据库的user表。
添加repository/UserRepository.java
package com.healtrav.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import com.healtrav.domain.User;
@RepositoryRestResource(collectionResourceRel = "user", path = "user")
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(@Param("username") String username);
}
JpaRepository的详细信息:
JpaRepository。前面提到过Spring Data JPA的命名规范,findByUsername就是利用了这样的规范,让Spring Data JPA实现按照username查找User。可以通过在方法名中添加Distinct,Asc,Desc,LessThen等来实现去除重复值,升序,降序,小于等的条件的查找,例如findDistinctByUsername
。
另外JpaRepository提供了大量的方法,从上面的链接可以看到,该接口及其继承的接口,提供了基本的查询,分页,删除,保存,修改,及批量功能。参考:http://docs.spring.io/spring-data/jpa/docs/current/reference/html/
修改resources/application.properties
# MySQL data source settings
spring.datasource.url=jdbc:mysql://localhost:3306/healtrav
spring.datasource.username=root
spring.datasource.password=
spring.datasource.initial-size=20
spring.datasource.max-idle=60
spring.datasource.max-wait=10000
spring.datasource.min-idle=10
spring.datasource.max-active=200
# auto create tables and data for database healtrav
spring.jpa.generate-ddl=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
spring.datasource.schema=..\..\..\db\schema.sql
spring.datasource.data=..\..\..\db\data.sql
# show each sql for debug
spring.jpa.show-sql = true
文件里面的datasource用来配置MySQL数据库连接。Spring Data还支持MongoDB,H2嵌入式数据库等。如果用MongoDB,H2等,只需要在Spring Initializr里面添加依赖,不需要添加datasource配置。另外配置增加了自动运行schema.sql和data.sql的功能,自动创建数据库表和添加数据(healtrav数据库必须已经存在)。show-sql是为了让Spring Data显示每个SQL语句,方便调试。
添加schema.sql
-- MySQL Script generated by MySQL Workbench
-- 09/20/16 18:12:37
-- Model: New Model Version: 1.0
-- MySQL Workbench Forward Engineering
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES';
-- -----------------------------------------------------
-- Schema healtrav
-- -----------------------------------------------------
DROP SCHEMA IF EXISTS `healtrav` ;
-- -----------------------------------------------------
-- Schema healtrav
-- -----------------------------------------------------
CREATE SCHEMA IF NOT EXISTS `healtrav` DEFAULT CHARACTER SET utf8 ;
USE `healtrav` ;
-- -----------------------------------------------------
-- Table `healtrav`.`user`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `healtrav`.`user` ;
CREATE TABLE IF NOT EXISTS `healtrav`.`user` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`username` VARCHAR(64) NOT NULL,
`password` VARCHAR(128) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `id_UNIQUE` (`id` ASC),
UNIQUE INDEX `username_UNIQUE` (`username` ASC))
ENGINE = InnoDB;
SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
小结
短短的这些代码,已经实现了对库表user的CRUD功能,并且提供了标准的Restful API。文件UserRepository.java中的@RepositoryRestResource(collectionResourceRel = "user", path = "user")注解,在localhost:8080/user下面,生成了Restful API,甚至还有API的接口说明。
验证
Windows版本的git bash提供了很多有用的工具,其中一个就是curl。curl可以用来验证刚才建立的service。
在git bash中进入user-service目录,用mvn运行:./mvnw spring-boot:run
在另一个git bash中,运行命令:
$ curl http://localhost:8080/
会得到当前的Restful URI列表。其中一个是"href" : "http://localhost:8080/user{?page,size,sort}",
运行命令:
$ curl http://localhost:8080/user
会得到user路径下面的URI列表,及一个user数组。但是现在这个user数组是空的,因为我们还没有添加用户信息。
运行命令:
$ curl -i -X POST -H "Content-Type:application/json" -d '{ "username" : "cuiwader", "password" : "123" }' http://localhost:8080/user
会添加数据到数据库,并且会返回新添加的用户的信息。
运行命令:
$ curl http://localhost:8080/user/search/findByUsername?username=cuiwader
会调用UserRepository.java中,接口UserRepository的findByUsername方法。而且该方法的实现也是Spring Data JPA根据方法的名字自动生成的。
再次运行命令:
$ curl http://localhost:8080/user
会得到user路径下面的URI列表,及一个user数组。这次这个数组多了一个元素,即刚才添加的那个元素,但是请大家仔细看下面的输出,数组元素没有id字段,因为默认id是不导出的。可以通过继承RepositoryRestMvcConfiguration,重写configureRepositoryRestConfiguration(RepositoryRestConfiguration config)方法,并且在方法里面调用
config.exposeIdsFor(class)的方式增加id导出。但是应该使用_links的href去调用后台,而不是自己拼接id。输出如下:
$ curl http://localhost:8080/user-service/user
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 726 0 726 0 0 15782 0 --:--:-- --:--:-- --:--:-- 15782{
"_embedded" : {
"user" : [ {
"username" : "cuiwader",
"password" : "123",
"_links" : {
"self" : {
"href" : "http://localhost:8080/user-service/user/1"
},
"user" : {
"href" : "http://localhost:8080/user-service/user/1"
}
}
} ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/user-service/user"
},
"profile" : {
"href" : "http://localhost:8080/user-service/profile/user"
},
"search" : {
"href" : "http://localhost:8080/user-service/user/search"
}
},
"page" : {
"size" : 20,
"totalElements" : 1,
"totalPages" : 1,
"number" : 0
}
}
解决复杂问题
这里还有一个遗留问题,如果业务逻辑特别复杂,不能通过简单的方法命名规范来实现怎么办呢?一般遇到这样的问题,只需要抱有一个信念:Spring如此灵活,不可能有解决不了的问题。另外自己遇到的问题,别人也一定遇到过,只是暂时还没有找到解决方案。目前我所知道的解决方案有3个:@Query,存储过程和定制实现。
@Query
推荐使用命名参数方式的@Query注解,还是以findByUsername为例:
@Query("select u from user u where u.username = :username")
User findByUsername(@Param("username") String username);
存储过程
在User.java增加:
@Entity
@NamedStoredProcedureQuery(name = "User.findByUsername", procedureName = "findByUsername", parameters = {
@StoredProcedureParameter(mode = ParameterMode.IN, name = "username", type = String.class),
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "user", type = User.class) })
public class User {
...
在UserRepository中:
@Procedure("findByUsername")
User findByUserName(String username);
当然数据库里面需要有一个findByUserName的存储过程。
定制实现
增加一个接口:
package com.healtrav.repository;
import com.healtrav.domain.User;
public interface UserRepositoryCustom {
public User getByUsername(String username);
}
修改UserRepository的定义:
package com.healtrav.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import com.healtrav.domain.User;
@RepositoryRestResource(collectionResourceRel = "user", path = "user")
public interface UserRepository
extends JpaRepository<User, Long>, UserRepositoryCustom {
User findByUsername(@Param("username") String username);
}
添加UserRepositoryCustom的实现:
package com.healtrav.repository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.webmvc.RepositorySearchesResource;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.ResourceProcessor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.healtrav.domain.User;
@RestController
@RequestMapping("/user")
public class UserRepositoryImpl implements
UserRepositoryCustom, ResourceProcessor<RepositorySearchesResource> {
@Autowired
UserRepository userRepo;
@Override
@RequestMapping(value = "/search/getUserByUsername", method = RequestMethod.GET)
public User getByUsername(
@RequestParam(value = "username", required = true) String username) {
return userRepo.findByUsername(username);
}
@Override
public RepositorySearchesResource process(
RepositorySearchesResource resource) {
String href = resource.getId().getHref();
resource.add(new Link(href + "/getByUsername{?username}")
.withRel("getByUsername"));
return resource;
}
}
其实UserRepository继承UserRepositoryCustom没有什么实际的意义,Spring不会自动暴露UserRepositoryImpl到RESTful,意义在于让UserRepository提供了统一的访问接口。UserRepositoryImpl实现了ResourceProcessor反而更加重要,实际上在curl http://localhost:8081/user/search的时候,增加了一个输出:
"getByUsername" : {
"href" : "http://localhost:8081/user/search/getByUsernamee{?username}",
"templated" : true
}
下一章:用Spring Boot & Cloud,Angular2快速搭建微服务web应用 - AngularJS2客户端
更多推荐
已为社区贡献1条内容
所有评论(0)