Spring-data-mongodb подключается к нескольким базам данных в одном экземпляре Mongo

Я использую последнюю версию 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()

Таким образом, это заставляет вас использовать одно имя базы данных. Интерфейсы репозитория, которые вы затем создаете, используют это имя базы данных внутри MongoTemplate, которое передается в класс SimpleMongoRepository.

Куда бы я вставил несколько имен баз данных? Мне нужно создать несколько имен баз данных, несколько MongoTempate (по одному на имя базы данных) и несколько других классов конфигурации. И это все еще не заставляет интерфейсы моего репозитория использовать правильный шаблон. Если кто-то пробовал подобное, сообщите мне. Если разберусь, напишу ответ здесь.

Спасибо.


person sbzoom    schedule 22.08.2012    source источник
comment
@sbzomm У меня такой же сценарий, вы нашли решение?   -  person Sankar    schedule 16.04.2018
comment
Попробуйте этот подход — blog.marcosbarbero.com/. Выглядит довольно чистым и расширяемым.   -  person Aditya    schedule 31.07.2019


Ответы (6)


Вот ссылка на статью, я думаю, это то, что вы ищете 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;
}

С помощью Autowiring вы сопоставляете имя переменной с именем (id) в вашей конфигурации.

person john    schedule 31.08.2012
comment
к сожалению, это не то, что я ищу. Я видел такую ​​реализацию, и она прекрасно работает. Только не для моих целей. Это настройка, если у вас есть определенные коллекции в определенных базах данных. Мне нужны все коллекции во всех базах данных. Каждый клиент получает одну и ту же схему, только в разных местах. - person sbzoom; 04.09.2012
comment
Также обратите внимание, что mongo:repository больше не существует с версии 1.1. Атрибут mongo-template-ref теперь находится на уровне mongo:repositories. - person Zarathustra; 14.05.2014
comment
с весенних данных mongodb 1.6.x, mongo: repository больше не является дочерним элементом mongo: repositories - person Titi Wangsa bin Damhore; 30.01.2015
comment
@john, как я могу ссылаться на моног-шаблон, используя конфигурацию пружины аннотаций Java. - person vashishth; 19.11.2015
comment
Есть ли у кого-нибудь пример того, как эта реализация может работать с использованием конфигураций и аннотаций Java? Кажется, я не могу добиться такого же поведения. - person DaveStance; 29.06.2016
comment
@TitiWangsabinDamhore, как это исправить?? - person JaskeyLam; 03.08.2017

Итак, после долгих исследований и экспериментов я пришел к выводу, что это еще не возможно с текущим проектом spring-data-mongodb. Я попробовал метод бахи выше и столкнулся с определенным препятствием. MongoTemplate запускает свой метод ensureIndexes() из своего конструктора. Этот метод вызывает базу данных, чтобы убедиться, что в базе данных существуют аннотированные индексы. Конструктор для MongoTemplate вызывается при запуске Spring, поэтому у меня даже нет возможности установить переменную ThreadLocal. Я должен иметь уже установленную базу данных по умолчанию при запуске Spring, а затем изменять ее при поступлении запроса. Это недопустимо, потому что я не хочу и не имею базу данных по умолчанию.

Однако не все было потеряно. Наш первоначальный план состоял в том, чтобы каждый клиент работал на своем собственном сервере приложений, указывающем на свою собственную базу данных MongoDB на сервере MongoDB. Затем мы можем предоставить системную переменную -Dprovider=, и каждый сервер будет работать, указывая только на одну базу данных.

Нам было приказано иметь мультитенантное приложение, отсюда и попытка использовать переменную ThreadLocal. Но так как это не сработало, мы смогли запустить приложение так, как мы его изначально разработали.

Я считаю, что есть способ заставить все это работать, просто это требует больше, чем описано в других постах. Вы должны сделать свой собственный RepositoryFactoryBean. Вот пример из Справочные документы Spring Data MongoDB. Вам все равно придется реализовать свой собственный MongoTemplate и отложить или удалить вызов ensureIndexes(). Но вам придется переписать несколько классов, чтобы убедиться, что ваш MongoTemplate вызывается вместо Spring's. Другими словами, много работы. Работа, которую я хотел бы увидеть или даже сделать, у меня просто не было времени.

Спасибо за ответы.

person sbzoom    schedule 09.10.2012
comment
Есть ли какое-нибудь решение с последней версией, я столкнулся с той же проблемой, sureIndexes меня убивает :( - person Srisudhir T; 02.01.2014
comment
Я посмотрел исходный код MongoTemplate и больше не видел ensureIndexes() - так что может сработать. Человек, который мог бы знать, — это @Oliver Gierke, который также опубликовал ответ на этот вопрос — он один из основных разработчиков. - person sbzoom; 03.01.2014
comment
Наконец-то разобрался с проблемой, я использовал инициализацию Servlet 3.0 и не установил контекст приложения в монгоконтексте при создании фабрики, после настройки, теперь все гладко - person Srisudhir T; 10.01.2014
comment
Я создал проект github, который решает ту же проблему, он может создавать индексы в каждой базе данных. github.com/Loki-Afro/multi-tenant-spring-mongodb - person Zarathustra; 13.06.2014

Вы можете создать подкласс SimpleMongoDbFactory и разработать стратегию возврата базы данных по умолчанию, возвращаемой getDb. Один из вариантов — использовать локальные переменные потока для выбора используемой базы данных вместо использования нескольких шаблонов 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);
    }
}

Затем переопределите mongoDBFactory() в своем классе @Configuration, который наследуется от AbstractMongoConfiguration, вот так:

@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() после того, как вы закончите.

person baja    schedule 22.08.2012
comment
SimpleMongoRepository не имеет метода getDb(). Таким образом, вы не можете переопределить его или вызвать super.getDb(). Этот метод скрыт в шаблоне MongoTemplate. В SimpleMongoRepository есть ссылка на MongoOptions, а не на MongoTemplate, поэтому вы также не можете получить доступ к getDB(). Может быть, ThreadLocalMongoTemplate? Я буду продолжать исследования. Это хороший путь, хотя - спасибо. - person sbzoom; 23.08.2012
comment
Вы правы - я ошибся, вставив неправильное имя класса. Но суть та же, что описывает Оливер в своем комментарии. - person baja; 23.08.2012
comment
Спасибо за этот пример. У меня это работает очень легко. Есть ли способ реализовать сбор для каждого арендатора. Если у вас есть идеи, поделитесь со мной на этом нить. Я был бы очень признателен! - person beku8; 31.03.2013

Место, на которое стоит обратить внимание, — это интерфейс MongoDbFactory. Базовая реализация этого использует экземпляр Mongo и работает с ним на протяжении всего жизненного цикла приложения. Чтобы добиться использования базы данных для каждого потока (и, следовательно, для каждого запроса), вам, вероятно, придется реализовать что-то вроде строк AbstractRoutingDataSource. Идея в значительной степени состоит в том, что у вас есть шаблонный метод, который должен будет искать арендатора для каждого вызова (я думаю, ThreadLocal связанный), а затем выбирать Mongo экземпляр из набора предопределенных или некоторую пользовательскую логику, чтобы придумать новый для новый арендатор и т.д.

Имейте в виду, что MongoDbFactory обычно используется с помощью метода getDb(). Однако в MongoDB есть функции, которые требуют от нас предоставления файла getDb(String name). DBRefs (что-то вроде внешнего ключа в реляционном мире) может указывать на документы совершенно другой базы данных. Поэтому, если вы выполняете делегирование, либо избегайте использования этой функции (я думаю, что DBRefs, указывающие на другую БД, являются единственными местами, вызывающими getDb(name)), либо явно обрабатывайте ее.

С точки зрения конфигурации вы можете либо просто полностью переопределить mongoDbFactory(), либо вообще не расширять базовый класс и придумать собственную конфигурацию на основе Java.

person Oliver Drotbohm    schedule 23.08.2012
comment
Я разрываюсь между использованием ThreadLocal или нет. Но, вероятно, нет. Иногда я хочу, чтобы ClientA прочитал некоторые записи из базы данных ClientB. Я бы сделал второй запрос и передал бы имя базы данных ClientB. Что мне действительно нужно, так это интерфейс MongoRepository (и реализация), который добавляет имя базы данных к каждому запросу. count() -> count(имя_базы_данных). Или, может быть, вместо @Autowired экземпляров моих репозиториев я бы создал их с помощью MongoTemplate (или MongoDbFactory). Ни один из них не звучит так идеально. - person sbzoom; 23.08.2012
comment
Или, может быть, метод getDB/setDB в MongoRepository (и SimpleMongoRepository). Тогда я мог бы сделать: myRepository.setDB('name'); мойРепозиторий.findOne(id); Или, что еще лучше, myRepository.setDB('name').findOne(id); Я посмотрю, что у меня получится. - person sbzoom; 23.08.2012
comment
В SimpleMongoRepository есть только MongoOptions, а не MongoTemplate или MongoDbFactory. Таким образом, кажется, нет простого способа получить БД в репозитории, все это абстрагировано. - person sbzoom; 23.08.2012
comment
Кроме того, мне не нужны несколько экземпляров Mongo. Мне нужна только одна с несколькими базами данных. Поэтому я хочу несколько MongoTemplates. - person sbzoom; 23.08.2012
comment
У меня это работает очень легко. Есть ли способ реализовать сбор для каждого арендатора. Если у вас есть идеи, поделитесь со мной на этом нить. Я был бы очень признателен! - person beku8; 31.03.2013
comment
@sbzoom как насчет нескольких контекстов Spring, по одному для каждой базы данных? он не делает именно то, что вы хотите (один Mongo, несколько Dbs), но будет работать с дополнительной сантехникой. - person milan; 29.07.2013

Я использовал другую БД с помощью java Config, вот как я это сделал:

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

Поэтому, когда мне нужно изменить свою базу данных, я только выбираю, какую конфигурацию использовать.

person user3272931    schedule 24.09.2015
comment
Как изменить конфигурацию для использования? - person s1moner3d; 26.01.2016
comment
как насчет ваших репозиториев, если я использую CrudRepository? как внедрить разные шаблоны mongoTemplate в разные репо - person JaskeyLam; 04.08.2017

Насколько я понимаю, вы хотите больше гибкости в изменении текущей базы данных на лету.

Я связал проект, который реализует мультитенантность простым способом.

Его можно использовать в качестве отправной точки для приложения.

Он реализует SimpleMongoDbFactory и предоставляет пользовательский метод getDB для разрешения правильной базы данных для использования в определенный момент. Его можно улучшить разными способами, например, путем получения сведений о базе данных из HttpSession из объекта SpringSession, который, например, может кэшироваться Redis.

Чтобы иметь разные mongoTemplates, использующие разные базы данных одновременно, возможно, измените область действия вашего mongoDbFactory на сеанс.

Использованная литература:

multi-tenant-spring-mongodb

person Mario Paulo    schedule 27.03.2017