MongoDB
1、基本概述
MongoDB是以JSON为数据模型的文档型的介于关系数据库和非关系数据库之间的产品(虽然更多称它为非关系数据库)。它直接操作的对象是内存,通过映射把数据持久化到硬盘。
MongoDB追求一个对象一个数据表,无论对象与对象之间存在怎样的关系,它的表与表之间是没有关联性的。因此,对于关联关系,我们只需要在类中维护好它们的关系就好。因此严格来说,MongoDB又不属于数据库,它应该是一种以数据结构化存储方法的集合。
MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成,其文档就是一堆JSON数据,只是这些JSON数据基本是嵌套型的。
2、与MySql的区别
-
在Mysql等关系型数据库中需要建立和查询两个表才能完成的事情在MongoDB中只需要一个集合嵌套就能搞定。
-
Mysql关系型数据库有事务的ACID,而MongoDB是没有事务。
-
名词
mysql | mongodb
table表 | collection集合
row行 | document文档/数据记录行
column列 | field数据字段/域
-
mysql的缺点:
-
为了维护执行需要消耗大量的性能
-
影响读写
-
固定的表结构
-
高并发读写需求
-
-
mongodb的缺点:
- 主要是无事务处理
- 不提供sql支持
mongodb关联表设计
//一对一和多对一
{
id:xxx
name:xxx
other:{
id:xxx
name:Xxx
...
}
...
}
//一对多和多对多
{
id:xxx
name:xxx
other:[
{id:xxx,name:Xxx},
{id:xxxx,name:Xxxx}
...
]
...
}
3、MySql、MongDB、Redis选择
- 如果需要将mongodb作为后端db来代替mysql使用,即这里mysql与mongodb 属于平行级别,那么,这样的使用可能有以下几种情况的考量:
- mongodb所负责部分以文档形式存储,能够有较好的代码亲和性,json格式的直接写入方便(如日志之类)
- 从datamodels设计阶段就将原子性考虑于其中,无需事务之类的辅助。开发用如nodejs之类的语言来进行开发,对开发比较方便。
- mongodb本身的failover机制,无需使用如MHA之类的方式实现
- 将mongodb作为类似redis 、memcache来做缓存db,为mysql提供服务,或是后端日志收集分析。 考虑到mongodb属于nosql型数据库,sql语句与数据结构不如mysql那么亲和 ,也会有很多时候将mongodb做为辅助mysql而使用的类redis、memcache 之类的缓存db来使用。 亦或是仅作日志收集分析。或者是存储某文章相关的评论,n方级别的数据(sql是中间表的形式,数据量太大)
4、MongoDB语法
MongoDB底层是js,其操作语法与js相似,格式为:db.集合名.操作
进入monogodb的bin目录下执行
>./mongo; //进入mongodb
...
....
.....
>show dbs; //查看数据库
user 0.012GB
>use user; //进入user数据库
switched to db user
>show collections; //查看集合或者使用show tables
login_log
>db.login_log.find(); //查询login_log的所有文档
.....
1、创建集合
db.createCollection("user") //创建一个user集合
2、CRUD
1.新增操作
db.user.insert({name:"xiaoming",age:18}) //首次操作insert会直接创建表uesr
2.修改操作
db.集合名.update({条件键:条件值}, {$set:{更新键:更新值}})
db.user.update({name:"xiaoming"},{$set:{name:"dabao"}}) //修改条件为name=xiaoming的文档,其name改为dabao
3.删除操作
db.user.deleteMany({}) //删除user集合
db.user.delete({name:"dabao"}) //删除条件为name=dabao的文档
4.查询操作
db.集合名.find({条件键:条件值}, {显示键1:显示值1, 显示键2:显示值2, …})
db.user.find() //查找user所有文档
db.user.find({},{name:0}) //查询所有,但不显示name列
db.user.find({},{name:1,age=1}) //查询所有,仅显示name、age列
db.user.find({gender:1}, {name:1, age:1}) //查询gender=1的文档,仅显示name、age列
db.user.findOne({name: dabao}) //查询一条数据
3、算术运算
db.<集合名>.find({<key>: {$gt:<value>}}) //$gt、$gte、$lt、$lte,大于、大于等于、小于、小于等于
db.<集合名>.find({<key>: <value>}) //等于
4、条件
db.user.find({age: {$gt:30}}) //查询age大于30的所有user
db.user.find({age: {$gte:3, $lte:5}}) //查询age大于等于3且小于等于5的user
db.student.find({"_id":ObjectId("5c89ecd012c1bcb23ed4e906")}) //根据id查询
5、MongDB数据类型
跟java类似
类型 | 别名 |
---|---|
Double | double |
String | string |
Object | object |
Array | array |
Binary data | binData |
Undefined | undefined |
ObjectId | objectId |
Boolean | “bool” |
Date | “date” |
Null | “null” |
Regular Expression | “regex” |
DBPointer | “dbPointer” |
JavaScript | “javascript” |
Symbol | “symbol” |
JavaScript(with scope) | “javascriptWithScope” |
32-bit integer | “int” |
Timestamp | “timestamp” |
64-bit integer | “long” |
Min key | “minKey” |
Max key | “maxKey” |
6、SpringBoot集成MongoDB
application.properties配置数据源
spring.data.mongodb.uri.mongodb://localhost:27017/test
①依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
②domain
@Data
@Document("user")
@TableName("user")
public class User{
@Id //注意,贴了@Id注解后,springmvc自动映射MongoDB的数据类型为ObjectId的_id
private String id;
private String name;
//...
}
③repository层(即mysql的dao/mapper层)通用接口
public interface UserRepository extends MongoRepository<T,T>{
//泛型1对应的是domain,泛型2对应的是主键id的类型,基本上就是String
User findByName(String name);
}
④service层
MongoDB没有通用service接口层,里面的方法都需要自己手写并且实现
mongodb启动器实现了JPA规范,针对查询操作(当然有其他操作的能力)非常友好,先来看一个方法
public interface IUserService{
//....
}
直接根据repository中的方法名实现对应的方法,怎么做到的?先来了解JPA规范
1、JPA规范
JPA就是Java Persistence API,即用于对象持久化的 API,Java EE 5.0 平台标准的 ORM 规范,使得应用程序以统一的方式访问持久层。
使用JPA的优势:
(1) 标准化: 提供相同的 API,这保证了基于JPA 开发的企业应用能够经过少量的修改就能够在不同的 JPA 框架下运行。
(2) 简单易用,集成方便: JPA 的主要目标之一就是提供更加简单的编程模型,在 JPA 框架下创建实体和创建 Java 类一样简单,只需要使用 javax.persistence.Entity 进行注释;JPA 的框架和接口也都非常简单,
(3) 可媲美JDBC的查询能力: JPA的查询语言是面向对象的,JPA定义了独特的JPQL,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
(4) 支持面向对象的高级特性: JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,最大限度的使用面向对象的模型
回过头来,mongodb通过实现JPA规范,对方法名拆解,如findByName:find查询,By一个,Name属性为name的字段(域),通过方法名直接猜测方法的实现,只要方法名符合规范,返回值对应需求和真实结果即可。
但是,如果我们需要多条件查询的时候,如
@Query("{$and:[{'stu_name':?0},{'age':?1}]}")
List<Student> findStudentsByStu_nameAndAge(String stu_name,Integer age);
这个情况就会造成方法名称变得特别长
==》解决:使用MongoTemplate
2、MongoTemplate
MongoTemplate:MongoReposity的底层实现,MongoReposity只能操作实现它的对应对象,而MongoTemplate能够操作所有实现了MongoRepository<T,T>的对象,它提供统一API对MongoDB进行操作,对应的CRUD有不同的拼接类(跟mp操作mysql的Wrapper接口下的QueryWrapper和UpdateWrapper一样)。
MongoTemplate的API需要程序员提供已拼接的语句、操作对象作为参数,CRUD中:
- 新增操作只需要提供操作对象
- 删除对象需要提供语句拼接对象Query和操作对象
- 修改操作需要提供语句拼接对象Update和操作对象
- 查询操作需要提供语句拼接对象Query和操作对象
MongoTemplate和其他拼接类所在的都包是core包
public class Test{
@Test
public void InsertTest(){
MongoTemplate mongoTemplate = new MongoTemplate();
mongoTemplate.save(new Student(null, "张三", 15, 1, null));
//id由mdb提供
//Document模式
Document document=new Document()
.append("name","张三")
.append("age",15)
.append("grade_id",1);
mongoTemplate.save(document,"student");
//如果有对应的student对象请求封装传入,可以直接将值设置到
//BeanUtils.copyProperties(resource,target);
//将resource的值映射到target,内省机制,因此属性名必须对应
}
@Test
public void UpdateTest(){
MongoTemplate mongoTemplate = new MongoTemplate();
//修改stu_name="三驴"且age!=18的文档为stu_name="三驴弟弟",age=18
Query query=Query.query(Criteria.where("stu_name").is("三驴").and("age").ne(18));
Update update = new Update().set("stu_name", "三驴弟弟").set("age", 18);
UpdateResult updateResult = mongoTemplate.updateFirst(query, update, Student.class);
}
@Test
public void DeleteTest(){
MongoTemplate mongoTemplate = new MongoTemplate();
//删除所有grade_id=3
Query query = Query.query(Criteria.where("grade_id").is(3));
DeleteResult deleteResult = mongoTemplate.remove(query, Student.class);
//删除全部
DeleteResult deleteResult = mongoTemplate.remove(new Query(), Student.class);
//删除所有 age=4 -> 表 student
Query query = Query.query(Criteria.where("age").is(4));
DeleteResult deleteResult = mongoTemplate.remove(query, "student");
}
@Test
public void SelectTest(){
MongoTemplate mongoTemplate = new MongoTemplate();
//查询对应Document
mongoTemplate.findAll(Document.class, "student");
//查询所有的数据
mongoTemplate.findAll(Student.class);
//查询stu_name="三驴"
Query query=Query.query(Criteria.where("stu_name").is("三驴"));
//查询age>18
Query query=Query.query(Criteria.where("age").gt(18));
mongoTemplate.find(query,Student.class);
//查询stu_name="三驴" and age>16
Query query=Query.query(Criteria.where("stu_name").is("三驴").and("age").gt(16));
mongoTemplate.find(query,Student.class);
//查询stu_name like '%牛%'
Query query = Query.query(Criteria.where("stu_name").regex("牛"));
mongoTemplate.find(query, Student.class);
//查询stu_name like '五%'
Query query = Query.query(Criteria.where("stu_name").regex("^五"));
mongoTemplate.find(query, Student.class);
//查询age>15 并且按照age 降序
Query query = Query.query(Criteria.where("age").gt(15)).with(new Sort(Sort.Direction.DESC, "age")); mongoTemplate.find(query,Student.class);
//查询排序并分页
Query query=new Query();
query.with(new Sort(Sort.Direction.ASC,"age"));//按照age升序
query.with(new PageRequest(1,3));
//page 0第一页 1第二页 -> 3每页三条记录
mongoTemplate.find(query,Student.class);
}
}
3、分页查询
MongoDB的分页较原始,其通过传参给PageImpl(data当前页数据,pageable分页规则,totalCount分页数据总条数)实现分页,PageImpl是实现Page的一个类
public Page<StrategyComment> queryPage(StrategyQuery qo) {
Query query = new Query();
query.addCriteria(Criteria.where("strategyId").is(qo.getStrategyId()));
//分页数据总量totalCount
long totalCount = template.count(query, StrategyComment.class);
PageRequest pageable = PageRequest.of(qo.getCurrentPage() - 1, qo.getPageSize()); //分页规则
query.with(pageable);
//分页条件是strategyId,以及从第currentpage每页pagesize的数据
List<StrategyComment> data = template.find(query, StrategyComment.class);
return new PageImpl<>(data,pageable,totalCount);
}
4、注意事项
由于每个类都是继承自Object类的,而@Document的类(即MongoDB的类)会反射获取所有属性,因此会继承Object的_class属性,而在save操作的时候会把值设置到对应的class属性,所以需要在配置类中配置,去除class属性(class有下划线的),其实也可以每次在save之前setClass为null
启动类中的配置
//mongodb 去除_class属性
@Bean
public MappingMongoConverter mappingMongoConverter(MongoDatabaseFactory 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) {
ignore.printStackTrace();
} // Don't save _class to mongo
mappingConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
return mappingConverter;
}