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
s (по одному на имя базы данных) и несколько других классов конфигурации. И это все еще не заставляет мои интерфейсы репозитория использовать правильный шаблон. Если кто-то пробовал такое, дайте мне знать. Если я это выясню, я опубликую ответ здесь.
Благодарю.
7 ответов
Итак, после долгих исследований и экспериментов, я пришел к выводу, что это еще не возможно с нынешним spring-data-mongodb
проект. Я попробовал метод Baja выше и столкнулся с определенным препятствием. MongoTemplate
запускает его ensureIndexes()
метод из его конструктора. Этот метод вызывает базу данных, чтобы убедиться, что аннотированные индексы существуют в базе данных. Конструктор для MongoTemplate
вызывается, когда Spring
запускается, поэтому у меня никогда не было возможности установить ThreadLocal
переменная. Я должен иметь значение по умолчанию, уже установленное, когда Spring
запускается, затем изменяет его при поступлении запроса. Это недопустимо, потому что я не хочу и у меня нет базы данных по умолчанию.
Не все было потеряно все же. Наш первоначальный план состоял в том, чтобы каждый клиент работал на своем собственном сервере приложений, указывая на его собственный MongoDB
база данных на MongoDB
сервер. Тогда мы можем предоставить -Dprovider=
системная переменная, и каждый сервер работает, указывая только на одну базу данных.
Нам было поручено подать заявку на нескольких арендаторов, поэтому была предпринята попытка ThreadLocal
переменная. Но так как это не сработало, мы смогли запустить приложение так, как мы изначально его проектировали.
Я верю, что есть способ сделать все это работающим, просто требуется больше, чем описано в других постах. Вы должны сделать свой собственный RepositoryFactoryBean
, Вот пример из справочных документов Spring Data MongoDB. Вам все равно придется реализовать свой собственный MongoTemplate
и задержать или удалить ensureIndexes()
вызов. Но вам придется переписать несколько классов, чтобы убедиться, MongoTemplate
называется вместо Spring's
, Другими словами, много работы. Работа, которую я хотел бы увидеть или даже выполнить, у меня просто не было времени.
Спасибо за ответы.
Вот ссылка на статью, я думаю, это то, что вы ищете 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) в вашей конфигурации.
Вы можете хотеть подкласс SimpleMongoDbFactory
и выработать стратегию того, как БД по умолчанию возвращается getDb
возвращается Один из вариантов - использовать локальные переменные потока, чтобы выбрать 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);
}
}
Затем переопределить 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()
после того, как вы сделали.
Место, на которое стоит посмотреть MongoDbFactory
интерфейс. Базовая реализация этого берет экземпляр Mongo и работает с ним в течение всего времени жизни приложения. Чтобы добиться использования базы данных для каждого потока (и, следовательно, для каждого запроса), вам, вероятно, придется реализовать что-то в духе AbstractRoutingDataSource. Идея заключается в том, что у вас есть шаблонный метод, который должен искать клиента на вызов (ThreadLocal
связан я думаю), а затем выберите Mongo
экземпляр из набора предопределенных или какая-то пользовательская логика для создания новой для нового арендатора и т. д.
Имейте в виду, что MongoDbFactory
обычно используется через getDb()
метод. Однако в MongoDB есть функции, которые требуют от нас getDb(String name)
, DBRef
s (например, внешний ключ в реляционном мире) может указывать на документы совершенно другой базы данных. Поэтому, если вы выполняете делегирование, либо избегайте использования этой функции (я думаю, что DBRef
указатели на другую БД являются единственными местами, вызывающими getDb(name)
) или явно обработать это.
С точки зрения конфигурации вы можете просто переопределить mongoDbFactory()
полностью или просто не расширять базовый класс и придумать свою собственную конфигурацию на основе Java.
Я использовал другую БД, используя 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());
}
Поэтому, когда мне нужно изменить базу данных, я только выбираю, какую конфигурацию использовать.
Пример с 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;
...
Насколько я понимаю, вы хотите больше гибкости в изменении текущего БД на лету.
Я связал проект, который реализует мультитенантность простым способом.
Это может быть использовано в качестве отправной точки для приложения.
Он реализует SimpleMongoDbFactory и предоставляет собственный метод getDB для разрешения правильного использования БД в определенный момент. Его можно улучшить многими способами, например, путем получения подробных данных о базе данных из объекта HttpSession из объекта SpringSession, который, например, может кэшироваться Redis.
Чтобы иметь разные mongoTemplates, использующие разные базы данных одновременно, возможно, измените область действия вашего mongoDbFactory на сеанс.
Рекомендации: