Skip to content
MisakaTang's Blog
Go back

阿里云ODPS接入MybatisPlus动态数据源插件

Edit page

背景

项目需要查询阿里云 ODPS(MaxCompute)数据源,原有方式使用 JDBC 直连或 SDK 方式查询。

解决方案

配置 ODPS 数据源

application-qa.ymlapplication-prod.yml 中配置:

spring:
  datasource:
    dynamic:
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/your_db
          username: xxx
          password: xxx
          driver-class-name: com.mysql.cj.jdbc.Driver
        odps:
          url: jdbc:odps:https://service.cn-hangzhou.maxcompute.aliyun.com/api?project=${aliyun.maxcompute.project}&useProjectTimeZone=true
          username: ${aliyun.maxcompute.keyId}
          password: ${aliyun.maxcompute.keySecret}
          driver-class-name: com.aliyun.odps.jdbc.OdpsDriver
          hikari:
            minimum-idle: 0
            maximum-pool-size: 10

添加 ODPS JDBC 依赖

pom.xml 中添加:

<dependency>
    <groupId>com.aliyun.odps</groupId>
    <artifactId>odps-jdbc</artifactId>
    <version>3.10.1</version>
</dependency>

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.6.0</version>
</dependency>

自定义方言

为了支持 ODPS 数据源,需要自定义一个方言类 ODPSDialect 实现 IDialect 接口。

public class DynamicDialect implements IDialect {

    private static final Map<String, IDialect> DIALECT_MAP = ImmutableMap.of(
            "master", new MySqlDialect(),
            "odps", new MySqlDialect()
    );

    @Override
    public DialectModel buildPaginationSql(String originalSql, long offset, long limit) {
        String ds = DynamicDataSourceContextHolder.peek();
        IDialect iDialect = DIALECT_MAP.get(ds);
        if (iDialect == null) {
            throw new BusinessException("数据源需要手动设置方言");
        }
        return iDialect.buildPaginationSql(originalSql, offset, limit);
    }

}

手动设置插件的方言处理:

    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor interceptor = new PaginationInterceptor();
        interceptor.setDialectClazz(DynamicDialect.class.getName());
        return interceptor;
    }

Mapper 改造

@DS("odps")
public interface ODPSMapper {

    List<Map<String, Object>> listOrder(@Param("ds") String ds, @Param("date") String date);
}

创建对应的 XML 文件

src/main/resources/mapper/odps/ 下创建 ODPSMapper.xml

LocalDate 转 Timestamp 异常

问题:Cannot transform ODPS-SDK Java class java.time.LocalDate to java.sql.Timestamp

官方文档引用:

Currently, 16 ODPS data types are supported. Please see the following table for supported ODPS data types and corresponding JDBC interfaces.

ODPS TypeJDBC InterfaceJDBC Type
TINYINTjava.sql.ResultSet.getByteTINYINT
SMALLINTjava.sql.ResultSet.getShortSMALLINT
INTjava.sql.ResultSet.getIntINTEGER
BIGINTjava.sql.ResultSet.getLongBIGINT
FLOATjava.sql.ResultSet.getFloatFLOAT
DOUBLEjava.sql.ResultSet.getDoubleDOUBLE
DECIMALjava.sql.ResultSet.getBigDecimalDECIMAL
VARCHARjava.sql.ResultSet.getStringVARCHAR
CHARjava.sql.ResultSet.getStringCHAR
STRINGjava.sql.ResultSet.getStringVARCHAR
BOOLEANjava.sql.ResultSet.getBooleanBOOLEAN
DATEjava.sql.ResultSet.getDateDATE
DATETIMEjava.sql.ResultSet.getTimestampTIMESTAMP
TIMESTAMPjava.sql.ResultSet.getTimestampTIMESTAMP
TIMESTAMP_NTZjava.sql.ResultSet.getTimestampTIMESTAMP
BINARYjava.sql.ResultSet.getBytesBINARY
ARRAYjava.sql.ResultSet.getArrayARRAY

When the getObject() method is called, what is obtained is the Java type directly corresponding to each ODPS type without any conversion operation. Please see the following table for he correspondence between ODPS types and Java types.

ODPS TypeJava Type
TINYINTjava.lang.Byte
SMALLINTjava.lang.Short
INTjava.lang.Integer
BIGINTjava.lang.Long
FLOATjava.lang.Float
DOUBLEjava.lang.Double
DECIMALjava.math.BigDecimal
VARCHARcom.aliyun.odps.data.Varchar
CHARcom.aliyun.odps.data.Char
STRINGbyte[]
BOOLEANjava.sql.ResultSet.getBoolean
DATEjava.time.LocalDate
DATETIMEjava.time.ZonedDateTime
TIMESTAMPjava.time.Instant
TIMESTAMP_NTZjava.time.LocalDateTime
BINARYcom.aliyun.odps.data.Binary
ARRAYjava.util.List

NOTE: Possible timezone issue

DATETIME in MaxCompute is actually defined as EPOCH in milliseconds, which is UTC, and so is TIMESTAMP in JDBC. This driver fill the DATETIME value directly into JDBC TIMESTAMP and do no parse or format action. When application that using JDBC display a DATETIME as a human-readable string format, it is the application itself did the format using application defined or OS defined timezone. It is suggested to keep your application/OS timezone setting same to MaxCompute to avoid inconsistent datetime parse/format.

附录

相关文件清单

文件说明
application.ymlMyBatis 配置
application-qa.yml / application-prod.yml数据源配置
pom.xmlMaven 依赖
src/main/java/.../mapper/odps/*.javaODPS Mapper 接口
src/main/resources/mapper/odps/*.xmlODPS Mapper XML

数据源类型支持

数据库配置名称说明
MySQLmaster主数据源
ODPSodps阿里云 MaxCompute

版本信息


Edit page
Share this post on:

Previous Post
AI writing:使用 AI Coding 将博客从 Hexo 迁移到 Astro
Next Post
2025年总结