Spring Boot集成MongoDB
关于MongoDB
MongoDB是由C++编写的,基于分布式文件存储的NoSQL数据库。它将数据存储为一个文档,文档的数据结构由key-value组成,因而MongoDB文档类似JSON对象。MongoDB支持海量存储,性能很高,查询很快。MongoDB可以快速并且安全的实现故障节点转移,具有高可用性;MongoDB支持地理位置、二维空间索引;当数据规模增长过快、数据量过大时,MongoDB可以通过分片负载解决这个问题;MongoDB还有以下特点:
- 格式自由,数据格式不固定,在生产环境下修改结构不影响程序运行
- 强大的面向对象的查询语句
- 完整的索引支持,支持查询计划
- 支持复制和自动故障转移
- 支持二进制数据和大型文件如视频等高效存储
MongoDB主要有以下缺点:
- 对事务的支持较弱,需要4.0+的版本,并且需要自己搭建MongoDB复制集,单个MongoDB Server不支持事务
- 涉及到复杂的高度优化的查询方式,比如多数据实体关联查询
MongoDB和redis一样也是内存数据库,会将数据加载在内存进行操作,专门有一个线程不断获取需要持久化的数据,在一定的周期内将数据持久化到磁盘。
一些MongoDB命令
//User为集合名
//查询集合的所有索引
db.User.getIndexes()
//name是字段名,1升序,-1是降序
//创建普通索引
db.User.ensureIndex({"name":1})
//创建联合索引
db.User.ensureIndex({"name":1,"age":1})
//创建唯一索引
db.User.ensureIndex({"name":1},{"unique":true})
Spring Boot使用MongoDB
Spring Boot使用MongoDB有两种方式,分别是使用JPA和MongoTemplate
使用JPA操作MongoDB
与一般的JPA不同的是,持久层Repository接口继承的是MongoRepository,然后通过JPA的接口方法名称定义查询操作
public interface UserRepository extends MongoRepository<User,Long> {
List<User> findByUserName(String userName);
}
使用MongoTemplate操作MongoDB
首先添加依赖
<!-- mongodb -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
注:MongoDB要求每个文档有一个名为_id的field,映射时,使用@Id(org.springframework.data.annotation.Id)标注该属性作为_id
MongoTemplate相关API
/*
* MongoTemplate 对象方法
*/
//插入
template.save(Object object)//保存一个对象,覆盖具有相同id的对象
template.insert(Object object)//保存一个对象,如果文档具有相同id则报错
template.insertAll(Collection objsToSave);//保存一组对象,如果id重复会报错
//更新
template.updateFirst(Query query,Update update,Class cls);//使用提供的更新对象去更新匹配查询条件的第一个文档
template.updateMulti();//使用提供的更新对象去更新匹配查询条件的所有文档
//删除
template.remove(Query query,Class<?> entityClass);//删除符合查询条件的所有文档
//查询
template.find(Query query,Class<?> entityClass)//条件查询
/*
* Update 对象方法
*/
Update set(String key,Object val)//为键key指定值val,如果没有该字段则新建字段赋值
Update unset(String key)//删除字段
Update inc(String key,Number inc)//增加键值,inc可以为负
Update multiply(String key,Number multiplier)//乘以键值,multiplier为乘数
Update push(String key,Object val)//如果没有field为key的数组则新建一个数组并添加元素val,如果有则直接添加元素。
Update pop(String key,Update.Position pos)//删除field为key的数组中的元素,pos指定pop的方向,有FIRST\LAST等枚举值
Update addToSet(String key,Object val)//如果没有field为key的数组则新建一个数组并添加元素val,如果有则直接添加元素。如果元素已存在数组中则不会添加。
/*
* Criteria 对象方法
*/
Criteria is(Object o)//精准匹配
Criteria in(Collection<?> collection)//包含
Criteria where(String key)//要筛选的字段
Criteria and(String key)//与..key操作
Criteria andOperator(Criteria criteria)//与操作
Criteria orOperator(Criteria criteria)//或操作
Criteria notOperator(Criteria criteria)//非操作
Criteria gt(Object o)//大于
Criteria gte(Object o)//大于等于
Criteria lt(Object o)//小于
Criteria lte(Object o)//小于等于
Criteria regex(Pattern pattern);//正则匹配
/*
* Query 对象方法
*/
Query addCriteria(Criteria criteria)//添加查询条件
Query limit(int limit)//返回结果集大小
Query skip(int skip)//跳过结果集中的文档个数
Query with(Pageable pageable)//分页查询条件
Query with(Sort sort)//为结果定义排序
/*
* Pattern 方法
*/
Pattern pattern = Pattern.compile("^.*"+"david"+".*$");
关于正则表达式
- 在java中与其他语言不一样,在java中使用 \ 不具转义作用,需要 \\ 才能表示转义意义。而表示一个普通的反斜杠应该是 \\\\
- 正则表达式默认贪婪匹配原则,即匹配符合条件的最长的字符串,在限定字符表达式如 * , + , ? , {n} , {n,m}之后加上 ? 号后,则变成了懒惰匹配
一般语言的正则表达式字符含义
字符(串) | 说明 |
---|---|
abcde fghijk | 匹配字符串 “abcde fghijk” |
this\s+is\s+text | 可以匹配 “this is text” |
\ | 转义,表示下一个字符具有特殊意义 |
^ | 匹配输入字符串的开始位置 |
$ | 匹配输入字符串的结束位置 |
* | 0次或多次匹配前面的单个字符或者子表达式 |
+ | 1次或多次匹配前面的单个字符或者子表达式 |
? | 0次或1次匹配前面的单个字符或者子表达式 |
{n} | n>=0,表示前面的字符或子表达式正好匹配n次 |
{n,} | n>=0,表示前面的字符或子表达式最少匹配n次 |
{n,m} | n,m>=0,表示前面的字符或子表达式最少匹配n次,最多匹配m次 |
[xyz] | 表示匹配 [ ] 号中包含任一字符 |
[^xyz] | 表示任一匹配 [ ] 号中不包含字符 |
[a-z] | 表示a-z范围内任意字符 |
[^a-z] | 表示非a-z范围内任意字符 |
\d | 匹配任一数字字符 |
\D | 匹配任一非数字字符,等效于[^0-9] |
\n | 匹配换行符 |
\r | 匹配回车符 |
\s | 匹配任何空白字符 |
. | 匹配除了 \r\n 外的任一字符 |
测试程序
/*
* MongoDBConfig
*/
@Configuration
public class MongoDBConfig {
@Value("${spring.data.mongodb.database}")
private String database;
@Value("${spring.data.mongodb.uri}")
private String uri;
@Bean
public MongoDbFactory mongoDbFactory() throws UnknownHostException{
MongoClientURI mongoClientURI = new MongoClientURI(uri+"/"+database);
MongoDbFactory mongoDbFactory = new SimpleMongoDbFactory(mongoClientURI);
return mongoDbFactory;
}
@Bean
public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory){
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory);
return mongoTemplate;
}
}
/*
* User data object
*/
@Document(collection = "User")
public class User implements Serializable {
@Id
@Field("_id")
private String userId;
/**
* 用户名
*/
@Field("userName")
private String userName;
/**
* 用户生日
*/
@Field("birth")
private Long birth;
/**
* 用户年龄
*/
@Field("age")
private Integer age;
@Field("location")
private String location;
//getter and setter ..
}
/*
* UserDao Repository
*/
@Repository
public class UserDaoImpl {
@Resource
private MongoTemplate mongoTemplate;
@Override
public User save(User user) {
User result = mongoTemplate.save(user);
return result;
}
@Override
public User insert(User user) {
User result = mongoTemplate.insert(user);
return result;
}
@Override
public void insertAll(List<User> users) {
mongoTemplate.insertAll(users);
}
@Override
public void remove(String userId) {
mongoTemplate.remove(userId,"");
}
@Override
public void remove(Query query) {
mongoTemplate.remove(query,User.class);
}
@Override
public List<User> find(Query query) {
List<User> userList = mongoTemplate.find(query,User.class);
return userList;
}
@Override
public void updateFirst(Query query, Update update) {
mongoTemplate.updateFirst(query,update,User.class);
}
@Override
public void updateMulti(Query query, Update update) {
mongoTemplate.updateMulti(query,update,User.class);
}
}
/*
* 测试程序
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserDaoImplTest {
@Autowired
private UserDaoImpl userDao ;
@Test
public void save() {
User user = new User();
user.setUserId("10");
user.setUserName("邓艾");
user.setAge(17);
user.setLocation("桂林市");
userDao.save(user);
}
@Test
public void insert() {
User user = new User();
user.setUserId("11");
user.setUserName("测试");
user.setAge(18);
user.setLocation("郴州市");
userDao.insert(user);
}
@Test
public void insertAll() {
User user1 = new User();
user1.setUserId("12");
user1.setUserName("郭嘉");
user1.setAge(18);
user1.setLocation("哈尔滨市");
User user2 = new User();
user2.setUserId("11");
user2.setUserName("测试");
user2.setAge(18);
user2.setLocation("郴州市");
User user3 = new User();
user3.setUserId("13");
user3.setUserName("荀彧");
user3.setAge(21);
user3.setLocation("武汉市");
User user4 = new User();
user4.setUserId("13");
user4.setUserName("鲁肃");
user4.setAge(21);
user4.setLocation("苏州市");
List<User> userList = new ArrayList<>();
userList.add(user3);
userList.add(user4);
userDao.insertAll(userList);
}
@Test
public void remove() {
Query query = new Query(Criteria.where("age").gte(25));
userDao.remove(query);
}
@Test
public void find() throws Exception {
Query query = new Query();
Pattern pattern = Pattern.compile("^.*"+"州"+".*$");
//查询_id为7且userName为乐进的user
//query.addCriteria(Criteria.where("_id").is("7").and("userName").is("乐进"));
//查询location中包含“州”字的user
//query.addCriteria(Criteria.where("location").regex(pattern));
//查询 年龄大于等于24 或者 (年龄小于19且location包含“州”字)的user
//query.addCriteria(
// new Criteria().orOperator(
// Criteria.where("age").gte(24),
// new Criteria().andOperator(
// Criteria.where("age").lt(19),
// Criteria.where("location").regex(pattern)
// )
// )
//);
//按age升序分页查询
//Sort sort = new Sort(Sort.Direction.ASC,"age");
//PageRequest pageRequest = PageRequest.of(1,2,sort);
//query.with(pageRequest);
//查询age是17,18这两个数的user
//query.addCriteria(Criteria.where("age").in(17,18));
List<User> userList = userDao.find(query);
Assert.assertNotNull(userList);
}
@Test
public void updateFirst() {
Query query = new Query();
query.addCriteria(Criteria.where("_id").is("6"));
Update update = new Update();
update.set("language","Chinese");
userDao.updateFirst(query,update);
}
@Test
public void updateMulti() {
Query query = new Query();
query.addCriteria(Criteria.where("age").gte(21));
Update update = new Update();
update.inc("age",2);
userDao.updateMulti(query,update);
}
}
MongoTemplate 分组聚合
MongoTemplate分组聚合需要使用到Aggregation类,这个类里的一系列操作是管道操作。管道操作的性质是每一步顺序执行,上一步的操作结果会传递给下一步,示例
@Test
public void testGroup(){
//创建一个Aggregation
Aggregation agg = Aggregation.newAggregation(
//提取出即将要操作的User的字段
Aggregation.project("age","location"),
//筛选出User中age<21的
Aggregation.match(Criteria.where("age").lt(21)),
//将User按locaiton分组,聚合统计age的sum,命名为ageSum
Aggregation.group("location").sum("age").as("ageSum"),
//上一步聚合的结果只存在ageSum一个字段,
//这一步先提取出ageSum,再提取出上一步聚合的字段location
Aggregation.project("ageSum").and("location").previousOperation(),
//按ageSum升序排序
Aggregation.sort(Sort.Direction.ASC,"ageSum"),
//排除掉前1个元素
Aggregation.skip(Long.valueOf(1)),
//提取2个元素
Aggregation.limit(Long.valueOf(2))
);
//agg是分组聚合操作,TempUserAggregation是分组聚合的查询结果对应的对象,User是分组聚合操作的Mongodb的collection
AggregationResults<TempUserAggregation> results = mongoTemplate.aggregate(agg,"User",TempUserAggregation.class);
List<TempUserAggregation> tempUserAggregationList = results.getMappedResults();
Assert.assertNotNull(tempUserAggregationList);
}
Aggregation agg = Aggregation.newAggregation(
Aggregation.project("content", "files", "feedbackId", "type", "uid", "createAt", "readAlready", "status"),
Aggregation.match(
new Criteria().andOperator(
Criteria.where("type").is(Constant.CustomerFeedbackRecordType.CUSTOMER).and("uid").is(uid),
new Criteria().orOperator(criteriaList.toArray(new Criteria[criteriaList.size()]))
)
),
Aggregation.group("feedbackId")
.last("content").as("content")
.last("files").as("files")
.last("uid").as("uid")
.last("createAt").as("createAt")
.last("readAlready").as("readAlready")
.last("status").as("status"),
Aggregation.project("content", "files", "uid", "createAt", "readAlready", "status").and("feedbackId").previousOperation(),
Aggregation.sort(Sort.Direction.DESC, "createAt"),
Aggregation.skip((pageNumber-1)*pageSize.longValue()),
Aggregation.limit(pageSize.longValue())
);
//使用mongo的BasicDBObject查询
MongoClient mongoClient = new MongoClient( "1922.168.0.1" , 27017 );
MongoDatabase mongoDatabase =mongoClient.getDatabase("mongo");
MongoCollection<Document> collection = mongoDatabase.getCollection("user");
BasicDBObject query = new BasicDBObject();
query.put("times", new BasicDBObject("$gte", "2020-01-01 10:10:00").append("$lte","2020-03-03 10:20:00"));
Pattern pattern = Pattern.compile("^.*贾.*$", Pattern.CASE_INSENSITIVE);
query.put("userName", pattern);
query.put("id", "11");
MongoCursor<Document> cursor = collection.find(query).sort(Sorts.orderBy(Sorts.descending("times"))).skip(0).limit(10).iterator();
try {
while (cursor.hasNext()) {
User user = new User();
JSONObject jsonObject = JSONObject.parseObject( cursor.next().toJson().toString());
user.setId(jsonObject.getString("id"));
user.setUserId(jsonObject.getString("userId"));
user.setUserName(jsonObject.getString("userName"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
cursor.close();
}
拆分查询:当某个对象的属性是个对象数组时,又需要通过这个数组的对象的某个属性筛选查询时,这就需要拆分,对单个父类对象的数组属性拆分成,可以得到多个父类对象,每个父类对象的这个数组属性变成了单个元素属性。因此会带来数据结构的变化。关键字是unwind,例如:
{
"_id" : ObjectId("5951ca15567ebff0d5011fbb"),
"name" : "陈晓婵",
"address" : "北京朝阳",
"lunch" : [
{
"food" : "baozi",
"fruit" : "taozi"
},
{
"food" : "miaotiao",
"fruit" : "xigua"
}
]
}
-------------------------拆分前后--------------------------
{
"_id" : ObjectId("5951ca15567ebff0d5011fbb"),
"name" : "陈晓婵",
"address" : "北京朝阳",
"lunch" : {
"food" : "baozi",
"fruit" : "taozi"
}
}
/* 2 */
{
"_id" : ObjectId("5951ca15567ebff0d5011fbb"),
"name" : "陈晓婵",
"address" : "北京朝阳",
"lunch" : {
"food" : "miaotiao",
"fruit" : "xigua"
}
}
在java中,特别注意到是拆分前后带来的数据结构的变化mongoTemplate.aggregate(agg, “dc_core_report”, ReportUnwindVo.class);这个方法中第一个参数就是管道对象,第二是操作的mongodb的集合名称,第三个是查询结果映射对象。具体代码如下
List<ReportVo> result = new ArrayList<>();
Aggregation agg = Aggregation.newAggregation(
Aggregation.unwind("columns"),
Aggregation.match(Criteria.where("appId").is(appId)),
Aggregation.match(new Criteria().orOperator(Criteria.where("columns._id").is(new ObjectId(colId)), Criteria.where("columns._id").is(colId)))
);
AggregationResults<ReportUnwindVo> results = mongoTemplate.aggregate(agg, "dc_core_report", ReportUnwindVo.class);
List<ReportUnwindVo> reportEntityList = results.getMappedResults();
if(!CollectionUtils.isEmpty(reportEntityList)) {
//result = reportMapper.toVoList(reportEntityList);
}
return result;