Spring Boot 3 集成 Apache Calcite:多数据源查询的终极解决方案

熟悉 Spring Boot 3 的开发者,都知道它在简化开发流程、提高开发效率方面的出色表现吧!但是,在实际业务场景中,大家肯定都碰到过这样的棘手问题:订单数据存放在 MySQL 里,库存数据在 PostgreSQL 中,用户数据又保存在 MongoDB 中,当多种数据源同时存在时,想要实现统一查询简直比登天还难。

所以呢,今天我就亮出我的"终极大招"------Apache Calcite,着重给大家讲讲它怎样与 Spring Boot 3 实现无缝集成,还会分享一些可以直接拿来使用的经典应用场景。掌握了这一招,多数据源查询的难题就能轻松解决啦!

一、核心认知:Apache Calcite 为何是多数据源查询的利器?

在动手集成前,咱们先把核心逻辑搞明白:为啥 Calcite 能成为多数据源查询的"万能钥匙"?它的核心优势到底在哪?

1.1 不止是查询引擎:Calcite 的核心定位

Apache Calcite 本质是一个动态数据管理框架,而非传统的数据库。它最核心的价值在于"解耦"------将数据存储与数据查询分离,无论数据存在哪里、是什么格式,都能通过统一的 SQL 接口进行查询。

说通俗点,Calcite 就像个"超级数据翻译官"------不管数据藏在哪个数据源里、是什么格式,你只要写一套标准 SQL,它就能翻译成对应数据源能懂的指令,最后把结果整理成统一格式返回。这也是它能搞定多数据源查询的核心秘诀!

1.2 Calcite 的核心能力拆解

  • 统一 SQL 接口:支持标准 SQL,无论底层是关系型数据库(MySQL、PostgreSQL)、非关系型数据库(MongoDB、Redis),还是文件(CSV、Parquet)、大数据引擎(Hive、Spark),都能通过同一套 SQL 查询。
  • 强大的查询优化:内置基于规则和成本的查询优化器,能自动优化 SQL 执行计划,提升查询效率,尤其是在复杂多表关联、跨数据源查询场景下,优化效果明显。
  • 灵活的数据源适配:通过"适配器(Adapter)"机制适配不同数据源,社区已提供大量现成适配器,也支持自定义开发,适配特殊数据源。
  • 轻量级集成:核心依赖体积小,无复杂依赖,可轻松集成到 Spring Boot、Spring Cloud 等主流 Java 开发框架中,无需单独部署独立服务(也支持独立部署)。

二、重点实战:Spring Boot 3 集成 Calcite 核心步骤

既然大家都熟悉 Spring Boot 3 的基础操作,我就不啰嗦项目搭建这些常规步骤了,直接聚焦 Calcite 集成的核心环节,每一步都附完整代码和避坑提醒,跟着做就能成!

2.1 核心依赖引入

第一步先引依赖,在 pom.xml 里加好 Calcite 核心包、对应数据源的适配器,再配上 MyBatis Plus 的核心依赖(替换掉原来的 Jdbc 依赖就行),具体如下:

html 复制代码
<!-- Calcite 核心依赖 -->
<dependency>
    <groupId>org.apache.calcite</groupId>
    <artifactId>calcite-core</artifactId>
    <version>1.36.0</version> 
</dependency>

<!-- MySQL 适配器(用于适配 MySQL 数据源) -->
<dependency>
    <groupId>org.apache.calcite</groupId>
    <artifactId>calcite-mysql</artifactId>
    <version>1.36.0</version>
</dependency>

<!-- MongoDB 适配器(用于适配 MongoDB 数据源) -->
<dependency>
    <groupId>org.apache.calcite</groupId>
    <artifactId>calcite-mongodb</artifactId>
    <version>1.36.0</version>
</dependency>

<!-- Spring Boot 与 MyBatis Plus 集成核心依赖 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.5</version> <!-- 适配 Spring Boot 3 的稳定版 -->
</dependency>

<!-- 数据库连接池依赖(MyBatis Plus 需连接池支持) -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.20</version>
</dependency>

这里有 3 个避坑点必须强调下:

  1. Calcite 所有组件版本要统一,不然容易出现类加载异常;
  2. MyBatis Plus 得选适配 Spring Boot 3 的版本(3.5.3+);
  3. 一定要加连接池依赖,不然 Calcite 数据源没法被 MyBatis Plus 正常管理。

2.2 核心配置:Calcite 模型文件编写

模型文件是 Calcite 识别数据源的关键,一般用 JSON 格式,放在 resources 目录下命名为 calcite-model.json 就行。下面给大家一个适配 MySQL 和 MongoDB 双数据源的示例,直接改改连接信息就能用:

java 复制代码
{
  "version": "1.0",
  "defaultSchema": "ecommerce",
  "schemas": [
    {
      "name": "ecommerce",
      "type": "custom",
      "factory": "org.apache.calcite.adapter.jdbc.JdbcSchema$Factory",
      "operand": {
        "jdbcUrl": "jdbc:mysql://localhost:3306/ecommerce_order?useSSL=false&serverTimezone=UTC",
        "username": "root",
        "password": "123456",
        "driver": "com.mysql.cj.jdbc.Driver"
      }
    },
    {
      "name": "user_mongo",
      "type": "custom",
      "factory": "org.apache.calcite.adapter.mongodb.MongoSchema$Factory",
      "operand": {
        "host": "localhost",
        "port": 27017,
        "database": "user_db",
        "collection": "user_info"
      }
    }
  ]
}

几个关键配置给大家解释清楚,避免踩坑:

  • defaultSchema:默认查询的 Schema,可省略,查询时需指定 Schema 名称(如 ecommerce.order、user_mongo.user_info)。
  • factory:对应数据源的适配器工厂类,Calcite 已为主流数据源提供现成工厂,自定义数据源需实现自己的 Factory。
  • operand:数据源连接参数,根据数据源类型不同配置不同参数(如 MySQL 的 jdbcUrl、MongoDB 的 host/port)。

2.3 Spring Boot 集成 Calcite + MyBatis Plus 核心配置

这一步是核心,主要分两步走:

  1. 配置好 Calcite 数据源;

  2. 让 MyBatis Plus 用上这个数据源,顺便把 mapper 扫描、分页插件这些基础参数配好。直接上配置类代码:

    java 复制代码
    import com.baomidou.mybatisplus.annotation.DbType;
    import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
    import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
    import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
    import org.apache.calcite.jdbc.CalciteConnection;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    
    import javax.sql.DataSource;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.util.Properties;
    
    @Configuration
    // MyBatis Plus  mapper 接口扫描(指定 mapper 包路径)
    @MapperScan(basePackages = "com.example.calcite.mapper")
    public class CalciteMybatisPlusConfig {
    
        // 1. 配置 Calcite 数据源(核心,与原逻辑一致)
        @Bean
        public DataSource calciteDataSource() throws Exception {
            Properties props = new Properties();
            props.setProperty("model", "classpath:calcite-model.json");
            Connection connection = DriverManager.getConnection("jdbc:calcite:", props);
            CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class);
            return calciteConnection.getDataSource();
        }
    
        // 2. 配置 MyBatis Plus 的 SqlSessionFactory,指定使用 Calcite 数据源
        @Bean
        public SqlSessionFactory sqlSessionFactory(DataSource calciteDataSource) throws Exception {
            MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
            // 注入 Calcite 数据源
            sessionFactory.setDataSource(calciteDataSource);
            // 配置 mapper.xml 文件路径(如果使用 XML 方式编写 SQL)
            sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                    .getResources("classpath:mapper/*.xml"));
            // 配置 MyBatis Plus 全局参数(可选)
            org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
            configuration.setMapUnderscoreToCamelCase(true); // 下划线转驼峰
            sessionFactory.setConfiguration(configuration);
            // 注入 MyBatis Plus 插件(如分页插件)
            sessionFactory.setPlugins(mybatisPlusInterceptor());
            return sessionFactory.getObject();
        }
    
        // 3. MyBatis Plus 分页插件(可选,复杂查询分页用)
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor() {
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 适配 Calcite 兼容的 MySQL 语法
            return interceptor;
        }
    
        // 4. 配置事务管理器(可选,需要事务支持时添加)
        @Bean
        public PlatformTransactionManager transactionManager(DataSource calciteDataSource) {
            return new DataSourceTransactionManager(calciteDataSource);
        }
    }

核心逻辑给大家捋一捋:先通过 Calcite 创建统一的数据源,再把它注入到 MyBatis Plus 的 SqlSessionFactory 里。这样一来,咱们后续写代码就完全是 MyBatis Plus 的熟悉风格了,不管是 Mapper 接口还是 XML 映射文件,都能直接用,跨数据源查询的复杂逻辑全交给 Calcite 处理。

2.4 核心查询实现(MyBatis Plus 风格)

接下来就是大家最熟悉的查询实现环节了,我用 MyBatis Plus 最常用的"Mapper 接口+注解"和"XML"两种方式来演示,还是以 MySQL 订单表和 MongoDB 用户表的关联查询为例,大家可以根据自己的习惯选:

  1. 定义实体类(对应跨数据源查询结果,可使用 lombok 简化代码)

    java 复制代码
    import lombok.Data;
    
    @Data
    public class UserOrderVO {
        private String orderId;      // 订单 ID(来自 MySQL)
        private String orderTime;    // 下单时间(来自 MySQL)
        private BigDecimal amount;   // 订单金额(来自 MySQL)
        private String userName;     // 用户名(来自 MongoDB)
        private String phone;        // 手机号(来自 MongoDB)
        private String userId;       // 用户 ID(关联字段)
    }
  2. 定义 Mapper 接口(MyBatis Plus 风格,无需编写实现类)

    java 复制代码
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import org.apache.ibatis.annotations.Param;
    import org.apache.ibatis.annotations.Select;
    import java.util.List;
    
    // 继承 BaseMapper,获得 MyBatis Plus 基础 CRUD 能力
    public interface UserOrderMapper extends BaseMapper<UserOrderVO> {
        // 注解方式编写跨数据源关联 SQL
        @Select("SELECT " +
                "o.order_id AS orderId, o.order_time AS orderTime, o.amount, " +
                "u.user_name AS userName, u.phone, o.user_id AS userId " +
                "FROM ecommerce.order o " +  // ecommerce:MySQL 的 Schema;order:订单表
                "JOIN user_mongo.user_info u " +  // user_mongo:MongoDB 的 Schema;user_info:用户表
                "ON o.user_id = u.user_id " +
                "WHERE o.user_id = #{userId}")
        List<UserOrderVO> queryUserOrderByUserId(@Param("userId") String userId);
    
    }
  3. 编写 Service 层

    java 复制代码
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import org.springframework.stereotype.Service;
    import java.util.List;
    
    @Service
    public class UserOrderServiceImpl extends ServiceImpl<UserOrderMapper, UserOrderVO> implements UserOrderService {
        @Override
        public List<UserOrderVO> getUserOrderByUserId(String userId) {
            // 调用 Mapper 接口方法,实现跨数据源查询
            return baseMapper.queryUserOrderByUserId(userId);
            // 若使用 XML 方式:return baseMapper.queryUserOrderByUserIdWithXml(userId);
        }
    }
  4. 编写 Controller 层

    java 复制代码
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RestController;
    import java.util.List;
    
    @RestController
    public class CrossDataSourceQueryController {
        @Autowired
        private UserOrderService userOrderService;
    
        @GetMapping("/user/order/{userId}")
        public List<UserOrderVO> queryUserOrder(@PathVariable String userId) {
            // 调用 Service 方法,返回跨数据源查询结果
            return userOrderService.getUserOrderByUserId(userId);
        }
    }

最后再划 3 个重点,确保大家少走弯路:

  1. 实体类字段要和查询结果列名对应,用别名适配下划线转驼峰更省心;
  2. Mapper 接口继承 BaseMapper 后,MyBatis Plus 的分页、条件构造器这些功能都能直接用,复杂查询也能轻松搞定;
  3. 咱们写的都是标准 SQL,Calcite 会自动解析适配不同数据源,完全不影响大家原来的开发习惯。

三、深度解析:Calcite 的经典使用场景

讲完了集成步骤,再跟大家深度拆解下 Calcite 的经典落地场景。毕竟技术最终要服务于业务,这些场景都是我在实际项目中常用到的,拿来就能用!

第一个经典场景是多系统数据融合查询,这也是企业级中台的核心需求。做企业级中台的小伙伴肯定深有体会,大型企业里数据都是分散的------订单系统用 MySQL,用户系统用 MongoDB 存行为数据,库存系统用 PostgreSQL。要是想做"用户-订单-库存"全链路分析,传统做法得分别调三个系统的接口,再在业务层手动整合数据,不仅效率低,还容易出错。用 Calcite 分别适配这三个数据源后,只要写一套标准 SQL 就能实现跨数据源关联查询,咱们用 Spring Boot 3 搭好接口服务,业务层完全不用管数据存在哪,专注核心业务逻辑就行,亲测开发效率能提升 50%以上,再也不用写重复的接口调用和数据整合代码,而且 Calcite 的查询优化器会自动优化关联逻辑,查询效率也能跟上。

第二个场景是实时数据与离线数据联动查询,做电商的小伙伴应该经常遇到这类需求。比如实时订单数据存在 Kafka 里,历史订单数据存在 Hive 里,运营需要实时查看"今日订单+近 30 天历史订单"的汇总数据来做实时监控和决策。这种情况不用麻烦地把 Kafka 数据同步到 Hive,也不用把 Hive 数据同步到实时库,直接用 Calcite 的 Kafka 适配器(calcite-kafka)和 Hive 适配器(calcite-hive),就能把实时流数据和离线数据放到同一个查询体系里,写一条 SQL 就能实现"实时+离线"数据的联合查询,既省了大量数据同步成本,又能兼顾实时性和准确性,还支持增量查询。

第三个场景是自定义数据源适配,主要解决特殊格式数据查询的难题。企业里总有很多 CSV、Excel、Parquet 格式的文件数据,传统做法是先把这些文件导入数据库才能查询,步骤又多又耗时,尤其是临时做数据分析的时候,导入数据库的成本太高了。而 Calcite 内置了文件适配器(calcite-file),支持直接查询这些文件数据,根本不用导入数据库。咱们再结合 Spring Boot 3 的文件上传功能,还能实现"文件上传后直接用 SQL 查询"的需求,临时分析数据超方便。如果有企业内部的特殊格式文件,比如自定义的二进制文件,也可以自己实现 Calcite 的 SchemaFactory 和 TableFactory 接口,写个自定义适配器,就能适配这些特殊数据源了。

四、避坑指南:集成注意事项与优化建议

4.1 这些坑一定要避开!

  • 适配器版本要统一:Calcite 核心依赖和各数据源适配器的版本必须一致,不然很容易出现类加载异常,这个坑我踩过,大家一定要注意。
  • 模型文件配置要规范:Schema 名称、表名要清晰,别重复;数据源的地址、端口、账号密码这些连接参数一定要准确,错一个就会连接失败。
  • 要考虑数据源性能:跨数据源查询的性能取决于最慢的那个数据源,所以要确保每个数据源自身性能没问题,不然会拖慢整个查询。

4.2 优化小技巧,查询更快更稳

  • 启用 Calcite 缓存:配置一下 Calcite 的元数据缓存和查询计划缓存,能减少重复解析和元数据查询的时间,提升查询效率。
  • 优化 SQL 写法:尽量避免复杂的多表关联,能把过滤条件下推到数据源的就尽量下推。虽然 Calcite 会自动优化,但手动优化后的效果会更好。
  • 自定义优化规则:如果是特别复杂的业务场景,可以自己实现 Calcite 的 OptimizerRule 接口,写自定义的查询优化规则,进一步提升查询效率。

五、本文总结

最后总结一下,对于熟悉 Spring Boot 3 的咱们来说,集成 Calcite 的关键就是理解它"统一查询"的核心思想,把模型文件写对、核心 Bean 配置好,就能快速实现多数据源查询能力了。https://mybj123.com/28732.html

相关推荐
猿小羽2 小时前
Spring AI + MCP 实战:构建标准化 AI 智能代理与上下文集成
java·spring boot·llm·ai agent·spring ai·anthropic·mcp
高山上有一只小老虎2 小时前
mybatisplus分页查询版本 3.5.8 以下和版本 3.5.9及以上的区别
java·spring boot·mybatis
哪里不会点哪里.2 小时前
Spring Boot 项目搭建过程
java·spring boot·后端
Stecurry_302 小时前
Spring Boot 深度进阶:从配置管理到生产级实践
java·spring boot·后端
努力也学不会java2 小时前
【Spring Cloud】负载均衡-LoadBalance
java·人工智能·后端·spring·spring cloud·负载均衡
Sapphire~2 小时前
【模板】Jinja风格 Ruby风格
前端·后端
好学且牛逼的马2 小时前
【手写Easy-Spring|1】
java·后端·spring
Grassto2 小时前
HTTP请求超时?大数据量下的网关超时问题处理方案,流式处理,附go语言实现
后端·http·golang·go