Spring boot MongoDB多数据源,MongoRepository实现
背景
最近项目中存在模块化需求,在开发过程中需要建立多个MongoDB库来处理数据分割问题。这些文章通常可参考,其核心原理为:在Spring Boot容器中创建多份MongoDB模板实例以实现数据的分布式管理。如以下代码片段所示:
@Configuration
@EnableMongoRepositories(basePackages = {"com.sunliangliang.service.basic"}, mongoTemplateRef = "basicMongoTemplate")
@ConfigurationProperties(prefix = "basic.mongodb")
public class BasicMongoConfigure extends AbstractMongoConfigure {
@Override
@Bean(name = "basicMongoTemplate")
public MongoTemplate getMongoTemplate() throws Exception {
return new MongoTemplate(mongoDbFactory());
}
}
使用方式 :
- 使用时借助Autowired和@Qualifier实现对MongoTemplate实例的注入以便管理不同 mongo 数据库。(补充说明:可以通过 @Resource 注解引入 MongoTemplate 应用程序并将其实例命名为所需配置 bean 的名称从而创建相应的 MongoTemplate 实例)
通过MongoTemplate示例配置的basePackages指定其遵循了MongoRepository的model接口,并在应用数据库操作时被使用所设置的MongoTemplate实例。
这是最常用的Spring Boot与MongoDB跨库配置方案,在实际应用中发现该方案存在一定的局限性。具体而言,在主项目的环境中由于引入了现有配置项,在尝试使用该配置进行设置时发现:当使用MongoRepository时,默认无法按照配置文件中指定的basePackages路径来调用相关的MongoTemplate操作功能。
分析
基于前文所述的MongoDB多数据库实现中提到的核心技术是操作不同类型的MongoTemplate。其中核心在于操作不同的MongoTemplate类。通常情况下,默认会直接使用 Mongo Template 这一内置功能。然而我们也可以编写一个接口,并使其继承 MongoRepository 接口,并非必须要亲自去实现这个接口即可完成对 MongoDB 的操作。具体来说,在访问基于继承的 MongoRepository 接口时,默认我们会采用 Spring Data 框架中的 Simple.MongoRepository 类作为默认选择。值得注意的是,在这种设计模式下 Simple.MongoRepository 作为一个关键组件承担着通过调用 MongoOperations 接口来完成对 MongoDB 数据库的操作职责。而 MongoTemplate 则是定义了这一系列操作的具体方法集合,并且作为 MongoOperations 接口的一个重要实现类也理所当然地成为连接两者的核心桥梁。
思路
当使用SimpleMongoRepository处理数据库时,在其MongoOperations中动态调整其参数值,并将其称为MongoTemplate。因此,在这种情况下Spring AOP得以出现。
解决办法
1.为项目引入Spring AOP
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.编写AOP代码
package com.mongo;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.framework.ReflectiveMethodInvocation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;
import java.lang.reflect.Field;
/** * 通过AOP操作,动态更改更改mongo的repository层mongoTemplate<br/>
* 以实现mongo分库
* * @author RangoLan
* @desciption
* @date Created in 2018/10/17 15:20
*/
@Aspect
@Component
public class RepositoryAop {
@Autowired
WebApplicationContext context;
@Around("execution(* com.mongo.basic..*.*(..))")
public Object setMongoOperations(ProceedingJoinPoint joinPoint) throws Throwable {
setMongoTemplate4Repository(joinPoint, (MongoTemplate) context.getBean(AdminConfiguration.MONGO_ADMIN));
return joinPoint.proceed();
}
private void setMongoTemplate4Repository(ProceedingJoinPoint joinPoint, MongoTemplate template) throws NoSuchFieldException, IllegalAccessException {
// 通过反射获取到target
Field methodInvocationField = joinPoint.getClass().getDeclaredField("methodInvocation");
methodInvocationField.setAccessible(true);
ReflectiveMethodInvocation o = (ReflectiveMethodInvocation) methodInvocationField.get(joinPoint);
Field targetField = o.getClass().getDeclaredField("target");
targetField.setAccessible(true);
Object target = targetField.get(o);
// 获得SimpleMongoRepository,并往里面填入指定mongoTemplate
Object singletonTarget = AopProxyUtils.getSingletonTarget(target);
Field mongoOperationsField = singletonTarget.getClass().getDeclaredField("mongoOperations");
mongoOperationsField.setAccessible(true);
mongoOperationsField.set(singletonTarget, template);
}
}
单元测试OK。
注意:
后记
基于AOP的方式能够实现Repository层的mongodb分库。然而在常规做法中,这种操作相对来说较为繁琐。因此,在构建项目时应当尽量合理规划结构安排。与在MongoTemplate bean配置basePackages时相比,AOP机制允许我们从方法层面进行更加细致的操作控制。当然这是AOP的主要优势所在。不过从数据库设计的角度来看,一个表或collection的所有操作不应该集中在同一个库中吗?
