数据生成平台-02用户配置数据源的使用

1. 问题背景

利用业余时间开发已经过了一个月了,目前完成了设计的前半部分:数据源管理、数据源分组管理、数据集管理 。当然还只是初步功能实现,并且只支持MYSQL数据库。

记录下这个月碰到的问题:

数据库连接信息来自用户配置,若依框架用的是MyBatis。使用什么方式连接数据源成了最先面对的问题。
因此考虑了两个方案:

  1. 模拟使用MyBatis,切换数据源。并动态生成Mapper文件注册到MyBatis中完成入参和出参的映射。做过各种尝试失败了。
  2. 使用JDBC完成。但是复杂的字符串拼接工作低级低效。在前面MyBatis文章 MyBatis08-《通用源码指导书:MyBatis源码详解》笔记-JDBC和cache包 中有介绍SqlRunner类。比较完美的解决了我的需求。

注意:这里说的动态数据源不是在配置文件中 预先配置好数据源,再通过手动或者注解切换数据源完成数据的CRUD操作。这里的动态数据源是指:根据用户配置数据链接和账号信息,读取该数据库所有表结构,并动态读取数据

下面就对SqlRunner在项目中的使用做个介绍:

2.先写一个简单的SqlRunner示例


    public static void main(String[] args) throws SQLException {
//        url: jdbc:mysql://127.0.0.1:3306/data_generation?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
//        username: root
//        password: naruto1012
        DResource dResource = new DResource();
        dResource.setResourcePassword("naruto1012");
        dResource.setResourceUsername("root");
        dResource.setResourceName("数据生成平台测试SqlRunner用法");
        dResource.setResourceUrl("127.0.0.1");
        dResource.setResourcePort("3306");
        dResource.setResourceDatabase("data_generation");
        SqlRunner sqlRunner = getSqlRunner(dResource);
        //测试连接是否可用
        Map<String, Object> objectMap = sqlRunner.selectOne(testSqlStaic);
        System.out.println("测试连接查询:"+JSON.toJSONString(objectMap));
        //写一个简单的查询  不用遵循 select from where group order limit 顺序可随意拼接
        SQL sql = new SQL();
        String[] columnArr = {"resource_id,resource_name,resource_desc"};
        sql.SELECT(columnArr).LIMIT(2);
        sql.WHERE("resource_type=?");
        sql.FROM("d_resource")  ;
        List<Map<String, Object>> maps = sqlRunner.selectAll(sql.toString(), 1);
        System.out.println("查d_resource表:"+JSON.toJSONString(maps));
    }
    private final static String testSqlStaic = "select 1 from dual";

    /**
     * 创建SqlRunner
     * @param dResource
     * @return
     */
    public static SqlRunner getSqlRunner(DResource dResource) {
        try {
            return new SqlRunner(getDataSource(dResource).getConnection());
        } catch (SQLException e) {
            logger.error("数据源[{}]链接失败!",dResource.getResourceName(),e);
            removeCache(dResource.getResourceId());
            throw new ServiceException("数据源["+dResource.getResourceName()+"]链接失败!");
        }
    }

    /**
     * 创建dataSource
     * @param resource
     * @return
     */
    private static DataSource getDataSource(DResource resource){
        String url = "jdbc:%s://%s:%s/%s?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true";
        url = String.format(url,"mysql", resource.getResourceUrl(), resource.getResourcePort(), resource.getResourceDatabase());
        PooledDataSource dataSource = new PooledDataSource(
                "com.mysql.cj.jdbc.Driver",
                url,
                resource.getResourceUsername(),
                resource.getResourcePassword());
        dataSource.setLoginTimeout(3);
        dataSource.setDefaultNetworkTimeout(10000);
        return dataSource;
    }

这里需要一个DResource实体类:

package com.huazige.system.domain;

import com.huazige.common.annotation.Excel;
import com.huazige.common.core.domain.BaseEntity;
/**
 * 数据源对象 d_resource
 *
 * @author huazige
 * @date 2023-07-23
 */
public class DResource
{
    private static final long serialVersionUID = 1L;

    /**  */
    private String resourceId;

    /** 数据源名 */
    @Excel(name = "数据源名")
    private String resourceName;

    /** 数据源描述 */
    @Excel(name = "数据源描述")
    private String resourceDesc;

    /** 数据源类型:1 mysql 2 oracle */
    @Excel(name = "数据源类型:1 mysql 2 oracle")
    private Long resourceType;

    /** 数据源url */
    @Excel(name = "数据源url")
    private String resourceUrl;
    /** 数据源数据库 */
    @Excel(name = "数据源数据库")
    private String resourceDatabase;

    /** 数据源端口 */
    @Excel(name = "数据源端口")
    private String resourcePort;

    /** 数据源账号 */
    @Excel(name = "数据源账号")
    private String resourceUsername;

    /** 数据源密码 */
    @Excel(name = "数据源密码")
    private String resourcePassword;

    /** 0正常 1停用 */
    @Excel(name = "0正常 1停用")
    private String state;

    /** 0正常 1删除 */
    @Excel(name = "0正常 1删除")
    private String del;

运行结果:

11:39:09.936 [main] DEBUG org.apache.ibatis.logging.LogFactory - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
11:39:10.017 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
11:39:10.824 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 961712517.
测试连接查询:{"1":1}
查d_resource表:[{"RESOURCE_ID":1,"RESOURCE_NAME":"local-mysql-data-generation","RESOURCE_DESC":"本地连接mysql-data-generation"},{"RESOURCE_ID":2,"RESOURCE_NAME":"localhost","RESOURCE_DESC":"本地mysql"}]

从结果上看。SqlRunner配合SQL类可以非常简单的解决原始JDBC复杂的sql语句拼接和数据连接connection繁杂的开启关闭。并且参数的拼接也非常简单。

List<Map<String, Object>> maps = sqlRunner.selectAll(sql.toString(), 1);

这里的最后一个参数’1’就是传入的参数,对应

sql.WHERE("resource_type=?");

基于此,再来介绍’数据生成平台’中对SqlRunner和SQL的应用

3.'数据生成平台’中对SqlRunner和SQL的应用

package com.huazige.system.executor;

import com.huazige.common.core.domain.AjaxResult;
import com.huazige.common.enums.DisableStatus;
import com.huazige.common.exception.ServiceException;
import com.huazige.common.utils.StringUtils;
import com.huazige.system.domain.DResource;

import java.sql.SQLException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

import javax.sql.DataSource;

import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.jdbc.SqlRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @ClassName Execute
 * @Description sql执行
 * @Author Hzg
 * @Date 2023/8/5 17:48
 * @Version 1.0
 */
public class SQLExecutor {

    private static final Logger logger = LoggerFactory.getLogger(SQLExecutor.class);
    private static ConcurrentHashMap<String, SQLExecutor> executeMap = new ConcurrentHashMap<>();
    private DResource dResource ;
    private final String testSql = "select 1 from dual";
    /**
     * 是否缓存数据源
     */
    private Boolean cache;
    private SQLExecutor(DResource dResource,Boolean cache) {
        this.dResource=dResource;
        this.cache =cache;
    }
    public static void removeCache(String resourceId){
        if(executeMap.contains(resourceId)){
            executeMap.remove(resourceId);
        }
    }
    public static SQLExecutor getInstance(DResource dResource,Boolean cache){
        cache = cache==null?false:cache;
        if(dResource==null || StringUtils.equals(DisableStatus.DISABLE.getCode(),dResource.getState())){
            logger.error("获取sql执行器失败!dResource不能为空!");
            throw new ServiceException("获取sql执行器失败!数据源为空或已停用!");
        }
        if(cache){
            SQLExecutor sqlExecute = executeMap.get(dResource.getResourceId());
            if(sqlExecute !=null){
                return sqlExecute;
            }
        }
        ReentrantLock lock = new ReentrantLock();
        lock.lock();
        SQLExecutor execute = new SQLExecutor(dResource,cache);
        if(cache){
            executeMap.put(dResource.getResourceId(),execute);
        }
        lock.unlock();
        return execute;
    }

    public AjaxResult testConnection() {
        try {
            getSqlRunner().selectOne(testSql);
        } catch (SQLException e) {
            logger.error("数据源[{}]链接测试失败!",dResource.getResourceName());
            removeCache(dResource.getResourceId());
            throw new RuntimeException("链接失败!",e);
        }
        return AjaxResult.success("测试成功");
    }

    private DataSource getDataSource(){
        String url = "jdbc:%s://%s:%s/%s?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true";
        url = String.format(url,"mysql", dResource.getResourceUrl(), dResource.getResourcePort(), dResource.getResourceDatabase());
        PooledDataSource dataSource = new PooledDataSource(
            "com.mysql.cj.jdbc.Driver",
            url,
            dResource.getResourceUsername(),
            dResource.getResourcePassword());
        dataSource.setLoginTimeout(3);
        dataSource.setDefaultNetworkTimeout(10000);
        return dataSource;
    }


    public SqlRunner getSqlRunner() {
        try {
            return new SqlRunner(getDataSource().getConnection());
        } catch (SQLException e) {
            logger.error("数据源[{}]链接失败!",dResource.getResourceName(),e);
            removeCache(dResource.getResourceId());
            throw new ServiceException("数据源["+dResource.getResourceName()+"]链接失败!");
        }
    }
}

最后 配合org.apache.ibatis.jdbc.SQL 类,基本解决动态查询问题:

package com.huazige.system.executor;

import com.huazige.common.core.page.PageDomain;
import com.huazige.common.enums.DataSetConvertType;
import com.huazige.system.domain.DDataSet;
import com.huazige.system.domain.DDataSetMeta;
import com.huazige.system.domain.DResource;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.ibatis.jdbc.SQL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @ClassName SQLAssembleStrategy
 * @Description
 * @Author Hzg
 * @Date 2023/8/13 14:15
 * @Version 1.0
 */
public abstract class SQLAssembleTemplate {
    private static final Logger logger = LoggerFactory.getLogger(SQLAssembleTemplate.class);

    private List<DDataSetMeta> dataSetMetas;
    private DDataSet dataSet;
    private DResource resource;
    private String dataBaseType;

    private SQL sql = new SQL();

    public SQLAssembleTemplate(List<DDataSetMeta> dataSetMetas, String dataBaseType,DDataSet dataSet,DResource resource) {
        this.dataSetMetas = dataSetMetas;
        this.dataBaseType = dataBaseType;
        this.dataSet = dataSet;
        this.resource=resource;
    }

    private String getQueryColumnSql(DDataSetMeta dataSetMeta) {
        DataSetConvertType dataSetConvertType = DataSetConvertType.getByType(dataSetMeta.getConvertType());
        String columnSql = "";
        switch (dataSetConvertType) {
        case CUSTOM:
            columnSql =  dataSetMeta.getExpression();
            break;
        case CONVERT:
            columnSql =  typeConvert(dataSetMeta);
            break;
        case PROTOGENESIS:
            columnSql =  dataSetMeta.getSourceColumnName();
            break;
        }
        columnSql=columnSql + " AS "+dataSetMeta.getTargetColumnName();
        return columnSql;
    }

    /**
     * 列转换
     * @param dataSetMeta
     * @return
     */
    protected abstract String typeConvert(DDataSetMeta dataSetMeta);

    /**
     * 获取查询列
     * @return
     */
    private List<String> getQueryColumnSql(){
        List<String> columnList = new ArrayList<>();
        for (DDataSetMeta  dataSetMeta:dataSetMetas) {
            columnList.add(getQueryColumnSql(dataSetMeta));
        }
        return columnList;
    }
    private String getForm(){
        return dataSet.getTableName();
    }
    /**
     * 执行查询
     * @return
     */
    public List<Map<String, Object>> list() throws SQLException {
        String[] columnArray = getQueryColumnSql().toArray(new String[0]);
        sql.SELECT(columnArray).FROM(getForm());
        String sqlStr = sql.toString();
        logger.info("sql={}",sqlStr);
        return SQLExecutor.getInstance(resource,true).getSqlRunner().selectAll(sqlStr);
    }

    public List<Map<String, Object>> page(PageDomain page) throws SQLException {
        String[] columnArray = getQueryColumnSql().toArray(new String[0]);
        sql.SELECT(columnArray).FROM(getForm());

        Integer count = getCount();
        page.setTotal(count);
        if(count<1){
            return Lists.newArrayList();
        }

        if(page!=null){
            setPage(page);
        }
        String sqlStr = sql.toString();
        logger.info("sql={}",sqlStr);
        return SQLExecutor.getInstance(resource,true).getSqlRunner().selectAll(sqlStr);
    }
    private final String COUNT="COUNT";
    private Integer getCount() throws SQLException {
        SQL countSql = new SQL().SELECT("COUNT(*) "+ COUNT).FROM("("+sql.toString()+") a ");
        String countSqlStr = countSql.toString();
        logger.info("countSql={}",countSqlStr);
        Map<String, Object> objectMap = SQLExecutor.getInstance(resource, true).getSqlRunner().selectOne(countSqlStr);
        Object o = objectMap.get(COUNT);
        return o==null?0: NumberUtils.toInt(o.toString());
    }

    /**
     *设置分页查询
     */
    private void setPage(PageDomain page){
        sql.OFFSET(Math.toIntExact(page.getStartRow())).LIMIT(page.getPageSize());
    }
}

4.已实现功能截图:

4.1.连接测试:在这里插入图片描述

4.2.数据源分组

在这里插入图片描述

4.3.数据源列表

在这里插入图片描述

4.4.从数据库表创建数据源(从数据源读取表结构)

在这里插入图片描述

4.5.数据集创建(自定义转换)

在这里插入图片描述

4.6.预览数据集数据

在这里插入图片描述

5.动态数据源剩下的问题:

剩下的问题就是解析查询结果,映射到实体类了。MyBatis花了大力气解决此问题,当然MyBatis需要兼容多种情况。包括关联查询,一对多,多对多。而我从这个月看还没有用到要大量解析需求。后面如果有此需求再去探索下是否可以利用MyBatis已写好的解析类来解决此问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值