mongodb快速入门

通过 mongdb 初始,我们对 mongodb 有了一个基本的认识,接下来看一下究竟如何使用。
本部分包含的内容包括:
1、mongodb 在 mac 的安装,命令行及客户端工具的简单介绍;
2、在 spring 中如何使用 mongodb,增删改查示例;
3、mongodb 原生 api 的使用,增删改查示例;
4、mongodb 连接池的学习;

-------------------------------------------------------------------------------------------------------------------------------------------------
1、下载安装及工具简单介绍
1、从官网下载安装包 https://www.mongodb.com/download-center,解压到本地合适文件夹;
2、手动创建 data 目录以及 logs 跟 logs/mongodb.log 文件;
3、在 mongodb 安装目录创建 etc 目录,并在其下创建 mongodb.conf 文件,内容见下;
4、配置环境变量,export PATH=${PATH}:/software/mongodb-osx-x86_64-4.0.10/bin;
5、到 bin 目录执行./mongod -f ../etc/mongod.conf 启动,或者 nohup ./mongod -f ../etc/mongod.conf & 后台启动;
mongodb.conf 内容如下:
1
2
3
4
5
6
7
#mongodb config file
dbpath=/software/mongodb-osx-x86_64-4.0.10/data/db/ #数据存放目录
logpath=/software/mongodb-osx-x86_64-4.0.10/logs/mongod.log #日志存放目录
logappend = true #以天为单位,自动切割日志
port = 27017
fork = true
auth = false
下载官方工具 MongoDB Compass,启动后进行连接,过程略;
命令行及 compass 工具使用:
命令行:
1、连接数据库,./mongo ip 地址: 端口号,连接本地可以省略地址跟端口号,注意启动命令是 mongod,连接命令是 mongo

 

2、查看当前有几个数据库,show dbs ,类似于 mysql 的 show database;
3、切换数据库,use 库名;
4、查看库中所有表,show collections; 若当前库没有表,则显示为空;
5、往表中随便插入一条数据,db.myusers.insert({"name":"张三"})

 这里我们注意一下,show dbs 的时候,并没有 mytest 这个库,我们直接 use mytest,mongodb 并没有报错,而且我们可以直接 insert;这一点跟 mysql 是不一样的,mongodb 可以不用创建库,直接 use 之后 insert 的时候回自动创建该库,若没有 insert 操作,该库并不会实际创建;而且可以看到,对于插入的数据,mongodb 自动生成了 id;

compass 连接本地,可以在左侧看到当前实例的所有库,右侧则为查询展示内容,list 为 mongo 的 josn 视图,table 为将数据展示为传统的关系库表视图;,上方的 filter 可以书写查询条件,另外还有查询的执行计划,索引等内容,稍后详述;我们刚才加入的数据:
2、spring 集成 mongodb 使用
  目标:利用 spring 提供的 mongodb 的 api 实现对单表的增删改查
  1、创建 spring boot 项目,pom 文件添加
1
2
3
4
5
6
7
8
9
10
11
12
13
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongo-java-driver</artifactId>
    <version>3.8.0</version>
</dependency>

  2、application.yml 中添加

1
2
3
4
spring:
  data:
    mongodb:
      uri: mongodb://localhost:27017/wzy
  3、初始化数据:
  数据脚本如下:本文所采用的数据已被本地多次测试修改,但结构未发生变化,脚本参考如下:

   4、创建实体 bean

  其中,@Getter 等注解来自 lombok
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
@ToString
@Getter
@Setter
@Generated
@Builder
@Document(collection = "users")
public class User {
@Id
private String id;
    //姓名
    @Field("username")
    private String username;
    //国籍
    private String country;
    //年龄
    private int age;
    //薪水
    private BigDecimal salary;
    //身高
    private Double length;
    //地址
    private Address address;
    //爱好
    private HashMap favourites;
}

  5、创建 service

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
@Service
public class MongoTestService {
    @Autowired
    private MongoTemplate mongoTemplate;
    //查询
    public User getUserByName(String name){
        Query query = new Query();
        query.addCriteria(Criteria.where("username").is(name));
        User user = mongoTemplate.findOne(query, User.class);
        return user;
    }
 
    //插入
    public void insertUser(){
        HashMap map = new HashMap();
        map.put("movies", Arrays.asList("决战天门","倩女幽魂"));
        map.put("cities", Arrays.asList("大理","拉萨"));
        User user = User.builder()
                .username("luochengwu")
                .age(20)
                .country("china")
                .address(new Address("01002",""))
                .favourites(map)
                .salary(new BigDecimal("2345.67"))
                .build();
        mongoTemplate.save(user);
    }
 
    //更新
    public void updateUser(){
        //把李四年龄修改为30
        Query query1 = new Query();
        query1.addCriteria(Criteria.where("username").is("李四"));
        List<User> lisi1 = mongoTemplate.find(query1,User.class);
        System.out.println("before update --------"+lisi1.get(0).toString());
        Update update1 = new Update();
        update1.set("age",30);
        UpdateResult result =mongoTemplate.updateMulti(query1,update1,User.class);
        List<User> lisi11 = mongoTemplate.find(query1,User.class);
        System.out.println("after update --------"+result);
        System.out.println("after user --------"+lisi11.get(0));
 
        //略复杂的条件
        //住在横滨的日本人,喜欢的城市添加东京
        Query query2 = new Query();
        query2.addCriteria(Criteria.where("country").is("japan").and("address.add").is("横滨"));
        User user1 = mongoTemplate.findOne(query2,User.class);
        System.out.println("before update ------"+user1);
        Update update2 = new Update();
        update2.addToSet("favourites.cities","东京");
        UpdateResult result2 = mongoTemplate.updateMulti(query2,update2,User.class);
        User user2 = mongoTemplate.findOne(query2,User.class);
        System.out.println("after update --------"+result2);
        System.out.println("after user --------"+user2);
    }
 
    //删除
    public void deleteUser(){
        //删掉国籍为uk,且年龄为22的人
        Query query = new Query();
        query.addCriteria(Criteria.where("country").is("UK").and("age").is(22));
        User user = mongoTemplate.findOne(query,User.class);
        System.out.println("待删除的用户 ------" + user);
        DeleteResult result = mongoTemplate.remove(query,User.class);
        System.out.println("remove response ------"+ result);
        User delUser = mongoTemplate.findOne(query,User.class);
        System.out.println("query after delete ------" + delUser);
    }
}

  所碰到的问题:
  1、java 跟 mongodb 数据类型转换的问题,一般 java 的数据类型跟数据库 (比如 mysql) 的数据类型对应关系,数据库驱动已经帮我们做好了,那个“方言”的配置就是。mongodb 貌似做的不全,有些是需要我们自己进行配置的,比如本例中的 BigDecimal 跟 Decimal128;
  默认报错:
  解决办法:添加 converter
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
@Configuration
public class MongodbConfig extends AbstractMongoConfiguration {
    @Value("${spring.data.mongodb.uri}")
    private String dburi ;
    @Override
    public MongoClient mongoClient() {
        MongoClient mongoClient = new MongoClient();
        return mongoClient;
    }
    @Override
    protected String getDatabaseName() {
        int index = dburi.lastIndexOf("/")+1;
        return dburi.substring(index);
    }
    @Bean
    public MappingMongoConverter mappingMongoConverter(){
        DefaultDbRefResolver dbRefResolver = new DefaultDbRefResolver(this.mongoDbFactory());
        MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, this.mongoMappingContext());
        List<Object> list = new ArrayList<>();
        list.add(new BigDecimalToDecimal128Converter());
        list.add(new Decimal128ToBigDecimal());
        converter.setCustomConversions(new MongoCustomConversions(list));
        return converter;
    }
    @Bean
    public MongoMappingContext mongoMappingContext(){
        MongoMappingContext mappingContext = new MongoMappingContext();
        return mappingContext;
    }
    @Bean
    public MongoTemplate mongoTemplate(){
        return new MongoTemplate(this.mongoDbFactory(),this.mappingMongoConverter());
    }
}

  2、map 跟 hashmap 的问题,被更新字段的数据类型必须明确。写 Map 报错:

  解决办法,将相关字段改为实现类 HashMap,而不是用接口;这个当时困扰了一下,开始的时候没看懂这个异常;
  3、double 跟 Double 的问题,由于 mongodb 数据结构的自由,有些记录可能缺少某些字段,也就是字段值为 null,如果该字段为引用类型是没有问题的,但若该字段为 int,double 等基本数据类型时,会发生类型转换错误;
  解决办法,将字段改为包装类;so,pojo 的各个属性最好为引用类型而不是简单类型,在使用关系数据库的时候也应该如此,有些字段 (比如年龄,分数,价钱等),没填写跟值为 0 显然是两个概念。
3、mongodb 自带的原生 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
public class DocumentTest {
    private Logger log = LoggerFactory.getLogger(DocumentTest.class);
    private MongoDatabase db;
    private MongoCollection<Document> collection;
    private MongoClient client;
 
    @Before
    public void init(){
        client = new MongoClient("localhost");
        db = client.getDatabase("wzy");
        collection = db.getCollection("users");
    }
 
    //新增
    @Test
    public void insertTest(){
        Document doc1 = new Document();
        doc1.append("username","cang");
        doc1.append("age",20);
        doc1.append("country","japan");
        doc1.append("length",1.77f);
        doc1.append("salary",new BigDecimal("6565.22"));
 
        //对象
        Map<String,String> address = new HashMap<>();
        address.put("aCode","01001");
        address.put("add","大板");
        doc1.append("address",address);
 
        //数组
        Map<String,Object> fav = new HashMap<>();
        fav.put("movies", Arrays.asList("大决战","天龙"));
        fav.put("citys", Arrays.asList("杭州","苏州"));
        doc1.append("favourites",fav);
 
        collection.insertOne(doc1);
//        collection.insertMany();
    }
 
    //查询
    @Test
    public void queryTest(){//{"favourites.cites":["长沙","上海"]}
        List<Document> list = new ArrayList<>();
        //mongo查询结果遍历接口
        Block<Document> block = new Block<Document>() {
            @Override
            public void apply(Document document) {
                log.info(document.toJson());
                list.add(document);
            }
        };
        FindIterable<Document> find1 = collection.find(all("favourites.cites",Arrays.asList("长沙","上海")));
        find1.forEach(block);
        log.info("结果有{}条", list.size());
 
        list.removeAll(list);
        //like '%s%',需要正则,  like '%s%' and (country = 'English' or country='USA')
        String regexStr = ".*s.*"; //s前后都可以有任意字符
        Bson regex = regex("username",regexStr);
        Bson or = or(eq("country","English"),eq("country","USA"));
        FindIterable<Document> find2 = collection.find(and(regex,or));
        find2.forEach(block);
        log.info("查询结果有{}条",list.size());
 
    }
 
    //更新
    @Test
    public void updateTest(){
        //更新单个字段
        UpdateResult result = collection.updateMany(eq("username","cang"),new Document("$set",new Document("age",6)));
        //替换整个文档
//        UpdateResult result = collection.updateMany(eq("username","cang"),new Document(new Document("age",6)));
 
        UpdateResult result1 = collection.updateMany(eq("favourites.citys","杭州"),
                addEachToSet("favourites.movies",Arrays.asList("小电影")));
 
        log.info("更新了{}条",result1.getModifiedCount());
    }
 
    //删除
    @Test
    public void deleteTest(){
        DeleteResult result = collection.deleteMany(Filters.eq("username","luochengwu"));
        log.info("删除了{}行",result.getDeletedCount());
        DeleteResult result1 = collection.deleteMany(and(gt("age",8),lt("age",28)));
        log.info("删除了{}行",result1.getDeletedCount());
    }
}
  问题:经过前边对数据类型的趟坑,这里倒是没遇到什么异常;该部分最大的问题是对 mongodb 提供的原生 api 的不熟悉,尤其多个条件如何组合,and,or,exist 的如何表达,文档整体更新跟局部更新如何处理等,这个感觉没什么办法,只能多多使用慢慢熟悉了;
4、mongodb 连接池
  mongodb 团队已经帮我们做好了一个连接池,以供我们直接使用。使用方式也很简单,就是在获取 client 的时候将连接池参数对象传进去即可。
  连接池参数的设置是通过构造器模式来实现的,因为参数实在是比较多,我们可以根据自己需要进行自由设置。比如在第 3 步的单元测试类中,我们可以这样使用连接池:
1
2
3
4
5
6
7
8
9
10
11
12
13
@Before
public void init(){
    MongoClientOptions mco = MongoClientOptions.builder()
            .writeConcern(WriteConcern.ACKNOWLEDGED)
            .minConnectionsPerHost(100)
            .threadsAllowedToBlockForConnectionMultiplier(5)
            .maxWaitTime(120000)
            .connectTimeout(10000)
            .build();
    client = new MongoClient(new ServerAddress("localhost",27017), mco);
    db = client.getDatabase("wzy");
    collection = db.getCollection("users");
}
-------------------------------------------------------------------------------------------------
  以上,我们练习了 spring template 跟 mongo 原生 api 两种操作 mongodb 的方式。使用起来,要跟 sql 一样有查询条件,有 and,or,like 等,但显然,其语法跟细节跟 sql 是完全不想关的,精通 sql 跟熟练使用 mongodb 没有半毛钱关系。实际上,mongodb 是有挺多细节需要注意的,我们稍后将在典型场景中进行详述。