统一SQL使用
LightDB 统一SQL 是一款基于 Go 开发的 SQL 转换中间件,支持将 Oracle 常用 SQL 语法翻译转换为其他数据库的 SQL 。以主流数据库Oracle的SQL为基准,将SQL语句转换为其他信创数据库(LightDB、PostgreSQL、TDSQL、DM8、OceanBase、openGauss等)的SQL语句。目标是降低业务部门适配信创数据库的成本,让用户尽可能少地修改数据访问层代码,使基于Hibernate、JPA、MyBatis等框架的Java应用程序,基于oci、libmysqlclient、cres开发工具、hsdb的c/c++应用程序以及其它能够调用c函数的程序都能够直接切换到目标信创数据库,实现信创数据库之间的平滑迁移。
前提条件
开发环境需要具备 Java JRE 8 或更高版本。
<dependency>
<groupId>io.github.hslightdb</groupId>
<artifactId>sql-convert-runtime</artifactId>
<version>${latest.release.version}</version>
</dependency>
配置动态库
统一SQL Java SDK 依赖动态库,目前可通过两种方式之一来引用动态库:
- 引用 Maven 依赖
sql-convert-runtime-native
,配置正确的classifier
- 通过环境变量 + 动态库文件引入动态库
几种方式的优先级为:
- 若指定了启动参数
unisql.lib.full-path
,则最优先使用 - 再尝试从 jar 包(即 Maven 依赖)中获取动态库
- 最后尝试通过启动参数
unisql.lib.dir
寻找动态库
以下介绍各种方法的操作方式。
引入 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。
这个问题可采用以下办法来解决:
- 通过 Maven 的
<profiler>
来指定classifier
参数 - 下载
dll
或dylib
到本地开发机,启动时指定-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>
当然您也可以按下一节提供的方法,配置 unisql.lib.full-path
参数,直接指定到 dll
或 dylib
的路径来加载动态库。
使用启动参数或环境变量
统一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
配置说明
统一 SQL Java SDK 支持一些配置形式,在本小节进行说明。
系统参数
可通过系统参数(即 -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 |
环境变量
环境变量名 | 说明 |
---|---|
unisql_lib_dir | 指向动态库所在的目录,等同于参数 unisql.lib.dir ,环境变量与参数选一种方式配置即可;都配置则以系统参数优先 |
unisql_lib_full_path | 指定动态库所在完整路径,等同于参数 unisql.lib.full-path ,环境变量与参数选一种方式配置即可;都配置则以系统参数优先 |
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兼容模式 |
日志配置
统一 SQL 利用 SLF4j
打印日志,SLF4j
是目前应用最广泛的 Java 日志接口,可使用 Log4j2
与 logback
做日志实现,对各种应用场景都有较好的支持。
统一 SQL 的日志 Logger name 前缀为 com.hundsun.lightdb.unisql
。
如果没有特殊需求,一般情况下不需要额外的配置。以下给出一些特殊情况的日志配置案例。
开启 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 日志,日志配置的其他部分需要结合您项目的情况自行配置。
将统一 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>
关键点在于:
<Appenders>
中要有提供给统一 SQL 日志文件的配置,这里使用了 RollingFile<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"
。
导入目标库脚本
由于部分目标方言的特性缺失,统一 SQL 通过自定义数据库函数的方式来模拟来源方言的功能,需要用户在目标库执行一些 SQL 脚本。
SQL 包含在官网动态库的压缩包中的 dependency
目录,找到对应的 来源2目标
目录,执行里面的 SQL 脚本即可;动态库可以在 LightDB官网 的 LightDB中间件 -> 统一SQL 处进行下载。
PostgreSQL 目标脚本
脚本目录为 oracle2postgresql
,需要使用在数据库上具有 CREATE 权限的用户来执行。
MySQL 目标脚本
脚本目录为 oracle2mysql
,建议使用超级用户执行。如果无法使用超级用户,则需要一个对数据库对象 unisql.*
拥有以下权限的用户:
- CREATE
- EXECUTE
- GRANT OPTION
- CREATE ROUTINE
- ALTER ROUTINE
- SELECT
- UPDATE
- INSERT
对于 MySQL 数据库的特别说明:若您使用的是云服务厂商的云数据库服务,可能无法进行 CREATE DATABASE, GRANT 等操作,那么您可以采用以下步骤:
- 采用云服务提供的操作接口,完成数据库
unisql
的创建 - 采用云服务提供的操作接口,或具有对
unisql
数据库拥有上表中列出的权限的用户,执行 SQL 脚本,但不要执行CREATE DATABASE
以及与GrantUnisqlPermissions
相关的语句 - 采用云服务提供的操作接口,完成数据库
unisql
对所有用户的授权操作,至少需要 EXECUTE, SELECT, UPDATE, INSERT 权限 - 如果在执行创建自定义函数脚本的过程中报错:
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
编写代码
注意:
jdbc url
必须以jdbc:unisql:
开头,表示使用统一SQL代理;sourceDialect
表示源方言,以下示例是oracle
;targetDialect
表示目标方言,以下示例是postgresql
;
原生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);
}
}
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);
}
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());
}
基于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));
}
}
基于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));
}
}
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
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_