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、初始化数据:
数据脚本如下:本文所采用的数据已被本地多次测试修改,但结构未发生变化,脚本参考如下:
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 | db.users.drop(); var user1 = { "username" : "张三" , "country" : "china" , "address" : { "aCode" : "411000" , "add" : "长沙" }, "favorites" : { "movies" : [ "杀破狼2" , "战狼" , "雷神1" ], "cites" : [ "长沙" , "深圳" , "上海" ] }, "age" : 18, "salary" :NumberDecimal( "18889.09" ), "lenght" :1.79 }; var user2 = { "username" : "李四" , "country" : "English" , "address" : { "aCode" : "311000" , "add" : "地址" }, "favorites" : { "movies" : [ "复仇者联盟" , "战狼" , "雷神1" ], "cites" : [ "西安" , "东京" , "上海" ] }, "age" : 24, "salary" :NumberDecimal( "7889.09" ), "lenght" :1.35 }; var user3 ={ "username" : "王五" , "country" : "japan" , "address" : { "aCode" : "411000" , "add" : "长沙" }, "favorites" : { "movies" : [ "肉蒲团" , "一路向西" , "倩女幽魂" ], "cites" : [ "东莞" , "深圳" , "东京" ] }, "age" : 22, "salary" :NumberDecimal( "6666.66" ), "lenght" :1.85 }; var user4 = { "username" : "马六" , "country" : "USA" , "address" : { "aCode" : "411000" , "add" : "长沙" }, "favorites" : { "movies" : [ "蜘蛛侠" , "钢铁侠" , "蝙蝠侠" ], "cites" : [ "青岛" , "东莞" , "上海" ] }, "age" : 20, "salary" :NumberDecimal( "6398.22" ), "lenght" :1.77 }; var user5 = { "username" : "test" , "country" : "UK" , "address" : { "aCode" : "411000" , "add" : "TEST" }, "favorites" : { "movies" : [ "蜘蛛侠" , "钢铁侠" , "蝙蝠侠" ], "cites" : [ "青岛" , "东莞" , "上海" ] }, "salary" :NumberDecimal( "1969.88" ) }; db.users.insert(user1); db.users.insert(user2); db.users.insert(user3); db.users.insert(user4); db.users.insert(user5); |
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 是有挺多细节需要注意的,我们稍后将在典型场景中进行详述。