本篇博文开始讲解MongoDB的操作内容。
首先先讲一下MongoDB的添加文档操作,在本篇博文中,将会从shell、js脚本、MongoDB Compass、java原生驱动、spring封装几个方面来讲解如何插入MongoDB文档
MongoDB shell
从前面的方法汇总的集合方法中,我们可以看到shell提供了三个插入方法:
db.collection.insert() : 插入一个或多个指定文档
其语法为:
db.collection.insert(
,
{
writeConcern: ,
ordered:
}
)
其语法为:
db.collection.insertOne(
,
{
writeConcern:
}
)
其语法为:
db.collection.insertMany(
[ , , ... ],
{
writeConcern: ,
ordered:
}
)
上面的方法主要有三个参数:
document – 该参数指要插入的一个或多个文档的数据,如果是一个文档,则该参数为一个json数据,若是多个文档,则该参数是多个json数据组成的数组。
writeConcern – 可选参数,该参数指的是该次的插入操作的写入关注程度,其值为一个文档,表现为json数据。大家可以参考前面的写入关注的讲解。
ordered – 2.6版本以后的新参数,可选参数。如果值为true,则将数组中文档的进行有序插入,如果一个文档发生错误,mongodb将返回,而无需处理数组中的剩余文档。如果false,执行无序插入,如果错误发生在某个文档中,则继续处理数组中的剩余文档。默认为true。
注:
1.上面的三个方法是集合(collection)的方法,所以我们需要有对应的集合,当然如果我们执行方法时,指定的集合并不存在,MongoDB会自动创建该集合,并插入数据。
2.如果文档没有指定”_id”字段,那么mongodb将在插入之前为文档分配一个惟一的objectid,并将该objectId作为”_id”字段插入文档中。如果文档包含”_id”字段,则_id值必须是集合中唯一的,以避免重复的密钥错误。
比如下图所示,我用几个insert方法演示插入数据。由于insertOne()和insertMany()和该方法差不多,就不在演示了:
上图中从只插入一条数据、插入一条数据+写入关注、插入多条数据+写入关注+是否排序等几个方面演示。
其中上面的mongoTest集合原先并不存在,我们可以直接调用db.mongoTest.insert(...)
来默认创建并插入数据。
在shell中我们使用insert()方法插入文档后,不关错误 还是失败,都会返回一个BulkWriteResult()对象,内部描述了插入操作的结果。
从上面的插入的数据可以看出,在同一个集合中插入数据,我们不必遵循每个文档必须具有相同的字段名,且就算相同的字段名在不同的文档中其值可以为不同类型。在这点上MongoDB比关系型数据库更加灵活。当然建议大家还是将同一类型的文档放在一个集合中,不同类型的文档放在不同的集合中。
js脚本
我们除了可以在shell中插入数据,我们还可以在一个js脚本文件中插入数据。我们可以首先新建一个脚本文件,然后在文件中使用JavaScript语法和shell提供的方法,来执行js脚本来实现操作数据库。
比如下面的语句:
//获取数据库链接
var db = connect("localhost/words","yfl","yfl");
//自定义文档
var document1 = {key:"mongo_js",value:"测试js脚本插入数据"};
//调用集合mongoTest的insert方法
db.mongoTest.insert(document1);
var document2 = {key:"mongo_js2",value:"测试js脚本插入数据,附带参数"};
//自定义带有写入关注和是否顺序插入参数
var options = {writeConcern: { w: "majority", wtimeout: 5000 },ordered: false }
//带参数插入数据
db.mongoTest.insert(document2,options);
//自定义多个文档
var document3 = {key:"mongo_js3",value:"测试js脚本批量插入数据"};
var document4 = {key:"mongo_js4",value:"测试js脚本批量插入数据"};
//自定义一个文档数组,用于存放文档
var documentArray = new Array();
//调用数组的push方法将文档添加进数组
documentArray.push(document3);
documentArray.push(document4);
//将数组传入insert方法实现批量插入
db.mongoTest.insert(documentArray);
//获取集合对象
var mongoTest = db.getCollection("mongoTest");
//查询“key”这个键的值在"mongo_js","mongo_js4","mongo_js2","mongo_js3"之中的记录
var cursor = mongoTest.find({key:{$in:["mongo_js","mongo_js4","mongo_js2","mongo_js3"]}});
//打印出查询的数据
printjson(cursor.toArray());
上面的代码就是js中插入的数据的代码,从上面的代码,我们可以看到在js文件中我们既可以使用JavaScript语法又可以使用shell方法。相比较在shell中操作数据库相比,js脚本来操作数据库能够更加灵活,其易修改、易保存记录、可以定时执行等等优点。
执行js脚本只需要在cmd.exe中切换到mongoDB的安装目录的bin目录下,直接执行mongo js脚本路径
即可,如下图:
上面插入结果我们可以使用MongoDB Compass查看,如下图:
MongoDB Compass
上面的练习我们使用了shell插入了5条数据,这时候我们可以使用MongoDB Compass来查看先前的数据,如图:
我们可以可以看到上面操作插入的数据在这里都能看到。
下面我们开始演示如何在MongoDB Compass中插入数据:
首先我们点击左上面的绿色的“INSERT DOCUMENT”按钮,如图:
点击”INSERT”或”CANCEL”来确认插入或取消插入操作,确认之后,我们可以看到刚才插入的数据:
MongoDB Compass插入数据非常简单,每次只能插入一条。
MongoDB java Driver
接下来讲解的是如何使用MongoDB的java驱动来操作MongoDB,具体的准备和介绍大家可以看学习笔记(十)。
大家看下面的代码:
/**
* 用于演示MongoDB原生驱动添加数据
*/
public void addDataDemo1(){
// 获取MongoClient对象
MongoClient client = null;
// 直接使用默认host和port(该方法不能进行数据库授权认证)
// client = new MongoClient();
// 使用指定host和端口号,(该方法不能进行数据库授权认证)
// client = new MongoClient("localhost",27017);
// 使用封装的ServerAddress链接对象,无需授权时
// client = new MongoClient(new ServerAddress("localhost",27017));
// 使用封装的ServerAddress链接对象,需授权时,使用MongoCredential对象封装授权信息createCredential(String userName, String database, char[] password),
// MongoDB 的java驱动的文档中的授权说是可以缺少 MongoClientOptions的,但是在其api文档中并没有提供只有 ServerAddress、MongoCredential的构造方法,估计是他们文档更新不及时,这里就直接加上 MongoClientOptions参数。
// client = new MongoClient(new ServerAddress("localhost",27017), MongoCredential.createCredential("yfl","words","yfl".toCharArray()),MongoClientOptions.builder().build());
// 使用URI链接MongoDB,MonoClientURI格式为:mongodb://[用户名:密码@]host:port[/数据库],强烈建议使用该种身份验证
client = new MongoClient(new MongoClientURI("mongodb://yfl:yfl@localhost:27017/words"));
//获取MongoDatabase 对象
MongoDatabase database = client.getDatabase("words");
//获取集合对象
MongoCollection col = database.getCollection("mongoTest");
//创建将要插入的文档对象,插入的文档类型必须是Document类型
Document document = new Document("javakey","sss").append("value",5);
col.insertOne(document);
Document document1 = new Document("javakey","策划").append("value",5);
Document document2 = new Document("javakey","sdsd").append("value",10);
List list = new ArrayList();
list.add(document1);
list.add(document2);
col.insertMany(list);
}
首先讲解了我们如何在java程序中获取MongoClient 对象,在程序中,MongoDB有可能没有开启权限认证,也有可能开启了权限认证,当没开权限认证时,我们可以看到获取MongoClient 对象的方法都快可以。
但是开启权限认证之后,只有两种方法能够获取权限并获取到MongoClient对象,在以前的版本可以先获取MongoClient对象,然后获取对应的数据库,并调用数据库的授权方法来授权,但是现在的新版本好像取消掉了该种认证方式,认证只能在获取MongoClient对象的时候认证。
在获取MongoClient身份权限认证有两种方式:
1.是使用MongoClient的构造方法时,传入ServerAddress+MongoCredential+MongoClientOptions三个参数。ServerAddress参数是描述要链接数据库的host和port。MongoCredential是描述要连接数据库的身份验证信息,有用户名、密码、要链接的数据库名。MongoClientOptions描述各种设置来控制MongoClient的行为。
2.使用MongoClientURI对象来描述要链接数据库的信息以及权限信息,然后在构造MongoClient对象时传入该MongoClientURI对象。
当然出了以上演示的构造方法,MongoClient还提供了更多的构造方法,大家可以参考上篇的MongoClient对象的方法列表以及官方文档。
获取到MongoClient对象之后,我们就可以获取到对应的MongoDatabase 、MongoCollection 对象,获取到集合对象(MongoCollection )之后,我们就可以调用其提供的insert方法,如图:
其提供了8个insert方法,其中那个四个是插入单个文档的,有四个是插入多个文档的。其中传入的参数不同,其中设计到几个参数:
Object tDocument – 该参数就是要插入的数据库文档数据,其在官方API文档提供的方法的类型是TDocument,但是MongoDB并没有提供TDocument对象,在这例TDocument只是说明该参数是一个文档类型。具体的我们在插入的时候应该是使用Document对象,该对应在以前的版本里是使用BasicDBObject,但是新版本不在支持该种类型作为文档数据。在插入数据时,如果文档数据的类型不对,会报*** cannot be cast to org.bson.Document
。
List – 该参数表示List
,表示要插入文档的列表。
ClientSession – 与此操作关联的客户端会话(session)
InsertOneOptions – 应用于将单个文档插入到集合中的操作的选项。
InsertManyOptions – 应用于将多个文档插入到集合中的操作的选项。
上面的大家都可以查看对应的官方文档。
所以上面的代码我只是插入了一个单个文档,一个多文档插入。结果为:
从上面的代码看出,代码比较繁琐,需要每次都要获取连接,就好像以前的关系型数据库的操作一样,而且插入的数据必须是标准的Document对象,这样如果文档中嵌套文档,就非常麻烦,而且有时候,我们存储的数据不能确定其key值,有时候我们想将一个无论什么类型的数据想直接存放进去,就非常麻烦了。所以spring为了解决这一问题,其对驱动进行了封装。为spring-data-mongodb。
mongoTemplate
该对象是spring封装MongoDB的java驱动为spring-data-mongodb暴露出来的一个对象,在实际应用中,我们只需要进行相应的配置即可,至于如何配置,请看前面的博文,这里就不介绍了。
mongoTemplate的应用非常简单,只需要在使用的地方注入MongoTemplate对象即可,比如:
@Autowired
private MongoTemplate mongoTemplate;
由于数据库的链接什么的在配置文件中都配置好了,所以这里我们就不需要在麻烦的获取各种对象,我们只需要直接操作mongoTemplate的insert方法即可:
从上图可以看出,mongoTemplate提供给了我们6个insert方法,分别3个批量插入,3个单个插入。
其主要参数只有三个:
Collection《? extends Object》/ Object batchToSave :该参数就是我们要插入的数据,其类型为继承Object的类 ,也就是说只要是继承自Object的所有数据类型,我们都可以通过该方法存储,封装类会自己帮我们转换数据类型。
collectionName : 我们将要存储数据到哪个集合,若没有该参数,mongoTemplate会自动存储到默认的集合中。
Class entityClass :实体类的class,mongoTemplate使用该实体类class来确定将要存储数据库的集合。
注:至于mongoTemplate当我们不传collectionName或传 entityClass是如何确定存储的集合的,
(经实际测试,存储的默认集合名为要存储数据的类型所属类型,比如com.alibaba.fastjson.JSONObject存储到的是jSONObject集合中,com.mongo.mongodb.Business.entity.MongoAddDemo实体类中存储的是mongoAddDemo”集合,也就是类class的首字母小写作为集合名称。但是HashMap类型的数据不能存储户,会报错)
如果大家有兴趣研究mongoTemplate是如何来确定默认集合名称的可以看最下面的源代码(分析的很乱,很杂),下面的是使用mongoTemplate操作添加数据的方法。
package com.mongo.mongodb.Business;
import com.alibaba.fastjson.JSONObject;
import com.mongo.mongodb.Business.entity.MongoAddDemo;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class MongodbTest {
@Autowired
private MongoTemplate mongoTemplate;
/**
* 使用spring封装mongodb的对象mongoTemplate添加数据
*/
public void addDataDemo2(){
//因为如果要是用mongoTemplate对象来操作MongoDB,我们首先需要配置要链接的数据库,所以我们就不在需要获取各种对象了,
//只需要将mongoTemplate注入进来,然后操作insert方法
JSONObject json = new JSONObject();
json.put("key","json");
json.put("value","测试存放json");
mongoTemplate.insert(json,"mongoTest");//添加json数据
Map map = new HashMap();
map.put("key","map");
map.put("value","测试存放map");
mongoTemplate.insert(map,"mongoTest");//添加map数据
MongoAddDemo entity = new MongoAddDemo();
entity.setKey("entity");
entity.setValue("测试存放entity");
mongoTemplate.insert(entity,"mongoTest");//添加entity数据
}
}
注:如果实体类类型的数据含有“id”字段,则存储时,mongoTemplate会自动转换成mongo的”_id”字段。
——————————————————————————————————————————————–
获取默认集合名称源代码:
//1.MongoTemplate类
//首先根据根据传入的文档数据调用determineEntityCollectionName方法确定集合名称
public void insert(Object objectToSave) {
Assert.notNull(objectToSave, "ObjectToSave must not be null!");
this.ensureNotIterable(objectToSave);
this.insert(objectToSave, this.determineEntityCollectionName(objectToSave));
}
//2.MongoTemplate类
//对传进的参数进行判空,若参数不为空,则使用传入的文档数据对象调用getClass()获取该对象的class并作为参数调用determineCollectionName方法,来确定集合名称
private String determineEntityCollectionName(@Nullable T obj) {
return null != obj ? this.determineCollectionName(obj.getClass()) : null;
}
//3.MongoTemplate类
//首先对传入的class判空,然后调用mappingContext类的getRequiredPersistentEntity()方法,并将返回值强转为MongoPersistentEntity对象,
//然后获取到MongoPersistentEntity对象的getCollection()获取集合名称。从这里可以看出关键在于mappingContext.getRequiredPersistentEntity(entityClass)方法
String determineCollectionName(@Nullable Class> entityClass) {
if (entityClass == null) {
throw new InvalidDataAccessApiUsageException("No class parameter provided, entity collection can't be determined!");
} else {
//如果大家对getRequiredPersistentEntity方法是如何将class对象转换成MongoPersistentEntity对象可以看下面的源码。
return ((MongoPersistentEntity)this.mappingContext.getRequiredPersistentEntity(entityClass)).getCollection();
}
}
//4.MongoPersistentEntity类
//该类是一个接口类,其提供了四个接口方法,其中getCollection()方法用于提供获取集合名称的方法
public interface MongoPersistentEntity extends PersistentEntity {
String getCollection();
String getLanguage();
@Nullable
MongoPersistentProperty getTextScoreProperty();
boolean hasTextScoreProperty();
}
//5.BasicMongoPersistentEntity类,其实现了MongoPersistentEntity接口类,提供了用于获取集合名称的方法。其判断当前BasicMongoPersistentEntity对象的expression参数是否存在,若不存在,返回当前的collection参数,若存在则返回其getValue()的值
public String getCollection() {
return this.expression == null ? this.collection : (String)this.expression.getValue(this.context, String.class);
}
//6.我们看下BasicMongoPersistentEntity的构造方法
public BasicMongoPersistentEntity(TypeInformation typeInformation) {
super(typeInformation, BasicMongoPersistentEntity.MongoPersistentPropertyComparator.INSTANCE);
Class> rawType = typeInformation.getType();
String fallback = MongoCollectionUtils.getPreferredCollectionName(rawType);
this.context = new StandardEvaluationContext();
if (this.isAnnotationPresent(Document.class)) {
Document document = (Document)this.getRequiredAnnotation(Document.class);
this.collection = StringUtils.hasText(document.collection()) ? document.collection() : fallback;
this.language = StringUtils.hasText(document.language()) ? document.language() : "";
this.expression = detectExpression(document);
} else {
this.collection = fallback;
this.language = "";
this.expression = null;
}
}
//7我们主要看上面的fallback参数的生成方式
public static String getPreferredCollectionName(Class> entityClass) {
return StringUtils.uncapitalize(entityClass.getSimpleName());//传入class的名称计算出集合名称
}
public static String uncapitalize(String str) {
return changeFirstCharacterCase(str, false);//设置第一个字符为小写
}
//根据传入字符串以及第二个元素确定字符串首字母大写还是小写
private static String changeFirstCharacterCase(String str, boolean capitalize) {
if (!hasLength(str)) {
return str;
} else {
char baseChar = str.charAt(0);
char updatedChar;
if (capitalize) {
updatedChar = Character.toUpperCase(baseChar);
} else {
updatedChar = Character.toLowerCase(baseChar);
}
if (baseChar == updatedChar) {
return str;
} else {
char[] chars = str.toCharArray();
chars[0] = updatedChar;
return new String(chars, 0, chars.length);
}
}
}
getRequiredPersistentEntity():
//1.MappingContext类
//该方法是MappingContext接口类的一个方法。其调用自身提供的getPersistentEntity接口,
//将传入的接口转换成继承PersistentEntity的类E(`E extends PersistentEntity, P>`)
default E getRequiredPersistentEntity(Class> type) throws MappingException {
E entity = this.getPersistentEntity(type);
if (entity != null) {
return entity;
} else {
throw new MappingException(String.format("Couldn't find PersistentEntity for type %s!", type));
}
}
//2.MappingContext类
//MappingContext提供的接口类,用于将传入的类class转换成继承PersistentEntity的类E(`E extends PersistentEntity, P>`)
E getPersistentEntity(Class> var1);
//3.AbstractMappingContext类
//AbstractMappingContext是MappingContext实现类。实现了上面的getPersistentEntity()接口。
//其首先将class作为参数调用ClassTypeInformation的静态方法,将返回值强转成TypeInformation接口类,然后调用getPersistentEntity()方法
//ClassTypeInformation继承TypeDiscoverer类,TypeDiscoverer实现TypeInformation接口类,所以下面的强转关系成立
public E getPersistentEntity(Class> type) {
return this.getPersistentEntity((TypeInformation)ClassTypeInformation.from(type));//若想查看from方法功能看下一段源码
}
//4.AbstractMappingContext类
//获取当前类维持的persistentEntities 这个map中存储的key为type对应的MutablePersistentEntity对象值,
//若persistentEntities不包含key为type的,则判断当前type是否应该写入到persistentEntities中,
//若不应该写入,则persistentEntities 中维持一个key为type,value为空的Optional对象的数据。
//若允许写入,则计算对应的Optional对象值,写入并返回强制转换成MutablePersistentEntity对象
private final Map, Optional> persistentEntities = new HashMap();//当前类的用于存储常出现的实体类的map,key是TypeInformation对象
public E getPersistentEntity(TypeInformation> type) {
Assert.notNull(type, "Type must not be null!");//判空
try {
this.read.lock();//锁定读取锁
Optional entity = (Optional)this.persistentEntities.get(type);//从维持的persistentEntities获取保存当前TypeInformation对象的对应的值,
if (entity != null) {
MutablePersistentEntity var3 = (MutablePersistentEntity)entity.orElse((Object)null);
return var3;//如果维持的persistentEntities这个map中有当前传入文档的class这个key,获取其value值,使用orElse对value进行判空处理后,强转成MutablePersistentEntity对象。
}
} finally {
this.read.unlock();//释放读取锁
}
if (!this.shouldCreatePersistentEntityFor(type)) {//判断当前的type是否应该写入到维持的persistentEntities中,如果不,则将persistentEntities维持一个key为type,值为空的Optional对象的数据
try {
this.write.lock();//锁定写入锁
this.persistentEntities.put(type, this.NONE);
} finally {
this.write.unlock();//释放写入锁
}
return null;
} else if (this.strict) {
throw new MappingException("Unknown persistent entity " + type);
} else {
return (MutablePersistentEntity)this.addPersistentEntity(type).orElse((Object)null);//如果当前type应该写入到维持的persistentEntities这个map中,则写入,并返回当前type所对应的value值,并强转成MutablePersistentEntity对象。
}
}
//5.AbstractMappingContext
//给维持的persistentEntities这个map中添加一个key为TypeInformation对象的记录
protected Optional addPersistentEntity(TypeInformation> typeInformation) {
Assert.notNull(typeInformation, "TypeInformation must not be null!");//判空
Optional var3;
label151: {
try {
this.read.lock();//锁定读取锁
Optional persistentEntity = (Optional)this.persistentEntities.get(typeInformation);//判断维持的persistentEntities这个map中是否包含key为typeInformation的值,如果包含,则直接返回,若不包含则继续执行下面代码
if (persistentEntity == null) {
break label151;
}
var3 = persistentEntity;
} finally {
this.read.unlock();//释放读取锁
}
return var3;
}
Class type = typeInformation.getType();//获取typeInformation对象中的type参数(class类型,即为当初传入的文档数据类型的class,比如传入的是一个map文档,则为map.getClass())
var3 = null;
MutablePersistentEntity entity;
try {
this.write.lock();//锁定写入锁
//根据type创建一个MutablePersistentEntity对象。
//创建的是BasicMongoPersistentEntity对象,
//BasicMongoPersistentEntity对象继承BasicPersistentEntity对象,
//BasicPersistentEntity对象实现MutablePersistentEntity对象接口类
entity = this.createPersistentEntity(typeInformation);
//将创建的MutablePersistentEntity封装到Optional对象中,并维护到persistentEntities这个map中
this.persistentEntities.put(typeInformation, Optional.of(entity));
PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(type);//获取类的属性
Map descriptors = new HashMap();//创建用于存储PropertyDescriptor的map
PropertyDescriptor[] var6 = pds;
int var7 = pds.length;//获取属性数目
for(int var8 = 0; var8 < var7; ++var8) {//对属性进行存储,key为属性的name,值为PropertyDescriptor对象
PropertyDescriptor descriptor = var6[var8];
descriptors.put(descriptor.getName(), descriptor);
}
try {
//Invoke the given callback on all fields in the target class, going up the class hierarchy to get all declared fields
//在给定的目标类(type)的所有字段上请求给定的回调函数(persistentPropertyCreator),向上调整类层次结构以获取所有声明的字段
ReflectionUtils.doWithFields(type, persistentPropertyCreator, AbstractMappingContext.PersistentPropertyFilter.INSTANCE);。
persistentPropertyCreator.addPropertiesForRemainingDescriptors();
entity.verify();
if (this.persistentPropertyAccessorFactory.isSupported(entity)) {
entity.setPersistentPropertyAccessorFactory(this.persistentPropertyAccessorFactory);
}
} catch (MappingException var19) {
this.persistentEntities.remove(typeInformation);
throw var19;
}
} catch (BeansException var20) {
throw new MappingException(var20.getMessage(), var20);
} finally {
this.write.unlock();
}
if (this.applicationEventPublisher != null && entity != null) {
this.applicationEventPublisher.publishEvent(new MappingContextEvent(this, entity));
}
return Optional.of(entity);
}
form方法:
//1.ClassTypeInformation类
//其继承TypeDiscoverer类,TypeDiscoverer实现TypeInformation接口类
//先判空,然后调用Map的computeIfAbsent()方法,传入文档的class和一个用ClassTypeInformation的构造函数创建的ClassTypeInformation对象。
//并将返回结果强转成ClassTypeInformation对象。
private static final Map, ClassTypeInformation>> CACHE = new ConcurrentReferenceHashMap();
public static ClassTypeInformation from(Class type) {
Assert.notNull(type, "Type must not be null!");
return (ClassTypeInformation)CACHE.computeIfAbsent(type, (it) -> {
return new ClassTypeInformation(type);
});//将文档的class放入ClassTypeInformation对象中。
}
//2.Map类
//如果指定的键尚未与值mappingFunction关联,则尝试使用给定的映射函数计算其值,并将其输入到此映射中。
//也就是说该方法功能是将key(文档的class)与传入的mappingFunction是否有关联,若没有关联,则将其关联,并返回关联之后的mappingFunction对象,或新的mappingFunction对象。
default V computeIfAbsent(K key,
Function super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);//判空
V v;
if ((v = get(key)) == null) {//判断当前调用当前的方法的对象(上面的CACHE这个map)是否包含传入key(即文档的class)
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {//将文档的class运用apply函数(映射函数)
put(key, newValue);
return newValue;
}
}
return v;
}