Java 8 提供了很多工具,能让你连接 MongoDB,查询数据,甚至转换结果。
欢迎来到新的系列教程:使用 Java 8 学习 MongoDB。通过这份教程,希望大家对 Java 8 提供的大数据和新特性有基本的概念,同时我们会在本教程广泛使用这些新特性。
在这篇文章里面,我们将会学习:
-
什么是 MongoDB
-
如何安装 MongoDB
-
如何在 MongoDB 中创建一个集合
-
如何在集合中插入文档
-
如何编写一个简单的 Java 程序连接 MongoDB,并从 Mongo 集合中查找某一个值
下面是一些关于 MongoDB 的简单介绍。
MongoDB
MongoDB 是一种 NoSQL 类型的数据库。数据按照 BSON 格式进行存储,所有的数据按照键/值对(key/value)的方式进行存储。其中 key 表示属性,value 表示相对于 key 存储的属性值。
Documents
在 Mongo 里面,文档代表着一种能存储任意数量的键值对的数据结构。比如 “员工” 数据可以表示为一个文档,其中 name,address,age 和它们的值以键值对(key-value)的方式存储在该文档中。要注意的是,文档存储为一种二进制 JSON 格式,称为 BSON(Binary JSON)。
下面是一个文档的例子:
{
"_id": {
"$oid" : "58eb8c2b1de2b36bfcc74326"
},
"name": "Shamik Mitra"
}
集合
在 Mongo 中,经常把具有相同数据结构的文档放入一个容器,并将其称为集合。可以把集合视为关系数据库中的表,每一行数据其实就对应着一个文档。所以我们可以认为 Employee 集合包含了多个 Employee 文档。不过要注意的是,这只是逻辑上面的一种理解。按照定义,一个集合可包含任意类型的文档 — 例如,一个集合能包含 Employee 文档以及 Car 文档。这些数据结构是没有约束的。
注意:在设计数据结构的时候,创建的集合最好与文档有着相似的结构。
无模式(No Schema)
这是 SQL 和 NoSQL 数据库之间的主要区别之一。对我而言,我不喜欢这些术语。我更倾向于使用关系数据库和非关系数据库来描述他们。使用 NoSQL,不需要任何预定义的模式 — 它能包含任何 BSON 格式的数据。具体来说,任何数据结构都适用于无模式数据库,因此它适用于存储非结构化的数据。
这使得开发者能更容易的进行开发,因为在关系型数据库中, 所有的数据都有一个固定的模式。比如一张员工表中有 name, age 和 address 字段,它存储的数据都保持着相同的数据结构。如果现在需要修改结构,比方说我们想要添加一个 gender 属性,则必须要改变表的模式以纳入新的属性。但是在 Mongo 中,因为它是模式自由的,所以这些都不需要,我们可将任何数据结构和任意属性的组合放在一起,可存储任意格式的数据。
注意:虽然 MongoDB 是模式自由的,但在设计的过程中,逻辑上我们把相同结构的文档放到一个集合中,所以会存在一个隐形的模式。
横向扩展
大数据的成功依赖于它的横向扩展能力。Mongo 作为大数据技术的一部分,当然也是支持的。通过横向扩展,MongoDB 能够将数据分发到多个节点上,每个节点都可以是低配的电脑,我们也可以轻松地添加和删除这些节点。所以当我们需要存储更多的数据时,可以添加新的节点而不影响现有的架构,然而在纵向扩展中(例如 RDBMS 使用的方式),我们则需要一台超级计算机,并且数据采用中央化存储的方式。
注意:在向多节点分发数据的时候,Mongo 有一定的容错机制。如果一个节点挂掉了之后,我们会使用其他的节点获取数据。当然,如果使用纵向扩展,因为数据是中央化存储,如果出现故障的话,我们会丢失所有的数据,这将会导致单点故障。
分片(Sharding)
分片是 MongoDB 将庞大的数据块拆分成小的数据块的技术,之后,它会为每个块创建一份副本,然后这些块会被分发到多个节点中。当查询时,服务器通过元数据查询到数据所在的节点,然后从该节点返回查询结果。
Mongo 安装步骤
-
下载最新的 Mongo ZIP 包(下载地址)
-
创建目录 D:\InstalledApps,解压 ZIP 的文件到这个目录
-
将解压的文件夹重命名为 mongodb
-
在 mongodb 文件夹下新建 data 文件夹:D:\InstalledApps\mongodb\data
-
打开 cmd(命令行),输入以下命令:
cd D:\InstalledApps\mongodb\bin
进入 D:\InstalledApps\mongodb\bin
-
通过命令启动 MongoDB Server:
mongod.exe --dbpath D:\InstalledApps\mongodb\data
MongoDB Server 启动在 localhost:27017
安装 Mongo 客户端
我们将会使用 RoboMongo 作为 mongo 客户端。它有漂亮的图形化界面,所以我们可以轻松创建集合,并通过使用 RoboMongo GUI 添加文档。
RoboMongo 下载地址(戳这里),解压 ZIP 文件后,双击 RoboMongo.exe 运行程序。它将启动 RoboMongo GUI。
通过下面的步骤连接到 Mongo Server:
-
Host: localhost 和 port: 27017。
-
创建数据库:在 RoboMongo 图形化界面中,右键点击右侧面板的计算机图标以创建一个数据库。使用 test 作为数据库的名字。
-
创建集合:右键点击右侧面板的集合图标。创建一个新的集合,命名为 Employee。
-
插入一个文档:右键单击 Employee 集合,然后点击 Insert Document,在文本区域粘贴以下内容:
{ "name" : "Shamik" , "address" : "1 Nivedita Lane" , "age" : 32}
点击 save,这将在 Mongo Server 中插入文档。
这些操作其实也可以通过 Mongo 的控制台来完成。
编写 Java 代码
上述的步骤完成后,就该写 Java 代码连接 MongoDB 数据库了。要用到的工具有:
-
Eclipse Neon
-
适用于 Eclipse 的 Maven 插件
-
Java 8
第一步
首先,在 Eclipse 里创建一个名为 mongoExample 的 Maven 项目,并将 Java 编译器版本设置为 1.8。没有的话请去网上下载。
第二步
编写一个 pom.xml,如下所示:
<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.example</groupId>
<artifactId>mongoExample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
</project>
可以看到我们用了 mongo-java-driver 这个库配合 Java 8 来连接 Mongo Server。
创建 MongoContext 类
接下来,我们使用 Java 代码连接 Mongo Server。我们先写一个顶级 API 类,把连接 MongoDB Server 的操作抽象出来,并给调用者提供一些辅助方法:
package com.example.config;
import java.net.UnknownHostException;
import java.util.function.Function;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.MongoClient;
public class MongoContext {
private static MongoContext ctx = new MongoContext();
private MongoClient client;
private DB db;
private MongoContext(){
try{
init();
}catch(Exception ex){
ex.printStackTrace();
}
}
private void init() throws UnknownHostException{
this.client = new MongoClient("localhost" , 27017);
}
public static MongoContext get(){
return ctx;
}
public MongoContext connectDb(String dbname){
if(db !=null){
throw new RuntimeException("Already conected to " + db.getName() + "can't connect " + dbname);
}
this.db = client.getDB(dbname);
System.out.println("DB Details :: " + db.getName());
return ctx;
}
public <T,X> DBCursor findByKey(String collectionName,String key,T value,Function<T,X> convertDataType){
DBCollection collection = db.getCollection(collectionName);
BasicDBObject searchQuery = new BasicDBObject();
searchQuery.put(key, convertDataType.apply(value));
System.out.println("search Query ::" + searchQuery);
DBCursor cursor = collection.find(searchQuery);
return cursor;
}
}
代码说明
在代码中,我创建了一个单例对象 MongoContext。它的 init 方法会创建一个 MongoClient 类来连接 MongoDB。注意 MongoClient 类来自 Mongo Java Driver 这个库: this.client = new MongoClient("localhost" , 27017);
然后,我定义了静态的 get() 方法来返回类内部维护的 MongoContext 单例对象。
至此,成功建立了到 Mongo server 的连接。
下一步,指定需连接的数据库。我写了个泛型方法 connectDb(String dbName),调用它就可以指定要连接到的数据库。注意这个方法返回的类型是类的单例对象 MongoContext 本身。这里我用了链式代码的风格(Fluent API)来写。
下一步,从数据库中查询到我们需要的文档。
为此,我写了个泛型方法:
public <T,X> DBCursor findByKey(String collectionName,String key,T value,Function<T,X> convertDataType)
方法接受四个参数:
collectionName:代表要查询的集合名称,比如现在我们要查询的就是 Employee 集合。记住,可把集合看作关系型数据库里的表。
key:代表要从集合里查询的键,相当于在 SQL where 查询子句里指定的字段名。
value:代表键所对应的值,同样相当于 where 里给的值。
Function<T,X>:这里运用了 Java 8 的函数接口,可以把数据从一个类型 T 转换成另一个类型 X。( T -> X)。我们的这个泛型方法 findbyKey 需要用到这东西。
然而,现在的情况是,我们并不知道调用者到底想查询什么键,于是就会比较麻烦。例如说,在 Employee 集合里,员工姓名这个键是字符串类型,而年龄是整型。如果调用者想以姓名来查询,那对应的数据类型就得是字符串,同理查询年龄就得用整型。因此,我们得用一种特殊方法,能把提供的值转换成对应的类型。现在呢,我们用 Lambda 表达式来实现我们的需求。
在方法内,我们得到了要查询的集合名称,然后创建了 BasicDataObject 类的一个对象 searchQuery。随后,把参数里的键和值提供给对象来提供查询信息。最后,我们执行查询,并把得到的查询结果放进一个 DBCursor 对象里以进行维护。
代码测试
新建一个 Main.java 类:
/**
*
*/
package com.example.mongotest;
import com.example.config.MongoContext;
import com.mongodb.DBCursor;
public class Main {
public static void main(String[] args) {
DBCursor result = MongoContext.get().connectDb("test").findByKey("Employee", "age", 32,
(value) -> new Integer(value));
while (result.hasNext()) {
System.out.println(result.next());
}
}
}
在代码中,我创建了个 MongoContext 对象,连接到测试用的服务器。随后把要查询的集合名 Employee、键名 age 以及值 32 传递给 findByKey 方法调用。我还向方法提供了一条 Lambda 表达式,代表键 age 的对应数据类型是整型。
注意在 findByKey 的方法定义里,我用泛型参数 T 来代表值的数据类型。
那么,为什么方法要用函数接口?其实我可以让调用者直接给我提供类型 T 的信息来表示值的类型,但是,这样做有个好处,调用者可以在进行查询前,在函数接口里验证或检查用户所提供的查询数据是否合法、被修改之类。
代码执行结果:
DB Details :: test
search Query ::{ "age" : 32}
{ "_id" : { "$oid" : "58ecde108b308657b44937b1"} , "name" : "Shamik" , "adress" : "1 Nivedita Lane" , "age" : 32}