MongoDB学习02:使用MongoTemplate操作MongoDB

  • `MongoTemplate`的使用示例
  • `MongoTemplate`的使用
  • 向Spring容器注入`MongoTemplate`
  • 字段和类型映射
  • `_id`字段映射
  • `_class`字段映射
  • 自定义类型映射
  • 使用`MongoTemplate`进行CRUD
  • 保存(`insert`/`save`)文档的方法
  • 查询(`query`)文档的方法
  • 修改(`update`)文档的方法
  • 更新插入(`upsert`)文档的方法
  • 查询并修改(`find and modify`)文档的方法
  • 查询并删除(`find and remove`)文档的方法
  • 查询并替换(`find and replace`)文档的方法


SpringData的官方文档Spring Data MongoDB - Reference Documentation,阅读官方文档永远是最好的学习方法.

MongoTemplate的使用示例

  1. 创建SpringBoot项目,在pom.xml中添加依赖如下:
<dependency>
    <!-- 引入Spring操作MongoDB的库 -->
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-mongodb</artifactId>
</dependency>
  1. cn.maoritian.domain包下创建实体类Preson如下:
package cn.maoritian.domain;

public class Person {

    private String id;
    private String name;
    private int age;

    public String getId() {return id; }
    public String getName() {return name; }
    public int getAge() {return age; }

    public Person(String name, int age) {this.name = name; this.age = age; }

    @Override
    public String toString() {return "Person [id=" + id + ", name=" + name + ", age=" + age + "]"; }
}
  1. cn.maoritian包下创建SpringBoot启动类MongoApp,在其main()函数中进行对MongoDB的操作
package cn.maoritian;

import static org.springframework.data.mongodb.core.query.Criteria.where;

import cn.maoritian.domain.Person;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;

import com.mongodb.MongoClient;

public class MongoApp {

    private static final Log log = LogFactory.getLog(MongoApp.class);

    public static void main(String[] args) throws Exception {

        MongoOperations mongoOps = new MongoTemplate(new MongoClient(), "数据库名");
        mongoOps.insert(new Person("Joe", 34));	// 默认插入进名为person的collection

        log.info(mongoOps.findOne(new Query(where("name").is("Joe")), Person.class));

        mongoOps.dropCollection("person");
    }
}

程序输出如下:

22:35:59.212 [main] DEBUG org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator - Analyzing class class cn.maoritian.domain.Person for index information.
22:35:59.260 [main] DEBUG org.springframework.data.mongodb.core.MongoTemplate - Inserting Document containing fields: [name, age, _class] in collection: person
22:35:59.337 [main] DEBUG org.springframework.data.mongodb.core.MongoTemplate - findOne using query: { "name" : "Joe" } fields: Document{{}} for class: class cn.maoritian.domain.Person in collection: person
22:35:59.372 [main] INFO cn.maoritian.MongoApp - Person [id=5d7e4c39676d91262cdf734f, name=Joe, age=34]
22:35:59.384 [main] DEBUG framework.data.mongodb.core.MongoTemplate: 375 - Dropped collection [database.person]

MongoTemplate的使用

向Spring容器注入MongoTemplate

下面三种方式均可向Spring容器中注入MongoTemplate:

  1. 使用.xml文件注入:
<mongo:mongo-client host="localhost" port="27017"/>

<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
	<constructor-arg ref="mongoClient"/>
	<constructor-arg name="databaseName" value="数据库名"/>
</bean>
  1. 使用Java Config注入:
@Configuration
public class AppConfig {

    public @Bean MongoClient mongoClient() {
        return new MongoClient("localhost");
    }

    public @Bean MongoTemplate mongoTemplate() {
        return new MongoTemplate(mongoClient(), "数据库名");
    }
}
  1. 在SpringBoot项目中,还可以使用.yml文件注入
spring:
  data:
    mongodb:
      uri: mongodb://服务器IP地址:端口号/数据库名

其中,mongodb属性对应的所有yml配置项均为org.springframework.boot.autoconfigure.mongo.MongoProperties类的属性,其源代码如下:

package org.springframework.boot.autoconfigure.mongo;

import com.mongodb.MongoClientURI;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "spring.data.mongodb")	// 指明yml配置的前缀
public class MongoProperties {
    public static final int DEFAULT_PORT = 27017;
    public static final String DEFAULT_URI = "mongodb://localhost/test";
    private String host;
    private Integer port = null;
    private String uri;
    private String database;
    private String authenticationDatabase;
    private String gridFsDatabase;
    private String username;
    private char[] password;
    private Class<?> fieldNamingStrategy;
	
    // get,set方法...
}

字段和类型映射

_id字段映射

MongoDB要求每个文档包含一个_id字段,在实体类中,如下两种属性会被映射为_id字段:

  1. 使用@Id注解的属性会被映射为数据库_id字段.
  2. 名为id的属性会被映射为数据库_id字段.

_class字段映射

当一个POJO对象被转换为MongoDB中的文档时,会自动在文档末尾加入一个_class字段,其值为该POJO类的全限定类名(fully qualified classname).

下面例子展示一个实体类与其转化为的MongoDB文档间的关系:

public class Sample {
  Contact value;
}

public abstract class Contact { … }

public class Person extends Contact { … }

Sample sample = new Sample();
sample.value = new Person();

mongoTemplate.save(sample);
{
    "value" : { "_class" : "cn.maoritian.Person" },
    "_class" : "cn.maoritian.Sample"
}

如果不想在_class字段中保存整个类名,可以在POJO类上使用@TypeAlias注解,其值为_class字段的值.

@TypeAlias("pers")
class Person {
    ...
}
{
    "value" : { "_class" : "pers" },
    "_class" : "cn.maoritian.Sample"
}

自定义类型映射

通过向Spring容器中注入MongoTypeMapper 的实现类,我们可以配置自定义类型映射.

class CustomMongoTypeMapper extends DefaultMongoTypeMapper {
	//implement custom type mapping here
}
@Configuration
class SampleMongoConfiguration extends AbstractMongoConfiguration {

	@Override
	protected String getDatabaseName() {
		return "数据库名";
	}

	@Override
	public MongoClient mongoClient() {
		return new MongoClient();
	}

	@Bean
	@Override
	public MappingMongoConverter mappingMongoConverter() throws Exception {
		MappingMongoConverter mmc = super.mappingMongoConverter();
		mmc.setTypeMapper(customTypeMapper());
		return mmc;
	}

	@Bean
	public MongoTypeMapper customTypeMapper() {
		return new CustomMongoTypeMapper();
	}
}

使用MongoTemplate进行CRUD

对于每个实体类,MongoTemplate会将其对象存入一个单独的集合(collection)中,该集合的默认集合名为实体类名首字母变小写(如cn.maoritian.Person类的所有实例默认会被存入person集合中).

可以在实体类上加以@Document注解,其collection属性指定将该类的实例对象存入的集合名.

@Document(collection = "集合名")
public class Person {
 	// ...   
}

保存(insert/save)文档的方法

下面三个方法可以向MongoDB中保存文档

  • insert(Object objectToSave)insert(Object objectToSave, String collectionName): 将objectToSave存入MongoDB中,若与集合中现有文档发生_id字段冲突,则报错.
  • insertAll(Collection<Object> objectsToSave): 将集合objectsToSave中的所有对象存入MongoDB中,若与集合中现有文档发生_id字段冲突,则报错.
  • save(Object objectToSave)save(Object objectToSave, String collectionName): 将objectToSave存入MongoDB中,若与集合中现有文档发生_id字段冲突,则覆盖之前的文档.

查询(query)文档的方法

下面五个方法可以查询MongoDB中的文档

  • <T> List<T> findAll(Class<T> entityClass)<T> List<T> findAll(Class<T> entityClass, String collectionName): 查询所有T类型的文档.
  • <T> T findOne(Query query, Class<T> entityClass)T findOne(Query query, Class<T> entityClass, String collectionName): 查询一个满足query条件的文档.
  • <T> T findById(Object id, Class<T> entityClass)<T> T findById(Object id, Class<T> entityClass, String collectionName): 通过id查询文档.
  • <T> List<T> find(Query query, Class<T> entityClass)<T> List<T> find(Query query, Class<T> entityClass, String collectionName): 查询所有满足query条件的文档.
List<Person> result = mongoTemplate.find(query(where("age").lt(50)
	.and("accounts.balance").gt(1000.00d)), Person.class);

通过Query.addCriteria()方法,我们可以使用Criteria对象构造Query对象.Criteria的方法与MongoDB的操作符一一对应如下:

Criteria的方法

对应的MongoDB操作符

Criteria all(Object o)

$all

Criteria and(String key)

key增加链式Criteria

Criteria andOperator(Criteria… criteria)

$and

Criteria elemMatch(Criteria c)

$elemMatch

Criteria exists(boolean b)

$exists

Criteria gt(Object o)

$gt

Criteria gte(Object o)

$gte

Criteria in(Object… o)

$in

Criteria in(Collection<?> collection)

$in

Criteria is(Object o)

field matching({ key:value }).顺序敏感(field sensitive)

Criteria lt(Object o)

$lt

Criteria lte(Object o)

$lte

Criteria mod(Number value, Number remainder)

$mod

Criteria ne(Object o)

$ne

Criteria nin(Object… o)

$nin

Criteria norOperator(Criteria… criteria)

$nor

Criteria not()

$not

Criteria orOperator(Criteria… criteria)

$or

Criteria regex(String re)

$regex

Criteria size(int s)

$size

Criteria type(int t)

$type

修改(update)文档的方法

下面两个方法可以修改MongoDB中的文档

  • updateFirst(Query query, Update update, Class<?> entityClass)updateFirst(Query query, Update update, String collectionName): 修改第一个满足条件的文档.
  • updateMulti(Query query, Update update, Class<?> entityClass)updateMulti(Query query, Update update, String collectionName): 修改第一个满足条件的文档.

参数的意义如下:

  • query: 表示查询条件
  • update: 表示修改操作
import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query;
import static org.springframework.data.mongodb.core.query.Update;

WriteResult wr = mongoTemplate.updateMulti(new Query(where("accounts.accountType").is(Account.Type.SAVINGS)),
	new Update().inc("accounts.$.balance", 50.00), Account.class);

Update对象可以通过链式调用添加修改操作.常见的方法如下,与MongoDB的修改操作符有一一对应的关系

Update的实例方法

对应的MongoDB操作符

Update addToSet(String key, Object value)

$addToSet

Update currentDate(String key)

$currentDate

Update currentTimestamp(String key)

$currentDate

Update inc(String key, Number inc)

$inc

Update max(String key, Object max)

$max

Update min(String key, Object min)

$min

Update multiply(String key, Number multiplier)

$mul

Update pop(String key, Update.Position pos)

$pop

Update pull(String key, Object value)

$pull

Update pullAll(String key, Object[] values)

$pullAll

Update push(String key, Object value)

$push

Update pushAll(String key, Object[] values)

$pushAll

Update rename(String oldName, String newName)

$rename

Update set(String key, Object value)

$set

Update setOnInsert(String key, Object value)

$setOnInsert

Update unset(String key)

$unset

下面四个例子展示了Update对象和MongoDB操作符的对应关系:

new Update().push("category").each("spring", "data")
new Update().push("key").atPosition(Position.FIRST).each(Arrays.asList("Arya", "Arry", "Weasel"));
new Update().push("key").slice(5).each(Arrays.asList("Arya", "Arry", "Weasel"));
new Update().addToSet("values").each("spring", "data", "mongodb");
{ $push : { "category" : { "$each" : [ "spring" , "data" ] } } }
{ $push : { "key" : { "$position" : 0 , "$each" : [ "Arya" , "Arry" , "Weasel" ] } } }
{ $push : { "key" : { "$slice" : 5 , "$each" : [ "Arya" , "Arry" , "Weasel" ] } } }
{ $addToSet : { "values" : { "$each" : [ "spring" , "data" , "mongodb" ] } } }

更新插入(upsert)文档的方法

更新插入upsert(Query query, Update update, Class<?> entityClass)upsert(Query query, Update update, String collectionName)方法,执行的是保存或修改过程.

  • 若根据query条件能查到文档,则执行updateFirst()操作
  • 若根据query条件不能查到文档,则执行insert()操作
mongoTemplate.upsert(query(where("ssn").is(1111).and("firstName").is("Joe").and("Fraizer").is("Update")), 
	update("address", addr), Person.class);

查询并修改(find and modify)文档的方法

查询并修改find and modify有如下四个重载方法:

  • <T> T findAndModify(Query query, Update update, Class<T> entityClass);
  • <T> T findAndModify(Query query, Update update, Class<T> entityClass, String collectionName);
  • <T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass);
  • <T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass, String collectionName);

其中options参数封装了修改操作的一些选项如下:

  • returnNew属性取值true|false,默认值为false.表示返回的是修改前的查询结果还是修改后的结果
  • upsert属性取值true|false,默认值为false.取值为true表示执行upsert操作;取值为false表示执行update操作.
  • remove属性取值true|false,默认值为false.表示是否删除掉查询结果
mongoTemplate.insert(new Person("Harry", 23));
Query query1 = new Query(Criteria.where("firstName").is("Harry"));
Update update1 = new Update().inc("age", 1);
Person p1 = mongoTemplate.findAndModify(query1, update1, Person.class);
// 得到 Person [id=5d90533e74fa159cdb13cae0, firstName=Harry, age=1]

Query query2 = new Query(Criteria.where("firstName").is("Mary"));
Update update2 = new Update().inc("age", 1);
Person p2 = mongoTemplate.findAndModify(query2, update2, new FindAndModifyOptions().returnNew(true).upsert(true), Person.class);
// 得到 Person [id=5d9054b574fa159cdb13cae1, firstName=Mary, age=1]

查询并删除(find and remove)文档的方法

查询并删除文档的方法findAndRemove()findAndModify()的使用几乎完全相同,有以下几个重载的方法:

  • <T> List<T> findAllAndRemove(Query query, String collectionName)
  • <T> List<T> findAllAndRemove(Query query, Class<T> entityClass)
  • <T> List<T> findAllAndRemove(Query query, Class<T> entityClass, String collectionName)

查询并替换(find and replace)文档的方法

findAndModify()方法类似,findAndReplace()方法也需要指定一个option

Optional<User> result = template.update(Person.class)      	
    .matching(query(where("firstame").is("Tom")))          	
    .replaceWith(new Person("Dick"))
    .withOptions(FindAndReplaceOptions.options().upsert()) 	
    .as(User.class)                                        	
    .findAndReplace();