首先,免责声明:如果您正在编写微服务 (每个人现在都对吗?)并希望它是惯用的 ,那么通常不会在其中使用几个不同的数据源。

图片取自Pixabay© https: //pixabay.com/illustrations/software-binary-system-1-0-binary-557616/

为什么? 好吧,按照定义,微服务应该松散耦合,以便它们可以独立。 将多个微服务写入同一个数据库确实违反了这一原则,因为这意味着您的数据可以由几个独立的参与者以可能以不同的方式进行更改 ,这使得谈论数据一致性确实非常困难,而且,您很难说这些服务是独立的,因为它们至少具有它们共同依赖的一件事:共享(并且可能是固定的)数据。 因此,有一种称为数据库每个服务的设计模式,旨在通过对每个数据库实施一个服务来解决此问题。 这意味着每个微服务都充当客户端与其数据源之间的中介,并且只能通过该服务提供的接口来更改数据

但是,每个数据库一项服务等于一个服务一项数据库吗? 不,不是。 如果您考虑一下,那并不是一回事。

这意味着,如果我们有几个只能由一个微服务访问的数据库,并且通过该服务的接口实现了对这些数据库的任何外部访问,那么仍然可以认为该服务是惯用的。 它仍然是每个数据库一项服务,尽管不是每个服务一项数据库。

另外,也许您根本不关心微服务的惯用性。 这也是一个选择。 (不过这将取决于您的良心。)

那么,何时会有几个数据库要从同一服务访问? 我可以想到不同的选择:

  • 数据太大,无法存放在一个数据库中。
  • 您将数据库用作命名空间,以仅分隔属于不同域或功能区域的不同数据。
  • 您需要对数据库的不同访问权限-也许其中一个是关键任务,因此您将其置于各种安全层的后面,而另一个则不是那么重要,也不需要这种保护。
  • 这些数据库位于不同的区域,因为它们是由不同地方的人写入的,但需要从中央位置读取(反之亦然);
  • 真的,所有其他一切都导致了这种情况,您只需要忍受它。

如果您的应用程序是Spring Boot应用程序,并且您将Mongo用作数据库,那么最简单的方法就是使用Spring Data Repositories 。 您只需为mongo入门数据设置依赖项(我们将在此处以Gradle项目为例)。

dependencies {implementation("org.springframework.boot:spring-boot-starter-data-mongodb")implementation("org.springframework.boot:spring-boot-starter-web")implementation("com.fasterxml.jackson.module:jackson-module-kotlin")implementation("org.jetbrains.kotlin:kotlin-reflect")implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")testImplementation("org.springframework.boot:spring-boot-starter-test")
}

实际上,我们是使用Spring Initializer生成此示例项目的,因为这是开始基于Spring的新示例的最简单方法。 我们刚刚在生成器设置中选择了Kotlin和Gradle,并添加了Spring Web Starter和Spring Data MongoDB作为依赖项。 我们称这个项目为multimongo。

当我们创建一个项目并下载源代码时,我们可以看到Spring默认情况下创建了一个application.properties文件。 我更喜欢yaml ,所以我们将其重命名为application.yml并完成它。

所以。 我们如何使用Spring Data设置对默认mongo数据库的访问权限? 没什么容易的。 这就是application.yml

# possible MongoProperties
# spring.data.mongodb.authentication-database= # Authentication database name.
# spring.data.mongodb.database= # Database name.
# spring.data.mongodb.field-naming-strategy= # Fully qualified name of the FieldNamingStrategy to use.
# spring.data.mongodb.grid-fs-database= # GridFS database name.
# spring.data.mongodb.host= # Mongo server host. Cannot be set with URI.
# spring.data.mongodb.password= # Login password of the mongo server. Cannot be set with URI.
# spring.data.mongodb.port= # Mongo server port. Cannot be set with URI.
# spring.data.mongodb.repositories.type=auto # Type of Mongo repositories to enable.
# spring.data.mongodb.uri=mongodb://localhost/test # Mongo database URI. Cannot be set with host, port and credentials.
# spring.data.mongodb.username= # Login user of the mongo server. Cannot be set with URI.spring:data:mongodb:uri: mongodb://localhost:27017database: multimongo-core

现在,让我们想象一下一个非常简单而愚蠢的数据拆分案例。 假设我们有一个core数据库,用于存储我们的网上商店的产品。 然后,我们获得了有关产品价格的数据; 此数据不需要任何访问限制,因为网络上的任何用户都可以看到价格,因此我们将其称为external 。 但是,我们还有价格历史记录,可用于分析目的。 这是有限的访问信息,所以我们说,好的,它进入一个单独的数据库,我们将对其进行保护并调用internal

显然,就我而言,所有这些都仍在localhost上,并且不受保护,但是请允许我,这只是一个示例。

# Predefined spring data properties don't help us anymore.
# Therefore, we're creating our own configuration for the additional mongo instances.additional-db:internal:uri: mongodb://localhost:27017database: multimongo-internalexternal:uri: mongodb://localhost:27017database: multimongo-external

我们还将创建三个不同的目录,以将与数据访问相关的代码保留在其中: data.coredata.externaldata.internal


我们的Product.kt保留产品的实体和存储库, ProductPrice.ktProductPriceHistory.kt代表产品的当前价格和历史价格。 实体和存储库非常基础。

@Document
data class Product(@Idval id: String? = null,val sku: String,val name: String
)interface ProductRepository : MongoRepository<Product, String>
@Document(collection = "productPrice")
data class ProductPrice(@Idval id: String? = null,val sku: String,val price: Double
)interface ProductPriceRepository : MongoRepository<ProductPrice, String>
@Document(collection = "priceHistory")
data class PriceHistory(@Idval id: String? = null,val sku: String,val prices: MutableList<PriceEntry> = mutableListOf()
)data class PriceEntry(val price: Double,val expired: Date? = null
)interface PriceHistoryRepository : MongoRepository<PriceHistory, String>

现在,让我们为default mongo创建配置。

@Configuration
@EnableMongoRepositories(basePackages = ["com.example.multimongo.data.core"])
@Import(value = [MongoAutoConfiguration::class])
class CoreMongoConfiguration {@Beanfun mongoTemplate(mongoDbFactory: MongoDbFactory): MongoTemplate {return MongoTemplate(mongoDbFactory)}
}

我们在这里使用MongoAutoConfiguration类创建默认的mongo客户端实例。 但是,我们仍然需要一个明确定义的MongoTemplate bean。

如您所见, core配置仅扫描core目录。 这实际上是一切的关键:我们需要将我们的存储库放在不同的目录中,并且这些存储库将由不同的mongo模板进行扫描。 因此,让我们创建那些附加的mongo模板。 我们将使用一个基类,该基类将保留一些共享功能,我们将重复使用这些功能来创建mongo客户端。

@Configuration
class ExtraMongoConfiguration {val uri: String? = nullval host: String? = nullval port: Int? = 0val database: String? = null/*** Method that creates MongoClient*/private val mongoClient: MongoClientget() {if (uri != null && !uri.isNullOrEmpty()) {return MongoClient(MongoClientURI(uri!!))}return MongoClient(host!!, port!!)}/*** Factory method to create the MongoTemplate*/protected fun mongoTemplate(): MongoTemplate {val factory = SimpleMongoDbFactory(mongoClient, database!!)return MongoTemplate(factory)}
}

然后,最后,我们创建两个配置以容纳externalinternal数据库的mongo模板实例。

@EnableMongoRepositories(basePackages = ["com.example.multimongo.data.external"],mongoTemplateRef = "externalMongoTemplate")
@Configuration
class ExternalDatabaseConfiguration : ExtraMongoConfiguration() {@Value("\${additional-db.external.uri:}")override val uri: String? = null@Value("\${additional-db.external.host:}")override val host: String? = null@Value("\${additional-db.external.port:0}")override val port: Int? = 0@Value("\${additional-db.external.database:}")override val database: String? = null@Bean("externalMongoTemplate")fun externalMongoTemplate(): MongoTemplate = mongoTemplate()
}@EnableMongoRepositories(basePackages = ["com.example.multimongo.data.internal"],mongoTemplateRef = "internalMongoTemplate")
@Configuration
class InternalDatabaseConfiguration : ExtraMongoConfiguration() {@Value("\${additional-db.internal.uri:}")override val uri: String? = null@Value("\${additional-db.internal.host:}")override val host: String? = null@Value("\${additional-db.internal.port:0}")override val port: Int? = 0@Value("\${additional-db.internal.database:}")override val database: String? = null@Bean("internalMongoTemplate")fun internalMongoTemplate(): MongoTemplate = mongoTemplate()
}

因此,我们现在有三个mongo模板bean,它们由mongoTemplate()externalMongoTemplate()internalMongoTemplate()在三种不同的配置中创建。 这些配置扫描不同的目录,并通过@EnableMongoRepositories批注中的直接引用使用这些不同的mongo模板@EnableMongoRepositories这意味着它们将使用创建的bean。 春天没有问题。 依存关系将以正确的顺序解决。

那么,我们如何检查一切正常? 还有一个步骤需要完成:我们需要初始化一些数据,然后从数据库中获取数据。

由于这只是一个示例,因此我们将在应用程序启动时立即创建一些非常基本的数据,以确保它们在那里。 我们将为此使用ApplicationListener 。

@Component
class DataInitializer(val productRepo: ProductRepository,val priceRepo: ProductPriceRepository,val priceHistoryRepo: PriceHistoryRepository
) : ApplicationListener<ContextStartedEvent> {override fun onApplicationEvent(event: ContextStartedEvent) {// clean upproductRepo.deleteAll()priceRepo.deleteAll()priceHistoryRepo.deleteAll()val p1 = productRepo.save(Product(sku = "123", name = "Toy Horse"))val p2 = productRepo.save(Product(sku = "456", name = "Real Horse"))val h1 = PriceHistory(sku = p1.sku)val h2 = PriceHistory(sku = p2.sku)for (i in 5 downTo 1) {if (i == 5) {// current pricepriceRepo.save(ProductPrice(sku = p1.sku, price = i.toDouble()))priceRepo.save(ProductPrice(sku = p2.sku, price = (i * 2).toDouble()))// current price historyh1.prices.add(PriceEntry(price = i.toDouble()))h2.prices.add(PriceEntry(price = (i * 2).toDouble()))} else {// previous priceval expiredDate = Date(ZonedDateTime.now().minusMonths(i.toLong()).toInstant().toEpochMilli())h1.prices.add(PriceEntry(price = i.toDouble(), expired = expiredDate))h2.prices.add(PriceEntry(price = (i * 2).toDouble(), expired = expiredDate))}}priceHistoryRepo.saveAll(listOf(h1, h2))}
}

我们如何检查数据是否已保存到数据库? 由于它是一个Web应用程序,因此我们将在REST控制器中公开数据。

@RestController
@RequestMapping("/api")
class ProductResource(val productRepo: ProductRepository,val priceRepo: ProductPriceRepository,val priceHistoryRepo: PriceHistoryRepository
) {@GetMapping("/product")fun getProducts(): List<Product> = productRepo.findAll()@GetMapping("/price")fun getPrices(): List<ProductPrice> = priceRepo.findAll()@GetMapping("/priceHistory")fun getPricesHistory(): List<PriceHistory> = priceHistoryRepo.findAll()
}

REST控制器只是使用我们的存储库来调用findAll()方法。 我们没有对数据转换做任何事情,我们没有分页或排序,我们只是想看看有什么东西。 最后,可以启动应用程序,然后看看会发生什么。

[{"id": "5d5e64d80a986d381a8af4ce","name": "Toy Horse","sku": "123"},{"id": "5d5e64d80a986d381a8af4cf","name": "Real Horse","sku": "456"}
]

是的,我们创建了两个产品! 我们可以看到Mongo在保存时为其分配了自动生成的ID,我们仅定义了名称和伪SKU代码。

我们还可以在http:// localhost:8080 / api / price和http:// localhost:8080 / api / priceHistory上检查数据 ,并确保是的,实际上,这些实体也确实已创建。 我不会在此处粘贴此JSON,因为它并不相关。

但是,我们如何确保数据确实已保存到其他数据库(或从中读取)? 为此,我们可以使用任何允许我们连接到本地mongo实例的mongo客户端应用程序(我正在使用mongo的官方工具-MongoDB Compass )。


让我们检查保持当前价格的数据库中的内容。


如果我们想做对的事情(实际上不是所有的事,我们也可以使用集成测试来检查数据,而不是手动处理)(实际上不是所有的事情;我们需要使用嵌入式mongo数据库进行测试,但是这里我们将跳过这一部分)不会使教程太复杂)。 为此 ,我们将利用spring-test库中的MockMvc 。

<

@RunWith(SpringRunner::class)
@SpringBootTest
class MultimongoApplicationTests {@Autowiredprivate val productRepo: ProductRepository? = null@Autowiredprivate val priceRepo: ProductPriceRepository? = null@Autowiredprivate val priceHistoryRepo: PriceHistoryRepository? = null@Autowiredprivate val initializer: DataInitializer? = null@Autowiredprivate val context: ApplicationContext? = nullprivate var mvc: MockMvc? = null@Beforefun setUp() {val resource = ProductResource(productRepo!!,priceRepo!!,priceHistoryRepo!!)this.mvc = MockMvcBuilders.standaloneSetup(resource).build()initializer!!.onApplicationEvent(ContextStartedEvent(context!!))}@Testfun productsCreated() {mvc!!.perform(get(“/api/product”)).andExpect(status().isOk).andDo {println(it.response.contentAsString)}.andExpect(jsonPath(“$.[*].sku”).isArray).andExpect(jsonPath(“$.[*].sku”).value(hasItems(“123”, “456”)))}@Testfun pricesCreated() {mvc!!.perform(get(“/api/price”)).andExpect(status().isOk).andDo {println(it.response.contentAsString)}.andExpect(jsonPath(“$.[*].sku”).isArray).andExpect(jsonPath(“$.[*].sku”).value(hasItems(“123”, “456”))).andExpect(jsonPath(“$.[0].price”).value(5.0)).andExpect(jsonPath(“$.[1].price”).value(10.0))}@Testfun pricesHistoryCreated() {mvc!!.perform(get(“/api/priceHistory”)).andExpect(status().isOk).andDo {println(it.response.contentAsString)}.andExpect(jsonPath(“$.[*].sku”).isArray).andExpect(jsonPath(“$.[*].sku”).value(hasItems(“123”, “456”))).andExpect(jsonPath(“$.[0].prices.[*].price”).value(hasItems(5.0, 4.0, 3.0, 2.0, 1.0))).andExpect(jsonPath(“$.[1].prices.[*].price”).value(hasItems(10.0, 8.0, 6.0, 4.0, 2.0)))}
}

你可以找到完整的工作示例这里在我的github回购。 希望这可以帮助您解决在一个Spring Boot Web应用程序中使用多个mongo实例的问题! 这不是一个难题,但也不是一件容易的事。

当我在网上查看其他示例时,我还阅读了这篇文章 (Azadi Bogolubov 撰写的 “ Spring Data Configuration:Multiple Mongo Databases” ),它相当不错而且很全面。 但是,它不太适合我的情况,因为它完全覆盖了自动mongo配置。 另一方面,我仍然希望将其保留在我的默认数据库中,而不是其他数据库。 但是该文章中的方法基于相同的原理,即使用不同的mongo模板扫描不同的存储库

只是,使用默认配置,例如,一旦发生某些更改并且所有数据再次进入同一数据库,您就可以轻松摆脱多余的类。

然后,您可以轻松清除非默认配置,但仍保留默认配置,仅更改其扫描范围。 该应用程序仍将继续正常运行。 但是这两种方式都是完全有效的

本文也在此处的 Medium中发布。

翻译自: https://www.javacodegeeks.com/2019/09/spring-application-multiple-mongo-repositories-kotlin.html

操作方法:具有多个Mongo存储库和Kotlin的Spring Boot 2 Web应用程序相关推荐

  1. 定制Spring Data JPA存储库

    Spring Data是一个非常方便的库. 但是,由于该项目是一个相当新的项目,因此功能不佳. 默认情况下,Spring Data JPA将基于SimpleJpaRepository提供DAO的实现. ...

  2. 使用Spring Data REST将Spring Data JPA存储库导出为REST服务

    Spring Data模块提供了各种模块,以统一的方式处理各种类型的数据源,如RDBMS,NOSQL存储等. 在我以前的文章SpringMVC4 + Spring Data JPA +使用JavaCo ...

  3. Spring Data Solr教程:向所有存储库添加自定义方法

    如果我们在现实生活中的软件项目中使用Spring Data Solr,很可能我们迟早会遇到一个要求,该要求指出我们的应用程序必须能够与本地Solr服务器和SolrCloud进行通信 . 目前,满足此要 ...

  4. 自定义Spring Data JPA存储库

    Spring Data是一个非常方便的库. 但是,由于该项目是一个相当新的项目,因此功能不佳. 默认情况下,Spring Data JPA将基于SimpleJpaRepository提供DAO的实现. ...

  5. github组织存储库使用_为什么我不使用您的GitHub存储库

    github组织存储库使用 by Sam Westreich, PhD 由Sam Westreich博士 为什么我不使用您的GitHub存储库 (Why I'm not using your GitH ...

  6. Day08、BeautifulSoup解析库,MongoDB存储库,requests-html请求库

    一.解析库之bs4 ''' pip3 install beautifulsoup4 # 安装bs4 pip3 install lxml # 下载lxml解析器 ''' html_doc = " ...

  7. CodeGen准备存储库

    CodeGen准备存储库 CodeGen几乎总是与提供用于生成源文件的元数据的存储库结构一起使用,并且许多令牌需要使用存储库结构. 基本要求是有一个结构定义,并且该结构定义包含一个或多个字段定义.有些 ...

  8. 注意!今日起 GitHub 新建存储库的默认分支就不叫“master”了!

    整理 | 郑丽媛 头图 | CSDN下载自东方IC GitHub 宣布,自2020年10月1日起,在 GitHub 平台上创建的所有源代码存储库都将默认命名为 main ,而非原本的 master . ...

  9. 如何在GitHub上重命名存储库?

    本文翻译自:How do I rename a repository on GitHub? I wanted to rename one of my repositories on GitHub, b ...

最新文章

  1. 卷积Strassen算法
  2. oracle+标记要,oracle ORA-00031:session marked for kill(标记要终止的会话)解决方法
  3. 谷歌服务器——为什么选择Jetty?
  4. 一粒沙子变成芯片的全过程
  5. Total Physical Response TPR
  6. VS2015 C#利用QrCodeNet生成QR Code
  7. Android Audio打开输出设备流程(十五)
  8. java后台数据传到前台的流程_java serlve后台数据传到前台
  9. 堰流实验报告思考题_水力学思考题 -
  10. 1138: C语言合法标识符 C语言
  11. 百万在线:大型游戏服务端开发
  12. PHP 无限极分类下拉列表实现
  13. TLE两行数与轨道六根数转换
  14. 人一般长到几岁才会停止长高?
  15. 计算机组成原理oe表示什么意思,计算机组成原理课后习题答案解析
  16. Web安全学习笔记二 计算机网络与协议
  17. Debian与Ubuntu到底有什么不同,应该如何选择?
  18. 在线压缩转换文件的软件
  19. 寄存器与锁存器的区别
  20. Android终端Termux安装Scrapy

热门文章

  1. 2019.01.23【NOIP普及组】模拟赛C组总结
  2. 「LibreOJ NOIP Round #1」旅游路线
  3. BZOJ 3513: [MUTC2013]idiots [FFT]
  4. 操作系统复习笔记 06 CPU Scheduling CPU调度
  5. SpringCloud Zuul(三)之常见用法
  6. java各种集合的线程安全
  7. Java中classLoader浅析
  8. 参加双车项目的一些感触
  9. 定了!对于本周四(7.16日)抽奖活动取消简要说明,新抽奖活动暂定下周三(7.22日)...
  10. struts+hibernate+oracle+easyui实现lazyout组件的简单案例——struts.xml配置详情