原始博文链接

之前始终想试试MongoDB但总是没什么机会用,关系型数据库还是大多数利用场景的首选。最近有机会须要做个评测零碎,其中波及蕴含大量选项的不同品种的表单解决,同时规模并不大,遂思考一番后决定用MongoDB来作为存储后端,正好尝尝鲜,也能加重点CRUD的干燥感。本文次要进行一些根本介绍以及展现应用示例,总结了一些应用的感触和体验。

MongoDB简介

MongoDB算是相当驰名的一款开源NoSQL数据库了,它也曾经有十多年的历史,截止到目前(文章公布)MongoDB也曾经来到了4.4版本,以MongoDB数据库为根底成立的MongoDB公司目前除了MongoDB Server之外还提供了MongoDB Atlas(SaaS云服务数据库)、Charts(可视化图表)、Compass(数据库GUI工具)、Connectors(连接器)等各种配套服务和工具。有趣味能够看一看MongoDB的倒退历史(英),作为饭后读物非常不错,能够看到一款开源数据库的成长过程。

MongoDB说到底是一款开源的由C++编写的文档数据库,面向用户展示的构造与一般RDB也比拟类似,其外围概念根本都能在RDB中找到对应的内容:

MongoRDB阐明
databasedatabase数据库
collectiontable数据汇合 - 数据表
documentrow文档 - 数据记录
fieldcolumn字段 - 数据列
indexindex索引

其中最外围的便是文档,它代表了MongoDB中的一个数据记录。MongoDB的文档构造如下图所示,很显著相似于JSON,能够蕴含数组和其余文档。

无schema的构造(能够有)配合丰盛灵便的查问语句使得MongoDB相较于传统RDB具备很大的灵活性。

装置好MongoDB而后装个GUI(能够抉择MongoDB Compass),而后关上GUI工具连贯到数据库操作一番插入几条数据根本就可能明确其次要的能力。装置办法这里不赘述,因为顺手一查就有,不过装置时须要留神的是MongoDB原生反对分布式架构,包含分片、主备等等,留神装置设置。

SpringData MongoDB

理解了MongoDB之后就能够将其利用到理论应用场景中,而作为独立的存储工具,又必然波及到连贯交互,因而MongoDB提供了各种语言的Driver,其中也蕴含了Java。随着利用复杂度减少,各种工具提供了更高阶的形象或封装来简化对DB的操作以提高效率,SpringData便是其中之一,它作为Spring对于数据存储层拜访的对立根底工具,也提供了针对MongoDB的实现。

BSON

BSON是MongoDB数据存储和网络传输的次要格局,它意为Binary JSON,它的字面意思间接示意了它被创造进去的次要起因。BSON的二进制构造蕴含了类型和长度信息以获取更块的解析速度和更高效的查问性能,此外BSON扩大反对了非JSON原生的数据类型如日期、二进制数据等。得益于此,所有可能在JSON中表白的数据都可能间接在MongoDB中存储、读取。

此外MongoDB的查问同样采纳类JSON的格局来形容,集体感觉有一种谐和、自描述的感觉。配合各种操作符,表达能力很强且有很不错的可浏览性。这个个性也在Driver API中有所体现,以Java角度来说,数据和查问都能够应用同一种类型(org.bson.Document)来示意,十分优雅。举一个简略的查问条件例子:

{     status: "A",     $or: [ { price: { $lt: 30 } }, { item: /^p/ } ]} 

上述形容了查问抉择status为"A"且price大于30或item以字符p结尾(反对正则表达式)的所有文档。

Mongo Java Driver

Driver作为与MongoDB通信交互的根底,Spring Data所进行的操作最初还是会调用Driver提供的办法来发送申请,这些额定的封装操作最为重要的几点目标是查问的构建以及返回值到对象的映射,其实这也是ORM框架所做的次要内容。想较于JDBC只提供了对SQL字符串的提交解决,Mongo的Java Driver要丰盛许多,间接提供了对象的解码编码以及函数式查问构建,这也意味着只用原生Driver的API也能够很好地适配面向对象以及便捷查问。

Spring Data

Spring Data作为Spring对于数据存储层拜访的对立根底工具,也提供了针对MongoDB的封装。只管原生的Java Mongo Driver曾经提供了一些要害的能力,然而Spring Data并没有应用这些内容,查问的构建以及对象转换依然是Spring本人的一套。Spring Data MongoDB看起来与Driver原生提供的性能有些反复,然而毕竟Spring Data个性更加丰盛,同时提供了对立的Data Repository拜访形式。不过应用Spring Data这一套也并不是没有毛病,Spring的这层包装也会显得比拟重,若不是在Spring我的项目中应用,采纳原生Driver未尝不是好的抉择。

利用示例

上面别离对原生Driver和Spring Data MongoDB的应用做一些介绍和示例。

原生Driver

原生的Java Driver 4.1版本文档传送门在此,文档很全面,包含了教程、API文档、源码、更新信息等等。这里简述一些次要操作形容一番。

入口类是com.mongodb.client.MongoClient而并不是相似xxxConnection的模式,然而实质上依然是获取连贯对象。很显著Mongo Driver的形象水平自身就比拟高,MongoClient提供了多种创立形式:

// direct for localhost:27017MongoClient mongoClient1= MongoClients.create();// specify host and portMongoClient mongoClient2= MongoClients.create(        MongoClientSettings.builder()                .applyToClusterSettings(builder ->                        builder.hosts(Arrays.asList(new ServerAddress("hostOne", 27018))))                .build());// from connection stringMongoClient mongoClient = MongoClients.create("mongodb://admin:admin@localhost:27017/?authSource=admin&readPreference=primary&ssl=false");// print databases' namemongoClient.listDatabaseNames().forEach(System.out::println);

通常MongoClient作为单例存在即可,通过MongoClient能够获取到com.mongodb.client.MongoDatabase代表MongoDB中的Database,而Database对象又能够获取到具体的Collection即com.mongodb.client.MongoCollection,通过Collection对象来执行对应的增删改查:

// get databaseMongoDatabase database = mongoClient.getDatabase("mydb");// get collectionMongoCollection<Document> collection = database.getCollection("test");// insertDocument doc = new Document("name", "MongoDB")                    .append("type", "database");collection.insertOne(doc);List<Document> documents = new ArrayList<Document>();for (int i = 0; i < 100; i++) {    documents.add(new Document("i", i));}collection.insertMany(documents);// queryDocument myDoc = collection.find().first();// query with filterDocument filteredDoc = collection.find(and(gt("i", 50), lte("i", 100))).first();// updatecollection.updateOne(eq("i", 10), set("i", 110));UpdateResult updateResult = collection.updateMany(lt("i", 100), inc("i", 100));System.out.println(updateResult.getModifiedCount());// deletecollection.deleteOne(eq("i", 110));DeleteResult deleteResult = collection.deleteMany(gte("i", 100));System.out.println(deleteResult.getDeletedCount());

可见Mongo Driver提供了很多静态方法来构建查问条件,这些办法根本都属于com.mongodb.client.model.Filters这个类下。而在面向对象方面的对象映射上,MongoClient、MongoDatabase、MongoCollection均反对增加CodecRegistry来注册对象编码/解码器:

// build codecRegistry with automatic POJO codecsCodecRegistry pojoCodecRegistry = fromRegistries(MongoClientSettings.getDefaultCodecRegistry(),                fromProviders(PojoCodecProvider.builder().automatic(true).build()));// mongo client levelMongoClientSettings settings = MongoClientSettings.builder()        .codecRegistry(pojoCodecRegistry)        .build();MongoClient mongoClient = MongoClients.create(settings);// mongo database leveldatabase = database.withCodecRegistry(pojoCodecRegistry);// mongo collection levelcollection = collection.withCodecRegistry(pojoCodecRegistry);

配置了对象的codec之后就能够利用MongoCollection的泛型参数来间接执行对应类型的查问和插入(更新和删除操作与非对象codec模式无异):

// example POJOpublic final class Person {    private ObjectId id;    private String name;    private int age;    private Address address;        // getter & setter    // ... }public final class Address {    private String street;    private String city;    private String zip;        // getter & setter    // ... }// generic collectionMongoCollection<Person> collection = database.getCollection("people", Person.class);// insert pojoPerson ada = new Person("Ada Byron", 20, new Address("St James Square", "London", "W1"));collection.insertOne(ada);// query pojoPerson somebody = collection.find(eq("address.city", "Wimborne")).first();System.out.println(somebody.getName());

Spring Data

SpringData MongoDB文档传送门在此,内容不算特地多,对于尝鲜应用来说能够疏忽掉聚合、Reactive等一些“高级”个性。作为NoSQL里最像SQL的一类数据库,SpringData提供了MongoTemplate和MongoRepository两种应用形式。

MongoTemplate

与其余SpringData我的项目类似(例如Redis的RedisTemplate、JDBC的JdbcTemplate)提供了MongoTemplate这个外围操作类,它基本上涵盖了所有对MongoDB的反对并且提供了丰盛的操作个性。

MongoTemplate的构造函数有如下三个:

MongoTemplate(MongoClient mongo, String databaseName);MongoTemplate(MongoDatabaseFactory mongoDbFactory);MongoTemplate(MongoDatabaseFactory mongoDbFactory, MongoConverter mongoConverter);

接下来看一看MongoTemplate的根本应用:

// example pojo@Document("person")public class Person {  @MongoId  private String id;  @Field("personName")  private String name;  private int age;  public Person(String name, int age) {    this.name = name;    this.age = age;  }      // getter & setter ...  // ...}// insertPerson p = new Person("Bob", 33);mongoTemplate.insert(p);// raw queryBasicQuery query = new BasicQuery("{ age : { $lt : 50 }, accounts.balance : { $gt : 1000.00 }}");List<Person> result = mongoTemplate.find(query, Person.class);// criteria queryList<Person> result = template.query(Person.class)    .matching(where("age").lt(50)            .and("accounts.balance").gt(1000.00d))    .all();Person one = template.query(Person.class)    .matching(criteria.orOperator(where("name").ne("Jack"),                                  where("age").lt(10)))    .firstValue();// updatetemplate.update(RelationGroup.class)    .apply(new Update().pull("interviewees", "Jack")           .pull("interviewers", "Jack"))    .all();// upserttemplate.update(Person.class)    .matching(query(where("ssn").is(1111)                    .and("firstName").is("Joe")                    .and("Fraizer").is("Update"))    .apply(update("address", addr))    .upsert();// deletetemplate.remove(person, "person");template.remove(query(where("lastname").is("lannister")), Person.class);  template.findAllAndRemove(new Query().limit(3), "person");   
Repository

相熟Spring Data JPA的敌人肯定对Repository接口不会生疏,同样的Spring Data MongoDB也提供了Repository接口的反对,可能通过办法名称主动生成对应的查问实现,如果在应用时偏差传统的DAO层解决逻辑或者某些查问反复使用率很高,那么选用Repository的形式会非常便捷。应用Java Configuration模式的状况下,开启MongoDB Repository须要应用注解@EnableMongoRepositories

@Configuration@EnableMongoRepositoriesclass ApplicationConfig extends AbstractMongoClientConfiguration {  @Override  protected String getDatabaseName() {    return "e-store";  }  @Override  protected String getMappingBasePackage() {    return "com.oreilly.springdata.mongodb";  }}

Repository样例以及应用示例:

public interface PersonRepository extends PagingAndSortingRepository<Person, String> {    // parse query by method name    List<Person> findByLastname(String lastname);                          // support pageable    Page<Person> findByFirstname(String firstname, Pageable pageable);     // nested pojo    Person findByShippingAddresses(Address address);                       // find first    Person findFirstByLastname(String lastname)                            // support stream    Stream<Person> findAllBy();         // delete    List <Person> deleteByLastname(String lastname); }@Servicepublic sampleService {        @Autowired    private PersonRepository repository;      public Page<Person> getPersonPage(int page, int pageSize) {        return repository.findAll(PageRequest.of(page, pageSize));    }        public List<Person> findAllPerson() {        return new ArrayList<>(repository.findAll());    }    }

应用感触

在进行利用之前也有查问过什么时候应该应用MongoDB这个问题,须要决策是否应该采纳MongoDB而不是RDB这一更加传统、稳当的抉择,然而查了半天也没查到比拟好的解释,反倒是看到MongoDB官网有提供企业级的咨询服务来帮忙客户进行利用。集体感觉暂且抛开性能方面的抉择之外,最次要的思考因素有如下几点:

  1. 数据是否品种繁多且关联度很高、关联关系简单,在应用时须要大量的相似join的联合操作。这是RDB的强项,如果有这个个性那么Mongo不会是第一抉择。
  2. 数据操作是否存在大量的事务性跨汇合(表)操作、批量操作。同样事务也是RDB的强项,MongoDB只管也曾经开始反对事务,然而若事务操作是次要操作类型,应用MongoDB或者须要考量一番。
  3. 归为一类的数据(即一个汇合或一张表)其构造是否须要比拟高的灵活性(字段不固定、变动大),或者构造中蕴含可变长度的字段,或者蕴含嵌套形数据。显然灵便度是MongoDB的杀手锏,应用RDB的时候碰到此类数据比拟难解决,往往就是弄成Json或者自定义构造塞到某个字段上面,举个例子比方要存储一系列选项各不相同的考察问卷RDB就会比拟头疼。毫无疑问如果有很多此类数据那么MongoDB相对值得一试。
  4. 数据的量级是否会到千万或者亿级别以上,有高度伸缩性,且须要频繁查问。这一点其实有性能方面的考量,然而实质上来说还是要归功于Mongo原生的高度可扩展性和数据结构的适配。如果有这样的需要那么一般RDB基本上无奈间接满足,如果同时满足上述的一些条件,Mongo会是很好的抉择。

只管有些方面Mongo不善于,然而不善于并不阐明Mongo做不到,俗话说"天下大势,分久必合,合久必分",在DB上也有几分意思在外面,Mongo也在反对事务、可能应用DBRef来做援用、引入schema束缚等等,也有原生适配SQL且原生提供分片、正本的新型RDB呈现。

再具体到Java对Mongo的应用下面,能够看到SpringData对MongoDB的解决方案仍然是其对立的领域建模领域,简略来说就是映射为Java Bean或者说POJO,但作为Java Bean就必然有字段、有类型,这其实是与MongoDB反对schema-less这一劣势个性是有肯定抵触的,不过话又说回来没有schema、没有结构化、没有类型平安对于逻辑代码的编写并不是坏事,所以咱们能够看到包含Mongo在内的很多NoSQL当初也开始反对设置schema。综合思考来说,集体感觉如下状况在Java后端利用MongoDB是比拟难受的:

  • 间接存储文档数据,如文章或评论的内容、HTML页面等,这些内容无需通过代码逻辑解决间接返回给前端或者用户,充分利用MongoDB的个性。
  • 可能以"半结构化"的模式存储数据,在固定构造中内嵌灵便的子数据。具体到利用层面来说,依然保留Java Bean的映射关系,在Bean中应用包含Map、List、数组等容器类型的字段或者间接应用org.bson.Document类型,此外应用形象基类定义字段、各类数据应用不同的子类成员也是十分适配的一种建模形式。这样既不失固定构造带来的标准与便当,同时也保留了数据的灵活性。

当然这些只是一些比拟通俗的应用感触,仅供参考~MongoDB远不止于此,集体对于Mongo是比拟有好感的,用起来感觉不错,心愿能有机会多多应用,深刻开掘。