Spring-data-mongodb如何连接到Mongo中的多个数据库?

28
我正在使用最新的spring-data-mongodb(1.1.0.M2)和最新的Mongo驱动程序(2.9.0-RC1)。 我有这样一种情况:多个客户端连接到我的应用程序,我希望在同一个Mongo服务器中为每个客户端提供自己的“模式/数据库”。 如果我直接使用驱动程序,这不是一项非常困难的任务:
Mongo mongo = new Mongo( new DBAddress( "localhost", 127017 ) );

DB client1DB = mongo.getDB( "client1" );
DBCollection client1TTestCollection = client1DB.getCollection( "test" );
long client1TestCollectionCount = client1TTestCollection.count();

DB client2DB = mongo.getDB( "client2" );
DBCollection client2TTestCollection = client2DB.getCollection( "test" );
long client2TestCollectionCount = client2TTestCollection.count();

看,很简单。但是spring-data-mongodb不允许轻松地使用多个数据库。设置到Mongo的首选方式是扩展AbstractMongoConfiguration类:
你会发现需要覆盖以下方法:
getDatabaseName()

这意味着它强制你使用一个数据库名称。然后,您构建的存储库接口在传递到SimpleMongoRepository类中的MongoTemplate中使用该数据库名称。

那么我应该把多个数据库名称放在哪里呢?我必须创建多个数据库名称、多个MongoTemplate(每个数据库名称一个)和多个其他配置类。但这仍然无法使我的存储库接口使用正确的模板。如果有人尝试过这样的事情,请告诉我。如果我找到了答案,我会在这里发布。

谢谢。


1
@sbzomm 我遇到了同样的情况,你找到解决方案了吗? - Sankar
尝试一下这种方法-https://blog.marcosbarbero.com/multiple-mongodb-connectors-in-spring-boot/。看起来相当干净和可扩展。 - Aditya
7个回答

14

这里有一篇文章链接,我认为它可能是您正在寻找的 http://michaelbarnesjr.wordpress.com/2012/01/19/spring-data-mongo/

关键是提供多个模板

为每个数据库配置一个模板。

<bean id="vehicleTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
    <constructor-arg ref="mongoConnection"/>
    <constructor-arg name="databaseName" value="vehicledatabase"/>
</bean>

为每个数据库配置模板。

<bean id="imageTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
        <constructor-arg ref="mongoConnection"/>
        <constructor-arg name="databaseName" value="imagedatabase"/>
</bean>

<bean id="vehicleTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
    <constructor-arg ref="mongoConnection"/>
    <constructor-arg name="databaseName" value="vehicledatabase"/>
</bean>

现在,你需要告诉Spring你的存储库在哪里以便它可以注入它们。它们必须都在同一个目录中。我试图让它们位于不同的子目录中,但并没有正常工作。所以它们都在存储库目录中。

<mongo:repositories base-package="my.package.repository">
    <mongo:repository id="imageRepository" mongo-template-ref="imageTemplate"/>
    <mongo:repository id="carRepository" mongo-template-ref="vehicleTemplate"/>
    <mongo:repository id="truckRepository" mongo-template-ref="vehicleTemplate"/>
</mongo:repositories>
每个仓库都是一个接口,写法如下(是的,你可以将它们留空):
@Repository
public interface ImageRepository extends MongoRepository<Image, String> {

}

@Repository
public interface TruckRepository extends MongoRepository<Truck, String> {

}

私有变量imageRepository的名称是集合!Image.java将保存到imagedb数据库中的图像集合中。

下面是如何查找插入删除记录的方法:

@Service
public class ImageService {

    @Autowired
    private ImageRepository imageRepository;
}

通过自动装配,您将变量名称与配置中的名称(id)匹配。


7
很遗憾,那不是我要找的。我看到过这样的实现,它确实很好用,但不适合我的目的。这种设置是针对特定数据库中的特定集合。我想要所有数据库中的所有集合。每个客户端都使用相同的模式,只是在不同的位置。 - sbzoom
还要注意自1.1版本以来,mongo:repository已不再存在。现在mongo-template-ref属性位于mongo:repositories级别上。 - Zarathustra
从Spring Data MongoDB 1.6.x开始,mongo:repository不再是mongo:repositories的子级。 - Titi Wangsa bin Damhore
1
@john,我该如何在Java注解Spring配置中引用monog-template? - vashishth
1
有没有人能提供一个使用Java配置和注解实现的示例?我似乎无法达到相同的行为。 - DaveStance
@TitiWangsabinDamhore 怎么修复它? - JaskeyLam

11
你可能想要子类化 SimpleMongoDbFactory 并制定默认返回的数据库,而不是使用多个 MongoTemplate。其中一种选择是使用线程局部变量来决定要使用的数据库。
可以这样做:
public class ThreadLocalDbNameMongoDbFactory extends SimpleMongoDbFactory {
    private static final ThreadLocal<String> dbName = new ThreadLocal<String>();
    private final String defaultName; // init in c'tor before calling super

    // omitted constructor for clarity

    public static void setDefaultNameForCurrentThread(String tlName) {
        dbName.set(tlName);
    }
    public static void clearDefaultNameForCurrentThread() {
        dbName.remove();
    }

    public DB getDb() {
        String tlName = dbName.get();
        return super.getDb(tlName != null ? tlName : defaultName);
    }
}

然后,你需要在继承自AbstractMongoConfiguration@Configuration类中覆盖mongoDBFactory()方法,如下所示:

@Bean
@Override
public MongoDbFactory mongoDbFactory() throws Exception {
  if (getUserCredentials() == null) {
      return new ThreadLocalDbNameMongoDbFactory(mongo(), getDatabaseName());
  } else {
      return new ThreadLocalDbNameMongoDbFactory(mongo(), getDatabaseName(), getUserCredentials());
  }
}

在您的客户端代码中(可能是ServletFilter或类似的内容),您需要调用: ThreadLocalDBNameMongoRepository.setDefaultNameForCurrentThread() 在执行任何Mongo工作之前,并在完成后使用以下方法将其重置: ThreadLocalDBNameMongoRepository.clearDefaultNameForCurrentThread()


1
SimpleMongoRepository没有getDb()方法。因此您无法重写它或调用super.getDb()。该方法被嵌入在MongoTemplate中。SimpleMongoRepository引用的是MongoOptions而不是MongoTemplate,因此您也无法在那里获取到getDb()。也许可以使用ThreadLocalMongoTemplate?我会继续研究。这是一条好路线 - 谢谢。 - sbzoom
你是对的 - 我在粘贴错误的类名时犯了一个错误。但本质与Oliver在他的评论中描述的相同。 - baja
谢谢这个例子。我很容易就让它工作了。有没有办法实现每个租户的集合方法?如果你有任何想法,请在这个线程上与我分享。我会非常感激! - beku8

9
经过了大量的研究和实验,我得出结论:目前的 spring-data-mongodb 项目还无法实现这个功能。我尝试了上面 baja 的方法,但遇到了一个具体的障碍。 MongoTemplate 在其构造函数中运行其 ensureIndexes() 方法。此方法调用数据库以确保数据库中存在注释索引。当 Spring 启动时,MongoTemplate 的构造函数被调用,因此我甚至没有机会设置 ThreadLocal 变量。我必须在 Spring 启动时已经有一个默认值,然后在请求到来时再进行更改。这是不允许的,因为我既不想要也没有默认数据库。
但并非一切都失去了希望。我们最初的计划是让每个客户端在自己的应用服务器上运行,指向 MongoDB 服务器上自己的数据库。然后,我们可以提供一个 -Dprovider= 系统变量,每个服务器只运行指向一个数据库。
我们被要求开发一个多租户应用程序,因此尝试使用ThreadLocal变量。但由于它没有起作用,我们能够以最初设计的方式运行应用程序。
我相信有一种方法可以使这一切都能正常工作,但需要比其他帖子中描述的更多的工作。您必须制作自己的RepositoryFactoryBean。以下是来自Spring Data MongoDB Reference Docs的示例。您仍然需要实现自己的MongoTemplate并延迟或删除ensureIndexes()调用。但您需要重写几个类,以确保调用您的MongoTemplate而不是Spring的MongoTemplate。换句话说,需要大量工作。我希望看到这些工作的完成或者甚至亲自去完成,只是我没有时间。
感谢回复。

最新版本有没有解决方案?我也遇到了同样的问题,ensureIndexes让我很头疼 :( - Srisudhir T
我查看了MongoTemplate的源代码,没有看到ensureIndexes() - 所以它可能有效。最了解情况的人是@Oliver Gierke,他也回答了这个问题 - 他是主要的开发者之一。 - sbzoom
最终找出了问题所在,我使用了Servlet 3.0初始化,但在创建工厂时没有在mongocontext中设置应用程序上下文。设置好之后,现在一切都很顺利。 - Srisudhir T
我创建了一个 Github 项目,解决了同样的问题,它能够在每个数据库中创建索引。https://github.com/Loki-Afro/multi-tenant-spring-mongodb - Zarathustra

4
要看的是MongoDbFactory接口。基本实现需要一个Mongo实例,并在整个应用程序生命周期内使用它。要实现每个线程(因此是每个请求)的数据库使用,您可能需要实现类似于AbstractRoutingDataSource的东西。这个想法基本上是你有一个模板方法,每次调用时都必须查找租户(我猜是ThreadLocal绑定),然后从预定义的一组实例中选择一个Mongo实例或一些自定义逻辑来为新租户提供新的实例。
请记住,MongoDbFactory通常通过getDb()方法使用。然而,MongoDB中有一些需要我们提供getDb(String name)的功能。 DBRef(在关系世界中类似于外键)可以指向完全不同的数据库中的文档。因此,如果您正在委托处理,请避免使用该功能(我认为指向另一个数据库的DBRef是唯一调用getDb(name)的地方),或者显式处理它。
从配置的角度来看,您可以完全覆盖mongoDbFactory(),也可以根本不扩展基类,并自己提供Java配置。

1
我在使用ThreadLocal和不使用之间犹豫不决。但可能不用。有时我想让ClientA从ClientB的数据库中读取一些记录。我会进行第二次查询并传递ClientB数据库的名称。我真正需要的是一个MongoRepository接口(和实现),它为每个查询添加“databaseName”。count() -> count(databaseName)。或者,我可以使用MongoTemplate(或MongoDbFactory)来实例化我的存储库,而不是@Autowired实例。这些都不是很理想。 - sbzoom
1
或者在MongoRepository(和SimpleMongoRepository)上添加getDB/setDB方法。然后我可以这样做:myRepository.setDB('name'); myRepository.findOne(id); 或者,更好的是,myRepository.setDB('name').findOne(id); 我会看看能做到什么。 - sbzoom
1
SimpleMongoRepository只有MongoOptions而没有MongoTemplate或MongoDbFactory。因此,在存储库中似乎没有简单的方法来获取DB,所有内容都被抽象化了。 - sbzoom
1
另外,我不想要多个Mongo实例。我只想要一个实例,里面有多个数据库。因此,我需要多个MongoTemplate。 - sbzoom
我已经很容易地让它工作了。有没有办法实现按租户的集合方法?如果您有任何想法,请在线程中与我分享。我会非常感激! - beku8
@sbzoom,你觉得每个数据库都有一个Spring上下文怎么样?虽然它不能完全满足你的需求(单个Mongo,多个数据库),但在其基础上进行一些额外的编程可以实现。 - milan

1

我使用Java配置来使用不同的数据库,以下是我的做法:

@Bean 
public MongoDbFactory mongoRestDbFactory() throws Exception { 
    MongoClientURI uri=new MongoClientURI(environment.getProperty("mongo.uri")); 
    return new SimpleMongoDbFactory(uri);
}

@Override
public String getDatabaseName() {
    return "rest";
}

@Override
public @Bean(name = "secondaryMongoTemplate") MongoTemplate mongoTemplate() throws Exception{ //hay que cambiar el nombre de los templates para que el contendor de beans sepa la diferencia  
    return new MongoTemplate(mongoRestDbFactory());    
}

另一个是这样的:
@Bean 
public MongoDbFactory restDbFactory() throws Exception {
    MongoClientURI uri = new MongoClientURI(environment.getProperty("mongo.urirestaurants")); 
    return new SimpleMongoDbFactory(uri);
}

@Override
public String getDatabaseName() {
    return "rest";
}

@Override
public @Bean(name = "primaryMongoTemplate") MongoTemplate mongoTemplate() throws Exception{ 
    return new MongoTemplate(restDbFactory());    
}

因此,当我需要更改数据库时,我只需选择要使用的配置即可。

2
你如何更改配置以使用? - s1moner3d
如果我使用CrudRepository,那么你的存储库怎么样?如何为不同的存储库注入不同的mongoTemplate? - JaskeyLam

0

使用Spring Boot V2.6.2的示例:

"application.yml"文件的内容:

spring:
  application:
    name: myApp
  autoconfigure:
  data:
    mongodb:
      host: localhost
      port: 27017
      database: FirstDatabase
    mongodbreference:
      host: localhost
      port: 27017
      database: SecondDatabase

在一个名为 "MultipleMongoProperties.java" 的类中:
package your.packagename;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@ConfigurationProperties(prefix = "spring.data")
public class MultipleMongoProperties {

    private MongoProperties mongodb = new MongoProperties();

    private MongoProperties mongodbreference = new MongoProperties();
}

最后是类"MultipleMongoConfig.java":
    package your.package;
    
    import com.mongodb.client.MongoClients;
    import lombok.RequiredArgsConstructor;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.boot.autoconfigure.mongo.MongoProperties;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import org.springframework.data.mongodb.MongoDatabaseFactory;
    import org.springframework.data.mongodb.core.MongoTemplate;
    import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
    
    @Configuration
    @RequiredArgsConstructor
    @EnableConfigurationProperties(MultipleMongoProperties.class)
    public class MultipleMongoConfig {
    
        private static final Logger LOG = LoggerFactory.getLogger(Multip

leMongoConfig.class);

    private final MultipleMongoProperties mongoProperties;

    private MongoProperties mongoDestination;

    @Bean("referenceMongoTemplate")
    @Primary
    public MongoTemplate referenceMongoTemplate() {

        return new MongoTemplate(referenceFactory(this.mongoProperties.getMongodbreference()));
    }

    @Bean("destinationMongoTemplate")
    public MongoTemplate destinationMongoTemplate() {
        return new MongoTemplate(destinationFactory(this.mongoProperties.getMongodb()));
    }

    public MongoDatabaseFactory referenceFactory(final MongoProperties mongo) {
        this.setUriToMongoProperties(mongo);

        return new SimpleMongoClientDatabaseFactory(MongoClients.create(mongo.getUri()), mongo.getDatabase());
    }

    public MongoDatabaseFactory destinationFactory(final MongoProperties mongo) {
        this.setUriToMongoProperties(mongo);
        return new SimpleMongoClientDatabaseFactory(MongoClients.create(mongo.getUri()), mongo.getDatabase());
    }

    private void setUriToMongoProperties(MongoProperties mongo) {
        mongo.setUri("mongodb://" + mongo.getUsername() + ":" + String.valueOf(mongo.getPassword()) + "@" + mongo.getHost() + ":" + mongo.getPort() + "/" + mongo.getAuthenticationDatabase());

    }

}

在另一个类中,你只需要实现以下内容:
package your.package;

import com.mongodb.bulk.BulkWriteResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Component;

@Component
public class CollectionRepositoryImpl implements CollectionsRepository {

    @Autowired
    @Qualifier("referenceMongoTemplate")
    private MongoTemplate referenceMongoTemplate;

    @Autowired
    @Qualifier("destinationMongoTemplate")
    private MongoTemplate destinationMongoTemplate;
...

-2
据我所知,您希望在运行时更灵活地更改当前的数据库。
我链接了一个实现简单多租户的项目。
它可以作为应用程序的起点。
它实现了SimpleMongoDbFactory并提供了一个自定义的getDB方法来解析在某个时刻使用的正确的数据库。它可以通过从SpringSession对象的HttpSession中检索数据库详细信息来进行改进,例如可以由Redis缓存。
要同时使用不同的mongoTemplates使用不同的数据库,可能需要将mongoDbFactory的范围更改为会话。
参考资料: multi-tenant-spring-mongodb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接