Skip to content

Commit bc5cbba

Browse files
authored
Support reuse of templates across pooled connections (#1614)
Closes #1611 Signed-off-by: Thomas Segismont <tsegismont@gmail.com>
1 parent 9989e6d commit bc5cbba

5 files changed

Lines changed: 111 additions & 12 deletions

File tree

vertx-sql-client-templates/src/main/asciidoc/index.adoc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,24 @@ When you need to perform an insert or update operation, and you do not care of t
4747
{@link examples.TemplateExamples#insertExample}
4848
----
4949

50+
[TIP]
51+
====
52+
Creating a template instance with {@link io.vertx.sqlclient.templates.SqlTemplate#forQuery} or {@link io.vertx.sqlclient.templates.SqlTemplate#forUpdate} involves parsing the query, and that doesn't come for free.
53+
54+
To avoid paying the price of computing the actual query repeatedly, consider reusing your template instances.
55+
Typically, you will store an instance as a verticle field.
56+
====
57+
58+
[TIP]
59+
====
60+
When your template must be executed inside a transaction, you might create a temporary instance using {@link io.vertx.sqlclient.templates.SqlTemplate#withClient}:
61+
62+
[source,$lang]
63+
----
64+
{@link examples.TemplateExamples#templateInTransaction}
65+
----
66+
====
67+
5068
== Template syntax
5169

5270
The template syntax uses `#{XXX}` syntax where `XXX` is a valid https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.8[java identifier] string

vertx-sql-client-templates/src/main/java/examples/TemplateExamples.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package examples;
22

3+
import io.vertx.codegen.annotations.DataObject;
34
import io.vertx.codegen.format.QualifiedCase;
45
import io.vertx.codegen.format.SnakeCase;
5-
import io.vertx.codegen.annotations.DataObject;
6+
import io.vertx.core.Future;
67
import io.vertx.core.json.JsonObject;
78
import io.vertx.docgen.Source;
8-
import io.vertx.sqlclient.Row;
9-
import io.vertx.sqlclient.SqlClient;
10-
import io.vertx.sqlclient.Tuple;
9+
import io.vertx.sqlclient.*;
1110
import io.vertx.sqlclient.templates.RowMapper;
1211
import io.vertx.sqlclient.templates.SqlTemplate;
1312
import io.vertx.sqlclient.templates.TupleMapper;
@@ -424,4 +423,19 @@ public Tuple map(Function<Integer, String> mapping, int size, UserDataObject par
424423
throw new UnsupportedOperationException();
425424
}
426425
}
426+
427+
public void templateInTransaction(Pool pool) {
428+
SqlTemplate<Map<String, Object>, RowSet<UserDataObject>> template = SqlTemplate
429+
.forQuery(pool, "SELECT * FROM users WHERE id=#{id}")
430+
.mapTo(UserDataObjectRowMapper.INSTANCE);
431+
432+
// Store the resolved template instance for reuse (typically in a verticle field).
433+
434+
// Then create an ephemeral instance for execution inside a transaction
435+
436+
Future<RowSet<UserDataObject>> future = pool.withTransaction(conn -> {
437+
return template.withClient(conn) // Create an ephemeral instance, don't reuse it
438+
.execute(Map.of("id", 1));
439+
});
440+
}
427441
}

vertx-sql-client-templates/src/main/java/io/vertx/sqlclient/templates/SqlTemplate.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@ public interface SqlTemplate<I, R> {
5151
* @return the template
5252
*/
5353
static SqlTemplate<Map<String, Object>, RowSet<Row>> forQuery(SqlClient client, String template) {
54-
io.vertx.sqlclient.templates.impl.SqlTemplate sqlTemplate = io.vertx.sqlclient.templates.impl.SqlTemplate.create((SqlClientInternal) client, template);
55-
return new SqlTemplateImpl<>(client, sqlTemplate, Function.identity(), sqlTemplate::mapTuple);
54+
SqlClientInternal clientInternal = (SqlClientInternal) client;
55+
io.vertx.sqlclient.templates.impl.SqlTemplate sqlTemplate = io.vertx.sqlclient.templates.impl.SqlTemplate.create(clientInternal, template);
56+
return new SqlTemplateImpl<>(clientInternal, sqlTemplate, Function.identity(), sqlTemplate::mapTuple);
5657
}
5758

5859
/**
@@ -63,8 +64,9 @@ static SqlTemplate<Map<String, Object>, RowSet<Row>> forQuery(SqlClient client,
6364
* @return the template
6465
*/
6566
static SqlTemplate<Map<String, Object>, SqlResult<Void>> forUpdate(SqlClient client, String template) {
66-
io.vertx.sqlclient.templates.impl.SqlTemplate sqlTemplate = io.vertx.sqlclient.templates.impl.SqlTemplate.create((SqlClientInternal) client, template);
67-
return new SqlTemplateImpl<>(client, sqlTemplate, query -> query.collecting(SqlTemplateImpl.NULL_COLLECTOR), sqlTemplate::mapTuple);
67+
SqlClientInternal clientInternal = (SqlClientInternal) client;
68+
io.vertx.sqlclient.templates.impl.SqlTemplate sqlTemplate = io.vertx.sqlclient.templates.impl.SqlTemplate.create(clientInternal, template);
69+
return new SqlTemplateImpl<>(clientInternal, sqlTemplate, query -> query.collecting(SqlTemplateImpl.NULL_COLLECTOR), sqlTemplate::mapTuple);
6870
}
6971

7072

@@ -140,6 +142,28 @@ default <T> SqlTemplate<T, R> mapFrom(Class<T> type) {
140142
@GenIgnore
141143
<U> SqlTemplate<I, SqlResult<U>> collecting(Collector<Row, ?, U> collector);
142144

145+
/**
146+
* Returns a new template, using the specified {@code client}.
147+
* <p>
148+
* This method does not compute the template query again, so it can be useful to execute a template on a specific {@link io.vertx.sqlclient.SqlConnection}.
149+
* For example, after starting a transaction:
150+
*
151+
* <pre>
152+
* // Typically stored as a verticle field
153+
* // So that heavy computation of the template happens once
154+
* SqlTemplate<Map<String, Object>, RowSet<World>> template = SqlTemplate
155+
* .forQuery(pool, "SELECT id, randomnumber FROM tmp_world")
156+
* .mapTo(World.class);
157+
*
158+
* // Executing the template inside a transaction
159+
* Future<RowSet<World>> future = pool.withTransaction(conn -> template.withClient(conn).execute(Map.of()));
160+
* </pre>
161+
*
162+
* @param client the client that will execute requests
163+
* @return a new template
164+
*/
165+
SqlTemplate<I, R> withClient(SqlClient client);
166+
143167
/**
144168
* Execute the query with the {@code parameters}
145169
*

vertx-sql-client-templates/src/main/java/io/vertx/sqlclient/templates/impl/SqlTemplateImpl.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import io.vertx.core.Future;
44
import io.vertx.core.json.JsonObject;
55
import io.vertx.sqlclient.*;
6+
import io.vertx.sqlclient.impl.SqlClientInternal;
67
import io.vertx.sqlclient.templates.RowMapper;
78
import io.vertx.sqlclient.templates.TupleMapper;
89

910
import java.util.List;
11+
import java.util.Objects;
1012
import java.util.function.Function;
1113
import java.util.stream.Collector;
1214
import java.util.stream.Collectors;
@@ -16,12 +18,12 @@ public class SqlTemplateImpl<I, R> implements io.vertx.sqlclient.templates.SqlTe
1618
//
1719
public static final Collector<Row, Void, Void> NULL_COLLECTOR = Collector.of(() -> null, (v, row) -> {}, (a, b) -> null);
1820

19-
protected final SqlClient client;
21+
protected final SqlClientInternal client;
2022
protected final SqlTemplate sqlTemplate;
2123
protected final Function<I, Tuple> tupleMapper;
2224
protected Function<PreparedQuery<RowSet<Row>>, PreparedQuery<R>> queryMapper;
2325

24-
public SqlTemplateImpl(SqlClient client,
26+
public SqlTemplateImpl(SqlClientInternal client,
2527
SqlTemplate sqlTemplate,
2628
Function<PreparedQuery<RowSet<Row>>,
2729
PreparedQuery<R>> queryMapper,
@@ -47,6 +49,12 @@ public <U> io.vertx.sqlclient.templates.SqlTemplate<I, SqlResult<U>> collecting(
4749
return new SqlTemplateImpl<>(client, sqlTemplate, query -> query.collecting(collector), tupleMapper);
4850
}
4951

52+
@Override
53+
public io.vertx.sqlclient.templates.SqlTemplate<I, R> withClient(SqlClient client) {
54+
SqlClientInternal clientInternal = (SqlClientInternal) Objects.requireNonNull(client, "client is null");
55+
return new SqlTemplateImpl<>(clientInternal, sqlTemplate, queryMapper, tupleMapper);
56+
}
57+
5058
@Override
5159
public <U> io.vertx.sqlclient.templates.SqlTemplate<I, RowSet<U>> mapTo(Class<U> type) {
5260
return mapTo(row -> {

vertx-sql-client-templates/src/test/java/io/vertx/tests/sqlclient/templates/MySQLTest.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.vertx.mysqlclient.MySQLBuilder;
77
import io.vertx.mysqlclient.MySQLConnectOptions;
88
import io.vertx.sqlclient.Pool;
9+
import io.vertx.sqlclient.RowSet;
910
import io.vertx.sqlclient.templates.SqlTemplate;
1011
import org.junit.AfterClass;
1112
import org.junit.Before;
@@ -15,15 +16,17 @@
1516
import org.testcontainers.containers.GenericContainer;
1617

1718
import java.time.Duration;
19+
import java.util.Collections;
20+
import java.util.Map;
1821

1922
@RunWith(VertxUnitRunner.class)
2023
public class MySQLTest {
2124

22-
private static GenericContainer server;
25+
private static GenericContainer<?> server;
2326

2427
@BeforeClass
2528
public static void startDatabase() {
26-
server = new GenericContainer("mysql:8.0")
29+
server = new GenericContainer<>("mysql:8.0")
2730
.withEnv("MYSQL_USER", "mysql")
2831
.withEnv("MYSQL_PASSWORD", "password")
2932
.withEnv("MYSQL_ROOT_PASSWORD", "password")
@@ -76,4 +79,36 @@ public void testDurationMapping(TestContext ctx) {
7679
ctx.assertEquals(duration, row.getDuration());
7780
}));
7881
}
82+
83+
@Test
84+
public void executeWithOtherClient(TestContext ctx) {
85+
SqlTemplate<Map<String, Object>, RowSet<World>> template = SqlTemplate
86+
.forQuery(pool, "SELECT id, randomnumber FROM tmp_world")
87+
.mapTo(World.class);
88+
89+
pool.withTransaction(conn -> {
90+
// Create a table visible only within the current session
91+
return conn.query("CREATE TEMPORARY TABLE tmp_world (" +
92+
"id int(10) unsigned NOT NULL auto_increment, " +
93+
"randomnumber int NOT NULL default 0, " +
94+
"PRIMARY KEY (id)) " +
95+
"ENGINE=INNODB")
96+
.execute()
97+
.compose(v -> {
98+
return conn.query("INSERT INTO tmp_world (randomnumber) VALUES " +
99+
"(floor(0 + (rand() * 10000))), " +
100+
"(floor(0 + (rand() * 10000))), " +
101+
"(floor(0 + (rand() * 10000)))")
102+
.execute();
103+
})
104+
.compose(v -> {
105+
return template.withClient(conn).execute(Collections.emptyMap());
106+
});
107+
}).onComplete(ctx.asyncAssertSuccess(rows -> {
108+
ctx.assertEquals(3, rows.size());
109+
for (World world : rows) {
110+
ctx.assertNotNull(world.id);
111+
}
112+
}));
113+
}
79114
}

0 commit comments

Comments
 (0)