带有运行时参数的 PostgreSQL 视图

在许多情况下,应用程序需要足够灵活和多功能,以便能够运行动态报告,其中输入在运行时提供。

本文旨在通过利用PostgreSQL数据库支持的临时配置参数来展示如何实现这一点。

根据PostgreSQL文档,从7.3版本开始,可以使用set_config(name, value, is_local)函数设置配置参数。随后,可以使用current_setting(name)函数读取先前设置参数的值,必要时进行转换,并使用它。如果前一个函数的第三个参数为true,则更改的设置仅适用于当前事务。

这正是这里需要的 —— 一种提供可以作为原子操作一部分使用的运行时参数值的方法。

设置 

示例应用程序的构建包括:

  • Java 21

  • Spring Boot 版本 3.1.15

  • PostgreSQL 驱动版本 42.6.0

  • Liquibase 4.20.0

  • Maven 3.6.3

在应用程序级别,Maven项目配置为使用Spring Data JPA和Liquibase依赖。

该领域由产品组成,其价格以不同货币表示。为了进行货币之间的转换,存在货币汇率。目标是能够以某种货币表示的价格读取所有产品,按照某一天的汇率。

概念证明 

为了开始建模,首先在连接到数据库后创建一个新的模式。

create schema pgsetting;

共有三个实体:Product、Currency和CurrencyExchange。


@Entity
@Table(name = "product")
public class Product {@Id@Column(name = "id")private Long id;@Column(name = "name", nullable = false)private String name;@Column(name = "price", nullable = false)private Double price;@ManyToOne@JoinColumn(name = "currency_id")private Currency currency;...
}@Entity
@Table(name = "currency")
public class Currency {@Id@Column(name = "id", nullable = false)private Long id;@Column(name = "name", nullable = false)private String name;...
}@Entity
@Table(name = "currency_exchange")
public class CurrencyExchange {@Id@Column(name = "id", nullable = false)private Long id;@Column(name = "date", nullable = false)private LocalDate date;@ManyToOne@JoinColumn(name = "from_currency_id", nullable = false)private Currency from;@ManyToOne@JoinColumn(name = "to_currency_id", nullable = false)private Currency to;@Column(name = "value", nullable = false)private Double value;...
}

每一个都有一个对应的CrudRepository。


@Repository
public interface ProductRepository extends CrudRepository<Product, Long> { }@Repository
public interface CurrencyRepository extends CrudRepository<Currency, Long> { }@Repository
public interface CurrencyExchangeRepository extends CrudRepository<CurrencyExchange, Long> { }

数据源像往常一样在文件中配置application.properties,以及 Liquibase 更改日志文件的路径,该文件记录了一些简单的更改集,用于使用三个表及其之间的关系初始化架构。

有关详细信息,可以探索应用程序属性和db/changelog/schema-init.xml文件。

根变更日志文件是:


<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangeloghttps://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd"><include file="/db/changelog/schema-init.xml"/></databaseChangeLog>

当应用程序启动时,更改集按照声明的顺序执行。到目前为止,一切都很简单,没有什么异常——一个简单的 Spring Boot 应用程序,其数据库更改由 Liquibase 管理。

创建动态报告

假设当前应用程序定义了两种货币 - RON 和 EUR,以及两种产品,其价格以不同的货币记录。

货币

+--+----+|id|name|+--+----+|1 |RON ||2 |EUR |+--+----+

产品

+--+-------------------+-----+-----------+|id|name               |price|currency_id|+--+-------------------+-----+-----------+|1 |Swatch Moonlight v1|100  |2          ||2 |Winter Sky         |1000 |1          |+--+-------------------+-----+-----------+

11 月 15 日 货币汇率​​​​​​​

+--+----------+----------------+--------------+-----+|id|date      |from_currency_id|to_currency_id|value|+--+----------+----------------+--------------+-----+|1 |2023-11-15|2               |1             |5    ||2 |2023-11-15|2               |2             |1    ||3 |2023-11-15|1               |2             |0.2  ||4 |2023-11-15|1               |1             |1    |+--+----------+----------------+--------------+-----+

目标结果是一份产品报告,其中所有价格均以欧元为单位,使用 2023 年 11 月 15 日起的汇率。这意味着需要转换第二个产品的价格。

为了简化设计,之前设定的目标被分成更小的部分,然后被克服。从概念上讲,应获取产品并转换其价格(如果需要)。

  1. 获取产品。

  2. 使用请求日期的汇率以请求的货币转换价格。

前者是微不足道的。Spring Data Repository 方法可以轻松获取产品 - List findAll().

后者可以通过进行转换的查询来实现。​​​​​​

SELECT p.id,       p.name,       p.price * e.value price,              e.to_currency_id currency_id,       e.dateFROM product pLEFT JOIN currency_exchange e on p.currency_id = e.from_currency_id and        e.to_currency_id = 2 and        e.date = '2023-11-15'

为了将两者统一起来,完成以下工作:

  • 针对上述查询定义了一个视图 -product_view

它在product-view.sql文件中定义,并作为幂等操作添加到可重复的Liquibase 更改集中,只要发生更改,该更改集就会运行。​​​​​​​

<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangeloghttps://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd"><include file="/db/changelog/schema-init.xml"/><changeSet id="repeatable" author="horatiucd" runOnChange="true"><sqlFile dbms="postgresql" path="db/changelog/product-view.sql"/></changeSet></databaseChangeLog>
  • 一个新的实体——ProductView连同相应的存储库一起被定义为域的一部分。

@Entity@Immutablepublic class ProductView {     @Id    private Long id;     private String name;     private Double price;     private LocalDate date;     @ManyToOne    @JoinColumn(name = "currency_id")    private Currency currency;         ...}
@Repository
public interface ProductViewRepository extends org.springframework.data.repository.Repository<ProductView, Long> {List<ProductView> findAll();
}

该应用程序现在能够构建所需的报告,但仅限于硬编码的货币和汇率。

为了在运行时传递两者,在同一事务中执行以下操作:

  • 这两个参数值被设置为配置参数 -SELECT set_config(:name, :value, true)

  • ProductView使用存储库方法获取实体

此外,product_view修改为读取作为当前事务的一部分设置的配置参数并相应地选择数据。​​​​​​​

SELECT p.id,       p.name,       p.price * e.value price,       e.date,       e.to_currency_id currency_idFROM product pLEFT JOIN currency_exchange e on p.currency_id = e.from_currency_id and        e.to_currency_id = current_setting('pgsetting.CurrencyId')::int and        e.date = current_setting('pgsetting.CurrencyDate')::date;

current_setting('pgsetting.CurrencyId')调用current_setting('pgsetting.CurrencyDate')读取之前设置的参数,进一步转换使用。

实施需要一些额外的调整。

ProductViewRepository通过允许设置配置参数的方法进行了增强。

@Repository
public interface ProductViewRepository extends org.springframework.data.repository.Repository<ProductView, Long> {List<ProductView> findAll();@Query(value = "SELECT set_config(:name, :value, true)")void setConfigParam(String name, String value);
}

最后一个参数始终设置为true,因此该值仅在当前事务期间保留。

另外,ProductService定义a是为了清楚地标记事务中涉及的所有操作。​​​​​​​

@Servicepublic class ProductService {     private final ProductViewRepository productViewRepository;     public ProductService(ProductViewRepository productViewRepository) {        this.productViewRepository = productViewRepository;    }     @Transactional    public List<ProductView> getProducts(Currency currency, LocalDate date) {        productViewRepository.setConfigParam("pgsetting.CurrencyId",                String.valueOf(currency.getId()));         productViewRepository.setConfigParam("pgsetting.CurrencyDate",                DateTimeFormatter.ofPattern("yyyy-MM-dd").format(date));         return productViewRepository.findAll();    }}

参数的名称是定义中使用的名称product_view。

为了验证实施情况,设置了两项测试。​​​​​​​

@SpringBootTestclass Product1Test {     @Autowired    private CurrencyRepository currencyRepository;     @Autowired    private ProductRepository productRepository;     @Autowired    private CurrencyExchangeRepository rateRepository;     @Autowired    private ProductService productService;     private Currency ron, eur;    private Product watch, painting;    private CurrencyExchange eurToRon, ronToEur;    private LocalDate date;     @BeforeEach    public void setup() {        ron = new Currency(1L, "RON");        eur = new Currency(2L, "EUR");        currencyRepository.saveAll(List.of(ron, eur));         watch = new Product(1L, "Swatch Moonlight v1", 100.0d, eur);        painting = new Product(2L, "Winter Sky", 1000.0d, ron);        productRepository.saveAll(List.of(watch, painting));         date = LocalDate.now();        eurToRon = new CurrencyExchange(1L, date, eur, ron, 5.0d);        CurrencyExchange eurToEur = new CurrencyExchange(2L, date, eur, eur, 1.0d);        ronToEur = new CurrencyExchange(3L, date, ron, eur, .2d);        CurrencyExchange ronToRon = new CurrencyExchange(4L, date, ron, ron, 1.0d);        rateRepository.saveAll(List.of(eurToRon, eurToEur, ronToEur, ronToRon));    }}

前者使用记录的汇率获取欧元价格的产品。​​​​​​​

@Testvoid prices_in_eur() {    List<ProductView> products = productService.getProducts(eur, date);    Assertions.assertEquals(2, products.size());     Assertions.assertTrue(products.stream()            .allMatch(product -> product.getCurrency().getId().equals(eur.getId())));     Assertions.assertTrue(products.stream()            .allMatch(product -> product.getDate().equals(date)));     Assertions.assertEquals(watch.getPrice(),            products.get(0).getPrice());    Assertions.assertEquals(painting.getPrice() * ronToEur.getValue(),            products.get(1).getPrice());}

当调用时,product_view是:​​​​​​​

+--+-------------------+-----+-----------+----------+|id|name               |price|currency_id|date      |+--+-------------------+-----+-----------+----------+|1 |Swatch Moonlight v1|100  |2          |2023-11-15||2 |Winter Sky         |200  |2          |2023-11-15|+--+-------------------+-----+-----------+----------+

后者使用相同的汇率获取价格为 RON 的产品。​​​​​​​

@Testvoid prices_in_ron() {    List<ProductView> products = productService.getProducts(ron, date);    Assertions.assertEquals(2, products.size());     Assertions.assertTrue(products.stream()            .allMatch(product -> product.getCurrency().getId().equals(ron.getId())));     Assertions.assertTrue(products.stream()            .allMatch(product -> product.getDate().equals(date)));     Assertions.assertEquals(watch.getPrice() * eurToRon.getValue(),            products.get(0).getPrice());    Assertions.assertEquals(painting.getPrice(),            products.get(1).getPrice());}

当调用时,product_view是:​​​​​​​

+--+-------------------+-----+-----------+----------+|id|name               |price|currency_id|date      |+--+-------------------+-----+-----------+----------+|1 |Swatch Moonlight v1|500  |1          |2023-11-15||2 |Winter Sky         |1000 |1          |2023-11-15|+--+-------------------+-----+-----------+----------+


作者:Horatiu Dan

更多技术干货请关注公号【云原生数据库

squids.cn,云数据库RDS,迁移工具DBMotion,云备份DBTwin等数据库生态工具。

irds.cn,多数据库管理平台(私有云)。

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

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

相关文章

个人测试面试问题总结

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 &#x1f4d1;设计软件测试用例的方…

如何选择一款安全可靠的跨网安全数据交换系统?

随着网络和数据安全的重视程度增加&#xff0c;为了有效地保护内部的核心数据资产&#xff0c;普遍会采用内外网隔离的策略。像国内的政府机构、金融、能源电力、航空航天、医院等关乎国计民生的行业和领域均已进行了网络的隔离&#xff0c;将内部划分成不同的网段&#xff0c;…

体育场找座位 - 华为OD统一考试(C卷)

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 100分 题目描述 在一个大型体育场内举办了一场大型活动&#xff0c;由于疫情防控的需要&#xff0c;要求每位观众的必须间隔至少一个空位才允许落座。现在给出一排观众座位分布图&#xff0c;座位中存在已落座的观众&…

速达软件全系产品任意文件上传漏洞

声明 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 1. 速达软件产品简介 速达软件专注中小企业管理软件,产品涵盖进销存软…

智能监控/安防监控视频平台EasyCVR下级更新目录表出现离线情况的两种解决方案

GB28181安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备…

DataGrip连接虚拟机上Docker部署的Mysql出错解决

1.1 首先判断CentOS的防火墙&#xff0c;如果开启就关闭 //查看防火墙状态 systemctl status firewalld //关闭防火墙systemctl stop firewalld.service//关闭防火墙开机自启systemctl disable firewalld.service而后可以打开DataGrip连接了&#xff0c;如果连接不上执行如下…

python学习:opencv学习和numpy学习(持续更新)

目录 cv.imread() 读取图像 cv.imshow() 在窗口中显示图像 cv.imwrite() 保存图像 cv.waitKey() 等待任何键盘事件 release() 释放或关闭图像或视频捕获对象 cv.destroyAllWindows() 销毁所有窗口 cv.destroyAllWindow() 销毁某个窗口 cv.VideoCapture() 打开视频流 …

vue项目中添加刷新的按钮

刷新功能 点击导航的刷新按钮&#xff0c;刷新下方主体内容&#xff0c;我这边的项目分为左-上-下结构&#xff0c;上边为tabbar组件&#xff0c;下边为main组件&#xff0c;点击刷新整个流程是刷新按钮&#xff0c;去访问它父组件tabbar的兄弟组件main&#xff0c;使main组件…

从零开始学习 JS APL(七):实例解析关于京东案例头部案例和放大镜效果!!

大家好关于JS APl 知识点已经全部总结了&#xff0c;第七部部分全部都是案例部分呢&#xff01;&#xff01;&#xff08;素材的可以去百度网盘去下载&#xff01;&#xff01;&#xff01;&#xff09; 目录 前言 一、个人实战文档 放大镜效果 思路分析&#xff1a; 关于其它…

LeetCode算法题解(动态规划)|LeetCode583. 两个字符串的删除操作、LeetCode72. 编辑距离

一、LeetCode583. 两个字符串的删除操作 题目链接&#xff1a;583. 两个字符串的删除操作 题目描述&#xff1a; 给定两个单词 word1 和 word2 &#xff0c;返回使得 word1 和 word2 相同所需的最小步数。 每步 可以删除任意一个字符串中的一个字符。 示例 1&#xff1a; …

固态硬盘的优势是什么?

固态硬盘的优势是什么&#xff1f; 1、重量轻体积小。固态硬盘相比机械硬盘在体积上更小&#xff0c;重量更轻。一般来说&#xff0c;台式机的机械硬盘的尺寸在3.5英寸&#xff0c;而SATA接口的固态硬盘的尺寸仅有2.5英寸&#xff0c;而M.2接口的固态硬盘体积更小&#xff0c;只…

听力长难句

[ 01 ] Usually, when we think of DNA, we think of the DNA Thats in cells nucleus, but in fact a cell mitochondrion also contains DNA. In this particular situation, mitochondrial DNA analysis made more sense than nuclear DNA analysis. 翻译&#xff1a; 通…

新手管理者有哪些需要学习的内容?

作为新手管理者&#xff0c;需要学习的内容非常多。以下是一些重要的学习内容&#xff1a; 1. 领导力和管理技能&#xff1a;作为管理者&#xff0c;首先要学习如何有效地领导和管理团队。这包括学习如何激励员工、制定目标和计划、分配任务、解决冲突等。管理者需要具备良好的…

解决Ubuntu使用supervisor管控的程序不能使用麦克风的问题

项目场景&#xff1a; Ubuntu服务器有时候可能更多的是用来跑服务&#xff0c;但是如果用来调取麦克风又担心自己的程序崩溃掉&#xff0c;你可能自然想到使用supervisor来管控自己的程序&#xff0c;但是似乎并不是那么好用。 问题描述 Ubuntu正常使用机器的能力是没有问题的…

【医疗设备方案】脉搏式血氧仪方案

脉搏式血氧仪的主要测量数据分别是血氧饱和度、脉率、血流灌注指数。其中血氧饱和度是指在全部血容量中被结合氧气容量占全部可结合的氧气容量的百分比&#xff0c;是临床医疗上重要的基础数据之一。 脉搏式血氧仪测量原理 典型的脉搏式血氧仪带有一颗光电二极管PD和两颗发光二…

亚马逊云科技re:Invent大会:RAG技术赋能企业AI应用的新纪元

在最新一届re:Invent大会中&#xff0c;亚马逊云科技的数据和人工智能副总裁Swami Sivasubramanian博士提出了一系列AI产品&#xff0c;其中RAG技术成为了企业构建生成式AI应用的重要选择。这种技术的实质是将向量数据库与大语言模型相结合&#xff0c;赋予大模型记忆的能力&am…

LangChain的函数,工具和代理(五):Tools Routing

关于langchain的函数、工具、代理系列的博客我之前已经写了四篇&#xff0c;还没有看过的朋友请先看一下&#xff0c;这样便于对后续博客内容的理解&#xff1a; LangChain的函数&#xff0c;工具和代理(一)&#xff1a;OpenAI的函数调用 LangChain的函数&#xff0c;工具和代…

2023最全的Web自动化测试介绍(建议收藏)

做测试的同学们都了解&#xff0c;做Web自动化&#xff0c;我们主要用Selenium或者是QTP。 有的人可能就会说&#xff0c;我没这个Java基础&#xff0c;没有Selenium基础&#xff0c;能行吗&#xff1f;测试虽然属于计算机行业&#xff0c;但其实并不需要太深入的编程知识&…

C++模板初阶

文章目录 泛型编程函数模板格式模板调用的是同一个函数吗&#xff1f;模板的实现原理T不明确模板实例化的函数和普通函数 类模板类模板写法类模板用法 注意事项 泛型编程 假如我们要写一个两数交换的函数&#xff0c;按我们之前学的知识&#xff0c;我们会这样。 void Swap(i…

TypeScript 在前端开发中的应用范围有哪些?

引言 TypeScript是一种由Microsoft开发的开源编程语言&#xff0c;它是JavaScript的超集&#xff0c;添加了静态类型和其他一些面向对象的特性。在前端开发中&#xff0c;TypeScript的应用范围非常广泛&#xff0c;它不仅提供了更好的开发工具支持&#xff0c;还在代码质量、可…