Spring Boot集成MongoDB

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;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值