01 Giới thiệu
Khi nói đến đa nguồn dữ liệu, bạn có thể nghĩ ngay đến giải pháp đa nguồn dữ liệu trong Mybatis-Plus. Tuy nhiên, cách tiếp cận hôm nay sẽ làm bạn bất ngờ hoàn toàn — ít nhất là khiến tôi cảm thấy ngạc nhiên.
Bạn có từng tưởng tượng được việc truy vấn các đối tượng trong bộ nhớ hay tệp CSV bằng ngôn ngữ SQL? Hay thậm chí là liên kết các bảng từ nhiều nguồn khác nhau? Trước đây, điều đó dường như không thể thực hiện được. Nhưng giờ đây, với Apache Calcite, mọi điều đều có thể.
02 Tổng quan
Apache Calcite là một khung nền tảng mạnh mẽ dành cho cơ sở dữ liệu, đặc biệt phù hợp để truy cập và tối ưu hóa dữ liệu từ nhiều nguồn dữ liệu khác nhau thông qua giao diện SQL tiêu chuẩn.
Calcite sử dụng các trình thích ứng (Adapters) để kết nối với các nguồn dữ liệu bên ngoài. Mỗi trình thích ứng giúp một loại dữ liệu bên ngoài như tệp CSV, MySQL, đối tượng trong bộ nhớ Java, Redis… được xem như một bảng có thể truy vấn bằng SQL trong hệ thống Calcite.
Calcite hoạt động như một "khung tối ưu hóa truy vấn", nằm ở tầng trên cùng của các nguồn dữ liệu, cung cấp một giao diện SQL thống nhất. Nó không lưu trữ dữ liệu mà chỉ tối ưu hóa kế hoạch truy vấn và đẩy xuống các nguồn dữ liệu gốc để thực thi. Calcite không phải là một cơ sở dữ liệu, không hỗ trợ các lệnh DML như INSERT, UPDATE hay DELETE.
Trang chủ: https://calcite.apache.org/
Các trình thích ứng được hỗ trợ:
03 Thông số chính
Chúng ta sẽ tìm hiểu từng bước từ đối tượng trong bộ nhớ, tệp CSV, và cơ sở dữ liệu MySQL, rồi cuối cùng là hiệu quả khi kết hợp truy vấn.
3.1 Các thư viện cần thiết
<!-- Apache Calcite -->
<dependency>
<groupId>org.apache.calcite</groupId>
<artifactId>calcite-core</artifactId>
<version>1.38.0</version>
</dependency>
<dependency>
<groupId>org.apache.calcite</groupId>
<artifactId>calcite-csv</artifactId>
<version>1.38.0</version>
</dependency>
3.2 Giải thích thông số
Tập tin cấu hình chính là một file JSON chứa các tham số sau:
{
"version": "1.0",
"defaultSchema": "tên schema mặc định",
"schemas": [
{
"name": "tên schema",
"type": "custom",
"factory": "",
"operand": {
}
}
]
}
Tham số cấp cao nhất
version: Phiên bản định nghĩadefaultSchema: Schema mặc địnhschemas: Danh sách các schema, kiểu mảng
Thông số cụ thể của mỗi schema
name: Tên của schematype: Kiểu schema. Có thể là:custom,map,jdbccustom: Mở rộng, cần cấu hìnhfactorymap: Dạng đơn giản dựa trên bộ nhớ, không cầnfactoryjdbc: Kết nối JDBC truyền thốngfactory: Nhà máy tạo adapteroperand: Thông tin chi tiết về nguồn dữ liệu, mỗi loại sẽ có cấu hình khác nhau
04 Truy vấn đối tượng trong bộ nhớ
Truy vấn đối tượng trong bộ nhớ được hỗ trợ bởi calcite-core. Lớp schema chính là:
org.apache.calcite.adapter.java.ReflectiveSchema$Factory
Lưu ý ký tự $ biểu thị lớp nội bộ.
Cấu hình model
{
"version": "1.0",
"defaultSchema": "MEM",
"schemas": [
{
"name": "MEM",
"type": "custom",
"factory": "org.apache.calcite.adapter.java.ReflectiveSchema$Factory",
"operand": {
"class": "com.simonking.boot.calcite.schema.UserSchema"
}
}
]
}
Đối với đối tượng trong bộ nhớ, Calcite dùng phản chiếu (reflection), vì vậy cần khai báo class tương ứng với nguồn dữ liệu.
4.1 Định nghĩa nguồn dữ liệu
public class UserSchema {
public final User[] users;
public UserSchema() {
// Dữ liệu giả lập
this.users = new User[]{
new User(1, "zhangsan", 18),
new User(2, "admin", 20),
new User(3, "lisi", 22)
};
}
/**
* @Description: Định nghĩa thuộc tính
**/
@AllArgsConstructor
public class User {
/** ID người dùng */
public final Integer id;
/** Tên người dùng */
public final String name;
/** Tuổi */
public final Integer age;
}
}
Vì dữ liệu không thể chỉnh sửa, nên có thể khai báo lớp nguồn dữ liệu là final.
Các trường của bảng phải là trường công khai (public) và kiểu dữ liệu là mảng, nếu không sẽ không lấy được dữ liệu. Các trường của bảng phải thuộc lớp nội bộ, và các trường trong lớp nội bộ phải là công khai.
4.2 Truy vấn từ client
Giao thức truy vấn gần giống với JDBC thông thường.
@Test
void test01() throws Exception {
CalciteConnection calciteConn = getCalciteConnection();
String sql = "SELECT * FROM MEM.users";
Statement stmt = calciteConn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
doResult(rs);
}
Lưu ý rằng câu truy vấn phải bao gồm tên schema, nếu không sẽ không nhận diện đúng.
Phương thức lấy kết nối
private static CalciteConnection getCalciteConnection() throws Exception {
Class.forName("org.apache.calcite.jdbc.Driver");
Properties prop = new Properties();
prop.put("lex", "MYSQL");
prop.put("model", "src/main/resources/calcite-model.json");
Connection connection = DriverManager.getConnection("jdbc:calcite:", prop);
CalciteConnection calciteConn = connection.unwrap(CalciteConnection.class);
return calciteConn;
}
Các thông số trong Properties:
lex: Ngôn ngữ xử lý cú pháp, tương tự như kiểu SQL, mặc định là ORACLE. Đặt thành MYSQL để tên bảng không phân biệt hoa/thường.
model: Đường dẫn tới file cấu hình JSON.
Xuất kết quả
private void doResult(ResultSet rs) throws SQLException {
ResultSetMetaData meta = rs.getMetaData();
StringBuffer sb = new StringBuffer("[");
while (rs.next()) {
sb.append("{");
for (int i = 1; i <= meta.getColumnCount(); i++) {
sb.append(meta.getColumnName(i))
.append(":")
.append(rs.getObject(i))
.append(",");
}
sb.deleteCharAt(sb.length() - 1);
sb.append("},");
}
sb.deleteCharAt(sb.length() - 1);
sb.append("]");
System.out.println(sb);
}
Đây là phương pháp chuyển đổi kết quả truy vấn sang định dạng JSON để dễ đọc.
4.3 Kết quả truy vấn
05 Truy vấn tệp CSV
Để truy vấn tệp CSV, cần thêm thư viện mở rộng calcite-csv. Lớp schema chính:
org.apache.calcite.adapter.csv.CsvSchemaFactory
Cấu hình model
Chỉ cần bổ sung thêm schema mới.
{
"version": "1.0",
"defaultSchema": "MEM",
"schemas": [
{
"name": "MEM",
"type": "custom",
"factory": "org.apache.calcite.adapter.java.ReflectiveSchema$Factory",
"operand": {
"class": "com.simonking.boot.calcite.schema.UserSchema"
}
},
{
"name": "CSV",
"type": "custom",
"factory": "org.apache.calcite.adapter.csv.CsvSchemaFactory",
"operand": {
"directory": "csv",
"flavor": "scannable"
}
}
]
}
Thông số của nguồn dữ liệu: directory chỉ vị trí thư mục chứa CSV, mặc định nằm trong classpath. flavor xác định kiểu truy vấn, mặc định là scannable.
4.1 Định nghĩa tệp CSV
Chỉ cần tạo tệp CSV, tên tệp sẽ trở thành tên bảng.
4.2 Truy vấn từ client
@Test
void test02() throws Exception {
CalciteConnection calciteConn = getCalciteConnection();
String sql = "SELECT * FROM CSV.orders";
Statement stmt = calciteConn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
doResult(rs);
}
4.3 Kết quả truy vấn
06 Truy vấn MySQL
Truy vấn MySQL được hỗ trợ bởi calcite-core, lớp schema chính:
org.apache.calcite.adapter.jdbc.JdbcSchema$Factory
Cấu hình model
Chỉ cần thêm schema mới.
{
"version": "1.0",
"defaultSchema": "MEM",
"schemas": [
{
"name": "MEM",
"type": "custom",
"factory": "org.apache.calcite.adapter.java.ReflectiveSchema$Factory",
"operand": {
"class": "com.simonking.boot.calcite.schema.UserSchema"
}
},
{
"name": "CSV",
"type": "custom",
"factory": "org.apache.calcite.adapter.csv.CsvSchemaFactory",
"operand": {
"directory": "csv",
"flavor": "scannable"
}
},
{
"name": "MYSQL_DB",
"type": "custom",
"factory": "org.apache.calcite.adapter.jdbc.JdbcSchema$Factory",
"operand": {
"jdbcUrl": "jdbc:mysql://localhost:3306/test",
"jdbcUser": "root",
"jdbcPassword": "root"
}
}
]
}
6.1 Truy vấn từ client
Viết câu truy vấn như bình thường.
@Test
void test03() throws Exception {
CalciteConnection calciteConn = getCalciteConnection();
String sql = "SELECT * FROM MYSQL_DB.user_roles";
Statement stmt = calciteConn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
doResult(rs);
}
6.2 Kết quả truy vấn
07 Truy vấn liên kết
Dùng lại cấu hình trước đó, viết trực tiếp câu SQL liên kết.
08 Kết luận
Nhiều tổ chức chia cơ sở dữ liệu thành nhiều phần nhưng để giải quyết vấn đề truy vấn xuyên bộ, họ thường đồng bộ một cơ sở dữ liệu toàn cục. Với Apache Calcite, điều này hoàn toàn không còn cần thiết.
Trong quá trình thử nghiệm, tôi phát hiện rằng từ phiên bản 1.39.0 trở lên, khi truy vấn liên kết giữa các tệp CSV, có thể gặp lỗi trả về rỗng, dù truy vấn riêng lẻ vẫn hoạt động tốt. Có vẻ như đây là lỗi hoặc cải tiến mới của hệ thống. Ai biết rõ hơn vui lòng chia sẻ nhé.