手撸Mybatis(三)——收敛SQL操作到SqlSession

本专栏的源码:https://gitee.com/dhi-chen-xiaoyang/yang-mybatis。

引言

在上一章中,我们实现了读取mapper配置并构造相关的mapper代理对象,读取mapper.xml文件中的sql信息等操作,现在,在上一章的基础上,我们接着开始链接数据库,通过封装JDBC,来实现我们数据库操作。

数据库准备

我们创建一个user表,用于后续进行测试,user表的结构如下图所示:
image.png
user表的内容如下:
image.png

添加User类

我们根据表结构,创建对应的user类,user类的结构如下:

package com.yang.mybatis.test;

import java.io.Serializable;
import java.util.Date;

public class User implements Serializable {
    private Integer id;
    
    private String userName;
    
    private String password;
    
    private Integer age;
    
    private Date createTime;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}

JDBC基础操作

在使用jdbc之前,我们先引入mysql的依赖

<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>

jdbc的操作,一般分为下面几个步骤:
1)加载JDBC驱动程序
2)创建数据库链接
3)创建一个preparedStatement
4)执行SQL语句
5)遍历数据集
6)处理异常,关闭JDBC对象资源
示例代码如下:

 public static void main(String[] args) throws SQLException {
        String url = "jdbc:mysql://localhost:3306/test?useSSL=false";
        String username = "用户名";
        String password = "密码";

        Connection conn = null;
        try {
           // 1. 加载驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
           // 2. 创建数据库链接
            conn = DriverManager.getConnection(url, username, password);
            conn.setAutoCommit(false);

           // 3. 创建preparedStatement
            String sql = "select user_name from user where id = ?";
            PreparedStatement preparedStatement = conn.prepareStatement(sql);
            preparedStatement.setInt(1, 1);
            // 4. 执行sql
           ResultSet resultSet = preparedStatement.executeQuery();
           // 5. 遍历结果
           if (resultSet.next()) {
                System.out.println(resultSet.getString(1));
            }
            conn.commit();
        } catch (SQLException | ClassNotFoundException e) {
            conn.rollback();
            e.printStackTrace();
        } finally {
          // 6. 释放连接
            conn.close();
        }
    }

执行上述代码,结果如下:
image.png

将sql操作,收敛到SqlSession

在上一章,当我们调用mapper的方法时,最终是通过在MapperProxy中获取对应的MybatisStatement,然后打印出sql信息的,但是如果后续操作数据库是,也在MapperProxy中执行sql的话,不太方便管理。因此,我们添加一个IMybatisSqlSession类,后续对于数据库的操作,收敛到此类进行。
首先,我们添加IMybatisSqlSession:

package com.yang.mybatis.session;

public interface IMybatisSqlSession {

    <T> T execute(String method, Object parameter);

    <T> T getMapper(Class<T> type);
}

然后添加其默认实现:

package com.yang.mybatis.session;

import com.yang.mybatis.proxy.MapperProxyFactory;

public class DefaultMybatisSqlSession implements IMybatisSqlSession {
    private MapperProxyFactory mapperProxyFactory;

    public DefaultMybatisSqlSession(MapperProxyFactory mapperProxyFactory) {
        this.mapperProxyFactory = mapperProxyFactory;
    }

    @Override
    public <T> T execute(String method, Object parameter) {
        return (T)("你被代理了!" + method + ",入参:" + parameter);
    }

    @Override
    public <T> T getMapper(Class<T> type) {
        return (T) mapperProxyFactory.newInstance(type, this);
    }
}

添加IMybatisSqlSession的工厂接口

package com.yang.mybatis.session;

public interface MybatisSqlSessionFactory {
    IMybatisSqlSession openSession();
}

添加工厂的默认实现:

package com.yang.mybatis.session;

import com.yang.mybatis.proxy.MapperProxyFactory;

public class DefaultMybatisSqlSessionFactory implements IMybatisSqlSessionFactory {
    private MapperProxyFactory mapperProxyFactory;

    public DefaultMybatisSqlSessionFactory(MapperProxyFactory mapperProxyFactory) {
        this.mapperProxyFactory = mapperProxyFactory;
    }

    @Override
    public IMybatisSqlSession openSession() {
        return new DefaultMybatisSqlSession(mapperProxyFactory);
    }
}

修改MapperProxyFactory,在新建MapperProxy的时候,将imybatisSqlSession传递给MapperProxy。

package com.yang.mybatis.proxy;

import com.yang.mybatis.config.MybatisConfiguration;
import com.yang.mybatis.session.IMybatisSqlSession;

import java.lang.reflect.Proxy;

public class MapperProxyFactory {
    private MybatisConfiguration mybatisConfiguration;


    public MapperProxyFactory(MybatisConfiguration mybatisConfiguration) {
        this.mybatisConfiguration = mybatisConfiguration;
    }

    public Object newInstance(Class mapperType, IMybatisSqlSession mybatisSqlSession) {
        MapperProxy mapperProxy = new MapperProxy(mapperType, mybatisSqlSession);
        return Proxy.newProxyInstance(mapperType.getClassLoader(),
                new Class[]{mapperType},
                mapperProxy);
    }

    public MybatisConfiguration getMybatisConfiguration() {
        return mybatisConfiguration;
    }

    public void setMybatisConfiguration(MybatisConfiguration mybatisConfiguration) {
        this.mybatisConfiguration = mybatisConfiguration;
    }
}

然后修改MapperProxy,在真正执行的时候,通过iMybatisSqlSession的execute,来执行sql操作,以此实现sql操作由iMybatisSqlSession来统一管理。

package com.yang.mybatis.proxy;

import com.yang.mybatis.session.IMybatisSqlSession;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MapperProxy<T> implements InvocationHandler {

    private Class<T> mapperInterface;

    private IMybatisSqlSession sqlSession;

    public MapperProxy(Class<T> mapperInterface, IMybatisSqlSession mybatisSqlSession) {
        this.mapperInterface = mapperInterface;
        this.sqlSession = mybatisSqlSession;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }
        String methodName = this.mapperInterface.getName() + "." + method.getName();
        return sqlSession.execute(methodName, args);
    }
}

最后,我们添加sqlSession工厂类的创建者,通过创建者模式,来创建SqlSession工厂

package com.yang.mybatis.session;

import com.yang.mybatis.config.MybatisConfiguration;
import com.yang.mybatis.config.MybatisSqlStatement;
import com.yang.mybatis.config.parser.IMybatisConfigurationParser;
import com.yang.mybatis.config.parser.IMybatisMapperParser;
import com.yang.mybatis.proxy.MapperProxyFactory;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class MybatisSqlSessionFactoryBuilder {
    private IMybatisConfigurationParser mybatisConfigurationParser;

    private IMybatisMapperParser mybatisMapperParser;

    private String configPath;

    public IMybatisSqlSessionFactory buildSqlSessionFactory() {
        if (configPath == null || configPath.isEmpty()) {
            throw new RuntimeException("配置文件路径不合法==========");
        }
        if (this.mybatisMapperParser == null || this.mybatisConfigurationParser == null) {
            throw new RuntimeException("缺少解析器=======");
        }
        MybatisConfiguration mybatisConfiguration = mybatisConfigurationParser.parser(configPath);
        List<String> mapperPaths = mybatisConfiguration.getMapperPaths();
        for (String mapperPath : mapperPaths) {
            List<MybatisSqlStatement> mybatisSqlStatements = this.mybatisMapperParser.parseMapper(mapperPath);
            Map<String, List<MybatisSqlStatement>> mapperNameGroupMap = mybatisSqlStatements.stream()
                    .collect(Collectors.groupingBy(MybatisSqlStatement::getNamespace));

            for (Map.Entry<String, List<MybatisSqlStatement>> entry : mapperNameGroupMap.entrySet()) {
                String mapperName = entry.getKey();
                List<MybatisSqlStatement> sqlSessionList = entry.getValue();
                mybatisConfiguration.putMybatisSqlStatementList(mapperName, sqlSessionList);
            }
        }

        MapperProxyFactory mapperProxyFactory = new MapperProxyFactory(mybatisConfiguration);
        return new DefaultMybatisSqlSessionFactory(mapperProxyFactory);
    }

    public MybatisSqlSessionFactoryBuilder setConfigPath(String configPath) {
        this.configPath = configPath;
        return this;
    }

    public MybatisSqlSessionFactoryBuilder setMybatisConfigurationParser(IMybatisConfigurationParser iMybatisConfigurationParser) {
        this.mybatisConfigurationParser = iMybatisConfigurationParser;
        return this;
    }

    public MybatisSqlSessionFactoryBuilder setMybatisMapperParser(IMybatisMapperParser iMybatisMapperParser) {
        this.mybatisMapperParser = iMybatisMapperParser;
        return this;
    }
}

添加测试代码,进行测试:

package com.yang.mybatis.test;

import com.yang.mybatis.config.parser.XmlMybatisConfigurationParser;
import com.yang.mybatis.config.parser.XmlMybatisMapperParser;
import com.yang.mybatis.session.IMybatisSqlSession;
import com.yang.mybatis.session.IMybatisSqlSessionFactory;
import com.yang.mybatis.session.MybatisSqlSessionFactoryBuilder;


public class Main {
    public static void main(String[] args) {
        String configPath = "mybatis-config.xml";
        IMybatisSqlSessionFactory mybatisSqlSessionFactory = new MybatisSqlSessionFactoryBuilder()
                .setMybatisMapperParser(new XmlMybatisMapperParser())
                .setMybatisConfigurationParser(new XmlMybatisConfigurationParser())
                .setConfigPath(configPath)
                .buildSqlSessionFactory();

        IMybatisSqlSession mybatisSqlSession = mybatisSqlSessionFactory.openSession();

        IUserMapper userMapper = mybatisSqlSession.getMapper(IUserMapper.class);
        System.out.println(userMapper.queryUserName(1));
    }
}

image.png

SqlSession获取执行方法对应的sql语句

首先,修改MybatisConfiguration,之前是将每一个mapper和他所拥有的MybatisSqlStatement列表关联起来,现在感觉粒度太大,因此,该为每一个mapper的方法和对应的MybatisSqlStatement关联。

package com.yang.mybatis.config;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MybatisConfiguration implements Serializable {
    private Map<String, MybatisEnvironment> id2EnvironmentMap = new HashMap<>();

    private MybatisEnvironment defaultMybatisEnvironment;

    private List<String> mapperPaths = new ArrayList<>();

    private Map<String, MybatisSqlStatement> mapperMethod2SqlStatementsMap = new HashMap<>();

    public void putMapperMethod2MybatisSqlStatement(String mapperMethod, MybatisSqlStatement mybatisSqlStatement) {
        this.mapperMethod2SqlStatementsMap.put(mapperMethod, mybatisSqlStatement);
    }

    public MybatisSqlStatement getMybatisSqlStatement(String mapperMethod) {
        return this.mapperMethod2SqlStatementsMap.get(mapperMethod);
    }

    public Map<String, MybatisSqlStatement> getMapperMethod2SqlStatementsMap() {
        return this.mapperMethod2SqlStatementsMap;
    }


    public void addEnvironment(String id, MybatisEnvironment mybatisEnvironment) {
        this.id2EnvironmentMap.put(id, mybatisEnvironment);
    }

    public MybatisEnvironment getEnvironment(String id) {
        return id2EnvironmentMap.get(id);
    }

    public MybatisEnvironment getDefaultMybatisEnvironment() {
        return defaultMybatisEnvironment;
    }

    public void setDefaultMybatisEnvironment(MybatisEnvironment defaultMybatisEnvironment) {
        this.defaultMybatisEnvironment = defaultMybatisEnvironment;
    }

    public void addMapperPath(String mapperPath) {
        this.mapperPaths.add(mapperPath);
    }

    public List<String> getMapperPaths() {
        return this.mapperPaths;
    }

    public List<MybatisEnvironment> getEnvironments() {
        return new ArrayList<>(id2EnvironmentMap.values());
    }

}

然后修改MybatisSqlSessionFactoryBuilder:

package com.yang.mybatis.session;

import com.yang.mybatis.config.MybatisConfiguration;
import com.yang.mybatis.config.MybatisSqlStatement;
import com.yang.mybatis.config.parser.IMybatisConfigurationParser;
import com.yang.mybatis.config.parser.IMybatisMapperParser;
import com.yang.mybatis.proxy.MapperProxyFactory;

import java.util.List;

public class MybatisSqlSessionFactoryBuilder {
    private IMybatisConfigurationParser mybatisConfigurationParser;

    private IMybatisMapperParser mybatisMapperParser;

    private String configPath;

    public IMybatisSqlSessionFactory buildSqlSessionFactory() {
        if (configPath == null || configPath.isEmpty()) {
            throw new RuntimeException("配置文件路径不合法==========");
        }
        if (this.mybatisMapperParser == null || this.mybatisConfigurationParser == null) {
            throw new RuntimeException("缺少解析器=======");
        }
        MybatisConfiguration mybatisConfiguration = mybatisConfigurationParser.parser(configPath);
        List<String> mapperPaths = mybatisConfiguration.getMapperPaths();
        for (String mapperPath : mapperPaths) {
            List<MybatisSqlStatement> mybatisSqlStatements = this.mybatisMapperParser.parseMapper(mapperPath);
            for (MybatisSqlStatement mybatisSqlStatement : mybatisSqlStatements) {
                String methodName = mybatisSqlStatement.getNamespace() + "." + mybatisSqlStatement.getId();
                mybatisConfiguration.putMapperMethod2MybatisSqlStatement(methodName, mybatisSqlStatement);
            }
        }

        MapperProxyFactory mapperProxyFactory = new MapperProxyFactory(mybatisConfiguration);
        return new DefaultMybatisSqlSessionFactory(mapperProxyFactory);
    }

    public MybatisSqlSessionFactoryBuilder setConfigPath(String configPath) {
        this.configPath = configPath;
        return this;
    }

    public MybatisSqlSessionFactoryBuilder setMybatisConfigurationParser(IMybatisConfigurationParser iMybatisConfigurationParser) {
        this.mybatisConfigurationParser = iMybatisConfigurationParser;
        return this;
    }

    public MybatisSqlSessionFactoryBuilder setMybatisMapperParser(IMybatisMapperParser iMybatisMapperParser) {
        this.mybatisMapperParser = iMybatisMapperParser;
        return this;
    }
}

修改DefaultMybatisSqlSession类,获取方法对应的MybatisStatement

package com.yang.mybatis.session;

import com.yang.mybatis.config.MybatisSqlStatement;
import com.yang.mybatis.proxy.MapperProxyFactory;


public class DefaultMybatisSqlSession implements IMybatisSqlSession {
    private MapperProxyFactory mapperProxyFactory;

    public DefaultMybatisSqlSession(MapperProxyFactory mapperProxyFactory) {
        this.mapperProxyFactory = mapperProxyFactory;
    }

    @Override
    public <T> T execute(String method, Object parameter) {
        MybatisSqlStatement mybatisSqlStatement = this.mapperProxyFactory.getMybatisConfiguration().getMybatisSqlStatement(method);
        return (T)("method:" + method + "sql:"+ mybatisSqlStatement.getSql() + ",入参:" + parameter);
    }

    @Override
    public <T> T getMapper(Class<T> type) {
        return (T) mapperProxyFactory.newInstance(type, this);
    }
}

添加测试方法进行测试:

 public static void main(String[] args) {
        String configPath = "mybatis-config.xml";
        MybatisSqlSessionFactory mybatisSqlSessionFactory = new MybatisSqlSessionFactoryBuilder()
                .setMybatisMapperParser(new XmlMybatisMapperParser())
                .setMybatisConfigurationParser(new XmlMybatisConfigurationParser())
                .setConfigPath(configPath)
                .buildSqlSessionFactory();

        IMybatisSqlSession mybatisSqlSession = mybatisSqlSessionFactory.openSession();

        IUserMapper userMapper = mybatisSqlSession.getMapper(IUserMapper.class);
        System.out.println(userMapper.queryUserAge(1));
    }

测试结果如下:
image.png

参考文章

https://blog.csdn.net/weixin_43520450/article/details/107230205

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MLP手写数字识别(2)-模型构建、训练与识别(tensorflow)

查看tensorflow版本 import tensorflow as tfprint(Tensorflow Version:{}.format(tf.__version__)) print(tf.config.list_physical_devices())1.MNIST的数据集下载与预处理 import tensorflow as tf from keras.datasets import mnist from keras.utils import to_categori…

MLP实现fashion_mnist数据集分类(2)-函数式API构建模型(tensorflow)

使用函数式API构建模型&#xff0c;使得模型可以处理多输入多输出。 1、查看tensorflow版本 import tensorflow as tfprint(Tensorflow Version:{}.format(tf.__version__)) print(tf.config.list_physical_devices())2、fashion_mnist数据集分类模型 2.1 使用Sequential构建…

pygame鼠标绘制

pygame鼠标绘制 Pygame鼠标绘制效果代码 Pygame Pygame是一个开源的Python库&#xff0c;专为电子游戏开发而设计。它建立在SDL&#xff08;Simple DirectMedia Layer&#xff09;的基础上&#xff0c;允许开发者使用Python这种高级语言来实时开发电子游戏&#xff0c;而无需被…

自定义数据上的YOLOv9分割训练

原文地址&#xff1a;yolov9-segmentation-training-on-custom-data 2024 年 4 月 16 日 在飞速发展的计算机视觉领域&#xff0c;物体分割在从图像中提取有意义的信息方面起着举足轻重的作用。在众多分割算法中&#xff0c;YOLOv9 是一种稳健且适应性强的解决方案&#xff0…

环形链表 [两道题目](详解)

环形链表&#xff08;详解&#xff09; 第一题&#xff1a; 题目&#xff1a; 题目链接 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表…

使用protoc-jar-maven-plugin生成grpc项目

在《使用protobuf-maven-plugin生成grpc项目》中我们使用protobuf-maven-plugin完成了grpc代码的翻译。本文我们将只是替换pom.xml中的部分内容&#xff0c;使用protoc-jar-maven-plugin来完成相同的功能。总体来说protoc-jar-maven-plugin方案更加简便。 环境 见《使用proto…

【数据结构(邓俊辉)学习笔记】列表01——从向量到列表

文章目录 0.概述1. 从向量到列表1.1 从静态到动态1.2 从向量到列表1.3 从秩到位置1.4 列表 2. 接口2.1 列表节点2.1.1 ADT接口2.1.2 ListNode模板类 2.2 列表2.2.1 ADT接口2.2.2 List模板类 0.概述 学习了向量&#xff0c;再介绍下列表。先介绍下列表里的概念和语义&#xff0…

global IoT SIM解决方案

有任何关于GSMA\IOT\eSIM\RSP\业务应用场景相关的问题&#xff0c;欢迎W: xiangcunge59 一起讨论, 共同进步 (加的时候请注明: 来自CSDN-iot). Onomondo提供的全球IoT SIM卡解决方案具有以下特点和优势&#xff1a; 1. **单一全球配置文件**&#xff1a;Onomondo的SIM卡拥…

hive-row_number() 和 rank() 和 dense_rank()

row_number() 是无脑排序 rank() 是相同的值排名相同&#xff0c;相同值之后的排名会继续加&#xff0c;是我们正常认知的排名&#xff0c;比如学生成绩。 dense_rank()也是相同的值排名相同&#xff0c;接下来的排名不会加。不会占据排名的坑位。

C#知识|将选中的账号信息展示到控制台(小示例)

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 上篇学习了控件事件的统一关联&#xff0c; 本篇通过实例练习继续学习事件统一处理中Tag数据获取、对象的封装及泛型集合List的综合运用。 01 实现功能 在上篇的基础上实现&#xff0c;点击选中喜欢的账号&#xff0…

WebSocket 多屏同显和异显

介绍 多屏同显:通过在一个应用上进行操作之后,另一个应用也能跟着一起发生改变,例如app1播放了晴天这首音乐,那么app2也要同步播放这首音乐,确保所有屏幕显示的内容完全相同。多屏异显:每个屏幕可以显示不同的内容,或者在内容更新时存在一定的延迟,而不需要严格保持同步…

MyBatis 使用 XML 文件映射

在MyBatis中 我们可以使用各种注解来配置我们Mapper 类中的方法 我们为什么要使用XML文件呢&#xff1f; 如果我们是一条非常长的SQL 语句 使用 注解配置的话&#xff0c; 会非常不利于阅读 如下 所以&#xff0c;就需要使用到一个XML文件来对SQL语句进行映射&#xff0c;那么 …

力扣763. 划分字母区间

Problem: 763. 划分字母区间 文章目录 题目描述思路复杂度Code 题目描述 思路 1.创建一个名为 last 的数组&#xff0c;用于存储每个字母在字符串 s 中最后出现的位置。然后&#xff0c;获取字符串 s 的长度 len。 2.计算每个字母的最后位置&#xff1a;遍历字符串 s&#xff0…

(五)SQL系列练习题(上)创建、导入与查询 #CDA学习打卡

目录 一. 创建表 1&#xff09;创建课程表 2&#xff09;创建学生表 3&#xff09;创建教师表 4&#xff09;创建成绩表 二. 导入数据 1&#xff09;导入课程科目数据 2&#xff09;导入课程成绩数据 3&#xff09;导入学生信息数据 4&#xff09;导入教师信息数据 …

RocketMQ SpringBoot 3.0不兼容解决方案

很多小伙伴在项目升级到springBoot3.0版本以上之后&#xff0c;整合很多中间件会有很多问题&#xff0c;下面带小伙伴解决springBoot3.0版本以上对于RocketMQ 不兼容问题 报错信息 *************************** APPLICATION FAILED TO START *************************** Des…

电脑崩溃了,之前备份的GHO文件怎么恢复到新硬盘?

前言 之前咱们说到用WinPE系统给电脑做一个GHO镜像备份&#xff0c;这个备份可以用于硬盘完全崩溃换盘的情况下使用。 那么这个GHO镜像文件怎么用呢&#xff1f; 咱们今天详细来讲讲&#xff01; 如果你的电脑系统硬盘崩溃了或者是坏掉了&#xff0c;那么就需要使用之前备份…

ubuntu 小工具

随着在专业领域待得越久&#xff0c;发现总会遇到各种奇怪的需求。因此&#xff0c;这里介绍一些ubuntu上的一些小工具。 meld 对比文件 sudo apt-get install meld sudo apt --fix-broken install # maybeHow to compare two files

pytorch笔记:ModuleDict

1 介绍 在 PyTorch 中&#xff0c;nn.ModuleDict 是一个方便的容器&#xff0c;用于存储一组子模块&#xff08;即 nn.Module 对象&#xff09;的字典这个容器主要用于动态地管理多个模块&#xff0c;并通过键来访问它们&#xff0c;类似于 Python 的字典 2 特点 组织性 nn…

《金融研究》:普惠金融改革试验区DID工具变量数据(2012-2023年)

数据简介&#xff1a;本数据集包括普惠金融改革试验区和普惠金融服务乡村振兴改革试验区两类。 其中&#xff0c;河南兰考、浙江宁波、福建龙岩和宁德、江西赣州和吉安、陕西铜川五省七地为普惠金融改革试验区。山东临沂、浙江丽水、四川成都三地设立的是普惠金融服务乡村振兴…

可视化大屏应用(20):智慧水务、水利、防汛

hello&#xff0c;我是大千UI工场&#xff0c;本篇分享智智慧水务的大屏设计&#xff0c;关注我们&#xff0c;学习N多UI干货&#xff0c;有设计需求&#xff0c;我们也可以接单。 在智慧水务领域&#xff0c;可视化大屏具有以下几个方面的价值&#xff1a; 数据展示和监控 可…