MongoDB
针对 MongoDB 的数据存取操作的特点,以 JDBC 持久化模块的设计思想进行简单封装,采用会话机制,简化事务处理逻辑,支持多数据源配置和实体操作,基于操作器(IOperator)对象化拼装查询条件,并集成 MapReduce、GridFS、聚合及函数表达式等。
Maven包依赖
<dependency>
<groupId>net.ymate.platform</groupId>
<artifactId>ymate-platform-persistence-mongodb</artifactId>
<version>2.1.4</version>
</dependency>
模块配置
配置文件参数说明
# 默认数据源名称,默认值为default
ymp.configs.persistence.mongodb.ds_default_name=
# 数据源列表,多个数据源名称间用'|'分隔,默认为default
ymp.configs.persistence.mongodb.ds_name_list=
# 数据源访问用户名称,必要参数
ymp.configs.persistence.mongodb.ds.default.username=
# 数据源访问密码,可选参数
ymp.configs.persistence.mongodb.ds.default.password=
# 数据源访问密码是否已加密,默认为false
ymp.configs.persistence.mongodb.ds.default.password_encrypted=
# 数据源密码处理器,可选参数,用于对已加密码数据源访问密码进行解密,默认为空
ymp.configs.persistence.mongodb.ds.default.password_class=
# 集合前缀名称,可选参数,默认为空
ymp.configs.persistence.mongodb.ds.default.collection_prefix=
# 服务器主机连 接字符串,可选参数,若提供此参数则无需再提供username, password, database_name, servers等参数
ymp.configs.persistence.mongodb.ds.default.connection_url=
# 是否自动连接, 即模块初始化时完成连接动作, 默认值: false
ymp.configs.persistence.mongodb.ds.default.auto_connection=true
# 数据库名称,必填参数
ymp.configs.persistence.mongodb.ds.default.database_name=
# 包含用户身份验证数据的数据库名称,可选参数,默认值为admin
ymp.configs.persistence.mongodb.ds.default.authentication_database_name=
# 服务器主机集合,格式:<IP地址[:端口]>,多个主机之间用'|'分隔,默认为空
ymp.configs.persistence.mongodb.ds.default.servers=
# 自定义MongoDB客户端参数配置处理器
ymp.configs.persistence.mongodb.ds.default.options_handler_class=
配置注解参数说明
当 MongoDB 持久化模块初始化时,若在配置文件中存在数据源相关配置,则基于注解的数据源配置将全部失效。
@MongoConf
| 配置项 | 描述 |
|---|---|
| dsDefaultName | 默认数据源名称 |
| value | 数据源配置 |
@MongoDataSource
| 配置项 | 描述 |
|---|---|
| name | 数据源名称 |
| username | 数据库访问用户名称 |
| password | 数据库访问密码 |
| passwordEncrypted | 数据库访问密码是否已加密 |
| passwordClass | 数据库密码处理器 |
| collectionPrefix | 集合前缀名称 |
| databaseName | 数据库名称 |
| authenticationDatabaseName | 包含用户身份验证数据的数据库名称 |
| connectionUrl | 服务器主机连接字符串 |
| autoConnection | 是否自动连接 |
| servers | 服务器主机集合 |
| optionsHandlerClass | 数据源自定义配置处理器 |
数据源(DataSource)
多数据源连接
MongoDB 持久化模块默认支持多数据源配置,下面通过简单的配置来展示如何连接多个服务:
# 定义两个数据源分别用于连接本地和另一台IP地址两个MongoDB服务
ymp.configs.persistence.mongodb.ds_default_name=default
ymp.configs.persistence.mongodb.ds_name_list=default|othermongodb
# 默认数据源连接本地默认端口MongoDB服务
ymp.configs.persistence.mongodb.ds.default.username=clientuser
ymp.configs.persistence.mongodb.ds.default.password==12345678
ymp.configs.persistence.mongodb.ds.default.database_name=demo
ymp.configs.persistence.mongodb.ds.default.servers=localhost
# 名称为othermongodb数据源连接指定IP地址和端口的MongoDB服务
ymp.configs.persistence.mongodb.ds.othermongodb.username=clientuser
ymp.configs.persistence.mongodb.ds.othermongodb.password==12345678
ymp.configs.persistence.mongodb.ds.othermongodb.database_name=demo
ymp.configs.persistence.mongodb.ds.othermongodb.servers=10.211.55.5
通过注解方式配置多数据源,如下所示:
@MongoConf(dsDefaultName = "default", value = {
@MongoDataSource(
name = "default",
username = "clientuser",
password = "12345678",
databaseName = "demo",
servers = "localhost"),
@MongoDataSource(
name = "othermongodb",
username = "clientuser",
password = "12345678",
databaseName = "demo",
servers = {"10.211.55.5"})})
数据源路由(IDataSourceRouter)
在多数据源环境下,除通过数据源名称直接获取连接外,还可以实现 net.ymate.platform.core.persistence.IDataSourceRouter 接口来动态决定会话所使用的数据源,这是实现「分库」路由的基础。该接口仅定义一个方法:
/**
* 获取数据源名称
*
* @return 返回数据源名称
*/
String getDataSourceName();
将路由器对象传递给 IMongo.openSession(IDataSourceRouter, IMongoSessionExecutor) 即可让会话内操作路由到 getDataSourceName() 返回的数据源;此外 openSession(IDataSourceRouter)、openGridFsSession(IDataSourceRouter) 以及 openTransaction(IDataSourceRouter, ...) 等方法同样支持传入路由器。
示例:
// 自定义数据源路由器,根据用户ID取模选择MongoDB数据源
public class UserDataSourceRouter implements IDataSourceRouter {
private final String uid;
public UserDataSourceRouter(String uid) { this.uid = uid; }
@Override
public String getDataSourceName() {
return Integer.parseInt(uid) % 2 == 0 ? "default" : "othermongodb";
}
}
IMongo mongo = MongoDB.get();
mongo.openSession(new UserDataSourceRouter("1001"), session -> {
// 该会话内所有操作均路由到 othermongodb 数据源
return session.find(UserEntity.class);
});
MongoDB 连接持有者(IMongoConnectionHolder)
用于记录真正的数据库连接对象(MongoDatabase)原始的状态及与数据源对应关系,在 MongoDB 持久化模块中获取到的所有连接对象均由数据库连接持有者对象包装,基于数据库连接持有者接口可以进行如下操作:
示例:
public class Main {
public static void main(String[] args) throws Exception {
try (IApplication application = YMP.run(args)) {
if (application.isInitialized()) {
// 获取当前容器内MongoDB模块实例
IMongo mongo = application.getModuleManager().getModule(MongoDB.class);
// 获取默认数据源的连接持有者实例,等同于 mongo.getConnectionHolder("default");
IMongoConnectionHolder connectionHolder = mongo.getDefaultConnectionHolder();
// 获取指定名称数据源连接持有者实例
connectionHolder = mongo.getConnectionHolder("othermongodb");
// 获取数据库连接对象
MongoDatabase connection = connectionHolder.getConnection();
// 获取数据源配置对象
IMongoDataSourceConfig dataSourceConfig = connectionHolder.getDataSourceConfig();
// 获取当前数据源适配器对象
IMongoDataSourceAdapter dataSourceAdapter = connectionHolder.getDataSourceAdapter();
// 获取当前连接持有者所属MongoDB模块实例
IMongo owner = connectionHolder.getOwner();
}
}
}
}
数据源反注册(unregisterDataSource)
动态关闭并移除指定非默认数据源(释放 MongoClient 连接资源 + 移除配置),适用于运行时按需卸载数据源。
示例:
// 反注册名称为 "othermongodb" 的数据源(默认数据源 default 受保护,不可反注册)
MongoDB.get().unregisterDataSource("othermongodb");
- 当
dataSourceName为空或等于默认数据源名称(ds_default_name)时,将抛出IllegalArgumentException; - 内部关闭该数据源的
MongoClient并释放资源(各 MongoClient 实例相互独立,不会影响其它数据源),同时从缓存与配置中一并移除; - 后续按该名称访问将视为不存在(不会被再次惰性重建);
- 反注册时请确保该数据源当前无活跃会话/事务持有其连接。
数据实体(Entity)
数据实体是以对象的形式与数据库表之间的一种映射关系,实体中的属性与表中字段一一对应,与 JDBC 持久化模块中的实体在使用上做了相应的简化。
在 MongoDB 表中,记录的主键名称固定为 _id(在编写代码时可以使用 IMongo.Opt.ID 常量), 一般采用自动生成且无复合主键的情况,因此,在无特殊需求的情况下,编写实体类只需要继承 BaseEntity 类(注意: 与 JDBC 持久化模块中的实体基类名称同名),否则需要保证实体类实现 IEntity 接口 ,该类仅做了简单的主键映射,若需要自定义主键请为 id 属性赋值,否则将获取自动生成的值,其代码如下:
public class BaseEntity implements IEntity<String> {
@Id
@Property(name = IMongo.Opt.ID)
private String id;
@Override
public String getId() {
return id;
}
@Override
public void setId(String id) {
this.id = id;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
BaseEntity that = (BaseEntity) o;
return Objects.equals(id, that.id);
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.DEFAULT_STYLE);
}
}
数据实体注解
在 MongoDB 持久化模块中,可用于数据实体的注解仅支持以下几种,且 @Property 注解中的一些配置项在 MongoDB 中无效。
@Entity
声明一个类为数据实体对象。
| 配置项 | 描述 |
|---|---|
| value | 实体名称(数据库表名称),默认采用当前类名称 |
@Property
声明一个类成员为数据实体属性。
| 配置项 | 描述 |
|---|---|
| name | 实现属性名称,默认采用当前成员名称 |
| autoincrement | 是否为自动增长,默认为 false |
| useKeyGenerator | 指定键值生成器名称,默认为空表示不启用(仅当非自动增长且主键值为空时调用)。 目前框架提供了 IKeyGenerator.UUID 键值生成器,其采用 UUID 策略。可通过实现 IKeyGenerator 接口自行实现并通过 SPI 方式向框架注册。 |
| nullable | 允许为空,默认为 true |
@Readonly
声明一个成员为只读属性,数据实体更新时其值将被忽略,与 @Property 注解配合使用,无参数。
@Comment
实体或成员属性的注释内容。
@Default
为一个成员属性指定默认值。
| 配置项 | 描述 |
|---|---|
| value | 默认值 |
| ignored | 是否忽略,默认为 false |
实体类与表对应关系示例
假定数据库 demo 集合中的 user 表存在 nick_name、age、 gender 和 create_time 属性,通过实体类表示如下:
@Entity(value = "user")
public class UserEntity extends BaseEntity {
@Property(name = FIELDS.NICKNAME, nullable = false)
private String nickname;
@Property
private Integer age;
@Property
@Default("F")
private String gender;
@Property(name = FIELDS.CREATE_TIME, nullable = false)
@Readonly
private Date createTime;
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public interface FIELDS {
String NICKNAME = "nickname";
String GENDER = "gender";
String AGE = "age";
String CREATE_TIME = "create_time";
}
}
事务(Transaction)
MongoDB 持久化模块与 JDBC 持久化模块在事务的处理方式上有异曲同工之处。
MongoDB 在进行事务操作时必须开启副本集(同时只能在 PRIMARY 服 务上才能进行正常的写入操作),否则可能会产生类似如下所示的异常:
com.mongodb.MongoQueryException: Query failed with error code 20 and error message 'Transaction numbers are only allowed on a replica set member or mongos' on server xxx.xxx.xxx.xxx:27017
另外,请避免在已开启的事务中执行与文件存储(GridFS)相关操作(其不支持事务操作),可能产生如下异常:
Command failed with error 263 (OperationNotSupportedInTransaction): 'Cannot run 'listIndexes' in a multi-document transaction.' on server xxx.xxx.xxx.xxx:27017......
开启事务
在 MongoDB 持久化模块中的事务仅支持手动方式开启,目前提供了两种事务执行方式,分别针对无返回值和有返回值的情况。
示例: 无返回值事务
// 开启默认数据源事务
MongoDB.get().openTransaction(new ITrade() {
@Override
public void deal() throws Throwable {
// 具体业务逻辑
}
});
// 开启指定数据源事务
MongoDB.get().openTransaction("othermongodb", new ITrade() {
@Override
public void deal() throws Throwable {
// 具体业务逻辑
}
});
示例: 有返回值事务
// 开启默认数据源事务
UserEntity userEntity = MongoDB.get().openTransaction(new AbstractTrade<UserEntity>() {
@Override
public UserEntity dealing() throws Throwable {
// 具体业务逻辑
return null;
}
});
// 开启指定数据源事务
UserEntity userEntity = MongoDB.get().openTransaction("othermongodb", new AbstractTrade<UserEntity>() {
@Override
public UserEntity dealing() throws Throwable {
// 具体业务逻辑
return null;
}
});
示例: 传递自定义事务配置
// 自定义事务配置
ClientSessionOptions sessionOptions = ClientSessionOptions.builder()
.causallyConsistent(true)
.defaultTransactionOptions(TransactionOptions.builder()
.writeConcern(WriteConcern.MAJORITY)
.readConcern(ReadConcern.AVAILABLE)
.readPreference(ReadPreference.primary())
.build()).build();
// 无返回值事务调用
MongoDB.get().openTransaction(new ITrade() {
@Override
public void deal() throws Throwable {
// 具体业务逻辑
}
}, sessionOptions);
// 有返回值事务调用
UserEntity userEntity = MongoDB.get().openTransaction(new AbstractTrade<UserEntity>() {
@Override
public UserEntity dealing() throws Throwable {
// 具体业务逻辑
return null;
}
}, sessionOptions);
事务回滚(Rollback)
与 JDBC 持久化模块一样,在开启事务的业务逻辑处理过程中,只要抛出任何异常都将终止事务并回滚。
会话(Session)
会话是对应用中具体业务操作触发的一系列与 MongoDB 之 间的交互过程的封装,通过建立一个临时通道,负责与 MongoDB 之间连接资源的创建及回收,同时提供更为高级的抽象指令接口调用,基于会话的优点:
- 开发人员不需要担心连接资源是否正确释放。
- 严格的编码规范更利于维护和理解。
- 更好的业务封装性。
如何开启会话
示例: 使用默认数据源开启会话
IResultSet<UserEntity> users = MongoDB.get().openSession(new IMongoSessionExecutor<IResultSet<UserEntity>>() {
@Override
public IResultSet<UserEntity> execute(IMongoSession session) throws Exception {
return session.find(UserEntity.class, Query.create()
.cond(UserEntity.FIELDS.NICKNAME, Operator.create().eq("suninformation")));
}
});
示例: 使用指定的数据源开启会话
IResultSet<UserEntity> users = MongoDB.get().openSession("othermongodb", new IMongoSessionExecutor<IResultSet<UserEntity>>() {
@Override
public IResultSet<UserEntity> execute(IMongoSession session) throws Exception {
return session.find(UserEntity.class, new QueryBuilder() {{
and(query(UserEntity.FIELDS.NICKNAME, operator().eq("suninformation")),
query(UserEntity.FIELDS.GENDER, operator().eq("M")));
}});
}
});
示例: 手动开启与关闭会话
// 一定要确保连接使用完毕后关闭会话以释放连接
try (IMongoSession session = MongoDB.get().openSession()) {
IMongoConnectionHolder holder = session.getConnectionHolder();
MongoDatabase mongoDatabase = holder.getConnection();
//
MongoCollection<Document> collection = mongoDatabase.getCollection("user");
// 或 collection = session.getCollection(UserEntity.class);
// ......
}