
熟悉 Spring Boot 3 的開發(fā)者,都知道它在簡化開發(fā)流程、提高開發(fā)效率方面的出色表現(xiàn)吧!但是,在實際業(yè)務(wù)場景中,大家肯定都碰到過這樣的棘手問題:訂單數(shù)據(jù)存放在 MySQL 里,庫存數(shù)據(jù)在 PostgreSQL 中,用戶數(shù)據(jù)又保存在 MongoDB 中,當多種數(shù)據(jù)源同時存在時,想要實現(xiàn)統(tǒng)一查詢簡直比登天還難。
所以呢,今天我就亮出我的“終極大招”——Apache Calcite,著重給大家講講它怎樣與 Spring Boot 3 實現(xiàn)無縫集成,還會分享一些可以直接拿來使用的經(jīng)典應(yīng)用場景。掌握了這一招,多數(shù)據(jù)源查詢的難題就能輕松解決啦!
一、核心認知:Apache Calcite 為何是多數(shù)據(jù)源查詢的利器?
在動手集成前,咱們先把核心邏輯搞明白:為啥 Calcite 能成為多數(shù)據(jù)源查詢的“萬能鑰匙”?它的核心優(yōu)勢到底在哪?
1.1 不止是查詢引擎:Calcite 的核心定位
Apache Calcite 本質(zhì)是一個動態(tài)數(shù)據(jù)管理框架,而非傳統(tǒng)的數(shù)據(jù)庫。它最核心的價值在于“解耦”——將數(shù)據(jù)存儲與數(shù)據(jù)查詢分離,無論數(shù)據(jù)存在哪里、是什么格式,都能通過統(tǒng)一的 SQL 接口進行查詢。
說通俗點,Calcite 就像個“超級數(shù)據(jù)翻譯官”——不管數(shù)據(jù)藏在哪個數(shù)據(jù)源里、是什么格式,你只要寫一套標準 SQL,它就能翻譯成對應(yīng)數(shù)據(jù)源能懂的指令,最后把結(jié)果整理成統(tǒng)一格式返回。這也是它能搞定多數(shù)據(jù)源查詢的核心秘訣!
1.2 Calcite 的核心能力拆解
- 統(tǒng)一 SQL 接口:支持標準 SQL,無論底層是關(guān)系型數(shù)據(jù)庫(MySQL、PostgreSQL)、非關(guān)系型數(shù)據(jù)庫(MongoDB、Redis),還是文件(CSV、Parquet)、大數(shù)據(jù)引擎(Hive、Spark),都能通過同一套 SQL 查詢。
- 強大的查詢優(yōu)化:內(nèi)置基于規(guī)則和成本的查詢優(yōu)化器,能自動優(yōu)化 SQL 執(zhí)行計劃,提升查詢效率,尤其是在復(fù)雜多表關(guān)聯(lián)、跨數(shù)據(jù)源查詢場景下,優(yōu)化效果明顯。
- 靈活的數(shù)據(jù)源適配:通過“適配器(Adapter)”機制適配不同數(shù)據(jù)源,社區(qū)已提供大量現(xiàn)成適配器,也支持自定義開發(fā),適配特殊數(shù)據(jù)源。
- 輕量級集成:核心依賴體積小,無復(fù)雜依賴,可輕松集成到 Spring Boot、Spring Cloud 等主流 Java 開發(fā)框架中,無需單獨部署獨立服務(wù)(也支持獨立部署)。
二、重點實戰(zhàn):Spring Boot 3 集成 Calcite 核心步驟
既然大家都熟悉 Spring Boot 3 的基礎(chǔ)操作,我就不啰嗦項目搭建這些常規(guī)步驟了,直接聚焦 Calcite 集成的核心環(huán)節(jié),每一步都附完整代碼和避坑提醒,跟著做就能成!
2.1 核心依賴引入
第一步先引依賴,在 pom.xml 里加好 Calcite 核心包、對應(yīng)數(shù)據(jù)源的適配器,再配上 MyBatis Plus 的核心依賴(替換掉原來的 Jdbc 依賴就行),具體如下:
<!-- Calcite 核心依賴 -->
<dependency>
<groupId>org.apache.calcite</groupId>
<artifactId>calcite-core</artifactId>
<version>1.36.0</version>
</dependency>
<!-- MySQL 適配器(用于適配 MySQL 數(shù)據(jù)源) -->
<dependency>
<groupId>org.apache.calcite</groupId>
<artifactId>calcite-mysql</artifactId>
<version>1.36.0</version>
</dependency>
<!-- MongoDB 適配器(用于適配 MongoDB 數(shù)據(jù)源) -->
<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 的穩(wěn)定版 -->
</dependency>
<!-- 數(shù)據(jù)庫連接池依賴(MyBatis Plus 需連接池支持) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.20</version>
</dependency>
這里有 3 個避坑點必須強調(diào)下:
- Calcite 所有組件版本要統(tǒng)一,不然容易出現(xiàn)類加載異常;
- MyBatis Plus 得選適配 Spring Boot 3 的版本(3.5.3+);
- 一定要加連接池依賴,不然 Calcite 數(shù)據(jù)源沒法被 MyBatis Plus 正常管理。
2.2 核心配置:Calcite 模型文件編寫
模型文件是 Calcite 識別數(shù)據(jù)源的關(guān)鍵,一般用 JSON 格式,放在 resources 目錄下命名為 calcite-model.json 就行。下面給大家一個適配 MySQL 和 MongoDB 雙數(shù)據(jù)源的示例,直接改改連接信息就能用:
{
"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"
}
}
]
}
幾個關(guān)鍵配置給大家解釋清楚,避免踩坑:
- defaultSchema:默認查詢的 Schema,可省略,查詢時需指定 Schema 名稱(如 ecommerce.order、user_mongo.user_info)。
- factory:對應(yīng)數(shù)據(jù)源的適配器工廠類,Calcite 已為主流數(shù)據(jù)源提供現(xiàn)成工廠,自定義數(shù)據(jù)源需實現(xiàn)自己的 Factory。
- operand:數(shù)據(jù)源連接參數(shù),根據(jù)數(shù)據(jù)源類型不同配置不同參數(shù)(如 MySQL 的 jdbcUrl、MongoDB 的 host/port)。
2.3 Spring Boot 集成 Calcite + MyBatis Plus 核心配置
這一步是核心,主要分兩步走:
1.配置好 Calcite 數(shù)據(jù)源;
- 讓 MyBatis Plus 用上這個數(shù)據(jù)源,順便把 mapper 掃描、分頁插件這些基礎(chǔ)參數(shù)配好。直接上配置類代碼:
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 數(shù)據(jù)源(核心,與原邏輯一致)
@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 數(shù)據(jù)源
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource calciteDataSource) throws Exception {
MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
// 注入 Calcite 數(shù)據(jù)源
sessionFactory.setDataSource(calciteDataSource);
// 配置 mapper.xml 文件路徑(如果使用 XML 方式編寫 SQL)
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/*.xml"));
// 配置 MyBatis Plus 全局參數(shù)(可選)
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true); // 下劃線轉(zhuǎn)駝峰
sessionFactory.setConfiguration(configuration);
// 注入 MyBatis Plus 插件(如分頁插件)
sessionFactory.setPlugins(mybatisPlusInterceptor());
return sessionFactory.getObject();
}
// 3. MyBatis Plus 分頁插件(可選,復(fù)雜查詢分頁用)
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 適配 Calcite 兼容的 MySQL 語法
return interceptor;
}
// 4. 配置事務(wù)管理器(可選,需要事務(wù)支持時添加)
@Bean
public PlatformTransactionManager transactionManager(DataSource calciteDataSource) {
return new DataSourceTransactionManager(calciteDataSource);
}
}
核心邏輯給大家捋一捋:先通過 Calcite 創(chuàng)建統(tǒng)一的數(shù)據(jù)源,再把它注入到 MyBatis Plus 的 SqlSessionFactory 里。這樣一來,咱們后續(xù)寫代碼就完全是 MyBatis Plus 的熟悉風格了,不管是 Mapper 接口還是 XML 映射文件,都能直接用,跨數(shù)據(jù)源查詢的復(fù)雜邏輯全交給 Calcite 處理。
2.4 核心查詢實現(xiàn)(MyBatis Plus 風格)
接下來就是大家最熟悉的查詢實現(xiàn)環(huán)節(jié)了,我用 MyBatis Plus 最常用的“Mapper 接口+注解”和“XML”兩種方式來演示,還是以 MySQL 訂單表和 MongoDB 用戶表的關(guān)聯(lián)查詢?yōu)槔?,大家可以根?jù)自己的習慣選:
- 定義實體類(對應(yīng)跨數(shù)據(jù)源查詢結(jié)果,可使用 lombok 簡化代碼)
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(關(guān)聯(lián)字段)
}
- 定義 Mapper 接口(MyBatis Plus 風格,無需編寫實現(xiàn)類)
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 基礎(chǔ) CRUD 能力
public interface UserOrderMapper extends BaseMapper<UserOrderVO> {
// 注解方式編寫跨數(shù)據(jù)源關(guān)聯(lián) 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);
}
- 編寫 Service 層
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) {
// 調(diào)用 Mapper 接口方法,實現(xiàn)跨數(shù)據(jù)源查詢
return baseMapper.queryUserOrderByUserId(userId);
// 若使用 XML 方式:return baseMapper.queryUserOrderByUserIdWithXml(userId);
}
}
- 編寫 Controller 層
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) {
// 調(diào)用 Service 方法,返回跨數(shù)據(jù)源查詢結(jié)果
return userOrderService.getUserOrderByUserId(userId);
}
}
最后再劃 3 個重點,確保大家少走彎路:
- 實體類字段要和查詢結(jié)果列名對應(yīng),用別名適配下劃線轉(zhuǎn)駝峰更省心;
- Mapper 接口繼承 BaseMapper 后,MyBatis Plus 的分頁、條件構(gòu)造器這些功能都能直接用,復(fù)雜查詢也能輕松搞定;
- 咱們寫的都是標準 SQL,Calcite 會自動解析適配不同數(shù)據(jù)源,完全不影響大家原來的開發(fā)習慣。
三、深度解析:Calcite 的經(jīng)典使用場景
講完了集成步驟,再跟大家深度拆解下 Calcite 的經(jīng)典落地場景。畢竟技術(shù)最終要服務(wù)于業(yè)務(wù),這些場景都是我在實際項目中常用到的,拿來就能用!
第一個經(jīng)典場景是多系統(tǒng)數(shù)據(jù)融合查詢,這也是企業(yè)級中臺的核心需求。做企業(yè)級中臺的小伙伴肯定深有體會,大型企業(yè)里數(shù)據(jù)都是分散的——訂單系統(tǒng)用 MySQL,用戶系統(tǒng)用 MongoDB 存行為數(shù)據(jù),庫存系統(tǒng)用 PostgreSQL。要是想做“用戶-訂單-庫存”全鏈路分析,傳統(tǒng)做法得分別調(diào)三個系統(tǒng)的接口,再在業(yè)務(wù)層手動整合數(shù)據(jù),不僅效率低,還容易出錯。用 Calcite 分別適配這三個數(shù)據(jù)源后,只要寫一套標準 SQL 就能實現(xiàn)跨數(shù)據(jù)源關(guān)聯(lián)查詢,咱們用 Spring Boot 3 搭好接口服務(wù),業(yè)務(wù)層完全不用管數(shù)據(jù)存在哪,專注核心業(yè)務(wù)邏輯就行,親測開發(fā)效率能提升 50%以上,再也不用寫重復(fù)的接口調(diào)用和數(shù)據(jù)整合代碼,而且 Calcite 的查詢優(yōu)化器會自動優(yōu)化關(guān)聯(lián)邏輯,查詢效率也能跟上。
第二個場景是實時數(shù)據(jù)與離線數(shù)據(jù)聯(lián)動查詢,做電商的小伙伴應(yīng)該經(jīng)常遇到這類需求。比如實時訂單數(shù)據(jù)存在 Kafka 里,歷史訂單數(shù)據(jù)存在 Hive 里,運營需要實時查看“今日訂單+近 30 天歷史訂單”的匯總數(shù)據(jù)來做實時監(jiān)控和決策。這種情況不用麻煩地把 Kafka 數(shù)據(jù)同步到 Hive,也不用把 Hive 數(shù)據(jù)同步到實時庫,直接用 Calcite 的 Kafka 適配器(calcite-kafka)和 Hive 適配器(calcite-hive),就能把實時流數(shù)據(jù)和離線數(shù)據(jù)放到同一個查詢體系里,寫一條 SQL 就能實現(xiàn)“實時+離線”數(shù)據(jù)的聯(lián)合查詢,既省了大量數(shù)據(jù)同步成本,又能兼顧實時性和準確性,還支持增量查詢。
第三個場景是自定義數(shù)據(jù)源適配,主要解決特殊格式數(shù)據(jù)查詢的難題。企業(yè)里總有很多 CSV、Excel、Parquet 格式的文件數(shù)據(jù),傳統(tǒng)做法是先把這些文件導(dǎo)入數(shù)據(jù)庫才能查詢,步驟又多又耗時,尤其是臨時做數(shù)據(jù)分析的時候,導(dǎo)入數(shù)據(jù)庫的成本太高了。而 Calcite 內(nèi)置了文件適配器(calcite-file),支持直接查詢這些文件數(shù)據(jù),根本不用導(dǎo)入數(shù)據(jù)庫。咱們再結(jié)合 Spring Boot 3 的文件上傳功能,還能實現(xiàn)“文件上傳后直接用 SQL 查詢”的需求,臨時分析數(shù)據(jù)超方便。如果有企業(yè)內(nèi)部的特殊格式文件,比如自定義的二進制文件,也可以自己實現(xiàn) Calcite 的 SchemaFactory 和 TableFactory 接口,寫個自定義適配器,就能適配這些特殊數(shù)據(jù)源了。
四、避坑指南:集成注意事項與優(yōu)化建議
4.1 這些坑一定要避開!
- 適配器版本要統(tǒng)一:Calcite 核心依賴和各數(shù)據(jù)源適配器的版本必須一致,不然很容易出現(xiàn)類加載異常,這個坑我踩過,大家一定要注意。
- 模型文件配置要規(guī)范:Schema 名稱、表名要清晰,別重復(fù);數(shù)據(jù)源的地址、端口、賬號密碼這些連接參數(shù)一定要準確,錯一個就會連接失敗。
- 要考慮數(shù)據(jù)源性能:跨數(shù)據(jù)源查詢的性能取決于最慢的那個數(shù)據(jù)源,所以要確保每個數(shù)據(jù)源自身性能沒問題,不然會拖慢整個查詢。
4.2 優(yōu)化小技巧,查詢更快更穩(wěn)
- 啟用 Calcite 緩存:配置一下 Calcite 的元數(shù)據(jù)緩存和查詢計劃緩存,能減少重復(fù)解析和元數(shù)據(jù)查詢的時間,提升查詢效率。
- 優(yōu)化 SQL 寫法:盡量避免復(fù)雜的多表關(guān)聯(lián),能把過濾條件下推到數(shù)據(jù)源的就盡量下推。雖然 Calcite 會自動優(yōu)化,但手動優(yōu)化后的效果會更好。
- 自定義優(yōu)化規(guī)則:如果是特別復(fù)雜的業(yè)務(wù)場景,可以自己實現(xiàn) Calcite 的 OptimizerRule 接口,寫自定義的查詢優(yōu)化規(guī)則,進一步提升查詢效率。
五、本文總結(jié)
最后總結(jié)一下,對于熟悉 Spring Boot 3 的咱們來說,集成 Calcite 的關(guān)鍵就是理解它“統(tǒng)一查詢”的核心思想,把模型文件寫對、核心 Bean 配置好,就能快速實現(xiàn)多數(shù)據(jù)源查詢能力了。