Spring boot MongoDB多数据源,MongoRepository实现

背景

        最近项目中有需求,需要多个mongo库,分割数据。网上有很多文章可参考,其原理是:在Spring 容器中实例化多个MongoTemplate,代码示例:

@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());
    }
}

使用方式

1.使用时通过@Autowired 和@Qualifier注入MongoTemplate实例以操作不同的mongo数据库(PS:可使用@Resource注解,引入MongoTemplate 并为其实例命名为所需的已配置的bean名称,而引入相对应的MongoTemplate实例)

2.通过MongoTemplate示例配置的basePackages指定继承了MongoRepository的model接口,在操作数据库时,使用所配置的MongoTemplate实例,以操作不同的数据库。

这是最常见的Springboot Mongo多数据配置方法,但是项目中主项目因引入的子项目里已有配置,导致在主项目中MongoTemplate配置无效,MongoRepository无法按配置的basePackages路径使用对应的MongoTemplate操作mongo库

分析

         前述的实现mongo多数据库,核心是操作不同的MongoTemplate。除了直接使用MongoTemplate,我们通常会写一个接口继承MongoRepository接口,并且不需要实现该接口就可操作数据库。实际上在使用继承的MongoRepository接口的业务接口访问mongo时,使用的是SpringData的SimpleMongoRepository类的实例来操作数据库,而SimpleMongoRepository的一个非常重要的Filed是MongoOperations接口,而MongoTemplate正是MongoOperations的实现。SimpleMongoRepository也正是使用MongoOperations操作mongo数据库的。

思路

      在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。

注意: 由于SimpleMongoRepository在Spring容器中为单例,aop切点代码执行后,SimpleMongoRepository的MongoOperations已更改,其他未配置切点的Repository在操作mongo时,数据库会混乱。所以应该在AOP中明确配置各Repository所使用的MongoTemplate。

 

后记

         AOP方式实现,可实现Repository层的mongodb分库,但是相较于常规做法,还是比较麻烦一点,建议构建项目时结构应尽量合理些。相较于配置MongoTemplate bean时指定basePackages,AOP可做到方法级控制,当然这是AOP的优点,但是对于数据库来说,一个表或者collection的所有操作不该是在同一个库么?

 

 

 

 


版权声明:本文为qq_35124119原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。