MongoDB实战
Java实战MongoDB
要在Java应用程序中访问和使用MongoDB,需要使用Java MongoDB驱动程序。Java MongoDB驱动程序是一个库,提供了在Java应用程序中访问MongoDB服务器所需的对象和功能。 mongodb的Maven依赖
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.6.0</version>
</dependency>
Java MongoDB驱动程序提供了多个对象,让您能够连接到MongoDB数据库,进而查找和操作集合中的对象。这些对象分别表示MongoDB服务器连接、数据库、集合、游标和文档,提供了在Java应用程序中集成MongoDB数据库中的数据所需的功能。
理解Java对象MongoClient
Java对象MongoClient提供了连接到MongoDB服务器和访问数据库的功能。要在Java应用程序中实现MongoDB,首先需要创建一个MongoClient对象实例,然后就可使用它来访问数据库、设置写入关注以及执行其他操作(如表10.1所示)。 要创建MongoClient对象实例,需要从驱动程序中导入它,再使用合适的选项调用new MongoClient(),如下所示:
import com.mongodb.MongoClient;
MongoClient mongoClient = new MongoClient("localhost", 27017);
在需要密码的情况下,使用MongoCredential配置密码信息,使用ServerAddress配置服务信息。
ServerAddress serverAddress = new ServerAddress("localhost", 27017);
MongoCredential mongoCredential = MongoCredential.createCredential("admin", "admin", "123456".toCharArray());
// 第一个"admin" 为账号,第二个"admin"为创建账户时的数据库名称,第三个参数为密码
MongoClient mongoClient = new MongoClient(serverAddress,mongoCredential, MongoClientOptions.builder().build());
// MongoClientOptions 是连接的相关配置,类似数据库连接池的相关配置,使用默认即可
MongoClient的构造函数可接受多种不同形式的参数,下面是其中的一些。
- MongoClient():创建一个客户端实例,并连接到本地主机的默认端口。
- MongoClient(String host):创建一个客户端实例,并连接到指定主机的默认端口。
- MongoClient(String host, int port):创建一个客户端实例,并连接到指定主机的指定端口。
- MongoClient(MongoClientURI uri):创建一个客户端实例,并连接到连接字符串uri指定的服务器。uri使用如下格式: mongodb://username:password@host:port/database?options 创建MongoClient对象实例后,Java对象MongoClient的方法
- close() 关闭连接
- connect(address) 连接到另一个数据库,该数据库由一个DBAddress对象指定,如connect(new DBAddress(host, port, dbname))
- dropDatabase(dbName) 删除指定的数据库
- getDatabaseNames() 返回数据库名称列表
- getDB(dbName) 返回一个与指定数据库相关联的DB对象
- setReadPreference(preference) 将客户端的读取首选项设置为下列值之一: ReadPreference.primary() ReadPreference.primaryPreferred() ReadPreference.secondary() ReadPreference.secondaryPreferred() ReadPreference.nearest()
- setWriteConcern concern) 设置客户端的写入关注,可能取值如下: WriteConcern.SAFE WriteConcern.JOURNALED WriteConcern.JOURNAL_SAFE WriteConcern.NONE WriteConcern.MAJORITY
理解Java对象DB
Java对象DB提供了身份验证、用户账户管理以及访问和操作集合的功能。要获取DB对象实例,最简单的方式是调用MongoClient对象的方法getDB()。 下面的示例使用MongoClient获取一个DB对象示例:
import com.mongodb.MongoClient;
import com.mongodb.DB;
MongoClient mongoClient = new MongoClient("localhost", 27017);
DB db = mongoClient.getDB("myDB");
创建DB对象实例后,就可使用它来访问数据库了。
- addUser(username, password) 在当前数据库中添加一个具有读写权限的用户账户
- authenticate(username, password) 使用用户凭证向数据库验证身份
- createCollection(name, options) 在服务器上创建一个集合。参数options是一个BasicDBObject(将在后面介绍),指定了集合创建选项
- dropDatabase() 删除当前数据库
- getCollection(name) 返回一个与name指定的集合相关联的DBCollection对象
- getLastError() 返回最后一次访问数据库导致的错误
- getLastError(w, wtimeout, fsync) 设置数据库写入操作的写入关注、超时和fsync设置
- isAuthenticated() 如果数据库连接是经过身份验证的,就返回true
- removeUser(username) 从数据库删除用户账户
- setReadPreference(prefer ence) 与前一小节介绍的MongoClient的同名方法相同
- setWriteConcern(concern) 与前一小节介绍的MongoClient的同名方法相同
理解Java对象DBCollection
Java对象DBCollection提供了访问和操作集合中文档的功能。要获取DBCollection对象实例,最简单的方式是使用DB对象的方法getCollection()。 下面的实例使用DB对象获取一个DBCollection对象实例:
import com.mongodb.MongoClient;
import com.mongodb.DB;
import com.mongodb.DBCollection;
MongoClient mongoClient = new MongoClient("localhost", 27017);
DB db = mongoClient.getDB("myDB");
DBCollection collection = db.getCollection("myCollection");
创建DBCollection对象实例后,就可使用它来访问集合了。
- aggregate(pipeline) 应用聚合选项流水线。流水线中的每个选项都是一个表示聚合操作的BasicDBObject。BasicDBObject将在本章后面讨论,而聚合操作在第9章讨论过
- count() 返回集合中的文档数
- count(query) 返回集合中与指定查询匹配的文档数。参数query是一个描述查询运算符的BasicDBObject
- distinct(key, [query]) 返回指定字段的不同值列表。可选参数query让您能够限制要考虑哪些文档
- drop() 删除集合
- dropIndex(keys) 删除keys指定的索引
- ensureIndex(keys, [options]) 添加keys和可选参数options描述的索引,这两个参数的类型都是DBObject
- find([query], [fields]) 返回一个表示集合中文档的DBCursor对象。可选参数query是一个BasicDBObject对象,让您能够限制要包含的文档;可选参数fields也是一个BasicDBObject对象,让您能够指定要返回文档中的哪些字段
- findAndModify(query, [sort], update) 以原子方式查找并更新集合中的文档,并返回修改后的文档
- findOne([query], [fields], [sort]) 返回一个DBObject对象,表示集合中的一个文档。可选参数query是一个BasicDBObject对象,让您能够限制要包含的文档;可选参数fields是一个BasicDBObject对象,让您能够指定要返回文档中的哪些字段;可选参数sort也是一个BasicDBObject对象,让您能够指定文档的排列顺序
- getStats() 返回一个CommandResult对象,其中包含当前集合的信息
- group(key, cond, initial, reduce, [finalize]) 对集合执行分组操作(参见第9章)
- insert(object, [concern]) 在集合中插入一个对象
- insert(objects, [concern]) 将一个对象数组插入到集合中
- mapReduce(map, reduce, output, query) 对集合执行映射-归并操作(参见第9章)
- remove([query], [concern])) 从集合中删除文档。如果没有指定参数query,将删除所有文档;否则只删除与查询匹配的文档
- rename(newName) 重命名集合
- save(dbObject, [concern]) 将对象保存到集合中。如果指定的对象不存在,就插入它
- setReadPreference(preference) 与前面介绍的MongoClient的同名方法相同
- setWriteConcern(concern) 与前面介绍的MongoClient的同名方法相同
- update(query, update, [upsert], [multi]) 更新集合中的文档。参数query是一个BasicDBObject对象,指定了要更新哪些文档;参数update是一个BasicDBObject对象,指定了更新运算符;布尔参数upsert指定是否执行upsert;布尔参数multi指定更新多个文档还是只更新第一个文档
理解Java对象DBCursor
Java对象DBCursor表示MongoDB服务器中的一组文档。使用查找操作查询集合时,通常返回一个DBCursor对象,而不是向Java应用程序返回全部文档对象,这让您能够在Java中以受控的方式访问文档。 DBCursor对象以分批的方式从服务器取回文档,并使用一个索引来迭代文档。在迭代期间,当索引到达当前那批文档末尾时,将从服务器取回下批文档。 下面的示例使用查找操作获取一个DBCursor对象实例:
import com.mongodb.MongoClient;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
MongoClient mongoClient = new MongoClient("localhost", 27017);
DB db = mongoClient.getDB("myDB");
DBCollection collection = db.getCollection("myCollection");
DBCursor cursor = collection.find();
创建DBCursor对象实例后,就可使用它来访问集合中的文档了。
- batchSize(size) 指定每当读取到当前已下载的最后一个文档时,游标都将再返回多少个文档
- close() 关闭游标并释放它占用的服务器资源
- copy() 返回游标的拷贝
- count() 返回游标表示的文档数
- hasNext() 如果游标中还有其他可供迭代的对象,就返回true
- iterator() 为游标创建一个迭代器对象
- limit(size) 指定游标可最多表示多少个文档
- next() 将游标中的下一个文档作为DBObject返回,并将索引加1
- size() 计算与查询匹配的文档数,且不考虑limit()和skip()的影响
- skip(size) 在返回文档前,跳过指定数量的文档
- sort(sort) 按DBObject参数sort指定的方式对游标中的文档排序
- toArray([max]) 从服务器检索所有的文档,并以列表的方式返回。如果指定了参数max,则只检索指定数量的文档
理解Java对象BasicDBObject和DBObject
正如您在本书前面介绍MongoDB shell的章节中看到的,大多数数据库、集合和游标操作都将对象作为参数。这些对象定义了查询、排序、聚合以及其他运算符。文档也是以对象的方式从数据库返回的。 在MongoDB shell中,这些对象是JavaScript对象,但在Java中,表示文档和请求参数的对象都是特殊对象。服务器返回的文档是用DBObject对象表示的,这种对象提供了获取和设置文档中字段的功能。对于用作请求参数的对象,是用BasicDBObject表示的。 BasicDBObject是使用下面的构造函数创建的,其中key为JavaScript属性的名称,而value为JavaScript属性的值:
BasicDBObject(key, value)
要给BasicDBObject对象添加属性,可使用方法append()。 例如,假设您要创建一个查询对象,用于查找第一个字母为a且长度超过6的单词。在MongoDB shell中,可使用如下代码:
find({"first":"a", "size": {"$gt": 6}});
而在Java中,必须使用如下语法创建一个将传递给方法find()的BasicDBObject对象:
BasicDBObject query = new BasicDBObject("first", "a");
query.append("size", new BasicDBObject("$gt", 6));
使用这种方式,可创建需要传递给MongoDB数据库请求的对象。有关执行查询、更新、聚合等需要的对象的结构,请参阅介绍MongoDB shell的第5~9章。 DBObject提供了如下方法,可用于获取和设置服务器请求返回的文档的属性。
- get(fieldName):返回文档中指定字段的值。
- put(fieldName, value):设置文档中指定字段的值。
- putAll(map):将一个Java对象Map作为参数,并根据其中指定的键/值对设置文档的字段。
- toMap():以Map对象的方式返回文档中所有的字段。
使用Java查找文档
在Java应用程序中需要执行的一种常见任务是,查找一个或多个需要在应用程序中使用的文档。在Java中查找文档与使用MongoDB shell查找文档类似,您可获取一个或多个文档,并使用查询来限制返回的文档。
使用Java从MongoDB获取文档
DBCollection对象提供了方法find()和findOne(),它们与MongoDB shell中的同名方法类似,也分别查找一个和多个文档。 调用findOne()时,将以DBObject对象的方式从服务器返回单个文档,然后您就可根据需要在应用程序中使用这个对象,如下所示:
DBObject doc = myColl.findOne();
DBCollection对象的方法find()返回一个DBCursor对象,这个对象表示找到的文档,但不取回它们。可以多种不同的方式迭代DBCursor对象。 可以使用while循环和方法hasNext()来判断是否到达了游标末尾,如下所示:
DBCursor cursor = myColl.find();
while(cursor.hasNext()){
DBObject doc = cursor.next();
System.out.println(doc.toString());
}
还可使用方法toArray()将游标转换为Java Array对象,再使用任何Java技术迭代该对象。例如,下面的代码查找集合中的所有文档,再将前5个转换为一个List并迭代它:
DBCursor cursor = collection.find();
List<DBObject> docs = cursor.toArray(5);
for(final DBObject doc : docs) {
System.out.println(doc.toString());
}
使用Java在MongoDB数据库中查找特定的文档
一般而言,您不会想从服务器检索集合中的所有文档。方法find()和findOne()让您能够向服务器发送一个查询对象,从而像在MongoDB shell中那样限制文档。 要创建查询对象,可使用本章前面描述的BasicDBObject对象。对于查询对象中为子对象的字段,可创建BasicDBObject子对象;对于其他类型(如整型、字符串和数组)的字段,可使用相应的Java类型。 例如,要创建一个查询对象来查找size=5的单词,可使用下面的代码:
BasicDBObject query = new BasicDBObject("size", 5);
myColl.find(query);
要创建一个查询对象来查找size>5的单词,可使用下面的代码:
BasicDBObject query = new BasicDBObject("size",
new BasicDBObject("$gt", 5));
myColl.find(query);
要创建一个查询对象来查找第一个字母为x、y或z的单词,可使用String数组,如下所示:
BasicDBObject query = new BasicDBObject("first",
new BasicDBObject("$in", new String[]{"x", "y", "z"}));
myColl.find(query);
利用上述技巧可创建需要的任何查询对象:不仅能为查找操作创建查询对象,还能为其他需要查询对象的操作这样做。
使用Java计算文档数
使用Java访问MongoDB数据库中的文档集时,您可能想先确定文档数,再决定是否检索它们。无论是在MongoDB服务器还是客户端,计算文档数的开销都很小,因为不需要传输实际文档。 DBCursor对象的方法count()让您能够获取游标表示的文档数。例如,下面的代码使用方法find()来获取一个DBCursor对象,再使用方法count()来获取文档数:
DBCursor cursor = wordsColl.find();
Integer itemCount = cursor.count();
itemCount的值为与find()操作匹配的单词数。
使用Java对结果集排序
从MongoDB数据库检索文档时,一个重要方面是对文档进行排序。只想检索特定数量(如前10个)的文档或要对结果集进行分页时,这特别有帮助。排序选项让您能够指定用于排序的文档字段和方向。 DBCursor对象的方法sort()让您能够指定要根据哪些字段对游标中的文档进行排序,并按相应的顺序返回文档。方法sort()将一个BasicDBObject作为参数,这个对象将字段名用作属性名,并使用值1(升序)和-1(降序)来指定排序顺序。 例如,要按字段name升序排列文档,可使用下面的代码:
BasicDBObject sorter = new BasicDBObject("name", 1);
myCollection.find().sort(sorter);
在传递给方法sort()的对象中,可指定多个字段,这样文档将按这些字段排序。还可对同一个游标调用sort()方法多次,从而依次按不同的字段进行排序。例如,要首先按字段name升序排列,再按字段value降序排列,可使用下面的代码:
BasicDBObject sorter = new BasicDBObject("name", 1);
sorter.append("value", -1);
myCollection.find().sort(sorter);
也可使用下面的代码:
BasicDBObject sorter1 = new BasicDBObject("name", 1);
BasicDBObject sorter2 = new BasicDBObject("value", -1);
myCollection.find().sort(sorter1).sort(sorter2);
使用Java限制结果集
在大型系统上查询较复杂的文档时,常常需要限制返回的内容,以降低对服务器和客户端网络和内存的影响。要限制与查询匹配的结果集,方法有三种:只接受一定数量的文档;限制返回的字段;对结果分页,分批地获取它们。
使用Java限制结果集的大小
要限制find()或其他查询请求返回的数据量,最简单的方法是对find()操作返回的DBCursor对象调用方法limit(),它让DBCursor对象返回指定数量的文档,可避免检索的对象量超过应用程序的处理能力。 例如,下面的代码只显示集合中的前10个文档,即便匹配的文档有数千个:
DBCursor cursor = wordsColl.find();
cursor.limit(10);
while(cursor.hasNext()){
DBObject word = cursor.next();
System.out.println(word);
}
使用Java限制返回的字段
为限制文档检索时返回的数据量,另一种极有效的方式是限制要返回的字段。文档可能有很多字段在有些情况下很有用,但在其他情况下没用。从MongoDB服务器检索文档时,需考虑应包含哪些字段,并只请求必要的字段。 要对DBCollection对象的方法find()从服务器返回的字段进行限制,可使用参数fields。这个参数是一个BasicDBObject对象,它使用值true来包含字段,使用值false来排除字段。 例如,要在返回文档时排除字段stats、value和comments,可使用下面的fields参数:
BasicDBObject fields = new BasicDBObject("stats", false);
fields.append("value", false);
fields.append("comments", false);
DBCursor cursor = myColl.find(null, fields);
这里将查询对象指定成了null,因为您要查找所有的文档。 仅包含所需的字段通常更容易。例如,如果只想返回first字段为t的文档的word和size字段,可使用下面的代码:
BasicDBObject query = new BasicDBObject("first", "t");
BasicDBObject fields = new BasicDBObject("word", true);
fields.append("size", true);
DBCursor cursor = myColl.find(query, fields);
使用Java将结果集分页
为减少返回的文档数,一种常见的方法是进行分页。要进行分页,需要指定要在结果集中跳过的文档数,还需限制返回的文档数。跳过的文档数将不断增加,每次的增量都是前一次返回的文档数。 要对一组文档进行分页,需要使用DBCursor对象的方法limit()和skip()。方法skip()让您能够指定在返回文档前要跳过多少个文档。 每次获取下一组文档时,都增大方法skip()中指定的值,增量为前一次调用limit()时指定的值,这样就实现了数据集分页。 例如,下面的语句查找第11~20个文档:
DBCursor cursor = collection.find();
cursor.limit(10);
cursor.skip(10);
进行分页时,务必调用方法sort()来确保文档的排列顺序不变。
使用Java查找不同的字段值
一种很有用的MongoDB集合查询是,获取一组文档中某个字段的不同值列表。不同(distinct)意味着纵然有数千个文档,您只想知道那些独一无二的值。 DBCollection对象的方法distinct()让您能够找出指定字段的不同值列表,这种方法的语法如下:
distinct(key, [query])
其中参数key是一个字符串,指定了要获取哪个字段的不同值。要获取子文档中字段的不同值,可使用句点语法,如stats.count。参数query是一个包含标准查询选项的对象,指定了要从哪些文档中获取不同的字段值。 例如,假设有一些包含字段first、last和age的用户文档,要获取年龄超过65岁的用户的不同姓,可使用下面的操作:
BasicDBObject query = new BasicDBObject("age",
new BasicDBObject("$gt", 5));
lastNames = myCollection.distinct('last', query);
方法distinct()返回一个数组,其中包含指定字段的不同值,例如:
["Smith", "Jones", ...]
在Java应用程序中对查找操作结果进行分组
在Java中对大型数据集执行操作时,根据文档的一个或多个字段的值将结果分组通常很有用。这也可以在取回文档后使用代码来完成,但让MongoDB服务器在原本就要迭代文档的请求中这样做,效率要高得多。 在Java中,要将查询结果分组,可使用DBCollection对象的方法group()。分组请求首先收集所有与查询匹配的文档,再对于指定键的每个不同值,都在数组中添加一个分组对象,对这些分组对象执行操作,并返回这个分组对象数组。 方法group()的语法如下:
group({key, cond , initial, reduce, [finalize]})
其中参数key、cond和initial都是BasicDBObject对象,指定了要用来分组的字段、查询以及要使用的初始文档;参数reduce和finalize为String对象,包含以字符串方式表示的JavaScript函数,这些函数将在服务器上运行以归并文档并生成最终结果。有关这些参数的更详细信息,请参阅第9章。 为演示这个方法,下面的代码实现了简单分组,它创建了对象key、cond和initial,并以字符串的方式传入了一个reduce函数:
BasicDBObject key = new BasicDBObject("first", true);
BasicDBObject cond = new BasicDBObject("last", "a");
cond.append("size", 5);
BasicDBObject initial = new BasicDBObject("count", 0);
String reduce = "function (obj, prev) { prev.count++; }";
DBObject result = collection.group(key, cond, initial, reduce);
方法group()以DBObject对象的方式返回分组结果。下面的代码逐项地显示了分组结果的内容:
for (Object name: group.toMap().values()) {
System.out.println(name);
}
从Java应用程序发出请求时使用聚合来操作数据
在Java应用程序中使用MongoDB时,另一个很有用的工具是聚合框架。DBCollection对象提供了对数据执行聚合操作的方法aggregate(),这个方法的语法如下:
aggregate(operator, [operator, ...])
参数operator是一系列运算符对象,提供了用于聚合数据的流水线。这些运算符对象是使用聚合运算符创建的DBObject对象。聚合运算符在第9章介绍过,您现在应该熟悉它们。 例如,下面的代码定义了运算符$group和$limit,其中运算符$group根据字段word进行分组(并将该字段的值存储在结果文档的_id字段中),使用$avg计算size字段的平均值(并将结果存储在average字段中)。请注意,在聚合运算中引用原始文档的字段时,必须在字段名前加上$:
BasicDBObject groupOps = new BasicDBObject("_id", "$word");
groupOps.append("average", new BasicDBObject("$avg", "$size"));
BasicDBObject group = new BasicDBObject("$group", groupOps);
BasicDBObject limit = new BasicDBObject("$limit", 10);
AggregationOutput result = collection.aggregate(group, limit);
方法aggregate()返回一个包含聚合结果的AggregationOutput对象。AggregationOutput对象的方法results()返回一个可迭代的对象,您可使用它来访问结果。为演示这一点,下面的代码逐项显示聚合结果的内容:
for (Iterator<DBObject> items = result.results().iterator(); items. hasNext();){
System.out.println(items.next());
}
使用Java添加文档
在Java中与MongoDB数据库交互时,一项重要的任务是在集合中插入文档。要插入文档,首先要创建一个表示该文档的BasicDBObject对象。插入操作将BasicDBObject对象以BSON的方式传递给MongoDB服务器,以便能够插入到集合中。 有新文档的BasicDBObject版本后,就可将其存储到MongoDB数据库中,为此可对相应的DBCollection对象实例调用方法insert()。方法insert()的语法如下,其中参数docs可以是单个文档对象,也可以是一个文档对象数组:
insert(docs)
例如,下面的示例在集合中插入单个文档和一个文档数组:
BasicDBObject doc1 = new BasicDBObject("name", "Fred");
WriteResult result = myColl.insert(doc1);
BasicDBObject doc2 = new BasicDBObject("name", "George");
BasicDBObject doc3 = new BasicDBObject("name", "Ron");
WriteResult result = myColl.insert(new BasicDBObject[]{doc2, doc3});
请注意,方法insert()返回一个WriteResult对象,其中包含有关写入操作的信息。
使用Java删除文档
在Java中,有时候需要从MongoDB集合中删除文档,以减少消耗的空间,改善性能以及保持整洁。DBCollection对象的方法remove()使得从集合中删除文档非常简单,其语法如下:
remove([query])
其中参数query是一个BasicDBObject对象,指定要了删除哪些文档。请求将query指定的字段和值与文档的字段和值进行比较,进而删除匹配的文档。如果没有指定参数query,将删除集合中的所有文档。 例如,要删除集合words_stats中所有的文档,可使用如下代码:
DBCollection collection = myDB.getCollection('word_stats');
WriteResult results = collection.remove();
下面的代码删除集合words_stats中所有以a打头的单词:
DBCollection collection = myDB.getCollection('word_stats');
BasicDBObject query = new BasicDBObject("first", "a");
collection.remove(query);
使用Java保存文档
一种更新数据库中文档的便利方式是,使用DBCollection对象的方法save(),这种方法接受一个DBObject作为参数,并将其保存到数据库中。如果指定的文档已存在于数据库中,就将其更新为指定的值;否则就插入一个新文档。 方法save()的语法如下,其中参数doc是一个要保存到集合中的DBObject或BasicDBObject对象:
save(doc)
使用Java更新文档
将文档插入集合后,经常需要使用Java根据数据变化更新它们。DBCollection对象的方法update()让您能够更新集合中的文档,它多才多艺,但使用起来非常容易。下面是方法update()的语法:
update(query, update, [upsert], [multi])
参数query是一个BasicDBObject对象,指定了要修改哪些文档。请求将判断query指定的属性和值是否与文档的字段和值匹配,进而更新匹配的文档。参数update是一个BasicDBObject对象,指定了要如何修改与查询匹配的文档。第8章介绍了可在这个对象中使用的更新运算符。 参数upsert是个布尔值;如果为true且没有文档与查询匹配,将插入一个新文档。参数multi也是一个布尔值;如果为true将更新所有与查询匹配的文档;如果为false将只更新与查询匹配的第一个文档。 例如,对于集合中字段category为new的文档,下面的代码将其字段category改为old。在这里,upsert被设置为false,因此即便没有字段category为new的文档,也不会插入新文档;而multi被设置为true,因此将更新所有匹配的文档:
BasicDBObject query = new BasicDBObject("category", "New");
BasicDBObject update = new BasicDBObject("$set",
new BasicDBObject("category", "Old"));
update(query, update, false, true);
使用Java更新或插入文档
在Java中,DBCollection对象的方法update()的另一种用途是,用于执行upsert操作。upsert操作先尝试更新集合中的文档;如果没有与查询匹配的文档,就使用$set运算符来创建一个新文档,并将其插入到集合中。下面显示了方法update()的语法:
update(query, update, [upsert], [multi])
参数query指定要修改哪些文档;参数update是一个BasicDBObject对象,指定了要如何修改与查询匹配的文档。要执行upsert操作,必须将参数upsert设置为true,并将参数multi设置为false。 例如,下面的代码对name=myDoc的文档执行upsert操作。运算符$set指定了用来创建或更新文档的字段。由于参数upsert被设置为true,因此如果没有找到指定的文档,将创建它;否则就更新它:
BasicDBObject query = new BasicDBObject("name", "myDoc");
BasicDBObject setOp = new BasicDBObject("name", "myDoc");
setOp.append("number", 5);
setOp.append("score", 10);
BasicDBObject update = new BasicDBObject("$set", setOp);
update(query, update, true, false);
使用SpringBoot实战MongoDB
springboot整合是采用原生的mongo-driver 来实现,可以动态链接多个库,无需关注整合springboot,只要自己实现springboot-starter。 快速入门文档:http://mongodb.github.io/mongo-java-driver/3.11/driver/getting-started/quick-start/#mongodb-driver-quick-start 。介绍的非常详细,完全可以操作入门。 Maven依赖
<!--SpringBoot整合MongoDB-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!--MongoDB相关依赖-->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>3.9.1</version>
</dependency>
配置文件
# MongoDB数据库
spring.data.mongodb.uri=mongodb://127.0.0.1:27017/testMongoDB
spring.data.mongodb.uri=mongodb://username:password000@127.0.0.1:27017/database?authSource=admin
# 设置了密码的MongoDB配置方式
# MongoDB服务器连接地址
#spring.data.mongodb.host=127.0.0.1
# MongoDB服务器连接端口
#spring.data.mongodb.port=27017
# MongoDB的验证数据库
#spring.data.mongodb.authentication-database=admin
# MongoDB数据库用户
#spring.data.mongodb.username=root
# MongoDB数据库密码
#spring.data.mongodb.password=123456
# 带连接的数据库
#spring.data.mongodb.database=testMongoDB
MongoTemplate的基本方法 首先使用@Autowired注入MongoTemplate
@Autowired
private MongoTemplate mongoTemplate;
检索数据代码
// 查询name=zs
Query query = Query.query(Criteria.where("name").is("zs"));
mongoTemplate.find(query,User.class);
mongoTemplate.find(query,User.class,"mongodb_user");
// 查询所有
mongoTemplate.findAll(User.class);
mongoTemplate.findAll(User.class,"mongodb_user");
// 分页查询 page页码,pageSize每页展示几个
Pageable pageable = PageRequest.of(page - 1, pageSize, Sort.by(Sort.Order.desc("date")));
Query query = new Query().with(pageable);
return this.mongoTemplate.find(query, User.class,"mongodb_user");
// 查询多个
Query query= Query.query(Criteria.where("id").in("id1","id2","id3")).with(Sort.by(Sort.Order.desc("date")));
List<Publish> list= this.mongoTemplate.find(query, User.class);
// 查询数量
Criteria criteria = Criteria.where("userId").is("12345")
.and("name").is(new ObjectId("张三"))
.and("address").is("上海");
Query query = Query.query(criteria);
long count = this.mongoTemplate.count(query, User.class);
插入数据
List<User> list = new ArrayList<>();
User user= new User();//
user.setName("admin");
user.setAddress("测试");
list.add(user);
// 保存对象到mongodb
mongoTemplate.save(user);
mongoTemplate.insert(user);
// 根据集合名称保存对象到mongodb
mongoTemplate.save(user,"mongodb_user");
mongoTemplate.insert(user,"mongodb_user");
// 根据集合名称保存list到mongodb
mongoTemplate.save(list,"mongodb_user");
mongoTemplate.insert(list,"mongodb_user");
mongoTemplate.insert(list,User.class);
更新数据
User user = new User();
user.setId("5d1312aeb1829c279c6c256b");
user.setName("admin");
user.setAddress("测试");
Query query = Query.query(Criteria.where("_id").is("5d1312aeb1829c279c6c256b"));
Update update = Update.update("name","zs");
// 更新一条数据
mongoTemplate.updateFirst(query,update, User.class);
mongoTemplate.updateFirst(query,update, "mongodb_user");
mongoTemplate.updateFirst(query,update, User.class,"mongodb_user");
// 更新多条数据
mongoTemplate.updateMulti(query,update, User.class);
mongoTemplate.updateMulti(query,update,"mongodb_user");
mongoTemplate.updateMulti(query,update, User.class,"mongodb_user");
// 更新数据,如果数据不存在就新增
mongoTemplate.upsert(query,update, User.class);
mongoTemplate.upsert(query,update,"mongodb_user");
mongoTemplate.upsert(query,update, User.class,"mongodb_user");
删除数据
List<MongoDbJavaTest> list = new ArrayList<>();
User user= new User();
user.setId("5d1312aeb1829c279c6c256b");
list.add(user);
Query query = Query.query(Criteria.where("_id").in("5d1312aeb1829c279c6c256b","5d13133ab1829c29d02ce29c"));
// 根据条件删除
mongoTemplate.remove(query);
mongoTemplate.remove(user);
mongoTemplate.remove(User.class);
// 根据条件删除(可删除多条)
mongoTemplate.remove(query,User.class,"mongodb_user");
MongoDB常见的问题
Invalid mongo configuration, either uri or host/port/credentials/replicaSet must be specified
查看springframework\boot\autoconfigure\mongo\MongoClientFactorySupport中发现其中的validateConfiguration方法
private void validateConfiguration() {
if (hasCustomAddress() || hasCustomCredentials() || hasReplicaSet()) {
Assert.state(this.properties.getUri() == null,
"Invalid mongo configuration, either uri or host/port/credentials/replicaSet must be specified");
}
}
private boolean hasCustomAddress() {
return this.properties.getHost() != null || this.properties.getPort() != null;
}
private boolean hasCustomCredentials() {
return this.properties.getUsername() != null && this.properties.getPassword() != null;
}
private boolean hasReplicaSet() {
return this.properties.getReplicaSetName() != null;
}
会发现,如果使用uri,就不能使用host,port,username等字段,所以如果使用uri,就只使用uri,否则就使用后者。
SpringBoot中MongoTemplate源码
package org.springframework.boot.autoconfigure.data.mongo;
import com.mongodb.client.MongoClient;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Data's mongo support.
* <p>
* Registers a {@link MongoTemplate} and {@link GridFsTemplate} beans if no other beans of
* the same type are configured.
* <p>
* Honors the {@literal spring.data.mongodb.database} property if set, otherwise connects
* to the {@literal test} database.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ MongoClient.class, MongoTemplate.class })
@EnableConfigurationProperties(MongoProperties.class)
@Import({ MongoDataConfiguration.class, MongoDatabaseFactoryConfiguration.class,
MongoDatabaseFactoryDependentConfiguration.class })
@AutoConfigureAfter(MongoAutoConfiguration.class)
public class MongoDataAutoConfiguration {
}
创建MongoDatabaseFactory,MongoDatabaseFactory工厂类
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(MongoDatabaseFactory.class)
@ConditionalOnSingleCandidate(MongoClient.class)
class MongoDatabaseFactoryConfiguration {
@Bean
MongoDatabaseFactorySupport<?> mongoDatabaseFactory(MongoClient mongoClient, MongoProperties properties) {
return new SimpleMongoClientDatabaseFactory(mongoClient, properties.getMongoClientDatabase());
}
}
如何创建索引
@Configuration(proxyBeanMethods = false)
class MongoDataConfiguration {
@Bean
@ConditionalOnMissingBean
MongoMappingContext mongoMappingContext(ApplicationContext applicationContext, MongoProperties properties,
MongoCustomConversions conversions) throws ClassNotFoundException {
PropertyMapper mapper = PropertyMapper.get().alwaysApplyingWhenNonNull();
MongoMappingContext context = new MongoMappingContext();
mapper.from(properties.isAutoIndexCreation()).to(context::setAutoIndexCreation);
context.setInitialEntitySet(new EntityScanner(applicationContext).scan(Document.class));
Class<?> strategyClass = properties.getFieldNamingStrategy();
if (strategyClass != null) {
context.setFieldNamingStrategy((FieldNamingStrategy) BeanUtils.instantiateClass(strategyClass));
}
context.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
return context;
}
@Bean
@ConditionalOnMissingBean
MongoCustomConversions mongoCustomConversions() {
return new MongoCustomConversions(Collections.emptyList());
}
}
会扫描所有的@Document注解的类,放入到MongoMappingContext
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(MongoDatabaseFactory.class)
class MongoDatabaseFactoryDependentConfiguration {
private final MongoProperties properties;
MongoDatabaseFactoryDependentConfiguration(MongoProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean(MongoOperations.class)
MongoTemplate mongoTemplate(MongoDatabaseFactory factory, MongoConverter converter) {
return new MongoTemplate(factory, converter);
}
......
创建MongoTemplate
public MongoTemplate(MongoDatabaseFactory mongoDbFactory, @Nullable MongoConverter mongoConverter) {
this.writeConcernResolver = DefaultWriteConcernResolver.INSTANCE;
this.writeResultChecking = WriteResultChecking.NONE;
this.sessionSynchronization = SessionSynchronization.ON_ACTUAL_TRANSACTION;
Assert.notNull(mongoDbFactory, "MongoDbFactory must not be null!");
this.mongoDbFactory = mongoDbFactory;
this.exceptionTranslator = mongoDbFactory.getExceptionTranslator();
this.mongoConverter = mongoConverter == null ? getDefaultMongoConverter(mongoDbFactory) : mongoConverter;
this.queryMapper = new QueryMapper(this.mongoConverter);
this.updateMapper = new UpdateMapper(this.mongoConverter);
this.schemaMapper = new MongoJsonSchemaMapper(this.mongoConverter);
this.projectionFactory = new SpelAwareProxyProjectionFactory();
this.operations = new EntityOperations(this.mongoConverter.getMappingContext());
this.propertyOperations = new PropertyOperations(this.mongoConverter.getMappingContext());
this.queryOperations = new QueryOperations(this.queryMapper, this.updateMapper, this.operations, this.propertyOperations, mongoDbFactory);
this.mappingContext = this.mongoConverter.getMappingContext();
if (this.mappingContext instanceof MongoMappingContext) {
MongoMappingContext mappingContext = (MongoMappingContext)this.mappingContext;
if (mappingContext.isAutoIndexCreation()) {
this.indexCreator = new MongoPersistentEntityIndexCreator(mappingContext, this);
this.eventPublisher = new MongoMappingEventPublisher(this.indexCreator);
mappingContext.setApplicationEventPublisher(this.eventPublisher);
}
}
}
大致的逻辑梳理一下:系统启动的时候,spring-boot-autoconfigure根据spring.factories找那个配置的MongoDataAutoConfiguration去加载mongodb相关的配置,MongoDataAutoConfiguration这个类又会继续加载MongoDataConfiguration和MongoDbFactoryDependentConfiguration。MongoDataConfiguration在构建MongoMappingContext的时候回去扫描系统中所有的@Document和@Persistent,这就找到了所有的domain对象,MongoDbFactoryDependentConfiguration在构建mongoTemplate的时候,会遍历MongoMappingContext中的domain对象,然后查找对象是否需要创建索引并创建。如果创建索引的过程中出现DataIntegrityViolationException数据完整性约束的异常,直接忽略,因为有可能被另一个应用先创建出来了。