13 June 2017
mongodb是一个比较常见的document based的nosql数据库,我们目前主要存储大量的服务器心跳信息,所以要用到它。java调用mongodb的时候,会有多种database client的选型问题。
我来说说我遇到的一些问题。最早我采用的是原生的mongo db driver v3,这种方式调用和查询很方便,但是当文档类型一致,文档结构却很复杂的时候,通过手动的解析各个字段的时候,会变得很枯燥和很痛苦。
后来转型到spring data mongodb,这个可以直接映射到java object,缺点就是文档不是很人性化,特别是那些api,简直不知道在说什么。于是,我花了不少时间,学习了下通过MongoTemplate(spring data mongodb的核心类)来直接操作mongodb查询的测试用例。主要用来解决负责查询和映射的问题。
好了,直接进入主题
1 预备条件,一个简单的可以登录创建db,collection的mongo实例
2 配置下maven的依赖
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.10.3.RELEASE</version>
</dependency>
3, 配置下mongodb的config,如果是使用spring boot,会有一个默认的spring mongodb的配置,用户只需要在application.properties or application.yaml中修改链接信息就行,当然,我们还是要知其然,也知其所以然。
这里采用的是手工配置
package com.ly.openapi.api.configs;
import com.ly.openapi.api.db.mongo.converters.DateToZonedDateTimeConverter;
import com.ly.openapi.api.db.mongo.converters.ZonedDateTimeToDateConverter;
import com.mongodb.MongoClient;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.core.convert.CustomConversions;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.util.Assert;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Created by xlm45038 on 2017/6/1.
*/
@Configuration
public class MongoConfig {
@Value("${mongo.hosts}")
private String[] hosts;
@Value("${mongo.ports}")
private int[] ports;
@Value("${mongo.username}")
private String username;
@Value("${mongo.password}")
private String password;
@Value("${mongo.authdb}")
private String authenticationDatabase;
@Value("${mongo.db}")
private String database;
@Bean
MongoClient mongoClient() throws UnknownHostException {
String authDb = this.authenticationDatabase == null
? this.database : this.authenticationDatabase;
Assert.isTrue(hosts.length == ports.length, "length is not equal for hosts and ports");
List<ServerAddress> serverAddresses = new ArrayList<>();
List<MongoCredential> credentials = new ArrayList<>();
ServerAddress address;
MongoCredential credential;
for (int i = 0; i < hosts.length; i++) {
address = new ServerAddress(hosts[0], (ports[0]));
serverAddresses.add(address);
}
credential = MongoCredential.createCredential(this.username,
authDb, this.password.toCharArray());
credentials.add(credential);
return new MongoClient(
serverAddresses,
credentials);
}
@Bean
public MongoDbFactory mongoDbFactory() throws Exception {
return new SimpleMongoDbFactory(mongoClient(), this.database);
}
@Bean
public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory, MappingMongoConverter converter) throws UnknownHostException {
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory, converter);
return mongoTemplate;
}
@Bean
public MappingMongoConverter mappingMongoConverter(MongoDbFactory mongoDbFactory) {
MappingMongoConverter converter = new MappingMongoConverter(
new DefaultDbRefResolver(mongoDbFactory), new MongoMappingContext());
converter.setCustomConversions(mongoCustomConversions());
return converter;
}
@Bean
public CustomConversions mongoCustomConversions() {
return new CustomConversions(Arrays.asList(
new DateToZonedDateTimeConverter(),
new ZonedDateTimeToDateConverter()
));
}
}
这里的dependency就是MongoTemplate -> MongoDbFactory -> MongoClient
还有就是,mongodb默认不支持zoneddateTime,需要自己添加conversion
public class DateToZonedDateTimeConverter implements Converter<Date, ZonedDateTime> {
@Override
public ZonedDateTime convert(Date source) {
if (source == null) {
return null;
} else {
return ZonedDateTime.ofInstant(source.toInstant(), ZoneOffset.ofHours(8));
}
}
}
package com.ly.openapi.api.db.mongo.converters;
import org.springframework.core.convert.converter.Converter;
import java.time.ZonedDateTime;
import java.util.Date;
/**
* Created by xlm45038 on 2017/6/5.
*/
public class ZonedDateTimeToDateConverter implements Converter<ZonedDateTime, Date> {
@Override
public Date convert(ZonedDateTime source) {
if (source == null)
return null;
else
return Date.from(source.toInstant());
}
}
用到的配置文件 application.properties
mongo.hosts=10.100.189.30
mongo.ports=27017
mongo.username=admin
mongo.password=o123
mongo.authdb=admin
mongo.db=studyDB
最后就是一长段testcase,看方法名就知道我在测试什么,感觉还是example代码是最好的说明文档。
后期如果有需求,我会不定期更新。
package com.ly.openapi.api.db.mongo;
import com.ly.openapi.api.configs.RootConfig;
import com.mongodb.*;
import lombok.Data;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.regex.Pattern;
/**
* Created by hunter.xue on 2017/6/13.
*/
@ContextConfiguration(classes = {RootConfig.class})
@RunWith(value = SpringJUnit4ClassRunner.class)
public class MongoSpringDataTest {
private static DBCollection collection = null;
private static DB db = null;
@Autowired
private MongoClient mongoClient;
@Autowired
private MongoTemplate mongoTemplate;
@Before
public void insertDummyDocuments() throws UnknownHostException {
db = mongoClient.getDB("studyDB");
// get a single collection
collection = db.getCollection("dummyColl");
System.out.println(mongoTemplate.getDb().getMongo().getAddress());
System.out.println(mongoTemplate.getDb().getName());
System.out.println(mongoTemplate.getCollectionNames());
List<DBObject> list = new ArrayList<DBObject>();
Calendar cal = Calendar.getInstance();
for (int i = 1; i <= 5; i++) {
BasicDBObject data = new BasicDBObject();
data.append("number", i);
data.append("name", "mkyong-" + i);
cal.add(Calendar.DATE, 1);
list.add(data);
data = new BasicDBObject();
data.append("number", i);
data.append("name", "hunter-" + i);
cal.add(Calendar.DATE, 1);
list.add(data);
}
collection.insert(list);
}
@After
public void tearDown() {
collection.drop();
db.dropDatabase();
}
@Test
public void testFindAll1() {
Query query = new Query();
// query.addCriteria(
// Criteria.where("HeartbeatTime").exists(true).andOperator(
// Criteria.where("HeartbeatTime").gte(Date.from(start.toInstant())),
// Criteria.where("HeartbeatTime").lt(Date.from(end.toInstant()))
// )
// );
System.out.println(query.toString());
List<MyObject> datas = mongoTemplate.findAll(MyObject.class, "dummyColl");
datas.stream().forEach(data -> System.out.println(data));
}
@Test
public void testFindAll_implicitCollectionName() {
Query query = new Query();
System.out.println(query.toString());
List<MyObject> datas = mongoTemplate.findAll(MyObject.class);
datas.stream().forEach(data -> System.out.println(data));
}
@Test
public void testFindNameOnly() {
Query query = new Query();
query.fields().exclude("_id");
query.fields().exclude("name");
System.out.println(query.toString());
List<MyObject> datas = mongoTemplate.find(query, MyObject.class);
datas.stream().forEach(data -> System.out.println(data));
}
@Test
public void testFindNumberEqualsAndWithOrder() {
Query query = new Query();
query.addCriteria(
Criteria.where("number").is(5)
);
// query.with(new Sort(Sort.Direction.ASC, "number").and(new Sort(Sort.Direction.DESC,"name")));
query.with(new Sort(Sort.Direction.ASC, "number", "name"));
System.out.println(query);
List<MyObject> datas = mongoTemplate.find(query, MyObject.class);
datas.stream().forEach(System.out::println);
}
@Test
public void testFindNumberIn() {
Query query = new Query();
query.addCriteria(
Criteria.where("number").in(2, 4, 5)
);
query.with(new Sort(Sort.Direction.ASC, "number", "name"));
System.out.println(query);
List<MyObject> datas = mongoTemplate.find(query, MyObject.class);
datas.stream().forEach(System.out::println);
}
@Test
public void testBetweenAnd() {
Query query = new Query();
query.addCriteria(
Criteria.where("number").gt(2).lt(5)
);
query.with(new Sort(Sort.Direction.ASC, "number", "name"));
System.out.println("query>" + query.toString());
List<MyObject> datas = mongoTemplate.find(query, MyObject.class);
datas.stream().forEach(System.out::println);
}
@Test
public void testBetweenAnd2() {
Query query = new Query();
query.addCriteria(
new Criteria("number").lt(5).andOperator(
Criteria.where("number").gt(2),
new Criteria("number").lt(5))
);
query.with(new Sort(Sort.Direction.ASC, "number", "name"));
System.out.println("query>" + query.toString());
List<MyObject> datas = mongoTemplate.find(query, MyObject.class);
datas.stream().forEach(System.out::println);
}
@Test
public void testNe() {
Query query = new Query();
query.addCriteria(
Criteria.where("number").ne(4)
);
query.with(new Sort(Sort.Direction.ASC, "number", "name"));
System.out.println("query>" + query.toString());
List<MyObject> datas = mongoTemplate.find(query, MyObject.class);
datas.stream().forEach(System.out::println);
}
/**
测试基本的and查询
*/
@Test
public void testAnd() {
Query query = new Query();
query.addCriteria(
new Criteria().andOperator(
Criteria.where("number").is(2),
Criteria.where("name").is("hunter-3")
)
);
query.with(new Sort(Sort.Direction.ASC, "number", "name"));
System.out.println("query>" + query.toString());
List<MyObject> datas = mongoTemplate.find(query, MyObject.class);
datas.stream().forEach(System.out::println);
}
@Test
public void testRegex(){
Query query = new Query();
query.addCriteria(
Criteria.where("name").regex(Pattern.compile("HUN.*-[1-3]",Pattern.CASE_INSENSITIVE))
);
query.with(new Sort(Sort.Direction.ASC, "number", "name"));
System.out.println("query>" + query.toString());
List<MyObject> datas = mongoTemplate.find(query, MyObject.class);
datas.stream().forEach(System.out::println);
}
}
@Data
@Document(collection = "dummyColl")
class MyObject {
private int number;
private String name;
}
目前,spring data mongodb后台其实操作的是mongo java driver v2的代码。后期到了spring5.x时代,会迁移到mongo Java driver v3,到时候如果有api变动,我再写个new blog 追踪下。