当前位置:Java -> 如何在Java脚本中有效地使用JDBC工作
我想向你介绍一个名为SqlExecutor的Java类,代码行数不到170行,用于通过JDBC API调用SQL查询以简化工作。这个解决方案有什么特别之处呢?这个类可以嵌入到Java版本17的脚本中。
Java脚本的优势在于以文本格式轻松可移植,并且可以在无需事先编译的情况下运行,同时在运行时可以利用语言标准库中丰富的资源。使用脚本适用于各种原型,甚至可以解决更加复杂的数据导出或数据转换问题(连接到数据库之后)。脚本在任何我们不希望(或者不能)将实现内容放入标准Java项目的地方都非常有用。
不过,使用脚本也有一些限制。例如,代码必须写在一个单独的文件中。我们可以在运行脚本时包含所有必要的库,但这些库可能有额外的依赖,而只是在命令行中列出它们可能会很令人沮丧。分发这样的脚本可能会带来复杂性。基于上述原因,我认为最好避免在脚本中使用外部库。如果我们仍然希望选择脚本方式,最好选择纯JDBC。我们可以优势地使用多行文本字面量编写SQL查询,并且可以自动关闭像PreparedStatement(实现AutoCloseable接口)这样的对象。那问题出在哪里呢?
出于安全考虑,建议将SQL参数值映射到问号。我认为JDBC的主要障碍是使用问号(从1开始)的序列号码映射参数。第一版参数映射到SQL脚本通常会很顺利,但随着参数数量和额外SQL修改的增加,错误的风险也会增加。需要提醒的是,在第一个位置插入新参数时,后续行必须重新编号。
另一个复杂性在于使用IN运算符,因为对于枚举的每个值,在SQL模板中必须写一个问号,这必须映射到一个单独的参数。如果参数列表是动态的,SQL模板中问号的列表也必须是动态的。调试更多更复杂的SQL可能会耗费大量时间。
通过使用String模板插入SQL参数,我们需要更长时间等待。但是,可以通过简单封装接口PreparedStatement来简化插入SQL参数。在调用SQL语句之前,这个封装器将使用JPA风格的命名标签(以冒号开头的字母文本)附加参数。如果封装器还允许将所需方法链接成单个语句,最好具有返回类型Stream
对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 {…}
对类型SqlParamBuilder的实例可以重复使用于多个SQL语句。调用命令后,参数可以被更改,然后再次运行命令。参数会分配给最后使用的Object PreparedStatement。
创建自己的实现最快的方法可能就是下载示例脚本,重新设计方法mainRun(),并将连接参数修改为自己的数据库。使用自己的JDBC驱动运行。
推荐阅读: 百度面经(19)
本文链接: 如何在Java脚本中有效地使用JDBC工作