Skip to content

Commit 4190445

Browse files
committed
Documentation
1 parent c99f277 commit 4190445

File tree

3 files changed

+123
-39
lines changed

3 files changed

+123
-39
lines changed

docs/SqlParamBuilder.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,89 @@
22

33
Script template for working with relational database using JDBC.
44

5+
## The sample of usage
6+
7+
If you want to run the Java class as a script, you need to download and connect the JDBC driver.
8+
You can use a Bash script `SqlExecutor.sh` that downloads the JDBC driver for the H2 database for for demonstration purposes.
9+
10+
```java
11+
void mainStart(Connection dbConnection) throws Exception {
12+
try (SqlParamBuilder builder = new SqlParamBuilder(dbConnection)) {
13+
System.out.println("# CREATE TABLE");
14+
builder.sql("""
15+
CREATE TABLE employee
16+
( id INTEGER PRIMARY KEY
17+
, name VARCHAR(256) DEFAULT 'test'
18+
, code VARCHAR(1)
19+
, created DATE NOT NULL )
20+
""")
21+
.execute();
22+
23+
System.out.println("# SINGLE INSERT");
24+
builder.sql("""
25+
INSERT INTO employee
26+
( id, code, created ) VALUES
27+
( :id, :code, :created )
28+
""")
29+
.bind("id", 1)
30+
.bind("code", "T")
31+
.bind("created", someDate)
32+
.execute();
33+
34+
System.out.println("# MULTI INSERT");
35+
builder.sql("""
36+
INSERT INTO employee
37+
(id,code,created) VALUES
38+
(:id1,:code,:created),
39+
(:id2,:code,:created)
40+
""")
41+
.bind("id1", 2)
42+
.bind("id2", 3)
43+
.bind("code", "T")
44+
.bind("created", someDate.plusDays(7))
45+
.execute();
46+
builder.bind("id1", 11)
47+
.bind("id2", 12)
48+
.bind("code", "V")
49+
.execute();
50+
51+
System.out.println("# SELECT");
52+
List<Employee> employees = builder.sql("""
53+
SELECT t.id, t.name, t.created
54+
FROM employee t
55+
WHERE t.id < :id
56+
AND t.code IN (:code)
57+
ORDER BY t.id
58+
""")
59+
.bind("id", 10)
60+
.bind("code", "T", "V")
61+
.streamMap(rs -> new Employee(
62+
rs.getInt("id"),
63+
rs.getString("name"),
64+
rs.getObject("created", LocalDate.class)))
65+
.toList();
66+
System.out.printf("# PRINT RESULT OF: %s%n", builder.toStringLine());
67+
employees.stream().forEach(System.out::println);
68+
69+
assertEquals(3, employees.size());
70+
assertEquals(1, employees.get(0).id);
71+
assertEquals("test", employees.get(0).name);
72+
assertEquals(someDate, employees.get(0).created);
73+
assertEquals( """
74+
SELECT t.id, t.name, t.created
75+
FROM employee t
76+
WHERE t.id < [10]
77+
AND t.code IN ([T],[V])
78+
ORDER BY t.id
79+
""", builder.toString());
80+
}
81+
}
82+
83+
record Employee (int id, String name, LocalDate created) {}
84+
85+
static class SqlParamBuilder {…}
86+
```
87+
88+
The result of only one `SqlParamBuilder` object can be processed at a time.
89+
For multiple simultaneous processing, multiple objects of the `SqlParamBuilder` type must be opened.
590
For more information see a source code: [SqlExecutor.java](../src/main/java/net/ponec/script/SqlExecutor.java) .

pom.xml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,12 @@
1717
</properties>
1818

1919
<dependencies>
20-
<dependency>
21-
<groupId>org.junit.jupiter</groupId>
22-
<artifactId>junit-jupiter-engine</artifactId>
23-
<version>5.10.2</version>
24-
<scope>test</scope>
25-
</dependency>
26-
2720
<dependency>
2821
<groupId>com.h2database</groupId>
2922
<artifactId>h2</artifactId>
3023
<version>2.2.224</version>
31-
<scope>test</scope>
3224
</dependency>
25+
3326
<dependency>
3427
<groupId>org.jetbrains.kotlin</groupId>
3528
<artifactId>kotlin-stdlib-jdk8</artifactId>
@@ -41,6 +34,13 @@
4134
<version>${kotlin.version}</version>
4235
<scope>test</scope>
4336
</dependency>
37+
38+
<dependency>
39+
<groupId>org.junit.jupiter</groupId>
40+
<artifactId>junit-jupiter-engine</artifactId>
41+
<version>5.10.2</version>
42+
<scope>test</scope>
43+
</dependency>
4444
</dependencies>
4545

4646
<build>

src/main/java/net/ponec/script/SqlExecutor.java

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ void mainStart(Connection dbConnection) throws Exception {
3838
( id INTEGER PRIMARY KEY
3939
, name VARCHAR(256) DEFAULT 'test'
4040
, code VARCHAR(1)
41-
, created DATE NOT NULL
42-
)""")
41+
, created DATE NOT NULL )
42+
""")
4343
.execute();
4444

4545
System.out.println("# SINGLE INSERT");
@@ -81,26 +81,24 @@ AND t.code IN (:code)
8181
.bind("id", 10)
8282
.bind("code", "T", "V")
8383
.streamMap(rs -> new Employee(
84-
rs.getInt(1),
85-
rs.getString(2),
86-
rs.getObject(3, LocalDate.class)))
84+
rs.getInt("id"),
85+
rs.getString("name"),
86+
rs.getObject("created", LocalDate.class)))
8787
.toList();
88+
System.out.printf("# PRINT RESULT OF: %s%n", builder.toStringLine());
89+
employees.stream().forEach(System.out::println);
8890

8991
assertEquals(3, employees.size());
9092
assertEquals(1, employees.get(0).id);
9193
assertEquals("test", employees.get(0).name);
9294
assertEquals(someDate, employees.get(0).created);
93-
94-
System.out.println("# REUSE THE SELECT\n");
95-
List<Employee> employees2 = builder
96-
.bind("id", 100)
97-
.streamMap(rs -> new Employee(
98-
rs.getInt(1),
99-
rs.getString(2),
100-
rs.getObject(3, LocalDate.class)))
101-
.toList();
102-
assertEquals(5, employees2.size());
103-
employees2.stream().forEach(System.out::println);
95+
assertEquals( """
96+
SELECT t.id, t.name, t.created
97+
FROM employee t
98+
WHERE t.id < [10]
99+
AND t.code IN ([T],[V])
100+
ORDER BY t.id
101+
""", builder.toString());
104102
}
105103
}
106104

@@ -132,16 +130,16 @@ private <T> void assertEquals(T expected, T result) {
132130

133131
/**
134132
* Less than 170 lines long class to simplify work with JDBC.
135-
* Original source: <a href="https://github.com/pponec/DirectoryBookmarks/blob/development/utils/SqlExecutor.java">GitHub</a>
133+
* Original source: <a href="https://github.com/pponec/DirectoryBookmarks/blob/development/src/main/java/net/ponec/script/SqlExecutor.java">GitHub</a>
136134
* Licence: Apache License, Version 2.0
137135
* @author Pavel Ponec, https://github.com/pponec
138-
* @version 1.0.6
136+
* @version 1.0.7
139137
*/
140138
static class SqlParamBuilder implements Closeable {
141139
/** SQL parameter mark type of {@code :param} */
142140
private static final Pattern SQL_MARK = Pattern.compile(":(\\w+)");
143141
private final Connection dbConnection;
144-
private final Map<String, Object> params = new HashMap<>();
142+
private final Map<String, Object[]> params = new HashMap<>();
145143
private String sqlTemplate = null;
146144
private PreparedStatement preparedStatement = null;
147145

@@ -152,14 +150,13 @@ public SqlParamBuilder(Connection dbConnection) {
152150
/** Close statement (if any) and set a new SQL template */
153151
public SqlParamBuilder sql(String... sqlLines) {
154152
close();
155-
this.params.clear();
156-
this.sqlTemplate = sqlLines.length == 1 ? sqlLines[0] : String.join("\n", sqlLines);
153+
sqlTemplate = sqlLines.length == 1 ? sqlLines[0] : String.join("\n", sqlLines);
157154
return this;
158155
}
159156

160-
/** Assign a SQL value(s) */
161-
public SqlParamBuilder bind(String key, Object... value) {
162-
this.params.put(key, value.length == 1 ? value[0] : List.of(value));
157+
/** Assign a SQL value(s). In case a reused statement set the same number of parameters items. */
158+
public SqlParamBuilder bind(String key, Object... values) {
159+
params.put(key, values.length > 0 ? values : new Object[]{null});
163160
return this;
164161
}
165162

@@ -218,34 +215,32 @@ public void close() {
218215
throw new IllegalStateException("Closing fails", e);
219216
} finally {
220217
preparedStatement = null;
218+
params.clear();
221219
}
222220
}
223221

224222
public PreparedStatement prepareStatement() throws SQLException {
225-
final var sqlValues = new ArrayList<>(params.size());
223+
final var sqlValues = new ArrayList<>();
226224
final var sql = buildSql(sqlValues, false);
227225
final var result = preparedStatement != null
228226
? preparedStatement
229227
: dbConnection.prepareStatement(sql);
230228
for (int i = 0, max = sqlValues.size(); i < max; i++) {
231229
result.setObject(i + 1, sqlValues.get(i));
232230
}
233-
this.preparedStatement = result;
231+
preparedStatement = result;
234232
return result;
235233
}
236234

237235
private String buildSql(List<Object> sqlValues, boolean toLog) {
238236
final var result = new StringBuilder(256);
239237
final var matcher = SQL_MARK.matcher(sqlTemplate);
240238
final var missingKeys = new HashSet<>();
241-
final var singleValue = new Object[1];
242239
while (matcher.find()) {
243240
final var key = matcher.group(1);
244-
final var value = params.get(key);
245-
if (value != null) {
241+
final var values = params.get(key);
242+
if (values != null) {
246243
matcher.appendReplacement(result, "");
247-
singleValue[0] = value;
248-
final Object[] values = value instanceof List ? ((List<?>) value).toArray() : singleValue;
249244
for (int i = 0; i < values.length; i++) {
250245
if (i > 0) result.append(',');
251246
result.append(Matcher.quoteReplacement(toLog ? "[" + values[i] + "]" : "?"));
@@ -272,6 +267,10 @@ public String toString() {
272267
return buildSql(new ArrayList<>(), true);
273268
}
274269

270+
public String toStringLine() {
271+
return toString().replaceAll("\\s*\\R+\\s*", " ");
272+
}
273+
275274
@FunctionalInterface
276275
public interface SqlFunction<T, R> extends Function<T, R> {
277276
default R apply(T resultSet) {

0 commit comments

Comments
 (0)