Tổng quan dự án
Một dự án hệ thống thương mại điện tử gần đây yêu cầu truy vấn thông tin từ hai cơ sở dữ liệu khác nhau: MySQL cho dữ liệu đơn hàng và PostgreSQL cho dữ liệu người dùng. Khách hàng cần một giao diện truy vấn thống nhất có thể lấy thông tin từ cả hai nguồn dữ liệu này thông qua câu lệnh SQL.
Tại sao chọn Apache Calcite?
Đơn giản hóa quy trình phát triển
- Trừu tượng hóa ở mức cao: Apache Calcite cung cấp các lớp trừu tượng ở cấp độ cao, cho phép nhà phát triển tập trung vào logic nghiệp vụ mà không cần xử lý các chi tiết kết nối và thực thi truy vấn ở cấp độ cơ sở dữ liệu.
- Giảm công việc trùng lặp: Bằng cách sử dụng Calcite, có thể tránh việc phát triển lại các chức năng đã có, tiết kiệm thời gian và chi phí phát triển.
Khả năng phân tích và tối ưu hóa SQL mạnh mẽ
- Hỗ trợ tiêu chuẩn SQL: Apache Calcite hỗ trợ nhiều phương ngữ SQL (như MySQL, PostgreSQL, v.v.), có thể xử lý liền mạch các câu lệnh SQL từ các cơ sở dữ liệu khác nhau.
- Tối ưu hóa truy vấn: Bộ tối ưu hóa truy vấn tích hợp có thể thực hiện tối ưu hóa thông minh dựa trên đặc tính của các nguồn dữ liệu khác nhau, nâng cao hiệu suất truy vấn.
Linh hoạt và khả năng mở rộng
- Chế độ độ và bảng tùy chỉnh: Có thể thêm và quản lý động nhiều nguồn dữ liệu thông qua lập trình, mỗi nguồn dữ liệu có thể có các chế độ và cấu trúc bảng khác nhau.
- Cơ chế plugin: Hỗ trợ nhiều loại plugin, có thể mở rộng linh hoạt chức năng theo nhu cầu, chẳng hạn như hàm tùy chỉnh, thao tác tổng hợp, v.v.
Hiệu suất cao
- Tính toán trong bộ nhớ: Apache Calcite hỗ trợ xử lý dữ liệu trong bộ nhớ, giảm chi phí I/O, tăng tốc độ truy vấn.
- Tính toán phân tán: Mặc dù dự án này chủ yếu tập trung vào phiên bản đơn máy, Apache Calcite cũng có thể mở rộng đến môi trường phân tán, hỗ trợ xử lý tập dữ liệu lớn.
Khả năng tích hợp cao
- Tích hợp với các công cụ khác: Hỗ trợ tích hợp với các công cụ và công nghệ lớn dữ liệu khác (như Apache Flink, Presto, v.v.), tạo thành giải pháp phân tích dữ liệu hoàn chỉnh.
Các công ty sử dụng Apache Calcite
- Google sử dụng Apache Calcite trong một số hệ thống xử lý dữ liệu nội bộ, đặc biệt trong các kịch bản cần hiệu suất và tính linh hoạt cao.
- IBM sử dụng Apache Calcite trong các giải pháp kho dữ liệu và phân tích dữ liệu của mình để nâng cao hiệu suất và tính linh hoạt của truy vấn.
- Intel sử dụng Apache Calcite để hỗ trợ các công cụ và giải pháp phân tích dữ liệu lớn, đặc biệt trong lĩnh vực tính toán trong bộ nhớ.
- Alibaba Cloud: Sử dụng Apache Calcite trong nền tảng dữ liệu lớn của mình để cung cấp khả năng tối ưu hóa và thực thi truy vấn mạnh mẽ.
- MaxCompute (ODPS): Dịch vụ tính toán dữ liệu quy mô lớn của Alibaba sử dụng Calcite để xử lý truy vấn SQL.
- Elastic sử dụng Apache Calcite cho một số chức năng nâng cao trong Kibana như truy vấn phức tạp.
- Netflix sử dụng Apache Calcite để xây dựng lớp dữ liệu ảo hóa nội bộ, hỗ trợ các truy vấn và phân tích dữ liệu phức tạp.
- Microsoft sử dụng Apache Calcite trong một số sản phẩm và dịch vụ dữ liệu lớn, chẳng hạn như Azure Synapse Analytics.
- Teradata sử dụng Apache Calcite để nâng cao hiệu suất tối ưu hóa và thực thi truy vấn của hệ thống cơ sở dữ liệu.
- Uber sử dụng Apache Calcite để xử lý các tập dữ liệu khổng lồ và hỗ trợ các truy vấn và phân tích dữ liệu phức tạp.
Ứng dụng thực tế
Ảo hóa dữ liệu
- Tầng dữ liệu ảo: Tạo một tầng dữ liệu ảo, tập hợp dữ liệu từ các hệ thống phân tán khác nhau, cung cấp chế độ xem thống nhất.
- Quản lý nguồn dữ liệu động: Thêm và quản lý động các nguồn dữ liệu, hỗ trợ thiết kế kiến trúc dữ liệu linh hoạt.
Công cụ Thông minh Kinh doanh (BI)
- Tạo báo cáo: Làm thành phần cốt lõi của các công cụ BI, hỗ trợ tạo báo cáo phức tạp và phân tích dữ liệu.
- Phân tích tự phục vụ: Cung cấp chức năng phân tích tự phục vụ, cho phép người không chuyên kỹ thuật khám phá và phân tích dữ liệu.
Học máy và Trí tuệ nhân tạo
- Kỹ thuật đặc trưng: Sử dụng Calcite trong đường ống học máy để trích xuất đặc trưng và chuẩn bị dữ liệu.
- Đào tạo mô hình: Kết hợp với các khung AI khác, sử dụng Calcite để truy vấn và xử lý tập dữ liệu quy mô lớn.
Truy vấn đa nguồn dữ liệu
- Giao diện thống nhất truy cập nhiều cơ sở dữ liệu: Cho phép người dùng truy vấn dữ liệu được lưu trữ trong các cơ sở dữ liệu khác nhau (như MySQL, PostgreSQL, Oracle, v.v.) thông qua một giao diện duy nhất.
- Truy vấn liên hợp: Hỗ trợ truy vấn SQL phức tạp trên nhiều nguồn dữ liệu, ví dụ lấy dữ liệu liên quan từ các cơ sở dữ liệu khác nhau.
Tích hợp nền tảng dữ liệu lớn
- Tích hợp với hệ sinh thái Hadoop: Kết hợp với các công cụ dữ liệu lớn như Hive, HBase, Druid, v.v., cung cấp giao diện truy vấn thống nhất.
- Xử lý luồng và xử lý批次: Hỗ trợ các khung xử lý luồng như Apache Flink và Apache Beam, thực hiện phân tích dữ liệu thời gian thực.
Cơ sở dữ liệu nhúng
- Động cơ cơ sở dữ liệu nhẹ: Cung cấp một động cơ SQL nhẹ, phù hợp với các ứng dụng nhúng và cơ sở dữ liệu trong bộ nhớ.
- Tính toán trong bộ nhớ: Tận dụng tính toán trong bộ nhớ để tăng tốc độ truy vấn, phù hợp với các ứng dụng cần phản hồi nhanh.
Triển khai thực tế
Thêm các thư viện cần thiết
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Apache Calcite Core -->
<dependency>
<groupId>org.apache.calcite</groupId>
<artifactId>calcite-core</artifactId>
<version>1.32.0</version>
</dependency>
<!-- HikariCP Connection Pool -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<!-- MySQL Connector Java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- PostgreSQL JDBC Driver -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Cấu hình file application.yml
spring:
datasource:
donhang-db:
url: jdbc:mysql://localhost:3306/donhang_db?useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
nguoidung-db:
url: jdbc:postgresql://localhost:5432/nguoidung_db
username: postgres
password: postgres
driver-class-name: org.postgresql.Driver
jpa:
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
Cấu hình nguồn dữ liệu
package com.example.duonhuongdulieu.config;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class NguonDuLieuConfig {
@Bean(name = "mysqlNguonDuLieu")
public DataSource mysqlNguonDuLieu() {
// Cấu hình nguồn dữ liệu MySQL
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/donhang_db?useSSL=false&serverTimezone=UTC");
config.setUsername("root");
config.setPassword("root");
return new HikariDataSource(config);
}
@Bean(name = "postgresNguonDuLieu")
public DataSource postgresNguonDuLieu() {
// Cấu hình nguồn dữ liệu PostgreSQL
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/nguoidung_db");
config.setUsername("postgres");
config.setPassword("postgres");
return new HikariDataSource(config);
}
}
Tạo nhà máy nguồn dữ liệu tùy chỉnh
package com.example.duonhuongdulieu.nhaxaydung;
import com.example.duonhuongdulieu.schema.CheDoCuaToi;
import org.apache.calcite.adapter.jdbc.JdbcSchema;
import org.apache.calcite.adapter.java.ReflectiveSchema;
import org.apache.calcite.config.Lex;
import org.apache.calcite.jdbc.CalciteConnection;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class NhaMayNguonDuLieu {
public static CalciteConnection taoKetNoi(DataSource mysqlNguonDuLieu, DataSource postgresNguonDuLieu) throws SQLException {
// Định nghĩa chuỗi JSON mô hình Calcite
String modelJson = "{\n" +
" \"version\": \"1.0\",\n" +
" \"defaultSchema\": \"che_do_cua_toi\",\n" +
" \"schemas\": [\n" +
" {\n" +
" \"name\": \"che_do_cua_toi\",\n" +
" \"type\": \"custom\",\n" +
" \"factory\": \"" + ReflectiveSchema.Factory.class.getName() + "\",\n" +
" \"operand\": {\n" +
" \"class\": \"" + CheDoCuaToi.class.getName() + "\"\n" +
" }\n" +
" }\n" +
" ]\n" +
"}";
// Tạo kết nối Calcite
Connection connection = DriverManager.getConnection("jdbc:calcite:model=" + modelJson);
CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class);
// Lấy chế độ gốc và thêm các chế độ con
SchemaPlus schema = calciteConnection.getRootSchema().getSubSchema("che_do_cua_toi");
schema.add("donhang", JdbcSchema.create(calciteConnection.getRootSchema(), "donhang", mysqlNguonDuLieu, null, Lex.MYSQL));
schema.add("nguoidung", JdbcSchema.create(calciteConnection.getRootSchema(), "nguoidung", postgresNguonDuLieu, null, Lex.POSTGRESQL));
return calciteConnection;
}
}
Tạo chế độ tùy chỉnh
package com.example.duonhuongdulieu.schema;
import org.apache.calcite.schema.impl.AbstractSchema;
import java.util.Map;
public class CheDoCuaToi extends AbstractSchema {
@Override
protected Map getBangMap() {
// Trả về ánh xạ bảng, ở đây không cần xử lý thêm
return super.getTableMap();
}
}
Bộ điều khiển truy vấn
package com.example.duonhuongdulieu.controller;
import com.example.duonhuongdulieu.nhaxaydung.NhaMayNguonDuLieu;
import org.apache.calcite.jdbc.CalciteConnection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
@RestController
public class DieuKhienTruyVan {
private final DataSource mysqlNguonDuLieu;
private final DataSource postgresNguonDuLieu;
@Autowired
public DieuKhienTruyVan(@Qualifier("mysqlNguonDuLieu") DataSource mysqlNguonDuLieu,
@Qualifier("postgresNguonDuLieu") DataSource postgresNguonDuLieu) {
this.mysqlNguonDuLieu = mysqlNguonDuLieu;
this.postgresNguonDuLieu = postgresNguonDuLieu;
}
@GetMapping("/truyvan")
public List truyVan(@RequestParam String sql) throws SQLException {
// Tạo kết nối Calcite
CalciteConnection connection = NhaMayNguonDuLieu.taoKetNoi(mysqlNguonDuLieu, postgresNguonDuLieu);
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
// Xử lý kết quả truy vấn
List ketQua = new ArrayList<>();
while (resultSet.next()) {
int soCot = resultSet.getMetaData().getColumnCount();
List<String> hang = new ArrayList<>();
for (int i = 1; i <= soCot; i++) {
hang.add(resultSet.getString(i));
}
ketQua.add(hang);
}
// Đóng tài nguyên
resultSet.close();
statement.close();
connection.close();
return ketQua;
}
}
Lớp ứng dụng chính
package com.example.duonhuongdulieu;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DuonHuongDuLieuApplication {
public static void main(String[] args) {
SpringApplication.run(DuonHuongDuLieuApplication.class, args);
}
}
Thiết lập bảng kiểm thử
Bảng `donhang` trong MySQL
CREATE TABLE donhang (
id INT PRIMARY KEY,
nguoidung_id INT,
so_tien DECIMAL(10, 2),
ngay_dat DATETIME
);
Bảng `nguoidung` trong PostgreSQL
CREATE TABLE nguoidung (
id SERIAL PRIMARY KEY,
ten VARCHAR(100),
email VARCHAR(100)
);
Thực hiện truy vấn kiểm thử
Thực hiện một truy vấn liên hợp từ hai nguồn dữ liệu khác nhau:
SELECT d.id AS donhang_id, n.ten AS ten_nguoidung, d.so_tien, d.ngay_dat
FROM donhang d
JOIN nguoidung n ON d.nguoidung_id = n.id;
Kết quả kiểm thử
$ curl -X GET "http://localhost:8080/truyvan?sql=SELECT%20d.id%20AS%20donhang_id,%20n.ten%20AS%20ten_nguoidung,%20d.so_tien,%20d.ngay_dat%20FROM%20donhang%20d%20JOIN%20nguoidung%20n%20ON%20d.nguoidung_id%20=%20n.id"
[
["1", "Alice", "199.99", "2025-04-10 21:30:00"],
["2", "Bob", "250.75", "2025-04-10 20:45:00"]
]