SpringBoot2 | 第十二篇:整合MongoDB

SpringData 已经集成了很多技术,也包含了 MongoDB,这篇笔记就以整合 SpringData MongoDB 为例。

[TOC]

为什么使用NoSQL?

今天我们可以通过第三方平台(如:Google,Facebook等)可以很容易的访问和抓取数据。用户的个人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经成倍的增加。我们如果要对这些数据进行挖掘,那SQL数据库已经不适合这些应用了, NoSQL数据库的发展也却能很好的处理这些大的数据。

MongoDB

MongoDB 是由 C++ 语言编写的,是一个基于分布式文件存储的开源数据库系统。在高负载的情况下,添加更多的节点,可以保证服务器性能。 MongoDB 旨在为 WEB 应用提供可扩展的高性能数据库解决方案MongoDB 将数据存储为一个文档,数据结构由键值对(key => value)组成。MongoDB 文档类似于 JSON 对象,key 必须为字符串类型,value 可以包含基本类型(string,int,float,timestamp,binary 等),其他文档,基本类型数组以及文档数组。

1565426843793

SpringData MongoDB

由于 SpringData MongoDBSpringData JPA 都属于 SpringData 系列,所以它们实现了 SpringData 同一套规范,在操作上有些非常相似。例如:继承 MongoRepository<T, ID>就会有一套crud 自动生成了。开箱即用~~

环境/版本一览:

  • 开发工具:Intellij IDEA 2018.2.2
  • springboot: 2.0.5.RELEASE
  • jdk:1.8.0_171
  • maven:3.3.9
  • spring-boot-starter-data-mongodb:2.0.10.RELEASE

1、pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 为了控制打印Json格式数据方便查看 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
</dependencies>

2、application.yml

1
2
3
4
5
6
7
8
9
spring:
data:
mongodb:
# 默认uri`mongodb://localhost:27017/test`
# 数据库有用户名和密码可参考:mongodb://name:password@localhost:27017/db
# uri: mongodb://fatal:123456@localhost:27017/test
uri: mongodb://localhost:27017/test
username: fatal
password: 123456

3、enums

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.fatal.enums;

import lombok.Getter;

/**
* @author Fatal
* @date 2019/8/9 0009 9:35
*/
@Getter
public enum OperationEnum {

/**
* 成功
*/
SUCCESS("success", "成功"),

/**
* 失败
*/
FAIL("fail", "失败");

private String code;

private String message;

OperationEnum(String code, String message) {
this.code = code;
this.message = message;
}

}

4、utils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.fatal.utils;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

/**
* @author Fatal
* @date 2019/8/9 0009 22:47
*/
public class JsonUtil {

/**
* 控制台输出Json格式的对象
* @param object
* @return
*/
public static String toJson(Object object) {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setPrettyPrinting();
Gson gson = gsonBuilder.create();
return gson.toJson(object);
}

}

5、entity

Customer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.fatal.entity;

import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.data.mongodb.core.mapping.Document;

import java.util.Date;

/**
* Customer 实体
* @author Fatal
* @date 2019/8/9 0009 8:15
* @desc 在类上加上`SpringData Mongodb 的 @Document`,可以更改该集合的名称
*/
@Data
@Accessors(chain = true)
@Document(collection = "customer")
public class Customer {

/**
* 该 id 主要供 mongodb 内部使用
*/
private String id;

private String name;

private Date birthday;

private Integer age;

private String hobby;

private String phone;

}

Address

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.fatal.entity;

import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

/**
* 地址 实体
* @author Fatal
* @date 2019/8/9 0009 17:58
*/
@Data
@Accessors(chain = true)
@Document(collection = "address")
public class Address {

/**
* 该 id 主要供 mongodb 内部使用
*/
private String id;

/**
* @Field 用于与数据库的字段做映射
*/
@Field(value = "customer_id")
private String customerId;

private String address;

}

6、dto

CustomerDTO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.fatal.dto;

import com.fatal.entity.Customer;
import lombok.Data;
import org.springframework.beans.BeanUtils;

import java.util.Date;

/**
* Customer 数据传输对象
* @author Fatal
* @date 2019/8/9 0009 10:10
*/
@Data
public class CustomerDTO {

private String id;

private String name;

private Date birthday;

private Integer age;

private String hobby;

public static CustomerDTO of(Customer customer) {
CustomerDTO customerDTO = new CustomerDTO();
BeanUtils.copyProperties(customer, customerDTO);
return customerDTO;
}

}

CustomerDetailDTO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.fatal.dto;

import lombok.Data;
import lombok.experimental.Accessors;

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

/**
* `客户详细信息`数据传输对象
* @author Fatal
* @date 2019/8/9 0009 18:31
*/
@Data
@Accessors(chain = true)
public class CustomerDetailDTO {

private String id;

private String name;

private Date birthday;

private Integer age;

private String hobby;

private String phone;

private List<AddressDTO> addressList;

}

AddressDTO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.fatal.dto;

import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.data.mongodb.core.mapping.Field;

/**
* Address 数据传输对象
* @author Fatal
* @date 2019/8/9 0009 18:32
*/
@Data
@Accessors(chain = true)
public class AddressDTO {

private String id;

@Field(value = "customer_id")
private String customerId;

private String address;

}

7、Test

SingleTableTests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package com.fatal;

import com.fatal.dao.CustomerRepository;
import com.fatal.dto.CustomerDTO;
import com.fatal.entity.Customer;
import com.fatal.enums.OperationEnum;
import com.fatal.utils.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit4.SpringRunner;

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

/**
* @author Fatal
* @date 2019/8/9 0009 12:09
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class SingleTableTests {

@Autowired
private CustomerRepository repository;

private Customer customer;

private List<Customer> customers;

@Before
public void before() {
customer = new Customer()
.setName("fatal")
.setAge(18)
.setPhone("13750411111")
.setBirthday(new Date())
.setHobby("swim");
customers = new ArrayList<>(Arrays.asList(
new Customer()
.setName("micai")
.setAge(18)
.setPhone("13750411111")
.setBirthday(new Date())
.setHobby("swim eat"),
new Customer()
.setName("xiaomi")
.setAge(22)
.setPhone("13750411111")
.setBirthday(new Date(System.currentTimeMillis() + 1000))
.setHobby("swim eat"),
new Customer()
.setName("miqi")
.setAge(18)
.setPhone("13750411111")
.setBirthday(new Date(System.currentTimeMillis() + 2000))
.setHobby("swim eat")
));
}

@Test
public void saveTest() {
Customer save = repository.save(customer);
log.info("【新增 Customer】 [status = {}, entity = {}]",
OperationEnum.SUCCESS.getCode(), save);
}

@Test
public void updateTest() {
Customer customer = repository.findById("5d4e8ddbe429c6d1303ad1ad")
.orElseThrow(RuntimeException::new);
Customer save = customer.setHobby("eat");
repository.save(customer);
log.info("【更新 Customer】 [status = {}, entity = {}]",
OperationEnum.SUCCESS.getCode(), save);
}

@Test
public void findByIdTest() {
Customer customer = repository.findById("5d4e8ddbe429c6d1303ad1ad")
.orElseThrow(RuntimeException::new);
log.info("【查询 Customer】 findById [status = {}, entity = {}]",
OperationEnum.SUCCESS.getCode(), customer);
}

@Test
public void deleteByIdTest() {
repository.deleteById("5d4e8ddbe429c6d1303ad1ad");
log.info("【删除 Customer】 [status = {}, id = {}]",
OperationEnum.SUCCESS.getCode(), "5d4e8ddbe429c6d1303ad1ad");
}

@Test
public void saveAllTest() {
repository.saveAll(customers).forEach(System.out::println);
}

@Test
public void findCustomerByNameTest() {
Customer customer = repository.findCustomerByName("micai");
log.info("【查询 Customer】 findCustomerByName [status = {}, entity = {}]",
OperationEnum.SUCCESS.getCode(), customer);
}

@Test
public void findAllByAgeTest() {
repository.findAllByAge(18).forEach(System.out::println);
}

@Test
public void findAllByExampleTest() {
Example<Customer> example = Example.of(new Customer().setAge(18).setName("micai"));
repository.findAll(example).forEach(System.out::println);
}

@Test
public void pageTest() {
Example<Customer> example = Example.of(new Customer().setHobby("swim eat"));
/*
* 先根据年龄升序,年龄相同的根据生日降序
*/
PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(
new Sort.Order(Sort.Direction.ASC, "age"),
new Sort.Order(Sort.Direction.DESC, "birthday")
));
Page<Customer> page = repository.findAll(example, pageRequest);
Page<CustomerDTO> dtoPage = page.map(CustomerDTO::of);
System.out.println(JsonUtil.toJson(dtoPage));
}

}

MongoTemplateTests

这套 API 有点不一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package com.fatal;

import com.fatal.entity.Customer;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.core.MongoTemplate;
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 org.springframework.test.context.junit4.SpringRunner;

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

/**
* @author Fatal
* @date 2019/8/9 0009 13:51
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class MongoTemplateTests {

@Autowired
private MongoTemplate mongoTemplate;

private Customer customer;

private List<Customer> customers;

@Before
public void before() {
/*
* TODO: 这里的id自己手动添加,为了后面聚合测试做区分。
* 如果使用了数据库默认生成的id,会因为它是ObjectId("...")修饰了,而无法与外键值建立关联。
*/
customer = new Customer()
.setId("137063")
.setName("fatal")
.setAge(18)
.setPhone("13750411111")
.setBirthday(new Date())
.setHobby("swim");
customers = new ArrayList<>(Arrays.asList(
new Customer()
.setName("micai")
.setAge(18)
.setPhone("13750411111")
.setBirthday(new Date())
.setHobby("swim eat"),
new Customer()
.setName("xiaomi")
.setAge(22)
.setPhone("13750411111")
.setBirthday(new Date(System.currentTimeMillis() + 1000))
.setHobby("swim eat"),
new Customer()
.setName("miqi")
.setAge(18)
.setPhone("13750411111")
.setBirthday(new Date(System.currentTimeMillis() + 2000))
.setHobby("swim eat")
));
}

@Test
public void saveTest() {
mongoTemplate.save(customer);
}

@Test
public void updateById() {
Query query = new Query(Criteria.where("id").is("137063"));
Update update = new Update().set("name", "fatal1");
mongoTemplate.updateFirst(query, update, Customer.class);
}

@Test
public void findByQueryTest() {
Query query = new Query(Criteria.where("id").is("137063")
.and("name").is("fatal1"));
List<Customer> customers = mongoTemplate.find(query, Customer.class);
customers.forEach(System.out::println);
}

@Test
public void saveAllTest() {
mongoTemplate.insertAll(customers);
}
}

MultiTableTests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.fatal;

import com.fatal.dto.CustomerDetailDTO;
import com.fatal.entity.Address;
import com.fatal.utils.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.LookupOperation;
import org.springframework.data.mongodb.core.aggregation.MatchOperation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.test.context.junit4.SpringRunner;

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

/**
* @author Fatal
* @date 2019/8/9 0009 12:19
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class MultiTableTests {

@Autowired
private MongoTemplate mongoTemplate;

@Before
public void saveAddressTest() {
// 与手动添加id的Customer关联
Address address1 = new Address()
.setCustomerId("137063")
.setAddress("shantou");
Address address2 = new Address()
.setCustomerId("137063")
.setAddress("guangzhou");
// 与自动生成id的Customer关联
Address address3 = new Address()
.setCustomerId("5d4e908fe429c66df040ae69")
.setAddress("foshan");
Address address4 = new Address()
.setCustomerId("5d4e908fe429c66df040ae69")
.setAddress("hangzhou");
mongoTemplate.insertAll(Arrays.asList(address1, address2, address3, address4));
}

@Test
public void findAllAddressTest() {
List<Address> addressList = mongoTemplate.findAll(Address.class);
addressList.forEach(System.out::println);
}

/**
* @method public static LookupOperation lookup(String from, String localField, String foreignField, String as)
* @parameter
* from: 从表
* localField: 主表被关联的键(如果关联的是主键,必须写 “_id”,下划线不能省略)
* foreignField: 从表外键
* as: 从表集合名称
* @desc 从表关联主表时,如果主表的id是自动生成的,那么它的值为:ObjectId("..."),这时通过 $lookup 就不能完成关联了。
*/
@Test
public void findMultiply() {
MatchOperation match = Aggregation.match(Criteria.where("age").is(18));
// 关联操作
LookupOperation lookup = Aggregation.lookup("address", "_id", "customer_id", "addressList");
Aggregation aggregation = Aggregation.newAggregation(lookup, match);
List<CustomerDetailDTO> customerDetail = mongoTemplate.aggregate(aggregation, "customer", CustomerDetailDTO.class).getMappedResults();
System.out.println(JsonUtil.toJson(customerDetail));
}

}

8、测试

单表测试

MongoRepository<T, ID>

1、访问 SingleTableTests.saveTest()

控制台

1565429315242

RoBo 3T

1565429367173

2、访问 SingleTableTests.updateTest()

控制台

1565429450536

RoBo 3T

1565429608535

3、访问 SingleTableTests.findByIdTest()

控制台

1565429796050

4、访问 SingleTableTests.deleteByIdTest()

控制台

1565429831290

RoBo 3T

1565429845860

5、访问 SingleTableTests.saveAllTest()

RoBo 3T

1565429979998

6、访问 SingleTableTests.findCustomerByNameTest()

控制台

1565430029663

7、访问 SingleTableTests.findAllByAgeTest()

控制台

1565430162737

8、访问 SingleTableTests.findAllByExampleTest()

控制台

1565430187170

9、访问 SingleTableTests.pageTest()

控制台

1565430250266

MongoTemplate

1、访问 MongoTemplateTests.saveTest()

RoBo 3T

1565431018546

2、访问 MongoTemplateTests.updateById()

RoBo 3T

1565431261027

3、访问 MongoTemplateTests.findByQueryTest()

控制台

1565431369194

4、访问 MongoTemplateTests.saveAllTest()

RoBo 3T

1565431457885

多表测试

访问 MultiTableTests.findMultiply()

控制台

1565432140046

你可以把这个测试方法的聚合操作 localField 改为id(去掉下划线),你会发现关联不上了,所以 lookup Field 对数据库字段的名称有很高要求,下划线不能省略(与 Field 有关的都不能省略)。

总结

源码地址: https://github.com/ynfatal/springboot2-learning/tree/master/chapter12

学习 方志朋 前辈的经验