Spring Boot中使用MongoDB

Spring对MongoDB提供如下两种支持

  • MongoTemplate
  • Repository支持

对应于于JDBC Template和JPA这两种方式。
在这里插入图片描述

MongoDB Repository使用实例

导入依赖

spring-boot-starter-data-mongodb是在Spring Boot中使用MongoDB所必需的依赖。
joda-moneylombok是下面的示例中所需要的依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

<dependency>
	<groupId>org.joda</groupId>
	<artifactId>joda-money</artifactId>
	<version>RELEASE</version>
</dependency>

<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>

如果想使用嵌入式的mongodb,类似于H2这样的嵌入式数据库,则还需要导入如下依赖。

<dependency>
	<groupId>de.flapdoodle.embed</groupId>
	<artifactId>de.flapdoodle.embed.mongo</artifactId>
</dependency>

配置MongoDB

application.properties中进行如下配置
在这里插入图片描述
在这里插入图片描述

由于我是在一台服务器的Docker中安装的MongoDB,所以uri那里需要填上服务器所对应的IP地址或者相应的域名。如果是本机安装的MongoDB,写上localhost就行,本机安装的MongoDB默认端口是27017。

定义Model

Model中定义需要持久化到数据库中的对象

package geektime.spring.data.mongodemo.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.joda.money.Money;
//注意该id的注解是位于org.springframework.data.annotation包中,不是JPA中的ID
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.util.Date;

@Document //将该对象声明为要持久化到MongoDB中的文档
@Data //lombok的注解,负责自动增加getter和setter注解,重写toString和hashCode方法
@NoArgsConstructor //lombok的注解,负责自动增加无参构造
@AllArgsConstructor //lombok的注解,负责自动增加有参构造
@Builder //lombok的注解,运行以builder模式创建对象
public class Coffee {
    @Id
    private String id;//指明文档的ID,在MongoDB中存储时,String类型的ID会被转化为Object ID
    private String name;
    private Money price;//Money是专门用来表示金额的一个类
    private Date createTime;
    private Date updateTime;
}

由于金额的处理实际上是比较复杂的,所以需要使用joda-money来进行处理

<dependency>
	<groupId>org.joda</groupId>
	<artifactId>joda-money</artifactId>
	<version>RELEASE</version>
</dependency>

@Document注解:
使用了@Document注解,表明Coffee是一个文档实体,可以在Mongo数据库中执行读取和写入操作。默认情况下,集合名(这是Mongo中与关系型数据库的表对等的概念)是基于类名的,只不过第一个字母会变成小写。因为我们没有特别指定,所以Coffee对象将会持久化到名为coffee的集合中。但是,我们可以通过设置@Document的collection属性改变这种行为:

@Document(collection = "coffee_menu")

MongoDB中存储的数据为如下格式
在这里插入图片描述
存储在MongoDB中的是文档(Document)。price是Money类型的一个对象,存入MongoDB中的时候会将其序列化为一个BSON字符串(类似于JSON),存入的时候会将Money类型当作是一个Object类型的数据来进行存储,而MongoDB本身是支持Object类型的数据的。但是读取的时候不能自动将其从Document转化为Money对象。这就需要自定义一个Convertor

自定义Converter

package geektime.spring.data.mongodemo.converter;

import org.bson.Document;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
//Converter<Document, Money>从Document转化为Money
public class MoneyReadConverter implements Converter<Document, Money> {
    @Override
    public Money convert(Document source) {
        Document money = (Document) source.get("money");
        double amount = Double.parseDouble(money.getString("amount"));
        String currency = ((Document) money.get("currency")).getString("code");
        return Money.of(CurrencyUnit.of(currency), amount);
    }
}

定义MongoCustomConversions类型的Bean

@Bean
public MongoCustomConversions mongoCustomConversions() {
  return new MongoCustomConversions(Arrays.asList(new MoneyReadConverter()));
}

为什么会想到定义这么一个Bean呢?
我们查看Spring Boot关于mongo的自动配置。org.springframework.boot.autoconfigure.data.mongo,发现里面导入了MongoDataConfiguration.class
在这里插入图片描述
再看看MongoDataConfiguration.class。当我们自定义一个MongoCustomConversions的bean的时候就能实现相应的转换功能。

package org.springframework.boot.autoconfigure.data.mongo;

@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, Persistent.class));
		Class<?> strategyClass = properties.getFieldNamingStrategy();
		if (strategyClass != null) {
			context.setFieldNamingStrategy((FieldNamingStrategy) BeanUtils.instantiateClass(strategyClass));
		}
		context.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
		return context;
	}

	@Bean
	@ConditionalOnMissingBean//当没有MongoCustomConversions的时候自动注册一个空的converter,不执行任何转换操作,如果我们自定义了一个Bean,就不会执行这个操作。
	MongoCustomConversions mongoCustomConversions() {
		return new MongoCustomConversions(Collections.emptyList());
	}
}

定义Repository

package geektime.spring.data.mongodemo.repository;

import geektime.spring.data.mongodemo.model.Coffee;
import org.springframework.data.mongodb.repository.MongoRepository;

import java.util.List;

public interface CoffeeRepository extends MongoRepository<Coffee, String> {
    List<Coffee> findByName(String name);
}

进行使用

package geektime.spring.data.mongodemo;

import geektime.spring.data.mongodemo.converter.MoneyReadConverter;
import geektime.spring.data.mongodemo.model.Coffee;
import geektime.spring.data.mongodemo.repository.CoffeeRepository;
import lombok.extern.slf4j.Slf4j;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

@Slf4j
@SpringBootApplication
@EnableMongoRepositories//要使用该注解开启对MongoRepositories的支持
public class MongoRepositoryDemoApplication implements CommandLineRunner {
  @Autowired
  private CoffeeRepository coffeeRepository;

  public static void main(String[] args) {
    SpringApplication.run(MongoRepositoryDemoApplication.class, args);
  }

  @Bean
  public MongoCustomConversions mongoCustomConversions() {
    return new MongoCustomConversions(Arrays.asList(new MoneyReadConverter()));
  }

  @Override
  public void run(String... args) throws Exception {
    Coffee espresso = Coffee.builder()
            .name("espresso")
            .price(Money.of(CurrencyUnit.of("CNY"), 20.0))
            .createTime(new Date())
            .updateTime(new Date()).build();

    Coffee latte = Coffee.builder()
            .name("latte")
            .price(Money.of(CurrencyUnit.of("CNY"), 30.0))
            .createTime(new Date())
            .updateTime(new Date()).build();

	//插入
	coffeeRepository.insert(Arrays.asList(espresso, latte));
	//查询所有,查询结果按照name字段进行排序
    coffeeRepository.findAll(Sort.by("name"))
            .forEach(c -> log.info("Saved Coffee {}", c));
 	Thread.sleep(1000);
    latte.setPrice(Money.of(CurrencyUnit.of("CNY"), 35.0));
    latte.setUpdateTime(new Date());
    //再次存储相同的对象,相当于进行更新
    coffeeRepository.save(latte);
	//按照name字段进行查找
    coffeeRepository.findByName("latte")
            .forEach(c -> log.info("Coffee {}", c.toString()));
    //删除数据库中指定对象
    coffeeRepository.delete(espresso);
  }
}

MongoTemplate使用实例

使用MongoTemplate,就不需要定义Repository了。

使用示例如下。

package geektime.spring.data.mongodemo;

import com.mongodb.client.result.UpdateResult;
import geektime.spring.data.mongodemo.converter.MoneyReadConverter;
import geektime.spring.data.mongodemo.model.Coffee;
import lombok.extern.slf4j.Slf4j;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

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

@SpringBootApplication
@Slf4j
public class MongoDemoApplication implements ApplicationRunner {

	@Autowired//注入MongoTemplate的Bean
	private MongoTemplate mongoTemplate;

	public static void main(String[] args) {
		SpringApplication.run(MongoDemoApplication.class, args);
	}

	@Bean
	public MongoCustomConversions mongoCustomConversions() {
		return new MongoCustomConversions(Arrays.asList(new MoneyReadConverter()));
	}

	@Override
	public void run(ApplicationArguments args) throws Exception {
		Coffee espresso = Coffee.builder()
				.name("espresso")
				.price(Money.of(CurrencyUnit.of("CNY"), 20.0))
				.createTime(new Date())
				.updateTime(new Date()).build();
		//插入,只能单个进行插入并返回插入结果
		Coffee saved = mongoTemplate.save(espresso);
		log.info("Coffee {}", saved);
		//查询,按照名字查询。Criteria用于创建查询语句
		List<Coffee> list = mongoTemplate.find(
				Query.query(Criteria.where("name").is("espresso")), Coffee.class);
		log.info("Find {} Coffee", list.size());
		list.forEach(c -> log.info("Coffee {}", c));

		Thread.sleep(1000); // 为了看更新时间
		//执行更新操作
		UpdateResult result = mongoTemplate.updateFirst(query(where("name").is("espresso")),
				new Update().set("price", Money.ofMajor(CurrencyUnit.of("CNY"), 30))
						.currentDate("updateTime"),
				Coffee.class);
		log.info("Update Result: {}", result.getModifiedCount());
		//根据ID进行查找
		Coffee updateOne = mongoTemplate.findById(saved.getId(), Coffee.class);
		log.info("Update Result: {}", updateOne);
		//删除数据库中指定对象
		mongoTemplate.remove(updateOne);
	}
}

参考:玩转Spring 全家桶 丁雪峰

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐