用Spring Boot & Cloud,Angular2快速搭建微服务web应用 - 实现RESTful CRUD

来源:互联网 发布:淘宝办理出版物许可证 编辑:程序博客网 时间:2024/06/07 05:35

打算就”用Spring Boot & Cloud和Angular2快速搭建微服务web应用“这个题目写一系列文章,作为自己学习的一个记录。在参加完读脉组织的一个Java培训活动后,发现自己在这方面的知识已经落后好多年了。讲师Josh的激情演讲,也让我看到了自己和顶级程序员的差距。在此感谢读脉组织的这次活动,感谢Josh的激情演讲,给了我写这个系列文章的动力。

读脉:http://readmore.cc/

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;@SpringBootApplicationpublic 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;@Entitypublic 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 settingsspring.datasource.url=jdbc:mysql://localhost:3306/healtravspring.datasource.username=rootspring.datasource.password=spring.datasource.initial-size=20spring.datasource.max-idle=60spring.datasource.max-wait=10000spring.datasource.min-idle=10spring.datasource.max-active=200# auto create tables and data for database healtravspring.jpa.generate-ddl=truespring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialectspring.datasource.schema=..\..\..\db\schema.sqlspring.datasource.data=..\..\..\db\data.sql# show each sql for debugspring.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 EngineeringSET @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的接口说明。
参考:https://spring.io/guides/gs/accessing-data-rest/

验证

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  Speed100   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客户端
0 0
原创粉丝点击