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

2.1. 前提条件

开发环境需要具备 Java JRE 8 或更高版本。

<dependency>
    <groupId>io.github.hslightdb</groupId>
    <artifactId>sql-convert-runtime</artifactId>
    <version>${latest.release.version}</version>
</dependency>

2.2. 配置动态库

统一SQL Java SDK 依赖动态库,目前可通过两种方式之一来引用动态库:

  • 引用 Maven 依赖 sql-convert-runtime-native,配置正确的 classifier

  • 通过环境变量 + 动态库文件引入动态库

几种方式的优先级为:

  1. 若指定了启动参数 unisql.lib.full-path ,则最优先使用

  2. 再尝试从 jar 包(即 Maven 依赖)中获取动态库

  3. 最后尝试通过启动参数 unisql.lib.dir 寻找动态库

以下介绍各种方法的操作方式。

2.2.1. 引入 sql-convert-runtime-native

为方便使用,目前已将统一 SQL 的动态库二进制文件打包到 Jar 中,发布到了 Maven 中央仓库。

引用方法:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <!-- ... -->

    <properties>
        <!-- ... -->
        <sql-convert-runtime.version>最新版本</sql-convert-runtime.version>
        <sql-convert-runtime.classifier>linux</sql-convert-runtime.classifier>
    </properties>

    <!-- ... -->

    <dependencies>
        <!-- ... -->

        <dependency>
            <groupId>io.github.hslightdb</groupId>
            <artifactId>sql-convert-runtime</artifactId>
            <version>${sql-convert-runtime.version}</version>
        </dependency>
        <dependency>
            <groupId>io.github.hslightdb</groupId>
            <artifactId>sql-convert-runtime-native</artifactId>
            <version>${sql-convert-runtime.version}</version>
            <classifier>${sql-convert-runtime.classifier}</classifier>
        </dependency>

        <!-- ... -->
    </dependencies>

    <!-- ... -->
</project>

备注

注意 sql-convert-runtime-native 不能替代 sql-convert-runtime,两者都要引入。

其中 classifier 的取值包括:

取值

包大小

说明

linux

10MB

包含 Linux 系统 x86_64 和 aarch64 平台的动态库

linux.x86_64

5MB

仅包含 Linux x86_64 的动态库,适用于对包尺寸有严格要求的用户

linux.aarch64

5MB

仅包含 Linux aarch64 的动态库,适用于对包尺寸有严格要求的用户

debug

17MB

包含 Windows 与 Mac OS 的动态库,仅适用于开发阶段

开发人员的电脑往往不是 Linux 系统,不能直接使用 classifier 为 linux 的依赖,而运行项目的服务器一般都是 Linux 系统;这导致开发人员本地与正式打包环境要使用不同的 classifier。

这个问题可采用以下办法来解决:

  1. 通过 Maven 的 <profiler> 来指定 classifier 参数

  2. 下载 dlldylib 到本地开发机,启动时指定 -D 启动参数或环境变量

以下为使用 profiler 机制的例子:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <!-- ... -->

    <properties>
        <!-- ... -->
        <sql-convert-runtime.version>最新版本</sql-convert-runtime.version>
        <sql-convert-runtime.classifier>linux</sql-convert-runtime.classifier>
    </properties>

    <!-- ... -->

    <dependencies>
        <!-- ... -->

        <!-- 添加统一 SQL 需要的 Maven 依赖 -->
        <dependency>
            <groupId>io.github.hslightdb</groupId>
            <artifactId>sql-convert-runtime</artifactId>
            <version>${sql-convert-runtime.version}</version>
        </dependency>
        <dependency>
            <groupId>io.github.hslightdb</groupId>
            <artifactId>sql-convert-runtime-native</artifactId>
            <version>${sql-convert-runtime.version}</version>
            <classifier>${sql-convert-runtime.classifier}</classifier>
        </dependency>

        <!-- ... -->
    </dependencies>

    <!-- ... -->

    <profiles>
        <profile>
            <id>dev</id>
            <properties>
                <sql-convert-runtime.classifier>debug</sql-convert-runtime.classifier>
            </properties>
        </profile>
    </profiles>

    <!-- ... -->
</project>

配置来 profile 后,若使用 Intellij IDEA 打开工程,在 Maven 侧边栏中可以看到名为 dev 的 profile ,勾上之后重刷 Maven 工程,即可使用 debug classifier,进行本地开发。

../_images/ltsql-maven-001.png

这种方式不会影响 Linux 平台的打包,对 IDE 支持良好,也方便版本更新,推荐使用。

当然您也可以按下一节提供的方法,配置 unisql.lib.full-path 参数,直接指定到 dlldylib 的路径来加载动态库。

2.2.2. 使用启动参数或环境变量

统一SQL Java SDK 依赖动态库,动态库可以在 LightDB官网LightDB中间件 -> 统一SQL 处进行下载。

注意

LightDB官网发布的统一SQL动态库压缩包内只有生产环境Linux的动态库,开发环境windows的动态库(.dll)和mac的动态库(.dylib)请从sql-convert-runtime-native的jar包中解压出来。

配置动态库目录有多种方式,客户可以选择以下任意一种方式。

  • Java应用启动参数中增加 -Dunisql.lib.dir=/path/to/libdir

  • Java应用服务器上增加环境变量 export unisql_lib_dir=/path/to/libdir

  • 将动态库复制到操作系统临时目录中(可通过 System.getProperty("java.io.tmpdir") 获取)

  • 如果有特殊情况需要强制指定动态库文件完整路径,那么可以通过设置启动参数 unisql.lib.full-path 来达到目的,如 -Dunisql.lib.full-path=/path/to/unisql.xxx.x86_64.so

2.3. 配置说明

统一 SQL Java SDK 支持一些配置形式,在本小节进行说明。

2.3.1. 系统参数

可通过系统参数(即 -D 的参数)进行配置,支持的配置如下:

参数

说明

unisql.lib.dir

指向动态库所在的目录,也可通过环境变量 unisql_lib_dir 来配置

unisql.lib.full-path

指定动态库所在完整路径,具有最高优先级,可选配置

unisql.cache.maximum-size

缓存改写sql最大条数,默认 10000条。统一 SQL 会将 SQL 转换结果进行缓存,可通过本参数配置缓存最大条数

unisql.cache.expire-seconds

缓存过期时长,默认 900。

unisql.debug

是否开启 DEBUG 模式,将打印更多的日志;默认为 false

unisql.check.postgresql

是否对目标方言为 POSTGRESQL 的库进行检查,默认为 true

unisql.check.postgresql.schema

是否对目标方言为 POSTGRESQL 的库进行 Schema 检查,默认为 true

unisql.check.mysql

是否对目标方言为 MYSQL 的库进行检查,默认为 true

unisql.error.skip

转换过程中出现任何异常,SQL保持原样透传;默认为 false

2.3.2. 环境变量

环境变量名

说明

unisql_lib_dir

指向动态库所在的目录,等同于参数 unisql.lib.dir,环境变量与参数选一种方式配置即可;都配置则以系统参数优先

unisql_lib_full_path

指定动态库所在完整路径,等同于参数 unisql.lib.full-path ,环境变量与参数选一种方式配置即可;都配置则以系统参数优先

2.3.3. JDBC 连接参数

即 JDBC 连接 URL 中 ? 后面的参数部分。

参数名

说明

sourceDialect

来源方言,必须配置。目前支持 oracle

targetDialect

目标方言,必须配置。目前支持 postgresql、lightdb_oracle、mysql、ocean_base_oracle

targetDialect 对应目标数据库方言,详细对应信息如下:

说明

postgresql

PostgreSQL13

lightdb_oracle

LightDB23.2 oracle兼容模式

mysql

MySQL5.7

ocean_base_oracle

OceanBase3.2 oracle兼容模式

2.3.4. 日志配置

统一 SQL 利用 SLF4j 打印日志,SLF4j 是目前应用最广泛的 Java 日志接口,可使用 Log4j2logback 做日志实现,对各种应用场景都有较好的支持。

统一 SQL 的日志 Logger name 前缀为 com.hundsun.lightdb.unisql

如果没有特殊需求,一般情况下不需要额外的配置。以下给出一些特殊情况的日志配置案例。

2.3.4.1. 开启 DEBUG 级别

当开启统一 SQL 的 DEBUG 日志后,每次调用数据库查询时都会打印转换日志,内容包括来源 SQL 与转换后 SQL,方便调试。

log4j2 示例:

<?xml version="1.0" encoding="utf-8"?>
<configuration status="WARN" monitorInterval="30" packages="org.apache.logging.log4j.core.pattern">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Logger name="com.hundsun.lightdb.unisql" level="DEBUG" />
        <Root level="INFO">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</configuration>

只要在 <Loggers> 中增加 <Logger name="com.hundsun.lightdb.unisql" level="DEBUG" /> 配置即可开启统一 SQL 的 DEBUG 日志,日志配置的其他部分需要结合您项目的情况自行配置。

logback 示例:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <logger name="com.hundsun.lightdb.unisql" level="DEBUG" />
    <root level="INFO">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

只要加上 <logger name="com.hundsun.lightdb.unisql" level="DEBUG" /> 这句即可开启统一 SQL 的 DEBUG 日志,日志配置的其他部分需要结合您项目的情况自行配置。

2.3.4.2. 将统一 SQL 日志写入专门的文件

如果您需要将统一 SQL 的日志写入一个专门的日志文件,也可通过 log4j2 与 logback 的配置来完成,基本原理是为指定 Logger 指定特殊的 Appender。

以下案例基于 log4j2 ,将统一 SQL 的日志写入到 /path/to/unisql.log ,日志文件每 500MB 进行压缩归档,保留 15 天日志记录,总日志大小限制为 5GB:

<?xml version="1.0" encoding="utf-8"?>
<configuration status="WARN" monitorInterval="30" packages="org.apache.logging.log4j.core.pattern">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>

        <RollingFile name="UnisqlLog" fileName="/path/to/unisql.log" filePattern="/path/to/%d{yyyy-MM}/unisql.%d{yyyy-MM-dd}.%i.log.gz">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="500M"/>
            </Policies>
            <!-- <Delete> 规则可以删除其他所有 Appender 的日志,需要留意 -->
            <DefaultRolloverStrategy max="5000">
                <Delete basePath="/path/to" maxDepth="4">
                    <IfFileName glob="*/unisql*.log.gz">
                        <IfAny>
                            <IfLastModified age="15d"/>
                            <IfAccumulatedFileSize exceeds="5GB"/>
                        </IfAny>
                    </IfFileName>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>
    </Appenders>
    <Loggers>
        <Logger name="com.hundsun.lightdb.unisql" level="DEBUG" additivity="false">
            <AppenderRef ref="UnisqlLog" />
        </Logger>
        <Root level="INFO">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</configuration>

关键点在于:

  1. <Appenders> 中要有提供给统一 SQL 日志文件的配置,这里使用了 RollingFile

  2. <Loggers> 中针对统一 SQL 包名前缀的配置

警告

一定要注意,log4j2 的 <Delete> 规则有能力删除目录下所有文件!所以一定要仔细查看 log4j2 的官方文档,配好 glob 参数,避免用错。

官方文档: https://logging.apache.org/log4j/2.x/manual/appenders.html#rollingfileappender

如果您需要在主日志与分日志中都打印统一 SQL 的日志,可设置 additivity="true"

以下案例基于 logback ,与 log4j2 功能类似:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="UnisqlLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
        <file>/path/to/unisql.log</file>
        <append>true</append>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>/path/to/%d{yyyy-MM}/unisql.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <!-- 日志文件每 500MB 进行压缩归档,保留 15 天日志记录,最大日志 5GB -->
            <maxFileSize>500MB</maxFileSize>
            <maxHistory>15</maxHistory>
            <totalSizeCap>5GB</totalSizeCap>
        </rollingPolicy>
    </appender>

    <logger name="com.hundsun.lightdb.unisql" level="DEBUG" additivity="false">
        <appender-ref ref="UnisqlLog"/>
    </logger>

    <root level="INFO">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

如果您需要在主日志与分日志中都打印统一 SQL 的日志,可设置 additivity="true"

2.4. 导入目标库脚本

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

SQL 包含在官网动态库的压缩包中的 dependency 目录,找到对应的 来源2目标 目录,执行里面的 SQL 脚本即可;动态库可以在 LightDB官网 的 LightDB中间件 -> 统一SQL 处进行下载。

2.4.1. PostgreSQL 目标脚本

脚本目录为 oracle2postgresql ,需要使用在数据库上具有 CREATE 权限的用户来执行。

2.4.2. MySQL 目标脚本

脚本目录为 oracle2mysql ,建议使用超级用户执行。如果无法使用超级用户,则需要一个对数据库对象 unisql.* 拥有以下权限的用户:

  • CREATE

  • EXECUTE

  • GRANT OPTION

  • CREATE ROUTINE

  • ALTER ROUTINE

  • SELECT

  • UPDATE

  • INSERT

对于 MySQL 数据库的特别说明:若您使用的是云服务厂商的云数据库服务,可能无法进行 CREATE DATABASE, GRANT 等操作,那么您可以采用以下步骤:

  1. 采用云服务提供的操作接口,完成数据库 unisql 的创建

  2. 采用云服务提供的操作接口,或具有对 unisql 数据库拥有上表中列出的权限的用户,执行 SQL 脚本,但不要执行 CREATE DATABASE 以及与 GrantUnisqlPermissions 相关的语句

  3. 采用云服务提供的操作接口,完成数据库 unisql 对所有用户的授权操作,至少需要 EXECUTE, SELECT, UPDATE, INSERT 权限

  4. 如果在执行创建自定义函数脚本的过程中报错:This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable) ,解决方法是确保设置MySQL的参数:log_bin_trust_function_creators=1

2.5. 编写代码

注意

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

  • sourceDialect 表示源方言,以下示例是 oracle

  • targetDialect 表示目标方言,以下示例是 postgresql

2.5.1. 原生JDBC的方式

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.5.2. SpringBoot JdbcTemplate的方式

properties 配置如下:

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

代码如下:

@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.5.3. MyBatis的方式

MyBatis 的开发方式不需要调整,数据访问层的代码基本无需改动,以下以 foo 表为例。

表结构:

CREATE TABLE foo (
    col int4,
    nnn int2
);

DAO:

@Setter
@Getter
@NoArgsConstructor
@ToString
public class Foo {
    private Integer col;

    private Integer nnn;
}

Mapper:

@Mapper
public interface FooMapper {
    Foo getByCol(Integer col);
}

mapper xml:

<?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.demo.mapper.FooMapper">
    <select id="getByCol" parameterType="int" resultType="com.example.demo.dao.Foo">
        SELECT * FROM foo WHERE col=#{col}
    </select>
</mapper>

使用:

@Autowired
FooMapper fooMapper;

@Test
void testMyBatis() {
    Foo f = fooMapper.getByCol(917500598);
    System.out.println(f.toString());
}

2.5.4. 基于JRESCloud3.X开发框架 + mybatis + 多数据源

引入JRESCloud3.X框架依赖:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.hundsun.jrescloud</groupId>
            <artifactId>jrescloud-dependencies</artifactId>
            <version>3.1.2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

引入多数据源依赖:

<dependency>
    <groupId>com.hundsun.jrescloud.middleware</groupId>
    <artifactId>jrescloud-starter-mybatis</artifactId>
</dependency>

使用多数据源注解@EnableCloudDataSource注解用于开启多数据源:

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

    public static void main(String[] args) {
        CloudBootstrap.run(UnisqlMultiDataSourceApplication.class, args);
    }
}

在配置文件src/main/resources/application.properties中配置多数据源:

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

使用数据源指定注解@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
    @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();
}

基于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>

基于指定数据源unisql,单元测试类,测试mapper接口方法:

@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.5.5. 基于JRESCloud3.X开发框架 + Mybatis的数据库厂商标识(databaseIdProvider)+ 多数据源

在配置文件src/main/resources/application.properties中配置多数据源:

mybatis.mapper-locations=classpath:mapper/**/*.xml

hs.druid.validationQuery=select 1

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.default.driverClassName=com.hundsun.lightdb.unisql.proxy.Driver
hs.datasource.default.url=jdbc:unisql:postgresql://10.20.30.40:5432/test?sourceDialect=oracle&targetDialect=postgresql
hs.datasource.default.username=user
hs.datasource.default.password=password

基于java config准备mybatis自定义配置类配置数据库厂商标识(databaseIdProvider):

@Configuration
public class MybatisConfig {
    @Bean
    public DatabaseIdProvider databaseIdProvider() {
        /**
        * 注意:基于jrescloud 3.x ,多数据源,配置文件application.properties中default数据源对应的数据库产品最终决定databaseId的取值(只作用一次)
        *  举例,default数据源为基于统一sql配置的数据库产品PostgreSQL且作为目标sql,但应用层使用的源sql为oracle,此时在properties中配置映射关系p.setProperty("PostgreSQL", "oracle"); 最终databaseId为oracle
        */
        DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
        Properties p = new Properties();
        p.setProperty("MySQL", "mysql");
        // 使用统一sql, 目标sql:postgresql 源sql:oracle
        p.setProperty("PostgreSQL", "oracle");
        databaseIdProvider.setProperties(p);
        return databaseIdProvider;
    }
}

准备mapper接口

@Mapper
public interface TestMapper {
    List<Map<String, Object>> queryForList();
}

准备mapper接口对应的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">

    <!--databaseId属性值指向源sql,这里是mysql-->
    <select id="queryForList" resultType="map" databaseId="mysql">
        select 1 from dual
    </select>

    <!--databaseId属性值指向源sql,这里是oracle-->
    <select id="queryForList" resultType="map" databaseId="oracle">
        select nation, listagg(city, ',') within GROUP (order by city1, city2 desc) from temp group by nation
    </select>
</mapper>

单元测试类,测试基于数据库厂商标识(databaseIdProvider)的mapper接口方法queryForList:

@SpringBootTest
class UnisqlMultiDataSourceMapperTests {
    @Autowired
    private TestMapper testMapper;

    @Test
    void testMybatis() {
        // 统一sql,返回postgresql执行结果,源sql为oracle,目标sql为postgresql
        List<Map<String, Object>> list = testMapper.queryForList();
        Assert.isTrue(!CollectionUtils.isEmpty(list));
    }
}

2.5.6. C语言调用方式

#include <stdio.h>
#include <dlfcn.h>

int main()
{
    /*手动加载指定位置的so动态库*/
    void* handle = dlopen("./unisql.linux.x86_64.so", RTLD_LAZY);
    char* (*Transform)(char* sourceSQL, char* sourceDialect, char* targetDialect);

    /*根据动态链接库操作句柄与符号,返回符号对应的地址*/
    Transform = dlsym(handle, "Transform");

    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";

    printf("Before transfer sql is:%s\n",csql);

    char* result = Transform(csql, sourceDialect,targetDialect);

    printf("Aefore transfer sql is:%s\n",result);//输出结果来判断

    dlclose(handle);

    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'

//编译加入-ldl
//gcc transform.c -ldl -o Test

2.5.7. CRES调用方式

//类的头文件
#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'