聚合操作
什么是Mongodb聚合框架?
MongoDB聚合框架(Aggregation Framewordk) 是一个计算框架,他可以作用在一个或多个集合中,对集合中的数据进行一系列运算。将这些数据转化为期望的形式。
从效果而言, 聚合框架相当于SQL查询中 Group by
left join
原理
管道(Pipeline) 与 步骤(Stage)
整个聚合运算工程称之为管道, 它是由多个步骤组成
每个管道:
1:接收一系列文档(原始数据)
2:每一个步骤对这些文档进行一系列运算
3:结果文档输出给下一个步骤
基本语法
pipeline = [$stage1, $stage2, .... $stageN]
db.集合.aggregate(
pipeline,
{
options
}
)
eg:
db.user.aggregate(
[
{
$group:{
_id:"$name",
namecount:{$sum:1}
}
}
]
);
常见的步骤
$project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
m a t c h :用于过滤数据,只输出符合条件的文档。 match:用于过滤数据,只输出符合条件的文档。 match:用于过滤数据,只输出符合条件的文档。match使用MongoDB的标准查询操作。
$limit:用来限制MongoDB聚合管道返回的文档数。
$skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
$group:将集合中的文档分组,可用于统计结果。
$sort:将输入文档排序后输出。
$unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
$geoNear:输出接近某一地理位置的有序文档。
文档设计
范式
设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式,各种范式呈递次规范,越高的范式数据库冗余越小。
目前关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)。 ————百度百科
反范式
打破第三范式
符合第三范式设计
employee表:
字段 | 注释 |
---|---|
id | 主键 |
name | 员工姓名 |
dept_id | 部门id |
dept表:
字段 | 注释 |
---|---|
id | 主键 |
name | 部门名 |
需求:查询员工信息同时查询员工所在的部门信息
select e.*, d.* from employee e left join dept d on e.dept = d.id
打破第三范式
employee表:
字段 | 注释 |
---|---|
id | 主键 |
name | 员工姓名 |
dept_id | 部门id |
dept_name | 部门名 |
dept表:
字段 | 注释 |
---|---|
id | 主键 |
name | 部门名 |
需求:查询员工信息同时查询员工所在的部门信息
select * from employee
为了提高查询效率,一般会给表加一些冗余字段,这种操作称之为打破第三范式。这种方式一般用于当查询(DQL)远远大于增删改(DML)操作。
MongoDB不建议使用 left join ,推荐使用冗余字段,打破第三范式。
使用数组方式替换一对多关系
employee表结构:
employee{
id
name
}
dept表结构:
dept{
id
name
employees:[{},{}]
}
对象:
//employee
class Employee{
id
name
}
//dept
class Dept{
id
name
List<Employee> es
}
集成SpringBoot
导入依赖
Spring Data MongoDb
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</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>
<!--spring boot data mongodb-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
配置连接参数
# application.properties
# 配置数据库连接
#格式: mongodb://账号:密码@ip:端口/数据库?认证数据库
#spring.data.mongodb.uri=mongodb://root:admin@localhost/mongodemo?authSource=admin
spring.data.mongodb.uri=mongodb://localhost/mongodemo
# 配置MongoTemplate的执行日志
logging.level.org.springframework.data.mongodb.core=debug
domain
@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@ToString
@Document("users") //设置文档所在的集合
public class User {
//文档的id使用ObjectId类型来封装,并且贴上@Id注解,
// 自动映射为_id 自动封装ObjectId
@Id
private String id;
private String name;
private Integer age;
private List<String> hobby = new ArrayList<>();
}
MongoRepository
/**
* 自定义一个接口继承MongoRepository,
* 泛型1:domain类型
* 泛型2:文档主键类型
* 贴上@Repository注解,底层会创建出动态代理对象,交给Spring管理
*/
@Repository
public interface UserMongoRepository extends MongoRepository<User, ObjectId> {
// 使用Spring Data命名规范做高级查询
List<User> findByName(String name);
}
Spring Data方法命名规范
关键字 | 例子 | JPQL |
---|---|---|
And | findByNameAndAge(String name, Integer age) | where name = ? and age = ? |
Or | findByNameOrAge(String name, Integer age) | where name = ? or age = ? |
Is | findByName(String name) | where name = ? |
Between | findByAgeBetween(Integer min, Integer max) | where age between ? and ? |
LessThan | findByAgeLessThan(Integer age) | where age < ? |
LessThanEqual | findByAgeLessThanEqual(Integer age) | where age <= ? |
GreaterThan | findByAgeGreaterThan(Integer age) | where age > ? |
GreaterThanEqual | findByAgeGreaterThanEqual(Integer age) | where age >= ? |
After | 等同于GreaterThan | |
Before | 等同于LessThan | |
IsNull | findByNameIsNull() | where name is null |
IsNotNull | findByNameIsNotNull() | where name is not null |
Like | findByNameLike(String name) | where name like ? |
NotLike | findByNameNotLike(String name) | where name not like ? |
StartingWith | findByNameStartingWith(String name) | where name like ‘?%’ |
EndingWith | findByNameEndingWith(String name) | where name like ‘%?’ |
Containing | findByNameContaining(String name) | where name like ‘%?%’ |
OrderByXx[desc] | findByIdOrderByXx[Desc] (Long id) | where id = ? order by Xx [desc] |
Not | findByNameNot(String name) | where name != ? |
In | findByIdIn(List<Long> ids) | where id in ( … ) |
NotIn | findByIdNotIn(List<Long> ids) | where id not in ( … ) |
True | findByXxTrue() | where Xx = true |
False | findByXxFalse() | where Xx = false |
IgnoreCase | findByNameIgnoreCase(String name) | where name = ? (忽略大小写) |
实例代码
@Autowired
private UserMongoRepository repository;
// 插入/更新一个文档
@Test
public void testSaveOrUpdate() throws Exception {
User user = new User(null, 5L, "bunny", 20);
// 主键为null则新增,不为null则更新
repository.save(user);
}
// 删除一个文档
@Test
public void testDelete() throws Exception {
repository.deleteById(new ObjectId("xxx"));
}
// 查询一个文档
@Test
public void testGet() throws Exception {
Optional<User> optional = repository.findById(new ObjectId("xxx"));
optional.ifPresent(System.err::println);
}
// 查询所有文档
@Test
public void testList() throws Exception {
// 查询所有文档
List<User> list = repository.findAll();
list.forEach(System.err::println);
}
MongoTemplate
该对象有SpringBoot完成了自动配置,存入Spring容器中,我们直接注入就可以使用了,依靠该对象能完成任何的MongoDB操作,一般和MongoRepository分工合作,多数用于复杂的高级查询以及底层操作
//注入MongoTemplate
@Autowired
private MongoTemplate template;
条件限定
Query对象用于封装查询条件,配合Criteria一起使用,来完成各种条件的描述
//一个Criteria对象可以理解为是一个限定条件
Criteria.where(String key).is(Object val); //设置一个等值条件
Criteria.orOperator(Criteria ...); //设置一组或的逻辑条件
//模糊查询(了解)
Criteria.where(String key).regex(String regex); //使用正则表达式匹配查询
注意:Criteria对象可以由其静态方法和构造器获取
Criteria封装了所有对条件的描述,常见的有以下方法
lt / lte / gt / gte / ne / ...
最后通过Query对象的addCriteria把条件封装到Query对象中
Query对象.addCriteria(Criteria criteria); //添加查询条件
Query对象.skip(start).limit(pageSize); //分页查询
API方法
//根据条件查询集合中的文档
List<T> mongoTemplate.find(Query query, Class<T> type, String collectionName);
实例代码
@Autowired
private MongoTemplate mongoTemplate
// 分页查询文档,显示第2页,每页显示3个,按照id升序排列
@Test
public void testQuery1() throws Exception {
// 创建查询对象
Query query = new Query();
// 设置分页信息
query.skip(3).limit(3);
// 设置排序规则
query.with(new Sort(Sort.Direction.ASC,"id"));
List<User> list = mongoTemplate.find(query, User.class, "users");
list.forEach(System.err::println);
}
// 查询所有name为bunny的文档
@Test
public void testQuery2() throws Exception {
// 构建限制条件 {"name": "bunny"}
Criteria criteria = Criteria.where("name").is("bunny");
// 创建查询对象
Query query = new Query();
// 添加限制条件
query.addCriteria(criteria);
List<User> list = mongoTemplate.find(query, User.class, "users");
list.forEach(System.err::println);
}
// 查询所有name为bunny或者age<30的文档
@Test
public void testQuery3() throws Exception {
// 构建限制条件 { "$or": [{"name": "bunny"}, {"age": {"$lt": 30}}] }
Criteria criteria = new Criteria().orOperator(
Criteria.where("name").is("bunny"),
Criteria.where("age").lt(30)
);
// 创建查询对象
Query query = new Query();
// 添加限制条件
query.addCriteria(criteria);
List<User> list = mongoTemplate.find(query, User.class, "users");
list.forEach(System.err::println);
}
// 查询所有name含有wang并且30<=age<=32的文档
@Test
public void testQuery4() throws Exception {
// 构建限制条件 { "$and" : [{"name": {"$regex": ".*wang.*"} }, {"age": {"$gte": 30, "$lte": 32 } }] }
Criteria criteria = new Criteria().andOperator(
Criteria.where("name").regex(".*wang.*"),
Criteria.where("age").gte(30).lte(32)
);
// 创建查询对象
Query query = new Query();
// 添加限制条件
query.addCriteria(criteria);
List<User> list = mongoTemplate.find(query, User.class, "users");
list.forEach(System.err::println);
}
去除 _class属性
在配置类里加@Bean
//mongodb 去除_class属性
@Bean
public MappingMongoConverter mappingMongoConverter(MongoDbFactory factory, MongoMappingContext context, BeanFactory beanFactory) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, context);
try { mappingConverter.setCustomConversions(beanFactory.getBean(CustomConversions.class));
} catch (NoSuchBeanDefinitionException ignore) {
}
// Don't save _class to mongo
mappingConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
return mappingConverter;
}