MongoDB 入门:对象映射与多种访问方式
在 Java 开发领域,关系型数据库(RDBMS)的 ORM 工具(如 Hibernate, MyBatis)已经非常成熟。然而,随着 NoSQL 数据库特别是 MongoDB 的普及,开发者对于在 Java 中如何优雅、高效地操作 MongoDB 提出了新的需求。
虽然 MongoDB 是文档型数据库,天生具备 Schema-less 的特性,但在强类型的 Java 语言中,我们依然需要一种机制将 BSON 文档映射为 Java 对象,以便于业务逻辑的处理。这就是 ODM(Object-Document Mapping)应运而生的背景。
本文将探讨 MongoDB 开发中的 ORM/ODM 现状,并介绍一款基于 JDBC 协议的 MongoDB 新工具 —— dbVisitor。
为何使用 ORM/ODM
MongoDB 存储的是 BSON(Binary JSON)格式的文档,结构灵活。但在实际的 Java 工程开发中,我们面临以下挑战:
- 类型安全:Java 是强类型语言,直接操作
Document或Map对象容易出错且难以维护。 - 领域模型:业务逻辑通常基于 POJO领域模型构建,需要自动化的序列化/反序列化机制。
- 开发效率:手写繁琐的 BSON 构建代码远不如面向对象的操作直观。
- 统一规范:在同一个项目中,同时使用 RDBMS 和 MongoDB,开发者希望有一套统一的 API 风格。
ORM 与 ODM
在讨论工具之前,我们需要厘清 ORM 和 ODM 的概念。
- ORM (Object-Relational Mapping):对象-关系映射。主要用于关系型数据库(MySQL, Oracle 等)。它解决的是 面向对象模型 与 关系模型(二维表、外键关联)之间的不匹配问题。
- ODM (Object-Document Mapping):对象-文档映射。主要用于文档型数据库(MongoDB, Elasticsearch 等)。它解决的是 Java 对象 与 文档 之间的映射。由于 BSON 本身支持嵌套结构(数组、子文档),与 Java 对象的结构更为接近,因此 ODM 的映射通常比 ORM 更自然,但在处理复杂关联(Reference)时逻辑会有所不同。
简单来说,ORM 映射的是 “表”,ODM 映射的是 “文档”。
dbVisitor 与 MongoDB
MongoDB 的 JDBC 驱动与 ORM/ODM 工具,dbVisitor 本质上是一个数据库访问工具,它最为独特之处在于:它通过为 MongoDB 提供了复用 JDBC 接口的驱动实现(jdbc-mongo)。 这意味着你可以像操作 MySQL 一样,使用 JDBC 接口,通过原始 MongoDB 命令、甚至 MyBatis 风格的 Mapper 来操作 MongoDB。 特别贴心的的是 dbVisitor 还为 jdbc-mongo 做了专门的适配,您甚至都无须编写任何 MongoDB 命令就能实现 CRUD 操作。
功能特性
- JDBC 协议支持:
dbVisitor 提供了一个标准的 JDBC 驱动 (jdbc-mongo)。
你可以使用 JDBC 标准方式获取 MongoDB 连接,使用
PreparedStatement构建带有参数的查询。 可复用依赖已支持 JDBC 方法的组件,接入前请核对JDBC 限制。 - 原始命令: 为了降低学习成本,dbVisitor 支持使用原始 MongoDB 命令执行查询。
- 多模式 API:
- JdbcTemplate:适合直接执行命令,处理复杂且非结构化的数据。
- LambdaTemplate:提供类型安全的构造器 API,类似 MyBatis-Plus 的 LambdaQueryWrapper。
- Mapper 接口:支持注解(
@Insert,@Query)和 XML 文件配置,完全复用 MyBatis 的开发习惯。
- 动态命令:
XML 文件支持动态标签;注解中的命令可使用动态规则。MongoDB 条件仍需生成合法 BSON,不要把 SQL 的 WHERE 拼接规则直接套在 BSON 上。
- dbVisitor 鼓励开发者使用不同的方式来应对查询时的复杂场景。例如,使用原始命令构建复杂的查询,或者使用 LambdaTemplate 构建单表 CRUD。
选择访问方式
需要复用 JDBC/Mapper 接口时可以采用 jdbc-mongo;需要未覆盖的 MongoDB 能力时可直接使用官方 SDK。dbVisitor 统一调用方式,但不把 MongoDB 变成 关系型数据库。
使用方式
1. 依赖引入
在 pom.xml 中加入核心依赖与 MongoDB 适配器,当前指南版本:6.8.0
<dependencies>
<dependency>
<groupId>net.hasor</groupId>
<artifactId>dbvisitor</artifactId>
<version>6.8.0</version>
</dependency>
<dependency>
<groupId>net.hasor</groupId>
<artifactId>jdbc-mongo</artifactId>
<classifier>all</classifier>
<version>6.8.0</version>
</dependency>
</dependencies>
使用 Java 17+ 和已发布的 6.8.0,无需先编译框架源码;核心库、适配器与 Spring 扩展使用相同版本。
连接 URL 示例:jdbc:dbvisitor:mongo://127.0.0.1:27017/admin?user=root&password=123456。
2. 原生 Mongo 命令
适合需要完全控制命令或快速调试。
try (Connection c = DriverManager.getConnection(url, user, pwd)) {
JdbcTemplate jdbc = new JdbcTemplate(c);
// 插入
jdbc.execute("db.users.insertOne({name: ?, age: ?})", new Object[] { "Alice", 18 });
// 查询
Map<String, Object> row = jdbc.queryForMap("db.users.findOne({name: ?})", "Alice");
}
3. Mapper 接口
用注解描述命令,保持 MyBatis 风格。
public interface UserMapper {
@Insert("db.users.insertOne({name: :name, age: :age})")
int insert(User user);
@Query("db.users.find({age: {$gt: :age}})")
List<User> findByAge(@Param("age") int age);
}
Configuration config = new Configuration();
try (Session session = config.newSession(dataSource)) {
UserMapper mapper = session.createMapper(UserMapper.class);
User cindy = new User();
cindy.setName("Cindy");
cindy.setAge(22);
mapper.insert(cindy);
}
4. CRUD 构造器
无需拼接命令,直接基于实体编写条件。
// User 的完整字段映射见下方“映射实体”;这里使用同一个类。
try (Connection c = DriverManager.getConnection(url, user, pwd)) {
User u = new User();
u.setUserId("123");
u.setName("Alice");
u.setAge(18);
LambdaTemplate lambda = new LambdaTemplate(c);
int r1 = lambda.insert(User.class)
.applyEntity(u)
.executeSumResult();
List<User> list = lambda.query(User.class)
.eq(User::getAge, 18)
.queryForList();
// 按主键查询
User u2 = lambda.query(User.class)
.eq(User::getUserId, u.getUserId())
.queryForObject();
// 更新
int r2 = lambda.update(User.class)
.updateTo(User::getAge, 20)
.eq(User::getUserId, u.getUserId())
.doUpdate();
// 删除
int r3 = lambda.delete(User.class)
.eq(User::getUserId, u.getUserId())
.doDelete();
}
5. 映射实体
通过 @Table / @Column 注解声明集合与字段映射,支持主键、别名、TypeHandler 等。
@Table("users")
public class User {
@Column(value = "userId", primary = true)
private String userId;
@Column("name")
private String name;
@Column("age")
private Integer age;
// getter/setter 省略
}
这里的 userId 是业务键,不是 MongoDB 的 _id。primary = true 只指导 dbVisitor 的映射,不会创建唯一索引;需要唯一性时应另行建索引。
6. Mapper 文件
适合复杂动态条件;可与注解并存。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//dbvisitor.net//DTD Mapper 1.0//EN"
"https://www.dbvisitor.net/schema/dbvisitor-mapper.dtd">
<mapper namespace="net.hasor.scene.mongodb.dto.UserMapper">
<insert id="saveUser">
db.users.insertOne({userId: #{info.userId}, name: #{info.name}, age: #{info.age}})
</insert>
<select id="loadUser" resultType="net.hasor.scene.mongodb.dto.User">
db.users.find({name: #{name}})
</select>
<delete id="deleteUser">
db.users.remove({name: #{name}})
</delete>
</mapper>
@RefMapper("dbvisitor/mapper/user-mapper.xml")
public interface UserMapper {
int saveUser(@Param("info") User info);
User loadUser(@Param("name") String name);
int deleteUser(@Param("name") String name);
}
7. 选择建议
- JdbcTemplate:最大自由度,适合调试、特殊命令或聚合管道。
- Mapper 接口:轻量配置,适合中小项目或少量固定语句。
- LambdaTemplate:类型安全、无模板字符串,适合标准 CRUD 与中等复杂度条件。
- Mapper XML:最强动态命令能力,适合复杂查询/聚合、多条件组合。
- @Table/@Column:需要实体到文档映射的场景,便于 TypeHandler、字段别名管理。
框架整合
下面给出常见框架的快速整合指引(更多细节可参考 3.3 Spring 整合 对应章节):
Spring Boot
在使用之前首先引入依赖,当前版本:6.8.0
<dependency>
<groupId>net.hasor</groupId>
<artifactId>dbvisitor-spring-starter</artifactId>
<version>6.8.0</version>
</dependency>
<dependency>
<groupId>net.hasor</groupId>
<artifactId>jdbc-mongo</artifactId>
<classifier>all</classifier>
<version>6.8.0</version>
</dependency>
方式一:在 application.properties 配置文件中添加如下配置
# Spring JDBC 数据源配置
spring.datasource.driver-class-name=net.hasor.dbvisitor.driver.JdbcDriver
spring.datasource.url=jdbc:dbvisitor:mongo://127.0.0.1:27017/admin
spring.datasource.username=root
spring.datasource.password=123456
# 必选
dbvisitor.mapper-packages=com.example.demo.dao
dbvisitor.mapper-locations=classpath:dbvisitor/mapper/*.xml
方式二:在启动类上通过注解添加如下配置
@Configuration
@MapperScan(basePackages = "com.example.demo.dao",
mapperLocations = "classpath:dbvisitor/mapper/*.xml")
public class DemoApplication {
...
}
使用注入 Mapper
import net.hasor.dbvisitor.lambda.LambdaTemplate;
import net.hasor.dbvisitor.jdbc.core.JdbcTemplate; // 注意导包和 Spring 的 JdbcTemplate 区别
import org.springframework.beans.factory.annotation.Autowired;
public class ServiceTest {
@Autowired
private UserMapper userMapper;
@Autowired
private JdbcTemplate jdbc;
@Autowired
private LambdaTemplate lambda;
...
}
总结
dbVisitor 通过 JDBC 协议把 MongoDB 拉到与关系型数据库一致的开发体验之下:你可以用原生命令、类型安全的 Lambda、注解/XML Mapper,或零命令的通用 CRUD。根据场景选择合适的方式,可以在保持高效迭代的同时,兼顾类型安全和可维护性。在同一套 API 下混合使用 RDBMS 与 MongoDB,也能显著降低团队的认知成本。