2012-08-22 51 views
14

我使用最新的spring-data-mongodb(1.1.0.M2)和最新的Mongo Driver(2.9.0-RC1)。我有一种情况,我有多个客户端连接到我的应用程序,我想在同一个Mongo服务器中为每个客户提供他们自己的“模式/数据库”。如果我直接使用驱动程序,这不是一项非常困难的任务:Spring-data-mongodb连接到一个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中使用该数据库名称。

在哪里我会粘住多个数据库名称?我必须制作多个数据库名称,多个MongoTempate(每个数据库名称一个)以及多个其他配置类。而这仍然没有让我的存储库接口使用正确的模板。如果有人尝试过这样的事情,请告诉我。如果我弄明白了,我会在这里发布答案。

谢谢。

回答

7

因此,经过大量的研究和实验,我得出结论,这还不可能与当前的spring-data-mongodb项目。我尝试了巴哈的方法,并遇到了特定的障碍。 MongoTemplate从其构造函数中运行其ensureIndexes()方法。此方法调用数据库以确保数据库中存在带注释的索引。当Spring启动时,MongoTemplate的构造函数被调用,所以我甚至没有机会设置ThreadLocal变量。我必须在Spring启动时设置一个默认值,然后在请求进入时对其进行更改。这是不允许的,因为我不想要,也没有默认数据库。

虽然没有丢失。我们最初的计划是让每个客户端运行在自己的应用程序服务器上,指向MongoDB服务器上自己的MongoDB数据库。然后我们可以提供一个-Dprovider=系统变量,每个服务器只运行一个数据库。

我们被指示有一个多租户应用程序,因此试图在ThreadLocal变量。但是由于它不起作用,我们能够按照我们最初设计的方式运行应用程序。

我相信有一种方法虽然使这所有的工作,它只是需要比其他职位所描述的更多。你必须自己制作RepositoryFactoryBean。以下是Spring Data MongoDB Reference Docs的示例。您仍然必须实施自己的MongoTemplate,并延迟或删除ensureIndexes()呼叫。但是你必须重写几个类,以确保你的MongoTemplate被调用,而不是Spring's。换句话说,很多工作。工作,我希望看到发生甚至做,我只是没有时间。

感谢您的回复。

+0

是否有任何解决方案与最新版本,我面临同样的问题,ensureIndexes正在杀死我:( –

+0

我看着'MongoTemplate'的源代码,并没有看到'ensureIndexes()' - 所以它可能工作,那些知道的人是@Oliver Gierke,他也发布了这个问题的答案 - 他是主要开发人员之一 – sbzoom

+0

终于明白了这个问题,我使用Servlet 3.0初始化,并且没有设置应用程序上下文mongocontext在设置之后,现在一切都很顺利 –

7

您可能需要子类SimpleMongoDbFactory并制定如何返回由getDb返回的默认DB。一种选择是使用线程局部变量来决定使用Db,而不是使用多个MongoTemplates。

事情是这样的:

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中或一些这样的),你在进行任何Mongo工作之前需要调用: ThreadLocalDBNameMongoRepository.setDefaultNameForCurrentThread() 并随后将其重置为: ThreadLocalDBNameMongoRepository.clearDefaultNameForCurrentThread() 完成后。

+1

的SimpleMongoRepository不具有getDb()方法。所以你不能覆盖它或者调用super.getDb()。该方法被埋在MongoTemplate中。 SimpleMongoRepository引用了MongoOptions而不是MongoTemplate,所以你也不能到getDB()。也许是一个ThreadLocalMongoTemplate?我会继续研究。这是一条很好的道路 - 谢谢。 – sbzoom

+0

你说得对 - 我在粘贴不正确的类名时犯了一个错误。但是奥利弗在评论中描述的本质是一样的。 – baja

+0

感谢这个例子。我的工作非常简单。有没有什么办法可以实现按租户方式收集。如果你有任何想法,请与我分享[this](http://stackoverflow.com/questions/15728413/collection-based-multi-tenancy-in-spring-data-mongo)线程。我会很感激! – beku8

3

需要注意的是MongoDbFactory接口。它的基本实现需要一个Mongo实例,并且在所有应用程序生命周期内都可以使用它。要实现每线程(因此每个请求)的数据库使用情况,您可能必须沿着AbstractRoutingDataSource的方向执行某些操作。这个想法是非常多的,你有一个模板方法,将不得不查找租户每个调用(我猜想ThreadLocal界限),然后从一组预定义的或一些自定义逻辑中选择一个Mongo实例,以提供一个新的一个新的房客等

请记住,MongoDbFactory通常使用通过getDb()方法。但是,MongoDB中有一些功能需要我们提供getDb(String name)。 (像关系世界中的外键)可以指向文件一个完全不同的数据库。因此,如果您正在执行委派,请避免使用该功能(我认为指向另一个数据库的DBRef是唯一调用getDb(name)的地方)或明确处理它。

从配置的角度来看,您可以完全覆盖mongoDbFactory()或完全不扩展基类,并提出自己的基于Java的配置。

+1

我在使用ThreadLocal或不是之间撕裂。但可能不会。我有时希望ClientA从ClientB的数据库中读取一些记录。我会进行第二次查询并传递ClientB数据库的名称。我真正需要的是一个MongoRepository接口(和实现),它为每个查询添加一个“databaseName”。 count() - > count(databaseName)。或者,也许不是我的仓库的@Autowired实例,我会用MongoTemplate(或MongoDbFactory)实例化它们。这些都没有听起来那么理想。 – sbzoom

+1

或者可能是MongoRepository(和SimpleMongoRepository)上的getDB/setDB方法。然后我可以这样做:myRepository.setDB('name'); myRepository.findOne(ID);或者,更好的是,myRepository.setDB('name')。findOne(id);我会看看我能解决什么问题。 – sbzoom

+1

SimpleMongoRepository只包含MongoOptions,不包含MongoTemplate或MongoDbFactory。所以似乎没有简单的方法来获取数据库在Repository中,它都是抽象的。 – sbzoom

13

下面是一篇文章,我认为一个链接是你在找什么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)。

+4

不幸的是,这不是我正在寻找的。我看到了这样的实现,它确实很好地工作。只是不适合我的目的。这种设置是如果你在某些数据库中有特定的集合。我想要所有数据库中的所有集合。每个客户端都获得相同的模式,只是在不同的位置。 – sbzoom

+0

另请注意,从1.1开始,mongo:repository'''不再存在。 '''mongo-template-ref'''属性现在在mongo:repositories'''级别。 – Zarathustra

+0

从spring数据mongodb 1.6.x开始,mongo:存储库不再是mongo的子代:存储库 –

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

所以,当我需要改变我的数据库,我只选择配置使用哪

+1

如何更改要使用的配置? – s1moner3d

+0

如果我使用CrudRepository,那么您的存储库如何注入不同的mongo模板到不同的回购 – Jaskey

-1

据我了解,你想要更多的灵活性,在动态更改当前分贝。

我联系,实现以简单的方式多租户的项目。

它可以作为一个起点,为应用程序。

它实现SimpleMongoDbFactory并提供定制getDB方法来解决正确的分贝在某一时刻使用。它可以通过多种方式进行改进,例如,通过从SpringSession对象的HttpSession中检索数据库详细信息,例如Redis可以对其进行缓存。

有不同的mongoTemplates在同一时间使用不同的DBS,也许你mongoDbFactory的范围更改为会议。

参考文献:

multi-tenant-spring-mongodb