diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml
index b355d9d44..a34b37900 100755
--- a/hutool-db/pom.xml
+++ b/hutool-db/pom.xml
@@ -164,5 +164,11 @@
2.4.12
test
+
+ com.sap.cloud.db.jdbc
+ ngdbc
+ 2.24.7
+ test
+
diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/dialect/DialectFactory.java b/hutool-db/src/main/java/org/dromara/hutool/db/dialect/DialectFactory.java
index 53324c404..74ff6d3f7 100644
--- a/hutool-db/src/main/java/org/dromara/hutool/db/dialect/DialectFactory.java
+++ b/hutool-db/src/main/java/org/dromara/hutool/db/dialect/DialectFactory.java
@@ -89,6 +89,9 @@ public class DialectFactory {
} else if (DriverNames.DRIVER_GOLDENDB.equalsIgnoreCase(driverName)) {
// MySQL兼容
return new MysqlDialect(dbConfig);
+ } else if (DriverNames.DRIVER_HANA.equalsIgnoreCase(driverName)) {
+ // SAP HANA
+ return new HanaDialect(dbConfig);
}
}
// 无法识别可支持的数据库类型默认使用ANSI方言,可兼容大部分SQL语句
diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/dialect/DialectName.java b/hutool-db/src/main/java/org/dromara/hutool/db/dialect/DialectName.java
index f6288ad9d..4eb0ec6a2 100644
--- a/hutool-db/src/main/java/org/dromara/hutool/db/dialect/DialectName.java
+++ b/hutool-db/src/main/java/org/dromara/hutool/db/dialect/DialectName.java
@@ -25,7 +25,7 @@ import org.dromara.hutool.core.text.StrUtil;
* @author Looly
*/
public enum DialectName {
- ANSI, MYSQL, ORACLE, POSTGRESQL, SQLITE3, H2, SQLSERVER, SQLSERVER2012, PHOENIX, DM;
+ ANSI, MYSQL, ORACLE, POSTGRESQL, SQLITE3, H2, SQLSERVER, SQLSERVER2012, PHOENIX, DM, HANA;
/**
* 是否为指定数据库方言,检查时不分区大小写
diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/HanaDialect.java b/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/HanaDialect.java
new file mode 100644
index 000000000..e4a4ee4bb
--- /dev/null
+++ b/hutool-db/src/main/java/org/dromara/hutool/db/dialect/impl/HanaDialect.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dromara.hutool.db.dialect.impl;
+
+import org.dromara.hutool.core.text.StrUtil;
+import org.dromara.hutool.db.DbException;
+import org.dromara.hutool.db.Entity;
+import org.dromara.hutool.db.config.DbConfig;
+import org.dromara.hutool.db.dialect.DialectName;
+import org.dromara.hutool.db.sql.QuoteWrapper;
+import org.dromara.hutool.db.sql.SqlBuilder;
+import org.dromara.hutool.db.sql.StatementUtil;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Hana数据库方言
+ *
+ * @author daoyou.dev
+ */
+public class HanaDialect extends AnsiSqlDialect {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 构造
+ *
+ * @param config 数据库配置对象
+ */
+ public HanaDialect(final DbConfig config) {
+ super(config);
+ quoteWrapper = new QuoteWrapper('"');
+ }
+
+ @Override
+ public String dialectName() {
+ return DialectName.HANA.name();
+ }
+
+ /**
+ * 构建用于upsert的{@link PreparedStatement}。
+ * SAP HANA 使用 MERGE INTO 语法来实现 UPSERT 操作。
+ *
+ * 生成 SQL 语法为:
+ *
{@code
+ * MERGE INTO demo AS target
+ * USING (SELECT ? AS a, ? AS b, ? AS c FROM DUMMY) AS source
+ * ON target.id = source.id
+ * WHEN MATCHED THEN
+ * UPDATE SET target.a = source.a, target.b = source.b, target.c = source.c
+ * WHEN NOT MATCHED THEN
+ * INSERT (a, b, c) VALUES (source.a, source.b, source.c);
+ * }
+ *
+ * @param conn 数据库连接对象
+ * @param entity 数据实体类(包含表名)
+ * @param keys 主键字段数组,通常用于确定匹配条件(联合主键)
+ * @return PreparedStatement
+ * @throws DbException SQL 执行异常
+ */
+ @Override
+ public PreparedStatement psForUpsert(final Connection conn, final Entity entity, final String... keys) throws DbException {
+ SqlBuilder.validateEntity(entity);
+ final SqlBuilder builder = SqlBuilder.of(quoteWrapper);
+
+ final List columns = new ArrayList<>();
+
+ // 构建字段部分和参数占位符部分
+ entity.forEach((field, value) -> {
+ if (StrUtil.isNotBlank(field)) {
+ columns.add(quoteWrapper != null ? quoteWrapper.wrap(field) : field);
+ builder.addParams(value);
+ }
+ });
+
+ String tableName = entity.getTableName();
+ if (quoteWrapper != null) {
+ tableName = quoteWrapper.wrap(tableName);
+ }
+
+ // 构建 UPSERT 语句
+ builder.append("UPSERT ").append(tableName).append(" (");
+ builder.append(String.join(", ", columns));
+ builder.append(") VALUES (");
+ builder.append(String.join(", ", Collections.nCopies(columns.size(), "?")));
+ builder.append(") WITH PRIMARY KEY");
+
+ return StatementUtil.prepareStatement(false, this.dbConfig, conn, builder.build(), builder.getParamValueArray());
+ }
+}
diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/driver/DriverIdentifier.java b/hutool-db/src/main/java/org/dromara/hutool/db/driver/DriverIdentifier.java
index 5dc31f7eb..f136fb417 100644
--- a/hutool-db/src/main/java/org/dromara/hutool/db/driver/DriverIdentifier.java
+++ b/hutool-db/src/main/java/org/dromara/hutool/db/driver/DriverIdentifier.java
@@ -33,7 +33,7 @@ import java.util.List;
* @author Looly
* @since 6.0.0
*/
-public class DriverIdentifier implements DriverNames{
+public class DriverIdentifier implements DriverNames {
/**
* 单例驱动识别器
@@ -131,7 +131,9 @@ public class DriverIdentifier implements DriverNames{
new StartsWithDriverMatcher(DRIVER_GAUSS, "jdbc:zenith:"),
new StartsWithDriverMatcher(DRIVER_OPENGAUSS, "jdbc:opengauss:"),
// 中兴GoldenDB
- new StartsWithDriverMatcher(DRIVER_GOLDENDB, "jdbc:goldendb:")
+ new StartsWithDriverMatcher(DRIVER_GOLDENDB, "jdbc:goldendb:"),
+ // SAP HANA
+ new StartsWithDriverMatcher(DRIVER_HANA, "jdbc:sap:")
);
}
diff --git a/hutool-db/src/main/java/org/dromara/hutool/db/driver/DriverNames.java b/hutool-db/src/main/java/org/dromara/hutool/db/driver/DriverNames.java
index 9d1515502..2144e6c47 100644
--- a/hutool-db/src/main/java/org/dromara/hutool/db/driver/DriverNames.java
+++ b/hutool-db/src/main/java/org/dromara/hutool/db/driver/DriverNames.java
@@ -252,9 +252,12 @@ public interface DriverNames {
* JDBC 驱动 Greenplum
*/
String DRIVER_GREENPLUM = "com.pivotal.jdbc.GreenplumDriver";
-
/**
* JDBC 驱动 GoldenDB
*/
String DRIVER_GOLDENDB = "com.goldendb.jdbc.Driver";
+ /**
+ * JDBC 驱动 Sap Hana
+ */
+ String DRIVER_HANA = "com.sap.db.jdbc.Driver";
}
diff --git a/hutool-db/src/test/java/org/dromara/hutool/db/HanaTest.java b/hutool-db/src/test/java/org/dromara/hutool/db/HanaTest.java
new file mode 100644
index 000000000..c6f9a2cc8
--- /dev/null
+++ b/hutool-db/src/test/java/org/dromara/hutool/db/HanaTest.java
@@ -0,0 +1,82 @@
+package org.dromara.hutool.db;
+
+import org.dromara.hutool.core.lang.Console;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class HanaTest {
+ @BeforeAll
+ public static void createTable() {
+ final Db db = Db.of("hana");
+ final long count = db.count("SELECT * FROM SYS.TABLES WHERE TABLE_NAME = ? AND SCHEMA_NAME = CURRENT_SCHEMA", "user");
+ if (count > 0) {
+ db.execute("drop table \"user\"");
+ }
+ db.execute("CREATE COLUMN TABLE \"user\" (\"id\" INT NOT NULL, \"account\" VARCHAR(255), \"name\" VARCHAR(255), \"text\" VARCHAR(255), \"test1\" VARCHAR(255), \"pass\" VARCHAR(255), PRIMARY KEY (\"id\"))");
+ }
+
+ @Test
+ @Disabled
+ public void insertTest() {
+ for (int id = 100; id < 200; id++) {
+ Db.of("hana").insert(Entity.of("user")//
+ .set("id", id)//
+ .set("name", "测试用户" + id)//
+ .set("text", "描述" + id)//
+ .set("test1", "t" + id)//
+ );
+ }
+ }
+
+ /**
+ * 事务测试
+ * 更新三条信息,低2条后抛出异常,正常情况下三条都应该不变
+ *
+ * @throws SQLException SQL异常
+ */
+ @Test
+ @Disabled
+ public void txTest() throws SQLException {
+ Db.of("hana").tx(db -> {
+ final int update = db.update(Entity.of("user").set("text", "描述100"), Entity.of().set("id", 100));
+ db.update(Entity.of("user").set("text", "描述101"), Entity.of().set("id", 101));
+ if (1 == update) {
+ // 手动指定异常,然后测试回滚触发
+ throw new RuntimeException("Error");
+ }
+ db.update(Entity.of("user").set("text", "描述102"), Entity.of().set("id", 102));
+ });
+ }
+
+ @Test
+ @Disabled
+ public void pageTest() {
+ final PageResult result = Db.of("hana").page(Entity.of("\"user\""), new Page(2, 10));
+ for (final Entity entity : result) {
+ Console.log(entity.get("id"));
+ }
+ }
+
+ @Test
+ @Disabled
+ public void getTimeStampTest() {
+ final List all = Db.of("hana").findAll("user");
+ Console.log(all);
+ }
+
+ @Test
+ public void upsertTest() {
+ final Db db = Db.of("hana");
+ db.insert(Entity.of("user").set("id", 1).set("account", "ice").set("pass", "123456"));
+ db.upsert(Entity.of("user").set("id", 1).set("account", "daoyou").set("pass", "a123456").set("name", "道友"));
+ final Entity user = db.get(Entity.of("user").set("id", 1));
+ System.out.println("user=======" + user.getStr("account") + "___" + user.getStr("pass"));
+ assertEquals("daoyou", user.getStr("account"));
+ }
+}
diff --git a/hutool-db/src/test/resources/config/db.setting b/hutool-db/src/test/resources/config/db.setting
index d9d12b9bf..ab70d4a6b 100644
--- a/hutool-db/src/test/resources/config/db.setting
+++ b/hutool-db/src/test/resources/config/db.setting
@@ -108,3 +108,9 @@ url = jdbc:oceanbase://localhost:2881/test
user = root
pass = 123456
remarks = true
+
+[hana]
+url = jdbc:sap://localhost:30015/HAP_CONN?autoReconnect=true
+user = DB
+pass = 123456
+remarks = true