Spring对MongoDB提供如下两种支持
spring-boot-starter-data-mongodb
是在Spring Boot中使用MongoDB所必需的依赖。
joda-money
和lombok
是下面的示例中所需要的依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-mongodbartifactId>
dependency>
<dependency>
<groupId>org.jodagroupId>
<artifactId>joda-moneyartifactId>
<version>RELEASEversion>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
如果想使用嵌入式的mongodb,类似于H2这样的嵌入式数据库,则还需要导入如下依赖。
<dependency>
<groupId>de.flapdoodle.embedgroupId>
<artifactId>de.flapdoodle.embed.mongoartifactId>
dependency>
在application.properties
中进行如下配置
由于我是在一台服务器的Docker中安装的MongoDB,所以uri那里需要填上服务器所对应的IP地址或者相应的域名。如果是本机安装的MongoDB,写上localhost就行,本机安装的MongoDB默认端口是27017。
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.jodagroupId>
<artifactId>joda-moneyartifactId>
<version>RELEASEversion>
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
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());
}
}
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,就不需要定义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 全家桶 丁雪峰