Skip to content

Commit 10caab1

Browse files
authored
Merge pull request #598 from fxbin/dev
feat(duckdb): 初始化 DuckDB Starter 模块
2 parents a13f85b + 0edfef4 commit 10caab1

File tree

23 files changed

+2181
-25
lines changed

23 files changed

+2181
-25
lines changed

bubble-dependencies/pom.xml

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
<forest.version>1.8.0</forest.version>
5858
<java-diff-utils.version>4.15</java-diff-utils.version>
5959
<liteflow.version>2.15.2</liteflow.version>
60+
<duckdb.version>1.4.2.0</duckdb.version>
6061
</properties>
6162

6263
<dependencyManagement>
@@ -113,19 +114,19 @@
113114

114115
<dependency>
115116
<groupId>cn.fxbin.bubble</groupId>
116-
<artifactId>bubble-starter-lock</artifactId>
117+
<artifactId>bubble-starter-data-duckdb</artifactId>
117118
<version>2.0.0.BUILD-SNAPSHOT</version>
118119
</dependency>
119120

120121
<dependency>
121122
<groupId>cn.fxbin.bubble</groupId>
122-
<artifactId>bubble-starter-excel</artifactId>
123+
<artifactId>bubble-starter-lock</artifactId>
123124
<version>2.0.0.BUILD-SNAPSHOT</version>
124125
</dependency>
125126

126127
<dependency>
127128
<groupId>cn.fxbin.bubble</groupId>
128-
<artifactId>bubble-starter-flow</artifactId>
129+
<artifactId>bubble-starter-excel</artifactId>
129130
<version>2.0.0.BUILD-SNAPSHOT</version>
130131
</dependency>
131132

@@ -525,9 +526,6 @@
525526
<artifactId>forest-spring-boot3-starter</artifactId>
526527
<version>${forest.version}</version>
527528
</dependency>
528-
<<<<<<< HEAD
529-
530-
=======
531529

532530
<!-- duckdb -->
533531
<dependency>
@@ -536,7 +534,6 @@
536534
<version>${duckdb.version}</version>
537535
</dependency>
538536

539-
>>>>>>> df135a0 (feat(bubble-dependencies): 引入liteflow规则引擎依赖)
540537
<!-- java-diff-utils -->
541538
<dependency>
542539
<groupId>io.github.java-diff-utils</groupId>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>cn.fxbin.bubble</groupId>
8+
<artifactId>bubble-starters</artifactId>
9+
<version>2.0.0.BUILD-SNAPSHOT</version>
10+
</parent>
11+
12+
<artifactId>bubble-starter-data-duckdb</artifactId>
13+
<name>Bubble Starter Data DuckDB</name>
14+
15+
<dependencies>
16+
<dependency>
17+
<groupId>cn.fxbin.bubble</groupId>
18+
<artifactId>bubble-starter</artifactId>
19+
</dependency>
20+
21+
<dependency>
22+
<groupId>org.springframework.boot</groupId>
23+
<artifactId>spring-boot-starter-jdbc</artifactId>
24+
</dependency>
25+
26+
<dependency>
27+
<groupId>org.duckdb</groupId>
28+
<artifactId>duckdb_jdbc</artifactId>
29+
</dependency>
30+
31+
<dependency>
32+
<groupId>net.dreamlu</groupId>
33+
<artifactId>mica-auto</artifactId>
34+
<scope>provided</scope>
35+
</dependency>
36+
37+
<!-- Testing dependencies -->
38+
<dependency>
39+
<groupId>cn.fxbin.bubble</groupId>
40+
<artifactId>bubble-starter-test</artifactId>
41+
<scope>test</scope>
42+
</dependency>
43+
</dependencies>
44+
45+
</project>
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package cn.fxbin.bubble.data.duckdb;
2+
3+
import cn.fxbin.bubble.data.duckdb.core.DuckDbIngester;
4+
import cn.fxbin.bubble.data.duckdb.core.DuckDbManager;
5+
import cn.fxbin.bubble.data.duckdb.core.DuckDbTemplate;
6+
import lombok.extern.slf4j.Slf4j;
7+
import org.springframework.util.Assert;
8+
9+
import java.util.Iterator;
10+
import java.util.List;
11+
import java.util.Map;
12+
13+
/**
14+
* DuckDB 统一客户端入口
15+
*
16+
* <p>
17+
* 提供对默认 DuckDB 实例的直接访问,以及对动态 DuckDB 文件的管理。
18+
* 采用了外观模式(Facade Pattern),将 DuckDbTemplate、DuckDbIngester 和 DuckDbManager 的功能统一暴露。
19+
* </p>
20+
*
21+
* @author fxbin
22+
* @version v1.0
23+
* @since 2025/12/08 16:30
24+
*/
25+
@Slf4j
26+
public class DuckDbOperations {
27+
28+
private final DuckDbTemplate defaultTemplate;
29+
private final DuckDbManager manager;
30+
private final DuckDbIngester defaultIngester;
31+
32+
public DuckDbOperations(DuckDbTemplate defaultTemplate, DuckDbManager manager, DuckDbIngester defaultIngester) {
33+
Assert.notNull(defaultTemplate, "Default DuckDbTemplate must not be null");
34+
Assert.notNull(manager, "DuckDbManager must not be null");
35+
Assert.notNull(defaultIngester, "Default DuckDbIngester must not be null");
36+
this.defaultTemplate = defaultTemplate;
37+
this.manager = manager;
38+
this.defaultIngester = defaultIngester;
39+
}
40+
41+
// =================================================================================================================
42+
// 基础查询操作 (Delegate to defaultTemplate)
43+
// =================================================================================================================
44+
45+
/**
46+
* 获取默认的 DuckDbTemplate。
47+
*
48+
* @return 默认模板实例。
49+
*/
50+
public DuckDbTemplate template() {
51+
return defaultTemplate;
52+
}
53+
54+
/**
55+
* 在默认数据库上执行 SQL 查询。
56+
*
57+
* @param sql SQL 查询语句。
58+
* @return 结果列表(Map)。
59+
*/
60+
public List<Map<String, Object>> query(String sql) {
61+
return defaultTemplate.queryForList(sql);
62+
}
63+
64+
/**
65+
* 在默认数据库上执行 SQL 查询,并返回指定类型的对象列表。
66+
*
67+
* @param sql SQL 查询语句。
68+
* @param type 结果类型。
69+
* @param <T> 泛型类型。
70+
* @return 对象列表。
71+
*/
72+
public <T> List<T> query(String sql, Class<T> type) {
73+
return defaultTemplate.queryForList(sql, type);
74+
}
75+
76+
/**
77+
* 在默认数据库上执行 SQL 查询,并返回单个对象。
78+
*
79+
* @param sql SQL 查询语句。
80+
* @param type 结果类型。
81+
* @param <T> 泛型类型。
82+
* @return 单个对象,如果未找到则为 null。
83+
*/
84+
public <T> T queryForObject(String sql, Class<T> type) {
85+
try {
86+
return defaultTemplate.queryForObject(sql, type);
87+
} catch (org.springframework.dao.EmptyResultDataAccessException e) {
88+
return null;
89+
}
90+
}
91+
92+
/**
93+
* 获取表行数统计。
94+
*
95+
* @param tableName 表名。
96+
* @return 行数。
97+
*/
98+
public Long count(String tableName) {
99+
DuckDbTemplate.validateTableName(tableName);
100+
String sql = "SELECT count(*) FROM " + tableName;
101+
return defaultTemplate.queryForObject(sql, Long.class);
102+
}
103+
104+
/**
105+
* 在默认数据库上执行 SQL 语句(DDL/DML)。
106+
*
107+
* @param sql SQL 语句。
108+
*/
109+
public void execute(String sql) {
110+
defaultTemplate.execute(sql);
111+
}
112+
113+
// =================================================================================================================
114+
// 数据导入导出 (Ingestion & Parquet)
115+
// =================================================================================================================
116+
117+
/**
118+
* 向默认数据库追加数据(使用 Appender,高性能)。
119+
*
120+
* @param tableName 表名。
121+
* @param dataIterator 数据迭代器。
122+
*/
123+
public void ingest(String tableName, Iterator<Object[]> dataIterator) {
124+
defaultIngester.ingest(tableName, dataIterator);
125+
}
126+
127+
/**
128+
* 向默认数据库追加数据(列表方式)。
129+
*
130+
* @param tableName 表名。
131+
* @param rows 数据行。
132+
*/
133+
public void append(String tableName, List<Object[]> rows) {
134+
defaultTemplate.append(tableName, rows);
135+
}
136+
137+
/**
138+
* 将 Parquet 文件导入到表中。
139+
*
140+
* @param tableName 目标表名(将被创建)。
141+
* @param parquetPath Parquet 文件的路径。
142+
*/
143+
public void importParquet(String tableName, String parquetPath) {
144+
defaultTemplate.importParquet(tableName, parquetPath);
145+
}
146+
147+
/**
148+
* 将表(或查询结果)导出为 Parquet 文件。
149+
*
150+
* @param tableNameOrQuery 表名或 SELECT 查询。
151+
* @param outputPath Parquet 文件的输出路径。
152+
*/
153+
public void exportParquet(String tableNameOrQuery, String outputPath) {
154+
defaultTemplate.exportParquet(tableNameOrQuery, outputPath);
155+
}
156+
157+
// =================================================================================================================
158+
// 动态实例操作 (Delegate to manager)
159+
// =================================================================================================================
160+
161+
/**
162+
* 连接到指定的 DuckDB 文件。
163+
*
164+
* @param filePath 数据库文件路径。
165+
* @return 该文件的 DuckDbTemplate 实例。
166+
*/
167+
public DuckDbTemplate connect(String filePath) {
168+
return manager.getTemplate(filePath);
169+
}
170+
171+
/**
172+
* 以指定模式连接到指定的 DuckDB 文件。
173+
*
174+
* @param filePath 数据库文件路径。
175+
* @param readOnly 是否只读。
176+
* @return 该文件的 DuckDbTemplate 实例。
177+
*/
178+
public DuckDbTemplate connect(String filePath, boolean readOnly) {
179+
return manager.getTemplate(filePath, readOnly);
180+
}
181+
182+
/**
183+
* 关闭指定文件的连接。
184+
*
185+
* @param filePath 数据库文件路径。
186+
*/
187+
public void close(String filePath) {
188+
manager.close(filePath, false);
189+
manager.close(filePath, true); // 尝试关闭只读连接
190+
}
191+
192+
/**
193+
* 获取底层的 DuckDbManager。
194+
*
195+
* @return 管理器实例。
196+
*/
197+
public DuckDbManager manager() {
198+
return manager;
199+
}
200+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package cn.fxbin.bubble.data.duckdb.autoconfigure;
2+
3+
import cn.fxbin.bubble.data.duckdb.DuckDbOperations;
4+
import cn.fxbin.bubble.data.duckdb.core.DuckDbConnectionFactory;
5+
import cn.fxbin.bubble.data.duckdb.core.DuckDbIngester;
6+
import cn.fxbin.bubble.data.duckdb.core.DuckDbManager;
7+
import cn.fxbin.bubble.data.duckdb.core.DuckDbTemplate;
8+
import com.zaxxer.hikari.HikariDataSource;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.duckdb.DuckDBDriver;
11+
import org.springframework.beans.factory.annotation.Qualifier;
12+
import org.springframework.boot.autoconfigure.AutoConfiguration;
13+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
14+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
15+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
16+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
17+
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
18+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
19+
import org.springframework.context.annotation.Bean;
20+
21+
import javax.sql.DataSource;
22+
23+
/**
24+
* DuckDB 自动配置
25+
*
26+
* @author fxbin
27+
* @version v1.0
28+
* @since 2025/12/08 11:42
29+
*/
30+
@Slf4j
31+
@AutoConfiguration
32+
@ConditionalOnClass({DuckDBDriver.class, DataSource.class})
33+
@EnableConfigurationProperties(DuckDbProperties.class)
34+
@ConditionalOnProperty(prefix = "dm.data.duckdb", name = "enabled", havingValue = "true", matchIfMissing = true)
35+
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
36+
public class DuckDbAutoConfiguration {
37+
38+
@Bean
39+
@ConditionalOnMissingBean
40+
public DuckDbConnectionFactory duckDbConnectionFactory(DuckDbProperties properties) {
41+
return new DuckDbConnectionFactory(properties);
42+
}
43+
44+
/**
45+
* 定义 DuckDB 专用数据源
46+
* <p>
47+
* 注意:这里不使用 @Primary,也不使用 @ConditionalOnMissingBean(DataSource.class),
48+
* 以确保它能作为“第二数据源”与主业务库(如 MySQL)共存。
49+
* </p>
50+
*/
51+
@Bean("duckDbDataSource")
52+
public DataSource duckDbDataSource(DuckDbConnectionFactory connectionFactory, DuckDbProperties properties) {
53+
if (properties.getMode() == DuckDbProperties.Mode.FILE && !properties.isReadOnly()) {
54+
log.warn("DuckDB 配置为 FILE 模式,具有 READ_WRITE 访问权限。" +
55+
"确保没有其他进程正在访问 '{}'。DuckDB 强制执行单一写入者策略。", properties.getFilePath());
56+
}
57+
58+
HikariDataSource dataSource = connectionFactory.createDefaultDataSource();
59+
log.info("DuckDB 数据源已初始化: {}", dataSource.getJdbcUrl());
60+
return dataSource;
61+
}
62+
63+
@Bean
64+
@ConditionalOnMissingBean
65+
public DuckDbTemplate duckDbTemplate(@Qualifier("duckDbDataSource") DataSource dataSource) {
66+
return new DuckDbTemplate(dataSource);
67+
}
68+
69+
@Bean
70+
@ConditionalOnMissingBean
71+
public DuckDbManager duckDbManager(DuckDbProperties properties) {
72+
return new DuckDbManager(properties);
73+
}
74+
75+
@Bean
76+
@ConditionalOnMissingBean
77+
public DuckDbIngester duckDbIngester(@Qualifier("duckDbDataSource") DataSource dataSource) {
78+
return new DuckDbIngester(dataSource);
79+
}
80+
81+
@Bean
82+
@ConditionalOnMissingBean
83+
public DuckDbOperations duckDbOperations(DuckDbTemplate duckDbTemplate, DuckDbManager duckDbManager, DuckDbIngester duckDbIngester) {
84+
return new DuckDbOperations(duckDbTemplate, duckDbManager, duckDbIngester);
85+
}
86+
87+
}

0 commit comments

Comments
 (0)