springboot非配置实现动态多数据库查询

2023-10-27

1需求

  • 数据库配置信息不能在项目代码中配置或写死
  • 系统能接入用户配置的数据库并保存和读取
  • 每个用户可添加多个数据库(不同数据库类型、不同host)
  • 多个用户可添加相同的一个数据库
  • 同一个数据库只创建一个连接池
  • 数据库类型差异对业务逻辑透明

2确认下关系

在这里插入图片描述

3具体实现

1 创建两个数据库对象

一个是用户数据库,跟用户挂钩,包含一些除了数据库连接信息之外的其他用户数据

public class ExternalDataBaseDO implements Serializable {
    //数据库id
    @Id
    private String id;
    //数据源名称
    private String connectName;
    //数据库名
    private String dataBaseName;
    //登录名
    private String userName;
    //密码
    private String passWord;
    //连接地址
    private String host;
    //连接地址
    private String port;
    //数据库类型
    private String databaseType;
    //数据源提供者
    private String provider;
    ...
}

一个是真实唯一的数据库连接配置,数据来自用户信息,根据host/basename和数据库类型确定唯一

public class DatabaseConfig {
    private DatabaseType databaseType;
    private String host;
    private int port;
    private String databaseName;
    private String username;
    private String password;

    public DatabaseConfig(DatabaseType databaseType, String host, int port, String databaseName, String username, String password) {
        this.databaseType = databaseType;
        this.host = host;
        this.port = port;
        this.databaseName = databaseName;
        this.username = username;
        this.password = password;
    }

    public DatabaseConfig(ExternalDataBaseDO dataBaseDO) {
        this.databaseName = dataBaseDO.getDataBaseName();
        this.host = dataBaseDO.getHost();
        this.port = Integer.parseInt(dataBaseDO.getPort().trim());
        this.username = dataBaseDO.getUserName();
        this.password = dataBaseDO.getPassWord();
        switch (dataBaseDO.getDatabaseType()) {
            case "mssql":
                this.databaseType = DatabaseType.SQLSERVER;
                if (StrUtil.isBlank(dataBaseDO.getDataBaseName())) {
                    //默认数据库master
                    this.databaseName = "master";
                }
                break;
            case "postgresql":
                this.databaseType = DatabaseType.POSTGRESQL;
                if (StrUtil.isBlank(dataBaseDO.getDataBaseName())) {
                    //默认数据库postgres
                    this.databaseName = "postgres";
                }
                break;
            default:
                throw new MayException(CodeMsg.ERROR_DATABASE_TYPE);
        }
    }

    public String getUrl() {
        switch (databaseType) {
            case SQLSERVER:
                return String.format(DatabaseType.SQLSERVER.getJdbcUrlTemplate(),
                        host, port, databaseName);
            case POSTGRESQL:
                return String.format(DatabaseType.POSTGRESQL.getJdbcUrlTemplate(),
                        host, port, databaseName);
            default:
                throw new IllegalArgumentException("Unsupported database type: " + databaseType);
        }
    }
}

2 创建数据库类型的枚举类

作用是根据数据库类型得到驱动和url

public enum DatabaseType {

    MYSQL("com.mysql.jdbc.Driver", "jdbc:mysql://{host}/{database}"),
    SQLSERVER("com.microsoft.sqlserver.jdbc.SQLServerDriver", "jdbc:sqlserver://%s:%d;DatabaseName=%s;encrypt=false"),
    POSTGRESQL("org.postgresql.Driver", "jdbc:postgresql://%s:%d/%s?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL&allowMultiQueries=true");

    private final String driverClass;
    private final String jdbcUrlTemplate;

    DatabaseType(String driverClass, String jdbcUrlTemplate) {
        this.driverClass = driverClass;
        this.jdbcUrlTemplate = jdbcUrlTemplate;
    }

    public String getDriverClass() {
        return driverClass;
    }

    public String getJdbcUrlTemplate() {
        return jdbcUrlTemplate;
    }
}

3 创建数据库连接池

一个数据库配置一个连接池,这里使用的org.apache.commons.dbcp.BasicDataSource,其他连接池也行

public class ConnectionPool {
    private final DatabaseConfig databaseConfig;
    private final BasicDataSource dataSource;

    public ConnectionPool(DatabaseConfig databaseConfig) {
        this.databaseConfig = databaseConfig;
        this.dataSource = new BasicDataSource();
        this.dataSource.setDriverClassName(databaseConfig.getDatabaseType().getDriverClass());
        this.dataSource.setUrl(databaseConfig.getUrl());
        this.dataSource.setUsername(databaseConfig.getUsername());
        this.dataSource.setPassword(databaseConfig.getPassword());
        this.dataSource.setInitialSize(1); // 设置核心连接数为1 初始连接数
        this.dataSource.setMaxActive(10); // 设置最大连接数为10 最大连接数
        this.dataSource.setMaxWait(3000); // 设置最大连接等待时间毫秒 3秒
        this.dataSource.setMinEvictableIdleTimeMillis(10*60000); // 设置最小可空闲时间(10分钟)
        this.dataSource.setTimeBetweenEvictionRunsMillis(10*60000); // 检测空闲连接的时间间隔毫秒
    }

    public Connection getConnection() {
        try {
            System.out.println("已用连接数:" + dataSource.getNumActive());
            System.out.println("空闲连接数:" + dataSource.getNumIdle());
            return dataSource.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public DatabaseConfig getDatabaseConfig() {
        return databaseConfig;
    }
    public BasicDataSource getDataSource() {
        return dataSource;
    }
}

3 创建数据库通用连接类

负责维护数据库连接与用户数据库关系

public class DatabaseConnection {
    /**
     * 数据库唯一标识字符串(类型+ip+端口+用户名):数据库连接池
     */
    private static final Map<String, ConnectionPool> CONNECTION_FACTORIES = new ConcurrentHashMap<>();
    /**
     * 用户数据库配置id: 数据库唯一对象
     */
    private static final Map<String, DatabaseConfig> USERDBMAP = new ConcurrentHashMap<>();

    /**
     * 用户数据库Id获取数据库连接
     *
     * @param userDbId
     * @return
     */
    public static Connection getConnection(String userDbId) {
        DatabaseConfig databaseConfig = USERDBMAP.get(userDbId);
        if (databaseConfig == null) {
            return null;
        }
        return getConnection(databaseConfig);
    }

    /**
     * 缓存所有用户数据库连接
     *
     * @param dataBaseDO
     * @return
     */
    public static boolean addConnection(ExternalDataBaseDO dataBaseDO) {
        DatabaseConfig databaseConfig = new DatabaseConfig(dataBaseDO);
        String key = generateKey(databaseConfig);
        if (CONNECTION_FACTORIES.get(key) == null) {
            //创建连接池
            try {
                ConnectionPool connectionPool = new ConnectionPool(databaseConfig);
                Connection connection = connectionPool.getConnection();
                log.info("数据库已连接 {}:{}", dataBaseDO.getHost(), dataBaseDO.getDataBaseName());
                CONNECTION_FACTORIES.put(key, connectionPool);
                connection.close();
            } catch (SQLException e) {
                log.error("添加数据库时连接失败:{}", databaseConfig.toString());
                return false;
            }
        }
        //添加用户数据库映射
        USERDBMAP.put(dataBaseDO.getId(), databaseConfig);
        return true;
    }

    /**
     * 根据数据库唯一对象获取数据库连接
     *
     * @param databaseConfig
     * @return
     */
    public static Connection getConnection(DatabaseConfig databaseConfig) {
        String key = generateKey(databaseConfig);
        ConnectionPool connectionPool = CONNECTION_FACTORIES.get(key);
        //连接池为空则创建连接
        if (connectionPool == null) {
            synchronized (CONNECTION_FACTORIES) {
                connectionPool = CONNECTION_FACTORIES.get(key);
                if (connectionPool == null) {
                    connectionPool = new ConnectionPool(databaseConfig);
                    CONNECTION_FACTORIES.put(key, connectionPool);
                }
            }
        }
        return connectionPool.getConnection();
    }

    public static String generateKey(DatabaseConfig databaseConfig) {
        return databaseConfig.getDatabaseType().name() + "_" + databaseConfig.getHost() + "_" + databaseConfig.getPort() + "_" + databaseConfig.getDatabaseName();
    }

    /**
     * 查询所有连接池列表
     *
     * @return
     */
    public static List<ConnectionPool> getAllConnectionPools() {
        return new ArrayList<>(CONNECTION_FACTORIES.values());
    }

    /**
     * 查询用户数据库信息
     *
     * @return
     */
    public static DatabaseConfig getUserDbInfo(String userDbId) {
        return USERDBMAP.get(userDbId);
    }

    // 私有构造函数,避免在外部创建实例
    private DatabaseConnection() {
    }
}

其中 addConnection 方法是用户添加数据库时,保存用户数据库对象的同时建立数据库连接池

4 停下来测试一下

到这一步核心功能就已经实现好了,简单测试一下

public static void main(String[] args) throws SQLException {
        // 构建 DatabaseConfig ,切换DatabaseType和数据库检查不同类型数据库是否都能连接正常
        DatabaseConfig sqlserverConfig = new DatabaseConfig(
                DatabaseType.SQLSERVER,"192.168.10.153",1433,"myits","sa","admin888888"
        );

        // 获取连接
        Connection connection = DatabaseConnection.getConnection(sqlserverConfig);
        Connection connection2 = DatabaseConnection.getConnection(sqlserverConfig);
        Connection connection3 = DatabaseConnection.getConnection(sqlserverConfig);

        // 使用连接执行 SQL 查询操作
        PreparedStatement statement = connection.prepareStatement("SELECT * FROM STB WHERE OBJECTID=?");
        statement.setInt(1, 1);
        ResultSet resultSet = statement.executeQuery();
        while (resultSet.next()) {
            int id = resultSet.getInt("OBJECTID");
            String name = resultSet.getString("XIAN");
            String age = resultSet.getString("HXLX");
            System.out.println("id: " + id + ", name: " + name + ", age: " + age);
        }

        // 关闭连接并释放连接到连接池中
        statement.close();
        connection.close();
        Connection connection4 = DatabaseConnection.getConnection(sqlserverConfig);
    }

5 封装常用查询

上一步测试没问题就可以根据业务需求封装一个查询管理工具类,以后都通过这个管理类用用户数据库id来执行sql,不用在意数据库类型和连接信息

public class DatabaseQueryManage {

    /**
     * 查询表数据量
     *
     * @param databaseId
     * @param tableName
     * @return
     */
    public static long getTotalNum(String databaseId, String tableName) {
        String sql = "SELECT count(1) FROM " + tableName;
        Connection connection = DatabaseConnection.getConnection(databaseId);
        if (connection == null) {
            throw new MayException(CodeMsg.Request_ERROR, "数据库未加载到连接池" + databaseId);
        }
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            statement = connection.createStatement();
            resultSet = statement.executeQuery(sql);
            long total = 0;
            while (resultSet.next()) {
                total = resultSet.getLong(1);
            }
            return total;
        } catch (SQLException e) {
            log.error("查询表信息失败:{},数据库id:{},异常信息:{}", sql, databaseId, e.getMessage());
            throw new MayException(CodeMsg.SERVER_ERROR, "数据库查询表信息失败" + tableName);
        } finally {
            try {
                if (statement != null) {
                    statement.close();
                }
                if (resultSet != null) {
                    resultSet.close();
                }
                connection.close();
            } catch (SQLException e) {
                log.error("关闭数据库连接异常:{}", e.getMessage());
            }
        }
    }

    /**
     * 根据sql查询表数据
     *
     * @param sql
     * @return
     * @throws SQLException
     */
    public static List<Entity> query(String database, String sql) throws SQLException {
        Connection connection = DatabaseConnection.getConnection(database);
        if (connection == null) {
            throw new MayException(CodeMsg.Request_ERROR, "数据库未加载到连接池" + database);
        }
        try {
            return SqlExecutor.query(connection, sql, new EntityListHandler());
        } catch (SQLException e) {
            log.error("sql执行失败:{}", sql);
            throw new MayException(CodeMsg.Request_ERROR, "sql执行失败");
        } finally {
            try {
                connection.close();
            } catch (SQLException e) {
                log.error("关闭数据库连接异常:{}", e.getMessage());
            }
        }
    }

    /**
     * 生成分页查询sql
     *
     * @param userDbId   用户数据库id
     * @param tableName  表名
     * @param pageSize   每页行数
     * @param pageNumber 当前页数
     * @param fields     1查询字段 as xxx,2排序字段,3原字段
     * @return
     */
    public static String generateSelectSql(String userDbId, String tableName, int pageSize, int pageNumber, String... fields) {
        StringBuilder sb = new StringBuilder(fields[0]);
        sb.append(" FROM ").append(tableName);
        DatabaseConfig databaseConfig = DatabaseConnection.getUserDbInfo(userDbId);
        int offset = (pageNumber - 1) * pageSize;
        if (databaseConfig.getDatabaseType().equals(DatabaseType.POSTGRESQL)) {
            sb.insert(0, "SELECT ");
            sb.append(" ORDER BY ").append(fields[1]);
            sb.append(" LIMIT ").append(pageSize).append(" OFFSET ").append(offset);
        } else if (databaseConfig.getDatabaseType().equals(DatabaseType.SQLSERVER)) {
            if (fields.length > 2) {
                sb.insert(0, "SELECT " + fields[2] + " FROM (SELECT ROW_NUMBER() OVER (ORDER BY " + fields[1] + ") AS RowNumber,")
                        .append(") AS t WHERE t.RowNumber BETWEEN " + (offset + 1) + " AND " + (offset + pageSize));
            } else {
                sb.insert(0, "SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY " + fields[1] + ") AS RowNumber,")
                        .append(") AS t WHERE t.RowNumber BETWEEN " + (offset + 1) + " AND " + (offset + pageSize));
            }
        }
        return sb.toString();
    }

    /**
     * 查询表字段
     *
     * @return
     */
    public static List<String> getTableField(String userDbId, String table) {
        Connection connection = DatabaseConnection.getConnection(userDbId);
        if (connection == null) {
            log.error("数据库id未加载到连接池:{}", userDbId);
            throw new MayException(CodeMsg.SQL_ERROR, "数据库id未加载到连接池:" + userDbId);
        }
        DatabaseConfig databaseConfig = DatabaseConnection.getUserDbInfo(userDbId);
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            String sql = "";
            if (databaseConfig.getDatabaseType().equals(DatabaseType.SQLSERVER)) {
                sql = CommonConstant.SQL_SQLSERVER_FIELD.replace("${tableName}", table);
            } else if (databaseConfig.getDatabaseType().equals(DatabaseType.POSTGRESQL)) {
                sql = CommonConstant.SQL_POSTGRESQL_FIELD.replace("${tableName}", table);
            }
            statement = connection.createStatement();
            resultSet = statement.executeQuery(sql);
            List<String> rowdata = new ArrayList<>();
            while (resultSet.next()) {
                rowdata.add(resultSet.getString(1));
            }
            return rowdata;
        } catch (SQLException e) {
            log.error("查询表信息失败:{}", userDbId);
            throw new MayException(CodeMsg.SERVER_ERROR, "mssql数据库查询表信息失败");
        } finally {
            try {
                statement.close();
                resultSet.close();
                connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 查询数据库下所有表
     *
     * @return
     */
    public static List<KeyValOfStrVo> getTables(String userDbId) {
        DatabaseConfig config = DatabaseConnection.getUserDbInfo(userDbId);
        Connection connection = DatabaseConnection.getConnection(config);
        List<KeyValOfStrVo> names = new ArrayList<>();
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            statement = connection.createStatement();
            if (config.getDatabaseType().equals(DatabaseType.SQLSERVER)) {
                resultSet = statement.executeQuery(CommonConstant.SQL_SQLSERVER_TABLE);
            } else if (config.getDatabaseType().equals(DatabaseType.POSTGRESQL)) {
                resultSet = statement.executeQuery(CommonConstant.SQL_POSTGRESQL_TABLE);
            }
            while (resultSet.next()) {
                String tableName = resultSet.getString(2);
                if (StrUtil.isBlank(tableName)) {
                    names.add(new KeyValOfStrVo(resultSet.getString(1), resultSet.getString(1)));
                } else {
                    names.add(new KeyValOfStrVo(resultSet.getString(1), resultSet.getString(2)));
                }
            }
        } catch (SQLException e) {
            log.error("数据库连接失败");
            throw new MayException(CodeMsg.SERVER_ERROR, "mssql数据库连接失败");
        } finally {
            try {
                statement.close();
                resultSet.close();
                connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        return names;
    }

    /**
     * 查询数据库列表
     *
     * @return
     */
    public static List<String> getDatabaseList(ExternalDataBaseDO dataBase) {
        DatabaseConfig databaseConfig = new DatabaseConfig(dataBase);
        List<String> names = new ArrayList<>();
        Connection connection = DatabaseConnection.getConnection(databaseConfig);
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            statement = connection.createStatement();
            if (databaseConfig.getDatabaseType().equals(DatabaseType.POSTGRESQL)) {
                resultSet = statement.executeQuery(CommonConstant.SQL_POSTGRESQL_DBNAME);
            } else {
                resultSet = statement.executeQuery(CommonConstant.SQL_SQLSERVER_DBNAME);
            }
            while (resultSet.next()) {
                //从第一列获取库名
                names.add(resultSet.getString(1));
            }
        } catch (SQLException e) {
            log.error("数据库连接失败", e);
            throw new MayException(CodeMsg.SERVER_ERROR, "数据库连接失败:" + e.getMessage());
        }
        return names;
    }

}

6 管理连接池的初始化和销毁

项目启动时从持久化数据库内读取已添加的数据库

@Component
@Log4j2
public class StartUpHook implements ApplicationListener<ContextRefreshedEvent> {
  
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        log.info("从mongodb读取并初始化外部数据源");
        List<ExternalDataBaseDO> dataList = 从业务数据库查询出所有用户数据库表;
        if (CollectionUtil.isEmpty(dataList)) {
            return;
        }
        for (ExternalDataBaseDO mydatasource : dataList) {
            boolean res = DatabaseConnection.addConnection(mydatasource);
            if (!res) {
                log.error("数据库连接失败:{}:{}-{}", mydatasource.getHost(), mydatasource.getPort(), mydatasource.getDataBaseName());
            }
        }
    }
}

项目关闭时释放所有连接

@Component
public class ShutdownHook implements ApplicationListener<ContextClosedEvent> {

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        System.out.println("已收到停机请求,请耐心等待线程处理完工作");
        //循环遍历所有连接池,释放所有连接
        for (ConnectionPool connectionPool : DatabaseConnection.getAllConnectionPools()) {
            try {
                connectionPool.getDataSource().close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

springboot非配置实现动态多数据库查询 的相关文章

随机推荐

  • iOS检测网络连接状态

    请从Apple网站下载示例 点此下载 然后将Reachability h 和 Reachability m 加到自己的项目中 并引用 SystemConfiguration framework 就可以使用了 Reachability 中定义
  • Java 23种设计模式的分类和使用场景

    听说过GoF吧 GoF是设计模式的经典名著Design Patterns Elements of Reusable Object Oriented Software 中译本名为 设计模式 可复用面向对象软件的基础 的四位作者 他们分为是 E
  • Qt创建的子线程不断循环,主线程界面一直处于无响应状态

    说明 今天用子线程处理数据 但只创建了子线程 还没有来得及让子线程处理大量的数据 在子线程只作了简单处理 发现主线程界面一直不能响应 在主线程让子线程参数isStop true 也跳不出循环 while isStop emit mySign
  • KCF追踪器在opencv和RM中的应用

    1 理论部分 参考文档 KCF目标跟踪方法分析与总结 概念 1 判别式模型和生成式模型 判别式模型 根据训练数据得到分类函数和分界面 比如说根据SVM模型得到一个分界面 然后直接计算条件概率 P y x 我们将最大的 P y x 生成式模型
  • 模拟登陆 Selenium

    模拟登陆 使用爬虫实现登录操作 为何需要做模拟登陆 有些平台只有登录之后才可以访问其内部其他的子页面 如何实现模拟登陆 模拟点击登录按钮发起的请求即可 阻力 验证码的识别 验证码识别 使用线上的打码平台进行各种各样验证码的识别 不包含滑动验
  • eclipse创建动态web项目

    1 打开eclipse 2 依次选择File new Dynamic Web Project 点击new如果没有Dynamic Web Project 选择Other 3 在wizards下输入web 在下面的选框中选择 Dynamic W
  • 前端(十七)——gitee上开源一个移动端礼盒商城项目(前端+后台)

    博主 小猫娃来啦 文章核心 gitee上开源一个移动端礼盒商城项目 文章目录 前言 开源地址 项目运行命令 项目基本展示 前端效果细节展示视频 前端代码细节展示视频 后台效果展示 后台代码展示 经典优势 思维导图 实现思路 前言 项目样式老
  • 养娃探索记录

    0 3岁 搞好身体 3 6岁 培养好生活习惯 6 9岁 培养好学习习惯 9 12岁 培养自学能力 12 15岁 了解三百六十行 不同行业不同职业都是做什么的 15 18岁 确定未来发展方向和人生目标 自我决定理论对我们的教育有着重要的指导作
  • Anaconda安装Pytorch,以及在Pycharm中的配置

    Anaconda安装Pytorch 以及在Pycharm中的配置 有关Anaconda安装请移步安装Anaconda Python3 9 Tensorflow pytorch cpu 打开Anaconda Prompt 创建环境 这个步骤同
  • 八大排序(二)-----堆排序

    基本思想 1 将带排序的序列构造成一个大顶堆 根据大顶堆的性质 当前堆的根节点 堆顶 就是序列中最大的元素 2 将堆顶元素和最后一个元素交换 然后将剩下的节点重新构造成一个大顶堆 3 重复步骤2 如此反复 从第一次构建大顶堆开始 每一次构建
  • JavaScript 浅层克隆和深度克隆

    文章目录 JS 浅层克隆和深度克隆 1 相关知识点 2 浅层克隆 2 1 浅克隆函数 2 2 运用实例 3 深度克隆 3 1 深克隆步骤分析 3 2 深克隆函数 3 3 运用实例 3 4 hasOwnProperty JS 浅层克隆和深度克
  • 【自学笔记】后端01_Web服务器01_Tomcat

    一 web服务器 web服务器是一个应用程序 对HTTP协议的操作进行封装 使得程序员不必直接对协议进行操作 简化开发 主要功能是 提供网上信息浏览服务 说人话 服务器的作用就是封装了HTTP协议操作简化网站部署 让人可以在浏览器访问部署的
  • 【Leetcode笔记】重复的子字符串

    Leetcode原题链接 重复的子字符串 零 前情提要 自己写了个暴力法 LeetCode的一个巨长的字符串测试 然后嫌弃我时间太长了没通过 先放这里了 自己测试着没什么问题 代码 class Solution def repeatedSu
  • python基础:基本数据类型:整数,浮点数,字符串

    基本数据类型介绍 整数 浮点数 字符串 在介绍之前先来了解一个python在终端窗口运行的解释器 在你安装好python之后 运行终端命令行 输入python后回车 你会看到你的python版本以及提示信息和 gt gt gt 等待输入 你
  • 安卓刷机之pixel

    刷机记录 提示 本例子是pixel sailfish 刷rom 提示 刷rom及刷容比较简单一点 1 首先去谷歌的官网去下载手机对应的机型 2 下载刷机工具platform tools zip github地址好像404了 不过下载的地方很
  • windows 64位 apachect2.4+php5.5 无法加载php_curl模块

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 环境 windows2008 64位 单独装了apachect2 4 php5 5版本 今天服务迁移后 一直报call to undefined function curl
  • unity的触摸类touch使用

    这篇博文将简单的记录 如何用unity处理在移动设备上的触控操作 iOS和Android设备能够支持多点触控 在unity中你可以通过Input touches属性集合访问在最近一帧中触摸在屏幕上的每一根手指的状态数据 简单的触控响应实现起
  • Android studio单击按钮时,提示文字信息,并实现图片的显示

    MainActivity2 java package com example myapplication one import android media Image import android os Bundle import andr
  • 2021年计算机专业研究生分数线,2021年计算机科学与技术在职研究生分数线是多少?...

    计算机科学与技术为时下热门专业 国内对此专业人才的需求量比较大 薪资待遇也是比较不错的 国内有多所院校开设了此专业在职研课程班 但有学员对其分数线不了解 那么2021年计算机科学与技术在职研究生分数线是多少 据了解得知 就读计算机科学与技术
  • springboot非配置实现动态多数据库查询

    1需求 数据库配置信息不能在项目代码中配置或写死 系统能接入用户配置的数据库并保存和读取 每个用户可添加多个数据库 不同数据库类型 不同host 多个用户可添加相同的一个数据库 同一个数据库只创建一个连接池 数据库类型差异对业务逻辑透明 2