From 9939b69cb64042f19fd810a8ac64acb53e5144b6 Mon Sep 17 00:00:00 2001 From: ysy <550244300@qq.com> Date: Thu, 3 Apr 2025 13:45:17 +0800 Subject: [PATCH] feat: sap hana db --- hutool-db/pom.xml | 6 ++ .../cn/hutool/db/dialect/DialectFactory.java | 5 + .../cn/hutool/db/dialect/DialectName.java | 2 +- .../cn/hutool/db/dialect/DriverNamePool.java | 4 + .../hutool/db/dialect/impl/HanaDialect.java | 93 +++++++++++++++++++ .../src/test/java/cn/hutool/db/HanaTest.java | 89 ++++++++++++++++++ .../src/test/resources/config/db.setting | 6 ++ 7 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 hutool-db/src/main/java/cn/hutool/db/dialect/impl/HanaDialect.java create mode 100644 hutool-db/src/test/java/cn/hutool/db/HanaTest.java diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index b91cf5452..272897c17 100755 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -185,5 +185,11 @@ 23.5.0.24.07 test + + com.sap.cloud.db.jdbc + ngdbc + 2.24.6 + test + diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java index 7fcc12763..e2636f766 100755 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java @@ -62,6 +62,8 @@ public class DialectFactory implements DriverNamePool { return new PhoenixDialect(); } else if (DRIVER_DM7.equalsIgnoreCase(driverName)) { return new DmDialect(); + } else if (DRIVER_HANA.equalsIgnoreCase(driverName)) { + return new HanaDialect(); } } // 无法识别可支持的数据库类型默认使用ANSI方言,可兼容大部分SQL语句 @@ -168,6 +170,9 @@ public class DialectFactory implements DriverNamePool { } else if (nameContainsProductInfo.contains("goldendb")) { // GoldenDB driver = DRIVER_GOLDENDB; + } else if (nameContainsProductInfo.contains("sap")) { + // sap hana + driver = DRIVER_HANA; } return driver; diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectName.java b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectName.java index 275723b75..fab193b8d 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectName.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectName.java @@ -9,7 +9,7 @@ import cn.hutool.core.util.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/cn/hutool/db/dialect/DriverNamePool.java b/hutool-db/src/main/java/cn/hutool/db/dialect/DriverNamePool.java index 998bc0a0b..95a6a1a1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/DriverNamePool.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/DriverNamePool.java @@ -120,4 +120,8 @@ public interface DriverNamePool { * 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/main/java/cn/hutool/db/dialect/impl/HanaDialect.java b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/HanaDialect.java new file mode 100644 index 000000000..d0bdbe7ec --- /dev/null +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/HanaDialect.java @@ -0,0 +1,93 @@ +package cn.hutool.db.dialect.impl; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.hutool.db.Page; +import cn.hutool.db.StatementUtil; +import cn.hutool.db.dialect.DialectName; +import cn.hutool.db.sql.SqlBuilder; +import cn.hutool.db.sql.Wrapper; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Hana数据库方言 + * + * @author daoyou.dev + */ +public class HanaDialect extends AnsiSqlDialect { + + public HanaDialect() { + wrapper = new Wrapper('"'); + } + + @Override + public String dialectName() { + return DialectName.HANA.name(); + } + + @Override + protected SqlBuilder wrapPageSql(SqlBuilder find, Page page) { + // SAP HANA 使用 OFFSET LIMIT 分页 + return find.append(" LIMIT ").append(page.getPageSize()) + .append(" OFFSET ").append(page.getStartPosition()); + } + + /** + * 构建用于upsert的{@link PreparedStatement}。 + * SAP HANA 使用 MERGE INTO 语法来实现 UPSERT 操作。 + *

+ * 生成 SQL 语法为: + *

+	 *     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 SQLException SQL 执行异常 + */ + @Override + public PreparedStatement psForUpsert(Connection conn, Entity entity, String... keys) throws SQLException { + SqlBuilder.validateEntity(entity); + final SqlBuilder builder = SqlBuilder.create(wrapper); + + final List columns = new ArrayList<>(); + final List values = new ArrayList<>(); + + // 构建字段部分和参数占位符部分 + entity.forEach((field, value) -> { + if (StrUtil.isNotBlank(field)) { + columns.add(wrapper != null ? wrapper.wrap(field) : field); + values.add(value); + } + }); + + String tableName = entity.getTableName(); + if (wrapper != null) { + tableName = wrapper.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(conn, builder.toString(), values); + } +} + diff --git a/hutool-db/src/test/java/cn/hutool/db/HanaTest.java b/hutool-db/src/test/java/cn/hutool/db/HanaTest.java new file mode 100644 index 000000000..6fe0a7955 --- /dev/null +++ b/hutool-db/src/test/java/cn/hutool/db/HanaTest.java @@ -0,0 +1,89 @@ +package cn.hutool.db; + +import cn.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; + +/** + * hana操作单元测试 + * + * @author daoyou.dev + */ +public class HanaTest { + @BeforeAll + public static void createTable() throws SQLException { + Db db = Db.use("hana"); + 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() throws SQLException { + DbUtil.setReturnGeneratedKeyGlobal(false); + for (int id = 100; id < 200; id++) { + Db.use("hana").insert(Entity.create("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.use("hana").tx(db -> { + int update = db.update(Entity.create("user").set("text", "描述100"), Entity.create().set("id", 100)); + db.update(Entity.create("user").set("text", "描述101"), Entity.create().set("id", 101)); + if (1 == update) { + // 手动指定异常,然后测试回滚触发 + throw new RuntimeException("Error"); + } + db.update(Entity.create("user").set("text", "描述102"), Entity.create().set("id", 102)); + }); + } + + @Test + @Disabled + public void pageTest() throws SQLException { + PageResult result = Db.use("hana").page(Entity.create("\"user\""), new Page(2, 10)); + for (Entity entity : result) { + Console.log(entity.get("id")); + } + } + + @Test + @Disabled + public void getTimeStampTest() throws SQLException { + final List all = Db.use("hana").findAll("user"); + Console.log(all); + } + + @Test + public void upsertTest() throws SQLException { + DbUtil.setReturnGeneratedKeyGlobal(false); + Db db = Db.use("hana"); + db.insert(Entity.create("user").set("id", 1).set("account", "ice").set("pass", "123456")); + db.upsert(Entity.create("user").set("id", 1).set("account", "daoyou").set("pass", "a123456").set("name", "道友")); + Entity user = db.get(Entity.create("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 bb890d4db..1441d2be5 100644 --- a/hutool-db/src/test/resources/config/db.setting +++ b/hutool-db/src/test/resources/config/db.setting @@ -77,3 +77,9 @@ user = SYSDBA pass = 123456789 remarks = true +[hana] +url = jdbc:sap://127.0.0.1:30015/HAP_CONN?autoReconnect=true +user = DB +pass = 123456 +remarks = true +