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

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

图片取自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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/341510.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2020-09-03解决pip install安装非常慢[Errno 101] 网络不可达问题

转载链接&#xff1a;http://huanyouchen.github.io/2019/11/21/pip-install-package-very-slow/ 问题描述 使用pip安装seaborn提示&#xff1a; sudo python3.5 -m pip install seabornCollecting seabornWARNING: Retrying (Retry(total4, connectNone, readNone, redirect…

环网工业交换机ERPS技术解析

ERPS(Ethernet Ring Protection Switching&#xff0c;以太环网保护切换协议)是ITU开发的一种环网保护协议&#xff0c;也称G.8032。它是一个专门应用于以太环网的链路层协议。它在以太环网完整时能够防止数据环路引起的广播风暴&#xff0c;而当以太环网上一条链路断开时能迅速…

交换机和路由器的区别是什么?

在我们日常生活中&#xff0c;上网很多时候都要用到交换机和路由器&#xff0c;很多人只知道二者都是用于连接上网的设备&#xff0c;但很少知道它们之间有什么区别&#xff0c;以及各自的功能。 交换机和路由器的区别&#xff1a; 路由器可以给你的局域网自动分配IP&#xf…

import _ssl # if we can‘t import it, let the error propagate

转载链接&#xff1a; https://blog.csdn.net/u013398960/article/details/107524068 实测有用

apache flume_Flume:使用Apache Flume收集客户产品搜索点击数据

apache flume这篇文章涵盖了使用Apache flume收集客户产品搜索点击并使用hadoop和elasticsearch接收器存储信息。 数据可能包含不同的产品搜索事件&#xff0c;例如基于不同方面的过滤&#xff0c;分类信息&#xff0c;分页信息&#xff0c;以及进一步查看的产品以及某些被客户…

如何选配合适的百兆或者千兆工业交换机

工业上常常会用到工业交换机&#xff0c;工程商在做小型网络监控方案的时候&#xff0c;经常需要选配合适的百兆工业交换机或千兆工业交换机&#xff0c;但是&#xff0c;到底是配百兆工业交换机还是千兆工业交换机呢&#xff1f;如何计算百兆工业交换机和千兆工业交换机的码率…

2020年9月14日运行代码总结

由于研究ns3gym的使用&#xff0c;官网案例不足&#xff0c;需要运行别人的代码&#xff0c;在本机上运行成功&#xff0c;但在仿真机过程中出现一类列问题。一开始以为环境配置问题&#xff0c;升级环境与python3.6&#xff0c;但在python3.6.12的安装与python默认版本的更改中…

使用Selenium自动化测试处理多个浏览器选项卡

使用Selenium进行自动化测试一直是将萌芽的自动化测试人员培养为专业人员的生命线。 硒是开源的&#xff0c;在全球范围内被广泛采用。 结果&#xff0c;您会得到社区的大力支持。 有多种用于不同语言的框架&#xff0c;这些框架提供与Selenium的绑定。 因此&#xff0c;您已经…

三层交换机工作原理介绍

每个网络主机、工作站或者服务器都有自己的IP地址和子网掩码。当主机与服务器进行通信的时候&#xff0c;根据自身的IP地址和子网掩码、以及服务器的IP地址&#xff0c;来确定服务器是否和自己处于相同的网段&#xff1a; 1、如果判定在相同网段内&#xff0c;则直接通过地址解…

TCP流中各种队列:

TCP流中各种队列&#xff1a; RED队列的介绍 [https://blog.csdn.net/sinat_20184565/article/details/107521549]

三层交换机有什么优势?

三层交换机的技术日益成熟&#xff0c;应用日益广泛&#xff0c;在一定范围内&#xff0c;它比路由器更具优势&#xff0c;但是三层交换机与路由器还是有很大的区别&#xff0c;在局域网中&#xff0c;三层交换机有着明显的优势。 1、子网间的传输带宽可以任意分配。 在传统路…

C++中使用流读取数据 ifstream

C中使用流读取数据 ifstream https://www.cnblogs.com/hjj-fighting/p/10429178.html

私有环网协议MR-ring介绍

以太网从诞生到今天已经走过了二十几个念头&#xff0c;伴随着IP的大发展&#xff0c;以太网已经一统江湖&#xff0c;98%的局域网采用以太网标准构建。在这二十多年中&#xff0c;以太网不断推陈出新&#xff0c;在速率、安全、稳定性等各方面均有一系列的标准&#xff0c;而环…

断言工具的编写_编写干净的测试–用特定领域的语言替换断言

断言工具的编写很难为干净的代码找到一个好的定义&#xff0c;因为我们每个人都有自己的单词clean的定义。 但是&#xff0c;有一个似乎是通用的定义&#xff1a; 简洁的代码易于阅读。 这可能会让您感到有些惊讶&#xff0c;但我认为该定义也适用于测试代码。 使测试尽可能具…

指针的意义与作用

版权声明&#xff1a;本文为博主原创文章&#xff0c;遵循 CC 4.0 BY-SA 版权协议&#xff0c;转载请附上原文出处链接和本声明。 本文链接&#xff1a;http://blog.csdn.net/zhanshen112/article/details/80265830

网管型工业交换机如何创建网络冗余

与非网管型工业交换机相比&#xff0c;使用杭州飞畅网管型工业交换机的其中一个好处是其冗余功能。这允许您使用带有额外连接的以太网&#xff0c;因此如果网络上两点之间的一条路径出现故障&#xff0c;则可以使用另一条路径来传递消息。如果一个链路或工业交换机发生故障&…

使用var,Lombok和Fluxtion轻松处理事件

介绍 在本文中&#xff0c;我将结合使用Lombok和Fluxtion这两种产品&#xff0c;以演示工具如何在减少代码编写和交付时间的同时提高代码的可读性。 使用Java 10中的var可以进一步改善这种情况。 产品和var都在构建时使用推断来加速开发。 Fluxtion的精神是最大程度地减少浪费…

Ubuntu安装谷歌拼音输入法

https://blog.csdn.net/xiaokingzi/article/details/88396590?utm_mediumdistribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.edu_weight&depth_1-utm_sourcedistribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.edu_weight

网管型工业交换机如何提高网路流量过滤?

虽然一个非网管型工业交换机会从一个设备端过滤出许多数据包&#xff0c;但还是有很多数据包非网管型工业交换机无法处理&#xff0c;而这些数据包又必须从端口传输到所有设备上。当一个设备接收到不是特定给该设备的数据包时&#xff0c;它必须先耗费资源来处理这些信息数据最…