使用 Java 脚本
Java 脚本的优点是文本格式的易于移植,并且无需事先编译即可运行,同时我们在运行时从该语言的标准库中获得了大量资源。为各种原型提供了脚本的使用,其中可以解决更复杂的数据导出或数据转换(连接到数据库后)。无论我们不想(或不能)将实现放入标准 Java 项目中,脚本都很有用。
但是,脚本的使用有一些限制。例如,代码必须写在单个文件中。我们可以在运行脚本时包含所有必要的库,但这些库可能会有额外的依赖项,简单地在命令行上列出它们可能会令人沮丧。与分发这种脚本相关的复杂性可能不需要强调。由于上述原因,我认为最好避免使用脚本中的外部库。如果我们仍然想走脚本路线,那么选择就落在了纯JDBC上。多行文本文本可以有利地用于编写 SQL 查询,以及自动关闭对象,如 PreparedStatement(实现接口)。那么问题出在哪里呢?AutoCloseable
映射 SQL 参数值
出于安全原因,建议将 SQL 参数值映射到问号。我认为 JDBC 的主要障碍是使用问号的序列号(以 1 开头)映射参数。参数映射到 SQL 脚本的第一个版本通常效果很好,但随着参数数量和其他 SQL 修改的增加,出错的风险也会增加。我提醒您,通过在第一个位置插入一个新参数,必须对下一行重新编号。
另一个复杂因素是运算符的使用,因为对于枚举的每个值,必须在 SQL 模板中写入一个问号,该问号必须映射到单独的参数。如果参数列表是动态的,则 SQL 模板中的问号列表也必须是动态的。调试大量更复杂的 SQL 可能会开始花费大量时间。IN
要使用字符串模板插入 SQL 参数,我们将不得不等待更长的时间。但是,可以通过在 上添加一个简单的包装器来方便插入 SQL 参数,该包装器将(在调用 SQL 语句之前)使用 JPA 样式的命名标记(以冒号开头的字母数字文本)附加参数。如果包装器允许将必要的方法链接到单个语句中,最好使用返回类型,则包装器还可以简化从数据库(使用语句)读取数据的过程。interfacePreparedStatementSELECTStream<ResultSet>
SqlParamBuilder 类
带有附加参数的 SQL 命令的可视化有时对于调试或记录 SQL 查询很有用。我向您介绍类 SqlParamBuilder。实现的首要任务是用一个具有极简代码的 Java 类来满足所述需求。编程界面的灵感来自JDBI库。这些示例在内存模式下使用 H2 数据库。但是,连接数据库驱动程序是必要的。
爪哇岛
void mainStart(Connection dbConnection) throws Exception { try (var builder = new SqlParamBuilder(dbConnection)) { System.out.println("# CREATE TABLE"); builder.sql(""" CREATE TABLE employee ( id INTEGER PRIMARY KEY , name VARCHAR(256) DEFAULT 'test' , code VARCHAR(1) , created DATE NOT NULL ) """) .execute(); System.out.println("# SINGLE INSERT"); builder.sql(""" INSERT INTO employee ( id, code, created ) VALUES ( :id, :code, :created ) """) .bind("id", 1) .bind("code", "T") .bind("created", someDate) .execute(); System.out.println("# MULTI INSERT"); builder.sql(""" INSERT INTO employee (id,code,created) VALUES (:id1,:code,:created), (:id2,:code,:created) """) .bind("id1", 2) .bind("id2", 3) .bind("code", "T") .bind("created", someDate.plusDays(7)) .execute(); builder.bind("id1", 11) .bind("id2", 12) .bind("code", "V") .execute(); System.out.println("# SELECT"); List<Employee> employees = builder.sql(""" SELECT t.id, t.name, t.created FROM employee t WHERE t.id < :id AND t.code IN (:code) ORDER BY t.id """) .bind("id", 10) .bind("code", "T", "V") .streamMap(rs -> new Employee( rs.getInt("id"), rs.getString("name"), rs.getObject("created", LocalDate.class))) .toList(); System.out.printf("# PRINT RESULT OF: %s%n", builder.toStringLine()); employees.stream() .forEach((Employee employee) -> System.out.println(employee)); assertEquals(3, employees.size()); assertEquals(1, employees.get(0).id); assertEquals("test", employees.get(0).name); assertEquals(someDate, employees.get(0).created); } } record Employee (int id, String name, LocalDate created) {} static class SqlParamBuilder {…}
使用说明和最终想法
可以为多个 SQL 语句回收该类型的实例。调用命令后,可以更改参数并再次运行该命令。参数将分配给上次使用的对象。SqlParamBuilderPreparedStatement
方法自动关闭内部对象(如果之前打开过一个对象)。sql()PrepradedStatement
如果我们更改参数组(通常用于运算符),我们需要为相同的 .否则,将需要使用该方法。INPreparedStatementagainsql()
在最后一次命令执行后,需要一个对象来显式关闭 .但是,由于我们正在实现一个 接口 ,只需将整个块包含在一个块中即可。关闭不会影响包含的数据库连接。SqlParamBuilderAutoCloseabletry
在 Bash shell 中,可以使用脚本 SqlExecutor.sh 运行该示例,该脚本可以下载必要的 JDBC 驱动程序(此处为 H2 数据库)。
如果我们更喜欢 Kotlin,我们可以尝试 Bash 脚本 SqlExecutorKt.sh,它将准备好的 Kotlin 代码迁移到脚本并运行它。
我们不要因为该类存储在 Maven 类型的项目中这一事实而感到困惑。原因之一是运行 JUnit 测试的便利性。
该类根据 Apache 许可证 2.0 版获得许可。
创建自己的实现的最快方法可能是下载示例脚本,重新设计方法,然后将连接参数修改到自己的数据库。使用您自己的 JDBC 驱动程序运行。mainRun()