本文讲述了NoSQL(非关系型数据库)与构建Restful服务等,有一点的挑战性。
基本上,到目前为止,spring boot体系讲得也差不多了,当然后续还有一系列进阶的内容:例如单元测试,缓存与安全管理等。对于这里讲到的NoSQL,后期也会再深入,这里都是最基本的用法。
同时在RESTful服务中也使用到了新工具(postman)来进行接口测试等工作,也是非常方便。
希望以下内容对你有所帮助!
NoSQL是非关系型数据库。非关系和关系型数据库存在不同点,其中最重要的是NoSQL不使用SQL作为查询语句。其数据存储可以不需要固定的表格模式,有以下分类:
1 Key/Value 键值存储。数据存储通常是无数结构的,一般被当做字符串等,但是数据加载快,典型使用场景就是处理高并发或者用于日志系统,这一类数据库有Redis,Tokyo Cabinet等。
2 列存储数据库。一般用于分布式文件系统
3 文档型数据库。和Key / Value 键值存储类似。这一类有MongoDB等。
4 图形数据库,专注于构建关系图谱。有Neo4J,DEX等。
REST 是一种Web软件架构风格,(注意不是标准)匹配或兼容这种架构的网络服务是REST服务,服务简洁且有层次。在REST中,资源是由URL指定的,对资源增删改查操作可以通过HTTP协议提供的GET,POST,PUT,DELETE等方法实现。可以利用缓存来提高响应速度,同时REST通信会话由客户端来实现。
是一个使用C编写的基于内存的NoSQL数据库,目前最流行的键值对存储数据库。
安装环境为Centos 7
wget http://download.redis.io/releases/redis-4.0.10.tar.gz
# 如果没找到命令
yum install wget
不过我安装的时候遇到了这个问题:
使用 vim /etc/resolv.conf 打开配置文件
输入nameserver 8.8.8.8 以及 nameserver 8.8.4.4就可以了
首先解压下载的文件,然后进入解压目录进行编译,执行语句:
tar -zxvf redis-4.0.10.tar.gz
cd redis-4.0.10
make MALLOC=libc
make install
# 如果提示gcc 未找到命令
yum install gcc
安装成功后,需要进行配置,打开redis,解压src/redis.conf文件
vim redis.conf
# i 插入模式:
daemonize yes # 表示允许redis在后台启动
# bind 127.0.0.1 # 允许连接的实例地址,默认情况下只允许本地连接,将默认配置注释,外网就可以连接
requirepass ***** # 登陆的密码
protected-mode no # 关闭保护模式
为了远程连接上redis,需要关闭Centos防火墙,执行命令如下:
systemctl stop firewalld.service # 关闭防火墙
systemctl disable firewalld.service # 禁止防火墙开机启动
执行启动redis
redis-server redis.conf
redis-cli -a *** # 进入控制台,这个表示密码
windows版本连接就更简单了,之间去redis-windows选择zip下载就可以。
不过注意要在cmd管理员下运行
redis-server.exe redis.windows.conf
一定要保持开启才能运行项目
redis的Java客户端有很多,例如Jedis ,JRedis,Spring Data Redis等。Spring Boot借助于Spring Data Redis为Redis提供了开箱即用的自动化配置,开发者只需要添加相关依赖既可。
添加依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
在application.properties配置redis连接信息:
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=qazwsx123
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.min-idle=0
创建一个实体类:
import java.io.Serializable;
/**
* Created by sang on 2018/7/18.
*/
public class Book implements Serializable {
private Integer id;
private String name;
private String author;
//省略getter/setter
@Override
public String toString() {
return "Book{" +
"id=" + id +
", name='" + name + '\'' +
", author='" + author + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
创建BookController进行测试:
@RestController
public class BookController {
@Autowired(
required = false
)
RedisTemplate redisTemplate;
@Autowired(
required = false
)
StringRedisTemplate stringRedisTemplate;
@GetMapping("/test1")
public void test1() {
ValueOperations<String,String> ops1 = stringRedisTemplate.opsForValue();
ops1.set("name","三国演义");
String name = ops1.get("name");
ValueOperations ops2 = redisTemplate.opsForValue();
Book b1 = new Book();
b1.setId(1);
b1.setName("红楼梦");
b1.setAuthor("曹雪芹");
ops2.set("b1", b1);
Book book = (Book) ops2.get("b1");
System.out.println(book);
}
}
不过当我运行的时候发现了这个错误:
Description:
Failed to configure a DataSource: ‘url’ attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver class
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
不过可能再次运行的时候出现8080端口被占用情况(原因可能是没有杀死程序)那么最简单的办法就是重启或者在配置修改端口为8081
输入8081/test1便可以看到控制台信息
上文是单个redis实例整合,在实际项目中,开发者为了提高redis扩展性,往往搭建redis集群。
原理:所有的redis节点彼此互联,节点内部使用二进制协议优化传输速度与宽带。当一个节点挂掉后,集群超过半数的节点检测失效时才认为该节点失效。不同于Tomcat集群需要使用反向代理服务器,redis集群中的任意节点都可以直接与java客户端连接。数据分配是采用哈希槽(Hash SLOT)。
集群中的相关知识,将在后期补充。
MongoDB是一种面向文档的数据库管理系统,它是一个介于关系型数据库和非关系型数据库之间的产品,支持一种类似JSON的BSON数据格式,既可以存储简单的数据格式,也可以存储复杂的数据类型。总的来说,MongoDB是一款应用广泛的NoSQL数据库。
windows 去此处下载:MongoDB
安装完以后可以用Navicat 或者datagrip进行打开,注意此数据库一开始是没有密码的。
所以这在生产环境中非常不安全的,但是不同于关系型数据库,MongoDB每一库都有一个独立的密码,在哪一个库创建用户就要在哪一个库验证密码。要配置密码,首先创建一个用户,例如在admin库中创建一个用户:
use admin;
db.createUser({user:"jacin",pwd:"qazwsx123",roles:[{role:"readWrite",db:"test"}]})
对数据库test具有读写两项权限
引入依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-mongodbartifactId>
dependency>
配置MongoDB,在application.properties:
spring.data.mongodb.authentication-database=admin
spring.data.mongodb.database=test
spring.data.mongodb.host=127.0.0.1
spring.data.mongodb.port=27017
spring.data.mongodb.username=jacin
spring.data.mongodb.password=******
创建的实体库Book和之前一样:
创建BookDao:类似于Spring Data JPA的repository定义
MongoRepository已经预定义了针对实体类的查询、添加、删除等操作,可以按照命名规则定义方法。
import org.springframework.data.mongodb.repository.MongoRepository;
import java.util.List;
public interface BookDao extends MongoRepository<Book,Integer> {
List<Book> findByAuthorContains(String author);
Book findByNameEquals(String name);
}
创建Controller:
insert方法,插入集合中的数据。
@RestController
public class BookController {
@Autowired
BookDao bookDao;
@Autowired(
required = false
)
MongoTemplate mongoTemplate;
@GetMapping("/test2")
public void test2() {
List<Book> books = new ArrayList<>();
Book b1 = new Book();
b1.setId(3);
b1.setName("围城");
b1.setAuthor("钱钟书");
books.add(b1);
Book b2 = new Book();
b2.setId(4);
b2.setName("宋诗选注");
b2.setAuthor("钱钟书");
books.add(b2);
mongoTemplate.insertAll(books); //插入数据
List<Book> list = mongoTemplate.findAll(Book.class);
System.out.println(list);
Book book = mongoTemplate.findById(3, Book.class);
System.out.println(book);
}
@GetMapping("/test1")
public void test1() {
List<Book> books = new ArrayList<>();
Book b1 = new Book();
b1.setId(1);
b1.setName("朝花夕拾");
b1.setAuthor("鲁迅");
books.add(b1);
Book b2 = new Book();
b2.setId(2);
b2.setName("呐喊");
b2.setAuthor("鲁迅");
books.add(b2);
bookDao.insert(books);
List<Book> books1 = bookDao.findByAuthorContains("鲁迅");
System.out.println(books1);
Book book = bookDao.findByNameEquals("朝花夕拾");
System.out.println(book);
}
}
正常情况下,HttpSession是通过Servlet容器创建来进行管理的,创建成功后,都是保存在内存中,如果开发者需要对项目横向扩展搭建集群,利用一些硬件或者软件来做负载均衡。使用Redis来方便解决问题,就是把原本存储在不同服务器上的session拿出来放在一个独立服务器上。
需要添加redis 和session依赖:
下面创建一个controller来测试操作:
@RestController
public class HelloController {
@Value("${server.port}")
String port;
@PostMapping("/save")
public String saveName(String name, HttpSession session) {
session.setAttribute("name", name);
return port;
}
@GetMapping("/get")
public String getName(HttpSession session) {
return port + ":"
+ session.getAttribute("name").toString();
}
}
一个save接口用来向session存储数据,还有一个get接口用来从session 获取数据,这里注入了项目启动的端口号server.port,主要区分是哪个服务器提供的服务,真正的session此时存在了redis服务器上了。
本次使用nginx来进行负载均衡,环境是centos 7,关于安装与编译不再多说了。
项目完成后,将项目打成jar包上传到Centos上,然后执行如下两条命令启动项目:
nohup java -jar session-0.0.1-SNAPSHOT.jar --server.port=8080 &
nohup java -jar session-0.0.1-SHAPSHOT.jar --server.port=8081 &
nohup就是不挂使程序在后台运行,本次设置了2个端口,启动成功后开始配置负载均衡器了。
进入nginx修改配置文件:
/conf/nginx.conf:
upstream jacin.com {
server 192.168.66.130:8080 weight =1;
server 192.168.66.130:8081 weight =1;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://jacin.com
proxy_redirect default;
}
}
在修改的配置中,首先配置上游服务器,即两个real server,权重都是1(意味平均分配)然后在server中设置拦截规则,将请求转发到real server上。
记得重启nginx。
经过以上步骤,便可以利用redis实现session共享的功能。
添加依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-restartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.2version>
dependency>
相关application配置如下:
server.port=8082
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql:///jparestful
spring.datasource.username=root
spring.datasource.password=qazwsx123
spring.jpa.show-sql=true
spring.jpa.database=mysql
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
创建实体类:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity(name = "t_book")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String author;
//省略getter/setter
@Override
public String toString() {
return "Book{" +
"id=" + id +
", name='" + name + '\'' +
", author='" + author + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
创建BookRepository:
由于是继承,所以下面的方法也都可以不写,写只是告诉有一些基本的操作方法例如增删改查等。
import org.springframework.data.jpa.repository.JpaRepository;
//继承JpaRepository默认提供的一些基本的数据库操作方法
public interface BookRepository extends JpaRepository<Book,Integer> {
}
以上就是RESTful服务了。注意我一开始启动项目的时候并没有生成表格,后面才发现是我的启动程序多了东西,只需要@SpringBootApplication就可以了。
下面使用PostMan进行接口测试(注意这是软件,根据需要来下载,chrome也有,不过你懂的)
我们可以在t_book添加一些数据:
打开postman,
RESTFul API默认请求路径是实体类名小写再加上s后缀,本文实体类为book,加上s后缀,即默认路径名是books。
使用get命令来查询结果:
新建测试,我们在postman创建一个post请求,body放入新增的数据,选择json类型.
添加一个数据,此时已经写入数据库中。
按照id查询,主要在books后面加id号即可了。
修改测试:
发送Put请求,url是请求id号
默认情况下,请求路径为实体类名加小写s,如果开发者想对请求路径进行重定义,可以通过@RepositoryRestResource注解即可实现。
@RepositoryRestResource(path = "bs",collectionResourceRel = "bs",itemResourceRel = "b")
注解的path属性表示将所有请求路径中的books全都修改为bs,返回的JSON集合单个book的key修改为b.
默认的查询方法支持分页查询,排序查询以及按照id查询,如果开发者想要按照某个属性查询,只需在定义相关方法并暴露出去即可:
@RepositoryRestResource(path = "bs",collectionResourceRel = "bs",itemResourceRel = "b")
public interface BookRepository extends JpaRepository<Book, Integer> {
@Override
@RestResource(exported = false)
void deleteById(Integer integer);
@RestResource(path = "author",rel = "author")
List<Book> findByAuthorContains(@Param("author") String author);
@RestResource(path = "name",rel = "name")
Book findByNameEquals(@Param("name") String name);
}
只需要在BookRepository定义相关查询方法即可了,可以不用添加restresource注解。
通过/bs/search 来查看该实体类暴露了哪些查询方法。
快速构建RESTful服务除了结合Spring Data JPA之外,也可以结合MongoDB来实现,步骤如下:
添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
和之前的MongoDB设置一样,配置见上文:
创建实体类:
public class Book {
private Integer id;
private String name;
private String author;
//省略getter/setter
}
创建BookRepository:
import org.springframework.data.mongodb.repository.MongoRepository;
public interface BookRepository extends MongoRepository<Book,Integer> {
}
这样一个RESTful服务就搭建成功了。(注意要启动MongoDB)
对于上面的restful服务可能理解起来有困难,所以我单独写了这个模块来加强认识。
RESTful可以通过一套统一的接口为Web,iOS和Android提供服务。另外对于广大平台来说,比如微博开放平台,微信开放平台等,它们不需要有显式的前端,只需要一套提供服务的接口,RESTful无疑是最好的选择。(所以这就是我们使用PostMan来进行接口测试的原因)
传统的API接口:一般来说删除一个数据delete/{id} ,更新就是update/{id}等
url是一种资源,在之前一直用的是get和post.
基于REST构建的api就是restful风格:
GET /rest/api/getDogs --> GET /rest/api/dogs 获取所有小狗狗
GET /rest/api/addDogs --> POST /rest/api/dogs 添加一个小狗狗
GET /rest/api/editDogs/:dog_id --> PUT /rest/api/dogs/:dog_id 修改一个小狗狗
GET /rest/api/deleteDogs/:dog_id --> DELETE /rest/api/dogs/:dog_id 删除一个小狗狗
REST 是面向资源的,这个概念非常重要,而资源是通过 URI 进行暴露。
区别HTTP(面向无状态的)
对于RESTful 需要在日后继续加强。