本章节为使用者提供最简单的统一SQL的快速入门。

2.1. 在线验证

可以通过 统一SQL在线转换 来快速验证当前统一SQL已支持的功能。

2.2. 支持技术栈

  1. 统一SQL当前支持集成在Java技术栈和C技术栈中使用

2.3. 快速集成

2.3.1. 集成前提

  1. 在LightDB官网下载统一SQL 下载

  2. 基于Java开发时,开发环境需要具备 Java 8 或更高版本的 JDK。

  3. 确定源库方言和目标库方言在统一SQL支持范围内。

  4. 统一SQL自身依赖的自定义函数需要提前在目标库中完成创建。

  5. 统一SQL代理维护在公司仓库中,需要提前联系统一SQL产品组配置仓库地址。

  6. 请确保引入最新的 sql-convert-runtimesql-convert-runtime-native 版本,否则可能存在兼容性或引入失败问题。最新版本可查看 版本发布 24.2.6

  7. 对于统一SQL Windows 版 SDK,其版本号必须和 sql-convert-runtime 版本号匹配,否则会启动失败。

2.3.2. 集成统一SQL代理

统一SQL代理是标准的Maven依赖,直接通过 pom.xml 配置即可。C技术栈集成时无需此步骤。

<dependency>
    <groupId>com.hundsun.lightdb</groupId>
    <artifactId>sql-convert-runtime</artifactId>
    <version>24.2.5</version>
</dependency>

2.3.3. 集成统一SQL SDK

2.3.3.1. Linux系统集成

Linux版统一SQL SDK是标准的Maven依赖,直接通过 pom.xml 配置即可。

<dependency>
    <groupId>com.hundsun.lightdb</groupId>
    <artifactId>sql-convert-runtime-native</artifactId>
    <version>24.2.5</version>
</dependency>

2.3.3.2. Windows系统集成

  1. 获取统一SQL Windows版SDK

  2. Java程序启动时,通过 -Dunisql.lib.full-path=统一SQL SDK绝对路径 的方式进行集成。

2.3.3.3. C技术栈集成

  1. 将统一SQL SDK存放至系统能加载的链接库路径下

  2. 或者通过环境变量 export unisql_lib_dir=/path/to/libdir 来指定统一SQL SDK所在目录

警告

注意windows环境动态库只能用于开发联调, 测试, 验证,不能用于正式环境, 禁止 在生产使用。

2.3.4. 导入统一SQL脚本到目标库中

由于部分目标方言的特性缺失,统一SQL通过自定义数据库函数的方式来模拟源方言的功能,需要用户将该部分脚本导入到目标库中。

警告

  1. SQL 脚本中的自定义函数默认都是建立在 unisql 的schema或database下,使用前注意目标库中禁止存在重名的schema或database。

  2. 在脚本导入数据库之前,可以通过sql目录下的custom_schema.sh脚本,修改自定义函数所在的schema或database,并配合配置项: unisql.schema 一起使用。

SQL脚本包含在官网制品中的 sql 目录中,找到对应的 来源2目标 目录,执行里面的 SQL 脚本即可。

2.3.4.1. PostgreSQL 目标库脚本

脚本目录为 oracle2postgresql ,需要通过具有 CREATE 权限的用户来执行。

2.3.4.2. MySQL 目标库脚本

脚本目录为 oracle2mysql ,建议使用超级用户执行。或者目标用户对 unisql.* 下的数据库对象拥有以下权限:

  • CREATE

  • EXECUTE

  • GRANT OPTION

  • CREATE ROUTINE

  • ALTER ROUTINE

  • SELECT

  • UPDATE

  • INSERT

2.3.4.3. Gaussdb-Oracle 目标库脚本

脚本目录为 oracle2gaussdb-oracle ,需要通过具有 CREATE,GRANT 权限的用户来执行。

2.3.4.4. TDSQL-PostgreSQL(Oracle模式) 目标库脚本

脚本目录为 oracle2tdsql-pg-oracle ,需要通过具有 CREATE,GRANT 权限的用户来执行。

2.3.5. 在项目中使用统一SQL

2.3.5.1. 直接调用统一SQL的转换API

统一SQL代理暴露了转换API,用户可以直接调用该API完成统一SQL转换,方法说明如下:

public static String parse(String sql, String sourceDialect, String targetDialect)

注意

  • sql 源方言SQL语句,表示需要转换的SQL语句;

  • sourceDialect 源方言,当前支持oracle和mysql;

  • targetDialect 目标方言,参见 SQL参考

示例代码

@Test
public void testTransform(){
    // 源SQL语句
    String sql = "select client_id as inner_client_id, client_id, EXTRACT(DAY FROM to_timestamp(to_char(20230823),'yyyymmdd')-to_timestamp(to_char(id_end_date),'yyyymmdd')) as remarks  from hsamlbd.amlbd_ins_client where client_type in ('1','2') and to_date(id_end_date,'yyyymmdd') < to_date(20230823,'yyyymmdd') and id_end_date <> '19000101'";
    // 源方言
    String sourceDialect = DbType.ORACLE.name();
    // 目标方言
    String targetDialect = DbType.POSTGRESQL.name();
    System.out.printf("Before transfer sql is:%s \n",sql);
    String parse  = Transformer.parse(sql, sourceDialect,targetDialect );
    System.out.printf("After transfer sql is:%s \n",parse);
}

// 运行结果
Before transfer sql is:select client_id as inner_client_id, client_id, EXTRACT(DAY FROM to_timestamp(to_char(20230823),'yyyymmdd')-to_timestamp(to_char(id_end_date),'yyyymmdd')) as remarks  from hsamlbd.amlbd_ins_client where client_type in ('1','2') and to_date(id_end_date,'yyyymmdd') < to_date(20230823,'yyyymmdd') and id_end_date <> '19000101'
Aefore transfer sql is:SELECT client_id AS inner_client_id,client_id,EXTRACT(DAY FROM to_timestamp(CAST(20230823 AS text), 'yyyymmdd')-to_timestamp(CAST(id_end_date AS text), 'yyyymmdd')) AS remarks FROM hsamlbd.amlbd_ins_client WHERE client_type IN ('1','2') AND CAST(to_timestamp(id_end_date, 'yyyymmdd') AS timestamp)<CAST(to_timestamp(20230823, 'yyyymmdd') AS timestamp) AND id_end_date<>'19000101'

2.3.5.2. 通过原生JDBC的方式使用

注意

  • jdbc url 必须以 jdbc:unisql: 开头,表示使用统一SQL代理;

  • 连接参数中的 sourceDialect 表示源方言,以下示例是 oracle

  • 连接参数中的 targetDialect 表示目标方言,以下示例是 postgresql

public static final String URL = "jdbc:unisql:postgresql://10.20.30.40:5432/test?sourceDialect=oracle&targetDialect=postgresql";
public static final String USER = "user";
public static final String PASSWORD = "password";

@Test
void testJdbc() {
    try {
        Class.forName("org.postgresql.Driver");
        Class.forName("com.hundsun.lightdb.unisql.proxy.Driver");
        Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
        Statement stmt = conn.createStatement();
        // 原生oracle sql语句
        String oracleSQL = "select nation, listagg(city, ',') within GROUP (order by city1, city2 desc) from temp group by nation";
        // 返回postgresql执行结果
        ResultSet rs = stmt.executeQuery(oracleSQL);
        while (rs.next()) {
            System.out.println(rs.getString("nation"));
        }
        conn.close();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

@Test
void testJdbcPrepareStatement() {
    try {
        Class.forName("org.postgresql.Driver");
        Class.forName("com.hundsun.lightdb.unisql.proxy.Driver");
        Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
        Random random = new Random();
        PreparedStatement pstmt = conn.prepareStatement("insert into t(col) values (?)");
        int i = random.nextInt();
        pstmt.setInt(1, i);
        Statement stmt = conn.createStatement();
        pstmt.executeUpdate();
        log.info("开始随机向t表插入:{}", i);
        conn.close();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

2.3.5.3. 基于JRESCloud3.X开发框架 + JdbcTemplate使用

  1. 如何集成jrescloud微服务可参考 微服务开发手册

  2. 配置统一SQL数据源

    spring.datasource.driver-class-name=com.hundsun.lightdb.unisql.proxy.Driver
    spring.datasource.url=jdbc:unisql:postgresql://10.20.30.40:5432/test?sourceDialect=oracle&targetDialect=postgresql
    spring.datasource.username=user
    spring.datasource.password=password
    
  3. 通过JdbcTemplate使用统一SQL

    @Autowired
    JdbcTemplate jdbcTemplate;
    
    @Test
    void testSpringboot() {
        // 原生oracle sql语句
        String oracleSQL = "select nation, listagg(city, ',') within GROUP (order by city1, city2 desc) from temp group by nation";
        // 返回postgresql执行结果
        List<Map<String, Object>> list = jdbcTemplate.queryForList(oracleSQL);
    }
    

2.3.5.4. 基于JRESCloud3.X开发框架 + mybatis + 多数据源使用

  1. 如何集成jrescloud微服务可参考 微服务开发手册

  2. 根据手册集成多数据源依赖

<dependency>
    <groupId>com.hundsun.jrescloud.middleware</groupId>
    <artifactId>jrescloud-starter-mybatis</artifactId>
</dependency>
  1. 使用多数据源注解@EnableCloudDataSource开启多数据源

/**
* 多数据源注解@EnableCloudDataSource,开启多数据源功能
*/
@EnableCloudDataSource
@CloudApplication // 启动类注解
public class UnisqlMultiDataSourceApplication {

    public static void main(String[] args) {
        CloudBootstrap.run(UnisqlMultiDataSourceApplication.class, args);
    }
}
  1. 配置多数据源并使用统一SQL格式的jdbcUrl

hs.druid.validationQuery=select 1

hs.datasource.default.driverClassName=org.postgresql.Driver
hs.datasource.default.url=jdbc:postgresql://10.20.30.40:5432/test
hs.datasource.default.username=user
hs.datasource.default.password=password


hs.datasource.mysql.driverClassName=com.mysql.jdbc.Driver
hs.datasource.mysql.url=jdbc:mysql://10.20.30.40:3306/test?useSSL=false&serverTimezone=UTC
hs.datasource.mysql.username=user
hs.datasource.mysql.password=password

hs.datasource.unisql.driverClassName=com.hundsun.lightdb.unisql.proxy.Driver
hs.datasource.unisql.url=jdbc:unisql:postgresql://10.20.30.40:5432/test?sourceDialect=oracle&targetDialect=postgresql
hs.datasource.unisql.username=user
hs.datasource.unisql.password=password
  1. 使用数据源指定注解@TargetDataSource指定当前服务类或服务方法所使用的数据源

public interface TestMapper {
    @Select("SELECT nation,STRING_AGG(city, ',' ORDER BY city1,city2 DESC) FROM temp GROUP BY nation")
    List<Map<String, Object>> queryForList();

    @TargetDataSource("unisql") // 指定数据源unisql,该数据源将会使用统一SQL
    @Select("select nation, listagg(city, ',') within GROUP (order by city1, city2 desc) from temp group by nation")
    List<Map> queryForListUsingUnisql();

    @TargetDataSource("unisql")
    List<Map> queryListForUnisql();
}
  1. 基于mybatis,指定数据源unisql的mapper接口方法queryListForUnisql对应的SQL映射文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

    <mapper namespace="com.example.unisql.multi.db.mapper.TestMapper">

    <select id="queryListForUnisql" resultType="map">
        select nation, listagg(city, ',') within GROUP (order by city1, city2 desc) from temp group by nation;
    </select>
</mapper>
  1. 测试mapper接口方法,确定是否使用了统一SQL

@SpringBootTest
public class UnisqlMybatisTest {
    @Autowired
    private TestMapper testMapper;

    @Test
    void testMybatisUnisql() {
        List<Map> list = testMapper.queryForListUsingUnisql();
        Assert.isTrue(!CollectionUtils.isEmpty(list));
    }

    @Test
    void testMybatisListUnisql() {
        List<Map> list = testMapper.queryListForUnisql();
        Assert.isTrue(!CollectionUtils.isEmpty(list));
    }
}

2.3.5.5. C技术栈:通过引入统一SQL动态库头文件来使用统一SQL的API

#include <stdio.h>
// 通过引入统一SQL动态库头文件来使用统一SQL的API
#include <unisql.linux.x86_64.h>

int main() {
    // 原始SQL数据库类型
    int sourceDbTypeCode=5;
    // 目标SQL数据库类型
    int targetDbTypeCode=13;
    // 原始SQL
    char* sourceSqlStr="select * from `table` where a=? and b=?";
    // 目标SQL
    char* targetSqlStr=NULL;
    // 动态SQL参数的json字符串:如果用到对比服务时,需要传值。dbName:数据库唯一标识,sqlParameters:SQL预编译后绑定变量的值
    char* jsonParameter = "{\"dbName\":\"usersvr\",\"sqlParameters\":\"[[1$$'2']]\"}";
    // 转换结果 0表示成功,>0表示目标串长度不够,<0表示失败
    int transRet=0;
    // 预设目标SQL的长度为原始SQL的4倍
    int targetSqlStrLen = strlen(sourceSqlStr)*4;
    // 分配内存
    targetSqlStr=(char*)malloc(targetSqlStrLen);

    printf("Before transfer sql is:%s\n",sourceSqlStr);
    // 调用TransferSQL转换函数
    transRet = TransferSQL(sourceDbTypeCode, targetDbTypeCode, sourceSqlStr, targetSqlStr, targetSqlStrLen, jsonParameter);
    if (transRet>0){
        // 预设目标SQL的长度不够,重新分配内存
        targetSqlStr=(char*) realloc (targetSqlStr, transRet) ;
        transRet =TransferSQL (sourceDbTypeCode, targetDbTypeCode, sourceSqlStr, targetSqlStr, transRet, jsonParameter);
    }
    if(transRet == 0)
    {
        printf("After transfer sql is:%s\n",targetSqlStr);
    }else if(transRet < 0)
    {
        printf("SQL Convert Failed:%d\n",transRet);
    }

    // 释放内存
    free(targetSqlStr);
    Sleep(2000);
    return 0;
}

// 运行结果
Before transfer sql is:select * from `table` where a=? and b=?
After transfer sql is:SELECT * FROM `table` WHERE `a`=? AND `b`=?

// 编译加入 -ldl -I -L 动态库完整路径
// gcc -ldl main.c -o main -I./include -L./lib ./lib/unisql.linux.x86_64.so
// export LD_LIBRARY_PATH=动态库路径:$LD_LIBRARY_PATH

2.3.5.6. C技术栈:在CRES中使用统一SQL

//类的头文件
#ifndef UNISQL_HPP_
#define UNISQL_HPP_

#include <dlfcn.h>
using namespace std;

typedef char* (*Transform_)(char* sourceSQL, char* sourceDialect, char* targetDialect);

class Unisql {

public:
    Unisql()
    {
        libHandle = dlopen("./unisql.linux.x86_64.so", RTLD_LAZY);

        /*根据动态链接库操作句柄与符号,返回符号对应的地址*/
        trsnsform = (Transform_)dlsym(libHandle, "Transform");
    }
    ~Unisql()
    {
        dlclose(libHandle);
    }

private:
    bool  isInit = false;
    void* libHandle;
    Transform_ trsnsform;

public:
    char* Transform(char* sourceSQL, char* sourceDialect, char* targetDialect);
};

#endif /* UNISQL_HPP_ */

//类的实现文件
#include "Unisql.hpp"
#include <iostream>

char* Unisql::Transform(char* sourceSQL, char* sourceDialect, char* targetDialect)
{
    std::cout << "Before transfer sql is:" << sourceSQL << std::endl;

    char* result = (*trsnsform)(sourceSQL, sourceDialect, targetDialect);

    std::cout << "Aefore transfer sql is:" << result << std::endl;
}



//调用示例:
#include <iostream>
#include "Unisql.hpp"

using namespace std;

int main(int argc, char *argv[])
{
    Unisql *uniSql = new Unisql();


    char* csql = "select client_id as inner_client_id, client_id, EXTRACT(DAY FROM to_timestamp(to_char(20230823),'yyyymmdd')-to_timestamp(to_char(id_end_date),'yyyymmdd')) as remarks from hsamlbd.amlbd_ins_client where client_type in ('1','2') and to_date(id_end_date,'yyyymmdd') < to_date(20230823,'yyyymmdd') and id_end_date <> '19000101'";

    char* sourceDialect = "ORACLE";

    char* targetDialect = "POSTGRESQL";

    uniSql->Transform(csql, sourceDialect, targetDialect);

    delete uniSql;


    return 0;
}

//运行结果
Before transfer sql is:select client_id as inner_client_id, client_id, EXTRACT(DAY FROM to_timestamp(to_char(20230823),'yyyymmdd')-to_timestamp(to_char(id_end_date),'yyyymmdd')) as remarks  from hsamlbd.amlbd_ins_client where client_type in ('1','2') and to_date(id_end_date,'yyyymmdd') < to_date(20230823,'yyyymmdd') and id_end_date <> '19000101'
Aefore transfer sql is:SELECT client_id AS inner_client_id,client_id,EXTRACT(DAY FROM to_timestamp(CAST(20230823 AS text), 'yyyymmdd')-to_timestamp(CAST(id_end_date AS text), 'yyyymmdd')) AS remarks FROM hsamlbd.amlbd_ins_client WHERE client_type IN ('1','2') AND CAST(to_timestamp(id_end_date, 'yyyymmdd') AS timestamp)<CAST(to_timestamp(20230823, 'yyyymmdd') AS timestamp) AND id_end_date<>'19000101'