MongoDB: Object Mapping and APIs
Java ORM tools are mature for RDBMS, but MongoDB's document model needs ODM (Object-Document Mapping) for clean Java integration. This post surveys the landscape and introduces dbVisitor as a JDBC-based solution.
Why ORM/ODM?
MongoDB stores flexible BSON documents, but in Java we face:
- Type safety: Working directly with
DocumentorMapis error-prone and hard to maintain. - Domain models: Business logic is built on POJOs; we need automatic (de)serialization.
- Developer efficiency: Hand-writing BSON builders is clumsy compared to object-centric operations.
- Unified style: Projects often mix RDBMS and MongoDB; a consistent API style lowers cognitive load.
ORM vs ODM
- ORM (Object-Relational Mapping): Bridges OO models and relational tables (MySQL, Oracle, etc.).
- ODM (Object-Document Mapping): Bridges Java objects and document databases (MongoDB, Elasticsearch, etc.). BSON naturally supports nested structures, so ODM mappings can be simpler, though references are handled differently.
In short: ORM maps “tables,” ODM maps “documents.”
dbVisitor for MongoDB
dbVisitor is a database access toolkit that provides a MongoDB driver exposing supported JDBC interfaces (jdbc-mongo). You can operate MongoDB like MySQL using JDBC, raw commands, or MyBatis-style mappers. The Mongo adapter even lets you perform CRUD without writing Mongo commands.
Features
- JDBC protocol: Standard JDBC driver (jdbc-mongo); reuse components relying on supported methods after checking JDBC limitations. Build parameterized queries with
PreparedStatement. - Raw commands: Execute native MongoDB commands to reduce the learning curve.
- Multiple APIs:
- JdbcTemplate for direct commands and unstructured data.
- LambdaTemplate for type-safe builders (MyBatis-Plus–style LambdaQueryWrapper).
- Mapper interfaces with annotations (
@Insert,@Query) or XML—using familiar MyBatis development patterns.
- Dynamic Command: XML supports dynamic tags; annotation commands can use dynamic rules. MongoDB conditions must still produce valid BSON; do not apply SQL WHERE assembly rules directly to BSON. Choose raw commands for complex queries or LambdaTemplate for single-collection CRUD.
Access Options
Use jdbc-mongo when you need JDBC or Mapper interfaces; use the official SDK for MongoDB capabilities not covered by the adapter. dbVisitor unifies invocation style but does not turn MongoDB into a relational database.
Getting Started
1) Dependencies
Add core and MongoDB adapter. Guide version: 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>
Use Java 17+ and the released 6.8.0 dependencies; no framework source build is required. Keep core, adapter, and Spring extension versions aligned.
Connection URL example: jdbc:dbvisitor:mongo://127.0.0.1:27017/admin?user=root&password=123456.
2) Native Commands
Good for full control or quick debugging.
try (Connection c = DriverManager.getConnection(url, user, pwd)) {
JdbcTemplate jdbc = new JdbcTemplate(c);
// Insert
jdbc.execute("db.users.insertOne({name: ?, age: ?})", new Object[] { "Alice", 18 });
// Query
Map<String, Object> row = jdbc.queryForMap("db.users.findOne({name: ?})", "Alice");
}
3) Mapper Interfaces
Annotate commands to stay in the MyBatis style.
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 Builders
No need to handcraft commands; write conditions against entities.
// Use the same User class defined under "Map entities" below.
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();
// Query by PK
User u2 = lambda.query(User.class)
.eq(User::getUserId, u.getUserId())
.queryForObject();
// Update
int r2 = lambda.update(User.class)
.updateTo(User::getAge, 20)
.eq(User::getUserId, u.getUserId())
.doUpdate();
// Delete
int r3 = lambda.delete(User.class)
.eq(User::getUserId, u.getUserId())
.doDelete();
}
5) Entity Mapping
Use @Table / @Column to map collections and fields; supports primary keys, aliases, and TypeHandler.
@Table("users")
public class User {
@Column(value = "userId", primary = true)
private String userId;
@Column("name")
private String name;
@Column("age")
private Integer age;
// getters/setters omitted
}
6) Mapper Files
Best for complex dynamic conditions; can coexist with annotations.
<?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) Choosing an API
- JdbcTemplate: maximum freedom for debugging, special commands, or pipelines.
- Mapper interfaces: light config; good for small/mid projects or fixed statements.
- LambdaTemplate: type-safe, no template strings; ideal for standard CRUD and medium complexity.
- Mapper XML: strongest dynamic command; best for complex queries/aggregations and combinations.
- @Table/@Column: when you need entity-to-document mapping, aliases, or TypeHandlers.
Framework Integration
Quick integration examples (see 3.3 Spring Integration for details).
Spring Boot
Add dependencies first. Current version: 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>
Option 1: configure application.properties
# Spring JDBC data source
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
# Required
dbvisitor.mapper-packages=com.example.demo.dao
dbvisitor.mapper-locations=classpath:dbvisitor/mapper/*.xml
Option 2: configure via annotations on the bootstrap class
@Configuration
@MapperScan(basePackages = "com.example.demo.dao",
mapperLocations = "classpath:dbvisitor/mapper/*.xml")
public class DemoApplication {
...
}
Injecting mappers
import net.hasor.dbvisitor.lambda.LambdaTemplate;
import net.hasor.dbvisitor.jdbc.core.JdbcTemplate; // different from Spring's JdbcTemplate
import org.springframework.beans.factory.annotation.Autowired;
public class ServiceTest {
@Autowired
private UserMapper userMapper;
@Autowired
private JdbcTemplate jdbc;
@Autowired
private LambdaTemplate lambda;
...
}
Summary
dbVisitor brings MongoDB into the same developer experience as relational databases via JDBC. Use raw commands, type-safe Lambda, annotation/XML mappers, or zero-command CRUD. Pick the API that fits the scenario to balance speed, safety, and maintainability. Mixing RDBMS and MongoDB under one API lowers team cognitive load.