Java道经第3卷 - 第7阶 - MongoDB
文章目录
心法:本章使用 Maven 父子结构项目进行练习
练习项目结构如下:
|_ v3-7-ssm-mongodb
|_ 13701 test
武技:搭建练习项目结构
- 创建父项目 v3-7-ssm-mongodb,删除 src 目录。
- 在父项目中管理依赖:
<dependencyManagement>
<dependencies>
<!--spring-boot-starter-parent-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<!--声明该项目是一个POM类型的父项目,不参与打包,只用于传递依赖-->
<type>pom</type>
<!--导入该项目的 `<dependencyManagement>` 到本项目-->
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 在父项目中添加依赖:
<dependencies>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!--hutool-all-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool-all.version}</version>
</dependency>
</dependencies>
- 创建子项目 test,暂不用添加任何依赖。
S01. Mongo基础入门
E01. Mongo概念入门
心法:MongoDB 是一个由 C++ 语言编写的,基于分布式文件存储的数据库,面向文档(类似 JSON 对象)存储,旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。
MongoDB 类型:是非关系数据库当中功能最丰富,最像关系数据库的的产品:
- MongoDB 支持支持索引,支持复杂查询。
- MongoDB 不支持连表操作,但支持文档数据嵌套。
Mongo 对应 MySQL:
MongoDB 角色 | MySQL 角色 |
---|---|
database 数据库 | database 数据库 |
collection 集合 | table 表格 |
document 文档 | row 表格行 |
field 字段 | column 表格列 |
index 索引 | index 索引 |
primary key 主键 | primary key 主键 |
1. Mongo单机容器搭建
武技:在 Docker 中搭建 MongoDB 单机容器
- 创建相关目录:
# 创建Mongo相关目录
mkdir -p /opt/mongo/data
chmod -R 777 /opt/mongo/data
- 创建运行容器:
# 拉取镜像(二选一)
docker pull mongo:6.0.3;
docker pull registry.cn-hangzhou.aliyuncs.com/joezhou/mongo:6.0.3;
# 创建并启动Mongo容器
# args: `--auth`: 需要账号密码访问Mongo数据库
docker run -itd --name mongo --network my-net -p 27017:27017 \
-v /opt/mongo/data:/data \
registry.cn-hangzhou.aliyuncs.com/joezhou/mongo:6.0.3 --auth
# 查看Mongo容器
docker ps --format "{{.ID}}\t{{.Names}}\t{{.Ports}}"
docker logs mongo -f
# 开放Mongo容器端口
firewall-cmd --add-port=27017/tcp --permanent
firewall-cmd --reload
- 进入 Mongo 容器:进入内置的 admin 数据库中:
# 进入 Mongo 容器
docker exec -it mongo mongosh admin
- 创建 Mongo 用户:
# 创建Mongo用户: admin/admin
db.createUser({
user:'admin',
pwd:'admin',
roles:[{ role: 'root', db: 'admin' }]
});
# 登录Mongo容器
db.auth('admin', 'admin');
# 查看所有非空数据库: 默认包含 `admin/config/local` 三个数据库
show dbs
2. 使用IDEA连接Mongo服务器
武技:使用 IDEA 作为 Mongo 客户端
- 点击
Database - 加号 - Data Source - MongoDB
进入 Mongo 配置页面。 - 点击
Driver:MongoDB - Go to Driver - 加号 - Custom Jars
,然后选择 mongo-jdbc-standalone-1.20.jar(自行准备): - 在
General
选项卡中填写 Mongo 连接的基本信息,注意认证方式要选择SCRAM-SHA-1
哈希算法。
E02. Mongo常用角色
1. Mongo数据库
心法:一个 Mongo 服务中可以建立多个数据库,默认数据库为
db
,存储在 data 目录中。
Mongo 内置数据库分为 admin, local 和 config 三个:
admin 数据库:从权限的角度来看,这是 root 数据库:
- 要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。
- 一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。
local 数据库:这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合。
config 数据库:当 Mongo 用于分片设置时,config 数据库在内部使用,用于保存分片的相关信息。
武技:测试 Mongo 数据库常用操作
// 切换当前数据库为 mongo: 数据库不存在时自动创建
use mongo
// 查看所有数据库,空数据库不展示
show dbs
// 查看当前数据库
db
// 删除当前数据库
db.dropDatabase();
2. Mongo用户
心法:一个 Mongo 数据库中可以包含多个 Mongo 用户。
Mongo 内置角色:
- read:支持读取所有非系统集合和 system.js 集合上的数据。
- readWrite:包含 read 角色的所有特权,支持修改所有非系统集合和 system.js 集合上的数据 。
- dbAdmin:支持管理数据库对象,但不支持读写数据库。
- dbOwner:具有该数据库的全部权限。
- userAdmin:支持为当前用户创建,修改用户和角色,支持将该数据库的任意权限赋予任意的用户。
- readAnyDatabase:具有 read 每一个数据库的权限,但不包括应用到集群中的数据库。
- readWriteAnyDatabase:具有 readWrite 每一个数据库权限,但是不包括应用到集群中的数据库。
- userAdminAnyDatabase:具有 userAdmin 每一个数据库权限,但是不包括应用到集群中的数据库。
- dbAdminAnyDatabase:具有 dbAdmin 每一个数据库权限,但是不包括应用到集群中的数据库。
- root:包含以上全部角色特权,不具有备份恢复和直接操作
system.*
集合的权限,但 root 角色用户可以赋予自己这些权限。
武技:测试 Mongo 用户常用操作
// 查看当前数据库中的全部用户
show users
// 创建用户: joezhou/joezhou,拥有针对 mongo 仓库的 dbAdmin 角色
// 重复创建报错
db.createUser({
user: 'joezhou',
pwd: '123456',
roles: [{role: 'dbAdmin', db: 'mongo'}]
});
// 修改用户密码
db.changeUserPassword('joezhou', 'joezhou');
// 修改用户角色:拥有针对 mongo 仓库的 dbOwner 角色
db.updateUser('joezhou', {
roles: [{role: 'root', db: 'mongo'}]
});
// 删除用户
db.dropUser('joezhou');
3. Mongo集合
心法:Mongo 集合就是 Mongo 文档组,类似于 RDBMS 中的 table 表格,当第一个文档插入时,集合就会被创建。
Mongo 内置集合:
- system.users:存储 Mongo 用户相关信息的集合。
- system.version:存储 Mongo 版本相关信息的集合。
武技:测试 Mongo 集合常用操作
// 查看当前数据库中的全部集合
show collections;
// 创建集合
db.createCollection("test_collection");
// 创建容量固定的集合: 当达到最大值时,它会自动覆盖最早的文档,适用于日志等数据
db.createCollection("fix_collection", {
capped: true, // 固定容量
size: 10000, // 最大不超过10000字节
max: 1000 // 最多不超过1000个文档
});
// 删除指定的集合
db.fix_collection.drop();
db.test_collection.drop();
4. Mongo文档
心法:Mongo 文档采用类似于 JSON 结构的 BSON(Binary JSON)结构存储数据,底层同样为键值对结构。
Mongo 文档特点:
- Mongo文档中的键值对是有序的。
- Mongo文档中区分类型和大小写。
- Mongo文档中不能有重复的键。
武技:测试 Mongo 文档的常用操作
// 向当前数据库的user集合中插入一条数据: 集合自动创建
// 允许插入相同文档,但每条文档的 `_id` 值不同
db.user.insertOne({
id: 1,
name: "joezhou",
age: 16
});
// 向当前数据库的user集合中插入多条数据
// 集合不存时自动创建
db.user.insertMany([
{id: 2, name: "lucky", age: 16},
{id: 3, name: "zhaosi", age: 40}
]);
// 修改满足条件的第一个文档
db.user.updateOne(
{id: 1}, // 修改条件
{$set: {name: 'tom', age: 18}} // 修改的内容
);
// 修改满足条件的第一个文档
// `{upsert: true}`: 若满足条件的文档不存在则执行添加操作
// `{upsert: false}`: 若满足条件的文档不存在则不执行任何操作,默认值
db.user.updateOne(
{id: 4}, // 修改条件
{$set: {name: 'jack', age: 30}}, // 修改的内容
{upsert: true} // 若满足条件的文档不存在则执行添加操作
);
// 修改满足条件的全部文档
db.user.updateMany(
{id: 1}, // 修改条件
{$set: {name: 'guangkun', age: 50}} // 修改的内容
);
// 删除当前数据库的user集合中,符合条件的第一条文档
db.user.deleteOne({id: 1});
// 删除当前数据库的user集合中,符合条件的全部文档
db.user.deleteMany({id: 1});
// 查询全部文档
db.user.find();
// 查询全部文档,按id升序查询
db.user.find().sort({id: 1});
// 查询全部文档,按age降序查询
db.user.find().sort({age: -1});
// 跳过前4条文档后,查询2条文档
// 查询第N页,每页M条公式: .skip( (N-1)*M ).limit(M)
db.user.find().skip(4).limit(2);
// 查询ID为3,且姓名为lucky的全部文档
// 第2个参数用来设置结果集中展示那些列,1表示展示,默认展示全部列
db.user.find({id: 3, name: "lucky"}, {name: 1, id: 1});
// 查询ID为3,且姓名为lucky的第一个文档
db.user.findOne({id: 3, name: "lucky"});
// 查询ID为3,或姓名为lucky的全部文档
db.user.find({$or: [{id: 3}, {name: "lucky"}]});
// 查询ID为3,或姓名为lucky的第一个文档
db.user.findOne({$or: [{id: 3}, {name: "lucky"}]});
// 查询姓名符合正则的全部文档
db.user.find({name: /lu/});
// 查询姓名符合正则的第一个文档
db.user.findOne({name: /lu/});
// 查询年龄小于24的全部文档
db.user.find({age: {$lt: 24}});
// 查询年龄小于24的第一个文档
db.user.findOne({age: {$lt: 24}});
S02. Mongo项目整合
心法:org.springframework.data.mongodb.repository.MongoRepository<实体类, 主键类型> 是 Spring Data MongoDB 提供的一个接口数据访问接口,旨在简化在 Spring 应用中对 Mongo 的操作,使得开发者可以像操作传统数据库一样方便地操作 Mongo 中的数据。
武技:创建 v3-7-ssm-mongodb/test 子项目
- 添加三方依赖:
<!--三方依赖-->
<dependencies>
<!---spring-boot-starter-data-mongodb-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!--spring-boot-starter-web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter-test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
- 开发主配文件:
server:
port: 13701
spring:
data:
mongodb:
host: 192.168.40.77 # mongodb的连接地址
port: 27017 # mongodb的连接端口号
database: mongo # mongodb的连接的数据库
username: joezhou # mongodb账号
password: joezhou # mongodb密码
- 开发启动类:
package com.joezhou;
/** @author 周航宇 */
@SpringBootApplication
public class MongoApp {
public static void main(String[] args) {
SpringApplication.run(MongoApp.class, args);
}
}
E01. 开发Mongo实体类
Mongo 实体类常用注解如下:
注解 | 描述 |
---|---|
@Document | 标记实体类以映射 Mongo 文档 |
@Id | 标记属性以映射 Mongo 文档的主键 |
@Indexed | 标记属性以映射 Mongo 文档的索引字段 |
package com.joezhou.mongo;
/** @author 周航宇 */
@Data
@Document
public class History implements Serializable {
@Id
private Long id;
@Indexed
private Long userId;
private String nickname;
@Indexed
private Long productId;
private String productTitle;
private String productCover;
private Double productPrice;
private LocalDateTime created;
private LocalDateTime updated;
}
E02. 开发数据接口
- 开发数据层接口:
package com.joezhou.dao;
/** @author 周航宇 */
public interface HistoryRepository extends MongoRepository<History, Long> {
/**
* 根据用户主键获取该用户的浏览记录的数量
*
* @param userId 用户主键
* @return 浏览记录数量
*/
int countByUserId(Long userId);
/**
* 根据商品名称模糊查询浏览记录
*
* @param productTitle 商品名称
* @return 浏览记录列表
*/
List<History> findByProductTitleContaining(String productTitle);
/**
* 根据商品名称分页模糊查询浏览记录
*
* @param productTitle 商品名称
* @param pageable 分页条件
* @return 浏览记录列表
*/
Page<History> findByProductTitleContaining(String productTitle, Pageable pageable);
/**
* 根据商品名称分页模糊查询浏览记录,按 ID 降序
*
* @param productTitle 商品名称
* @param pageable 分页条件
* @return 浏览记录列表
*/
Page<History> findByProductTitleContainingOrderByIdDesc(String productTitle, Pageable pageable);
}
- 测试数据层接口:
package dao;
/** @author 周航宇 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MongoApp.class)
public class HistoryRepositoryTest {
@Autowired
private HistoryRepository historyRepository;
@Test
public void insert() {
// 单条插入:重复添加视为修改
History history$01 = new History();
history$01.setId(1L);
history$01.setUserId(1L);
history$01.setNickname("赵四");
history$01.setProductId(1L);
history$01.setProductTitle("小米手机");
history$01.setProductCover("小米手机.jpg");
history$01.setProductPrice(1000.00);
history$01.setCreated(LocalDateTime.now());
history$01.setUpdated(LocalDateTime.now());
System.out.println(historyRepository.save(history$01));
// 批量插入(循环插入100条数据)
LocalDateTime now = LocalDateTime.now();
List<History> histories = new ArrayList<>();
for (long i = 1000; i <= 1100; i++) {
History history = new History();
history.setId(i);
history.setUserId(i);
history.setNickname("用户" + i + "号");
history.setProductId(i);
history.setProductTitle("小米手机" + i + "号");
history.setProductCover("小米手机" + i + "号.jpg");
history.setProductPrice(i * 10.0);
history.setCreated(now);
history.setUpdated(now);
histories.add(history);
}
for (History history : historyRepository.saveAll(histories)) {
System.out.println(history);
}
}
@Test
public void select() {
// 按主键查询一条文档记录是否存在,存在返回true,不存在返回false
System.out.println(historyRepository.existsById(1000L));
// 按主键查询一条文档记录
Optional<History> optional$50 = historyRepository.findById(1000L);
if (optional$50.isPresent()) {
System.out.println(optional$50.get());
} else {
System.out.println("数据不存在");
}
// 按主键列表查询多条文档记录
Iterable<History> deptDocs = historyRepository.findAllById(List.of(1000L, 1001L, 1002L));
deptDocs.forEach(System.out::println);
// 查询全部文档记录
historyRepository.findAll().forEach(System.out::println);
}
@Test
public void count() {
// 查询全部文档数量
System.out.println(historyRepository.count());
// 查询1号用户的全部文档数量
System.out.println(historyRepository.countByUserId(1L));
}
@Test
public void search() {
// 查询商品名包含 "9" 的全部文档
historyRepository.findByProductTitleContaining("9").forEach(System.out::println);
}
@Test
public void page() {
int page = 1;
int size = 2;
// 查询第1页的两条数据(ES分页的page是从0开始的)
Pageable pageable = PageRequest.of(page - 1, size);
Page<History> page01 = historyRepository.findByProductTitleContaining("9", pageable);
System.out.printf("page=%s, size=%s, pages=%s, total=%s\n",
page01.getNumber(),
page01.getSize(),
page01.getTotalPages(),
page01.getTotalElements());
page01.getContent().forEach(System.out::println);
System.out.println();
Page<History> page02 = historyRepository.findByProductTitleContainingOrderByIdDesc("9", pageable);
System.out.printf("page=%s, size=%s, pages=%s, total=%s\n",
page02.getNumber(),
page02.getSize(),
page02.getTotalPages(),
page02.getTotalElements());
page02.getContent().forEach(System.out::println);
}
@Test
public void delete() {
// 按条件删除文档
History history = new History();
history.setId(1000L);
historyRepository.delete(history);
// 按主键删除一条文档
historyRepository.deleteById(1001L);
// 按主键列表批量删除多条文档
historyRepository.deleteAllById(List.of(1002L, 1003L, 1004L));
}
}
Java道经第3卷 - 第7阶 - MongoDB