我已经使用 Java JDBC(主要是桌面应用程序)很长时间了,我意识到我执行的许多操作都可以改进和简化。例如:
将 SQL 语句直接硬编码到 Java 中不太实用。
用“?”替换变量在 JDBC 中很好,但使用真实的变量名称(例如“USER-NAME”或类似的名称)会更好。
一次执行多个更新语句会非常酷。
为了改进 JDBC,我决定编写自己的工具,但在重新发明轮子之前,我想知道是否有任何 Java 实用程序能够:
读取并执行 .sql 脚本,最好存储在我的应用程序的 JAR 中。
在这些脚本中定义变量,最好使用真实姓名,而不是使用“?”性格。
从这些脚本运行查询 (SELECT) 和更新 (CREATE、INSERT、DELETE, ...) 语句。
在一个方法调用中执行多个更新语句。例如,这可以让我运行 DDL 和 DML 脚本来初始化数据库。
我了解 JDBC ScriptRunner 但它还不够完整。那里有更好的东西吗?老实说,我认为这样的工具会非常有用。
注意:我不介意导入库。
我使用Warework就是为了这个。这是一个很大的 JAR,但我认为它可以满足您的需求。我将向您展示它是如何工作的:
1- 在项目的源文件夹中创建此目录结构:
/META-INF/system/statement/sql
2- 在“/META-INF/system”目录中,创建一个名为“pool-service.xml”的文件,其中包含以下内容:
<?xml version="1.0" encoding="UTF-8"?>
<proxy-service xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://repository.warework.com/xsd/proxyservice-1.0.0.xsd">
<clients>
<client name="c3p0-client" connector="com.warework.service.pool.client.connector.C3P0Connector">
<parameter name="driver-class" value="com.mysql.jdbc.Driver" />
<parameter name="jdbc-url" value="jdbc:mysql://host:port/database-name" />
<parameter name="user" value="the-user-name" />
<parameter name="password" value="the-password" />
<parameter name="connect-on-create" value="true" />
</client>
</clients>
</proxy-service>
在此文件中,将参数值替换为连接数据库所需的值。保持“connect-on-create”等于“true”。
3- 在“/META-INF/system/statement/sql”目录中编写 .sql 脚本。您可以像这样编写 SELECT 语句(每个文件一条语句):
查找用户.sql
SELECT * FROM HOME_USERS A WHERE A.ID = ${USER_ID}
和UPDATE这样的语句(每个文件一个或多个语句):
创建用户.sql
INSERT INTO HOME_USERS (ID, NAME) VALUES (${USER_ID}, ${USER_NAME});
INSERT INTO ACTIVE_USERS (ID) VALUES (${USER_ID});
4- 要连接数据库,请执行以下操作:
// "Test.class" must be any class of your project (the same project where /META-INF/system directory exists).
// Do not change "full" and "relational-database" strings.
// If you change "system" for "test", then the directory will be /META-INF/test.
RDBMSView ddbb = (RDBMSView) ScopeFactory.createTemplate(Test.class, "full", "system").getObject("relational-database");
// Connect with the database.
ddbb.connect();
5- 从 .sql 文件运行 SELECT 语句,如下所示:
// Values for variables in the SELECT statement.
Hashtable values = new Hashtable();
// Set variables to filter the query.
values.put("USER_ID", new Integer(8375));
// Read '/META-INF/system/statement/sql/find-user.sql', replace variables and run.
// -1 values are for pagination (first -1 is the page, second -1 is the max rows per page).
ResultSet result = (ResultSet) ddbb.executeQueryByName("find-user", values, -1, -1);
6- 从 .sql 文件运行 UPDATE 语句,如下所示:
// Values for variables in the UPDATE statements.
Hashtable values = new Hashtable();
// Set variables for the update statement.
values.put("USER_ID", new Integer(3));
values.put("USER_NAME", "Oompa Loompa");
// Read '/META-INF/system/statement/sql/create-user.sql', replace variables and run.
// ';' is the character that separates each statement.
ddbb.executeUpdateByName("create-user", values, new Character(';'));
RDBMSView类提供了这些方法以及连接/断开连接、提交、回滚……您还可以直接从 String 对象运行语句。
关于命名参数,这里有一些我发现的解决方案
如果您只使用 SELECT/UPDATE/INSERT/DELETE 语句,那么从脚本中提取语句是一项非常简单的任务(但并非微不足道)。只需在任何“;”处拆分即可不在两个“'”字符之间的字符。如果您承认脚本中也有 CREATE TRIGGERS 或 CREATE PROCEDURE 等,那么它会成为一个更复杂的解析问题,因为它们有“;”作为他们罪孽的一部分。
我个人认为,将 DDL 排除在外并使用一些外部工具(如 Liquibase
)管理数据库创建更安全使用适用于 Java 17+ 的
SqlParamBuilder
类尝试以下解决方案:
void mainStart(Connection dbConnection) throws Exception {
try (var builder = new SqlParamBuilder(dbConnection)) {
builder.sql(Files.readString(Path.of("insert.sql")))
.bind("id", 1)
.bind("code", "T")
.bind("created", someDate)
.execute();
List<Employee> employees = builder
.sql(Files.readString(Path.of("select.sql")))
.bind("id", 10)
.bind("code", "T", "V")
.streamMap(rs -> new Employee(
rs.getInt("id"),
rs.getString("name"),
rs.getObject("created", LocalDate.class)))
.toList();
}
}
record Employee (int id, String name, LocalDate created) {}
static class SqlParamBuilder {…}
欲了解更多信息,请参阅链接: https://github.com/pponec/PPScriptsForJava/blob/main/docs/SqlParamBuilder.md