SpringBoot整合MongoDB实现增删改查及聚合操作

2,026 阅读9分钟

一、SpringBoot整合MongoDB

1-1、环境准备

1-1-1、引入依赖

<!--spring data mongodb-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

1-1-2、添加配置信息

配置mongodb连接有两种方式,一种是使用uri;另外一种则使用对应的配置文件

下面使用uri配置信息

jony:用户名
111111:密码
appdb:数据库
authSource:认证数据库

spring:
  data:
    mongodb:
      uri: mongodb://jony:111111@192.168.253.131:27017/appdb?authSource=admin
      #uri等同于下面的配置
      #database: appdb
      #host: 192.168.253.131
      #port: 27017
      #username: jony
      #password: 111111
      #authentication-database: admin

连接配置参考文档:docs.mongodb.com/manual/refe…

1-1-3、使用时注入mongoTemplate

需要使用的MongoDB的时候,需要将MongoTemplate注入到类中,然后执行对应的方法即可

@Autowired
MongoTemplate mongoTemplate;

1-2、集合(表)操作

首先进行判断集合是否存在,如果存在则删除,然后再创建

我们给appdb库中的emp集合进行操作,操作前emp是存在的,并且有数据,如下(连接终端可以使用 MongoDB Compass或者Navicat):

image.png

@Autowired
MongoTemplate mongoTemplate;

@Test
public void test(){
    boolean emp = mongoTemplate.collectionExists("emp");
    if(emp){
        mongoTemplate.dropCollection("emp");
    }
    mongoTemplate.createCollection("emp");
}

执行完成之后,集合中已没有任何数据(集合已经被删除,并重新创建)

image.png

1-3、文档(表)操作

1-3-1、相关注解

  • @Document

修饰范围: 用在类上 作用: 用来映射这个类的一个对象为mongo中一条文档数据。 属性:( value 、collection )用来指定操作的集合名称

  • @Id

修饰范围: 用在成员变量、方法上 作用: 用来将成员变量的值映射为文档的_id的值

  • @Field

修饰范围: 用在成员变量、方法上 作用: 用来将成员变量及其值映射为文档中一个key:value对。 属性:( name , value )用来指定在文档中 key的名称,默认为成员变量名

  • @Transient

修饰范围:用在成员变量、方法上 作用:用来指定此成员变量不参与文档的序列化

1-3-2、创建实体

使用@Document("emp")和MongoDB中的emp进行映射,同时使用lombok进行get/set/有参/无参构造生成

其中name使用@Field("username")进行映射

@Document("emp")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {

    @Id
    private Integer id;
    @Field("username")
    private String name;
    @Field
    private int age;
    @Field
    private Double salary;
    @Field
    private Date birthday;
}

1-3-3、添加文档

insert方法返回值是新增的Document对象,里面包含了新增后id的值。如果集合不存在会自动创建集 合。通过Spring Data MongoDB还会给集合中多加一个class的属性,存储新增时Document对应Java中 类的全限定路径。这么做为了查询时能把Document转换为Java类型。

1-3-3-1、使用save进行保存

使用save进行保存,当id重复的时候,就会对数据进行更新操作

@Test
public void testInsert(){
    Employee employee=new Employee(1,"王二麻子",25,10000.00,new Date());
    Employee emp = mongoTemplate.save(employee);
    System.out.println(emp);

}

执行结果:

image.png

将name修改为:王二麻子111,再次调用save

image.png

1-3-3-2、使用insert进行保存

使用save进行保存,当id重复的时候,就会抛出异常,

insert支持批量保存操作

image.png

1-3-3-3、使用insert批量保存

使用insert批量保存的时候,需要传入对象的类型,为了查询的时候能把Document转为Java类型

/**
 * 批量保存
 */
List<Employee> list = Arrays.asList(
        new Employee(2, "张三", 21,5000.00, new Date()),
        new Employee(3, "李四", 26,8000.00, new Date()),
        new Employee(4, "王五",22, 8000.00, new Date()),
        new Employee(5, "张龙",28, 6000.00, new Date()),
        new Employee(6, "赵虎",24, 7000.00, new Date()),
        new Employee(7, "赵六",28, 12000.00, new Date()));
//插入多条数据
Collection<Employee> insertList = mongoTemplate.insert(list, Employee.class);
System.out.println(insertList);

执行结果

image.png

1-3-4、查询文档

Criteria是标准查询的接口,可以引用静态的Criteria.where的把多个条件组合在一起,就可以轻松地将多个方法标准和查询连接起来,方便我们操作查询语句。

image.png

1-3-4-1、查询集合的全部文档

@Test
public void testFind(){
    System.out.println("-----------------查询所有文档---------------");
    //以下两种查询功能等同
    List<Employee> list = mongoTemplate.findAll(Employee.class);
    List<Employee> list2 = mongoTemplate.find(new Query(), Employee.class);
    
    list.forEach(System.out::println);
    
    System.out.println("-----------------查询所有文档---------------");
}

执行结果:

image.png

1-3-4-2、查询单个文档

查询单个文档,可以根据ID,或者findONe查询第一个文档

System.out.println("-----------------根据ID查询---------------");
Employee em = mongoTemplate.findById(1, Employee.class);
System.out.println(em);

System.out.println("-----------------findOne返回第一个文档---------------");
Employee emOne = mongoTemplate.findOne(new Query(), Employee.class);
System.out.println(emOne);

执行结果

image.png

1-3-4-3、单条件查询

通过Criteria.where()可以传入对应key值,然后设置对应条件生成查询Query,然后将Query传入mongoTemplate.find()即可完成查询

@Test
public void criteriaTest(){
    //查询薪资大于等于8000的员工
    Query query=new Query(Criteria.where("salary").gte(8000));
    //查询薪资大于4000小于10000的员工
    Query query1=new Query(Criteria.where("salary").gt(4000).lt(10000));
    //正则查询(模糊查询) java中正则不需要//
    Query query2=new Query(Criteria.where("name").regex("张"));
    
    //执行Query
    List<Employee> emplist=mongoTemplate.find(query2,Employee.class);
    emplist.forEach(System.out::println);
}

1-3-4-4、多条件查询

/**
 * 多条件 and or查询
 */
@Test
public void CriteriaOptionTest(){
    System.out.println("---------------Criteria多条件and、or查询----------------");
    Criteria criteria=new Criteria();
    //and 查询年龄大于25,薪资大于8000的员工
    //criteria.andOperator(Criteria.where("age").gt(25),Criteria.where("salary").gt(8000));

    //or 查询姓名是张三或者薪资大于5000的员工
    criteria.orOperator(Criteria.where("name").is("张三"),Criteria.where("salary").gt(5000));

    //将查询条件传入Query
    Query query=new Query(criteria);
    //执行查询
    List<Employee> employees = mongoTemplate.find(query, Employee.class);
    employees.forEach(System.out::println);
}

1-3-4-5、排序

通过使用query.with(Sort.by(Sort.Order.desc("xx")));进行对字段排序

/**
 * 排序
 */
@Test
public void CriteriraOrderTest(){
    Criteria criteria=new Criteria();
    criteria.andOperator(Criteria.where("age").gt(22),Criteria.where("salary").gt(6000));

    Query query=new Query(criteria);
    query.with(Sort.by(Sort.Order.desc("salary")));

    List<Employee> li = mongoTemplate.find(query, Employee.class);
    li.forEach(System.out::println);
}

执行结果:

image.png

1-3-4-6、使用skip、limit分页

image.png

1-3-4-7、使用 json字符串方式查询

/**
 * 通过Json查询
 */
@Test
public void jsonTest(){
    //查询name为张三
    String json="{name:'张三'}";
    //查询工资大于5000
    String json2="{salary:{$gt:5000}}";
    //查询年龄大于25或者工资小于7000
    String json3="{$or:[{age:{$gt:25}},{salary:{$lt:7000}}]}";
    Query query=new BasicQuery(json3);
    //按工资降序排序
    query.with(Sort.by(Sort.Order.desc("salary")));

    List<Employee> list = mongoTemplate.find(query, Employee.class);
    list.forEach(System.out::println);
}

执行结果

image.png

1-3-5、更新文档

在Mongodb中无论是使用客户端API还是使用Spring Data,更新返回结果一定是受行数影响。如果更新后的结果和更新前的结果相同,返回0

  • updateFirst() 只更新满足条件的第一条记录
  • updateMulti() 更新所有满足条件的记录
  • upsert() 没有符合条件的记录则插入数据

1-3-5-1、updateFirst更新第一条数据

@Test
public void updateTest(){
    Query query=new Query(Criteria.where("salary").gte(8000));

    List<Employee> li = mongoTemplate.find(query,Employee.class);
    System.out.println("----------更新前------------");
    li.forEach(System.out::println);

    //更新符合条件的一条数据
    Update update=new Update();
    update.set("salary",13000);
    mongoTemplate.updateFirst(query,update,Employee.class);


    System.out.println("==========更新后===========");
    li = mongoTemplate.find(query, Employee.class);
    li.forEach(System.out::println);
}

执行结果:

image.png

1-3-5-2、updateMulti更新所有符合条件数据

image.png

1-3-5-3、upsert没有符合的数据则插入

image.png

数据结果:

image.png

1-3-6、删除文档

@Test
    public void deleteTest(){
        //删除所有文档  数据量大建议使用 dropCollection效率更高
//        mongoTemplate.remove(new Query(),Employee.class);
        System.out.println("-----------删除前数据-------------");
        mongoTemplate.findAll(Employee.class).forEach(System.out::println);

        mongoTemplate.remove(new Query(Criteria.where("name").is("张三")),Employee.class);
        //删除后数据
        System.out.println("-----------删除后数据-------------");
        mongoTemplate.findAll(Employee.class).forEach(System.out::println);
    }

执行结果:

image.png

二、聚合操作

MongoTemplate提供了aggregate方法来实现对数据的聚合操作。如下在MongoTemplate中的aggregate中的方法

image.png

2-1、mongodb 命令和java对照

基于聚合管道mongodb提供的可操作的内容:

支持的操作java 接口说明
$projectAggregation.project修改输入文档的结构
$matchAggregation.match用于过滤数据
$limitAggregation.limit用来限制聚合管道返回的文档数
$skipAggregation.skip用来在聚合管道操作中跳过指定的文档
$unwindAggregation.unwind将文档的某一个数组类型拆分为多条
$groupAggregation.group将聚合中的文档分组,可用于统计结果
$sortAggregation.sort输入文档排序后输出
$geoNearAggregation.geoNear输出接近某一地理位置的有序文档

基于聚合操作Aggregation.group,mongodb提供可选的表达式

聚合表达式java接口说明
$sumAggregation.group().sum("field").as("sum")求和
$avgAggregation.group().avg("field").as("avg")求平均
$minAggregation.group().min("field").as("min")获取聚合所有文档对应值的最小值
$maxAggregation.group().max("field").as("max")获取聚合所有文档对应值的最大值
$pushAggregation.group().push("field").as("push")在结果文档中插入值到一个数组中
$addToSetAggregation.group().addToSet("field").as("addToSet")在结果文档中插入值到一个数组中,但不创建副本
$firstAggregation.group().first("field").as("first")根据资源文档的排序获取第一个文档数据
$lastAggregation.group().last("field").as("last")根据资源文档的排序获取最后一个文档数据

2-2、数据集示例

导入邮政编码数据集 :media.mongodb.org/zips.json

使用mongoimport工具导入数据(www.mongodb.com/try/downloa…

mongoimport -h 192.168.253.131 -d aggdemo -u jony -p 111111 --
authenticationDatabase=admin -c zips --file
D:\ProgramData\mongodb\import\zips.json

h,--host :代表远程连接的数据库地址,默认连接本地Mongo数据库;

--port:代表远程连接的数据库的端口,默认连接的远程端口27017;

-u,--username:代表连接远程数据库的账号,如果设置数据库的认证,需要指定用户账号;

-p,--password:代表连接数据库的账号对应的密码;

-d,--db:代表连接的数据库;

-c,--collection:代表连接数据库中的集合;

-f, --fields:代表导入集合中的字段;

--type:代表导入的文件类型,包括csv和json,tsv文件,默认json格式;

--file:导入的文件名称

--headerline:导入csv文件时,指明第一行是列名,不需要导入;

2-3、返回人口超过1000万的州

2-3-1、控制台实现方式

db.zips.aggregate( [
{ $group: { _id: "$state", totalPop: { $sum: "$pop" } } },
{ $match: { totalPop: { $gt: 10*1000*1000 } } }
] )

执行结果

image.png

这个聚合操作的等价SQL是:

SELECT state, SUM(pop) AS totalPop
FROM zips
GROUP BY state
HAVING totalPop >= (10*1000*1000)

2-3-2、java实现方式

  • 创建实体类
@Document("zips")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Zips {
    @Id //映射文档中的_id
    private String id;
    @Field
    private String city;
    @Field
    private Double[] loc;
    @Field
    private Integer pop;
    @Field
    private String state;
}
  • 实现思路
db.zips.aggregate( [
{ $group: { _id: "$state", totalPop: { $sum: "$pop" } } },
{ $match: { totalPop: { $gt: 10*1000*1000 } } }
] )

1、如上命令首先用到了group,java对应的类为:GroupOperation
2、其次用到了match,java对应的类为:MatchOperation \

通过上面两个方法,我们可以观察到对应的管道命令,在java中就是在管道命令后加上Operation

最终代码

public class AggregateTest extends  MongoDbApplicationTests{

    @Autowired
    MongoTemplate mongoTemplate;


//    db.zips.aggregate( [
//        { $group: { _id: "$state", totalPop: { $sum: "$pop" } } },
//        { $match: { totalPop: { $gt: 10*1000*1000 } } }
//    ] )
    @Test
    public void test() {
        //$group操作-->$group: { _id: "$state", totalPop: { $sum: "$pop" } }
        GroupOperation groupOperation=
                Aggregation.group("state").sum("pop").as("totalPop");

        //$match操作-->$match: { totalPop: { $gt: 10*1000*1000 } }
        MatchOperation matchOperation=
                Aggregation.match(Criteria.where("totalPop").gte(10*1000*1000));

        //按顺序组合每一个聚合步骤
        TypedAggregation<Zips> typedAggregation=
                Aggregation.newAggregation(Zips.class,groupOperation,matchOperation);

        //执行聚合操作,如果不使用 Map,也可以使用自定义的实体类来接收数据
        AggregationResults<Map> aggregationResults =
                mongoTemplate.aggregate(typedAggregation, Map.class);

        // 取出最终结果
        List<Map> mappedResults = aggregationResults.getMappedResults();
        for(Map map:mappedResults){
            System.out.println(map);
        }

    }
}

执行结果:

image.png

2-4、返回各州平均城市人口

db.zips.aggregate( [
{ $group: { _id: { state: "$state", city: "$city" }, cityPop: { $sum: "$pop" }
} },
{ $group: { _id: "$_id.state", avgCityPop: { $avg: "$cityPop" } } },
{ $sort:{avgCityPop:-1}}
] )

java实现

@Test
public void test2() {
    //$group
    GroupOperation groupOperation =
            Aggregation.group("state","city").sum("pop").as("cityPop");
    //$group
    GroupOperation groupOperation2 =
            Aggregation.group("_id.state").avg("cityPop").as("avgCityPop");
    //$sort
    SortOperation sortOperation =
            Aggregation.sort(Sort.Direction.DESC,"avgCityPop");
    // 按顺序组合每一个聚合步骤
    TypedAggregation<Zips> typedAggregation =
            Aggregation.newAggregation(Zips.class,
                    groupOperation, groupOperation2,sortOperation);
    //执行聚合操作,如果不使用 Map,也可以使用自定义的实体类来接收数据
    AggregationResults<Map> aggregationResults =
            mongoTemplate.aggregate(typedAggregation, Map.class);
    // 取出最终结果
    List<Map> mappedResults = aggregationResults.getMappedResults();
    for(Map map:mappedResults){
        System.out.println(map);
    }
}

执行结果

image.png

2-5、按州返回最大和最小的城市

db.zips.aggregate( [
{ $group:
{
_id: { state: "$state", city: "$city" },
pop: { $sum: "$pop" }
}
},
{ $sort: { pop: 1 } },
{ $group:
{
_id : "$_id.state",
biggestCity: { $last: "$_id.city" },
biggestPop: { $last: "$pop" },
smallestCity: { $first: "$_id.city" },
smallestPop: { $first: "$pop" }
}
},
{ $project:
{ _id: 0,
state: "$_id",
biggestCity: { name: "$biggestCity", pop: "$biggestPop" },
smallestCity: { name: "$smallestCity", pop: "$smallestPop" }
}
},
{ $sort: { state: 1 } }
] )

java 实现

@Test
    public void test3(){
        //$group
        GroupOperation groupOperation = Aggregation
                .group("state","city").sum("pop").as("pop");
        //$sort
        SortOperation sortOperation = Aggregation
                .sort(Sort.Direction.ASC,"pop");
        //$group
        GroupOperation groupOperation2 = Aggregation
                .group("_id.state")
                .last("_id.city").as("biggestCity")
                .last("pop").as("biggestPop")
                .first("_id.city").as("smallestCity")
                .first("pop").as("smallestPop");
        //$project
        ProjectionOperation projectionOperation = Aggregation
                .project("state","biggestCity","smallestCity")
                .and("_id").as("state")
                .andExpression(
                        "{ name: "$biggestCity", pop: "$biggestPop" }")
                .as("biggestCity")
                .andExpression(
                        "{ name: "$smallestCity", pop: "$smallestPop" }"
                ).as("smallestCity")
                .andExclude("_id");
        //$sort
        SortOperation sortOperation2 = Aggregation
                .sort(Sort.Direction.ASC,"state");
        // 按顺序组合每一个聚合步骤
        TypedAggregation<Zips> typedAggregation = Aggregation.newAggregation(
                Zips.class, groupOperation, sortOperation, groupOperation2,
                projectionOperation,sortOperation2);
        //执行聚合操作,如果不使用 Map,也可以使用自定义的实体类来接收数据
        AggregationResults<Map> aggregationResults = mongoTemplate
                .aggregate(typedAggregation, Map.class);
        // 取出最终结果
        List<Map> mappedResults = aggregationResults.getMappedResults();
        for(Map map:mappedResults){
            System.out.println(map);
        }
    }
}

执行结果:

image.png