convenience methods
Lukas Eder
lukas.eder at gmail.com
Wed Nov 22 11:28:42 UTC 2017
Hi Douglas,
I think that convenience methods are really very very helpful. jOOQ has
tons of them, for instance, because that really greatly reduces boiler
plate code on the call site and makes using the API much more effective and
fun. This also helps increase adoption.
One of the main reasons why Spring's JdbcTemplate, or Apache DbUtils, or
the simpler parts of the jOOQ API exists is the fact that the (synchronous)
JDBC API doesn't have any such methods. For instance, it would make total
sense to have
public interface Connection {
default PreparedStatement prepareStatement(String sql, Object...
bindings) {
PreparedStatement s = prepareStatement(sql);
for (int i = 0; i < bindings.length; i++)
s.setObject(i + 1, bindings[i]);
}
default int executeUpdate(String sql, Object... bindings) {
try (PreparedStatement s = prepareStatement(sql, bindings)) {
return s.executeUpdate();
}
}
}
I mean, who really enjoys counting bind indexes manually and sending a very
simple static INSERT statement with 10 values to the server by setting each
bind variable individually?
This convenience stuff has been dearly missing from the very first version
of JDBC :) (granted, it would have been difficult to add, without default
methods). There are many other possible convenience methods, including:
public interface ResultSet {
default Object[] get() {
Object[] result = new Object[getMetaData().getColumnCount()];
for (int i = 0; i < result.length; i++)
result[i] = getObject(i);
return result;
}
default void forEach(Consumer<Object[]> consumer) {
while (next())
consumer.accept(get());
}
}
I could go on and on, if you're interested. In fact, consider this a
feature request! :)
Further comments inline:
2017-11-16 17:25 GMT+01:00 Douglas Surber <douglas.surber at oracle.com>:
> As default methods these are all implemented by calling other interface
> methods. Clearly they aren’t necessary as the app can make the same
> sequence of calls. On the other hand their use will result in more compact
> and more readable code. Also, some of them handle some not so obvious
> behavior so that the programmer doesn’t have to think about it in the
> common use case. Again DataSource.getConnection is a good example;
Yes, a very good example. No one wants to type that builder() stuff all the
time even if it is useful for the special case. The convenience API should
be there for the more general case.
> look at Connection.connect. A third benefit and honestly the original
> motivation for many of them is as documentation, how to use the API.
>
That's an interesting way to look at it - it never occurred to me but
you're absolutely right. It's a nice way to document lower level API parts.
> As a general notion, do these methods carry their weight? Are the benefits
> they provide worth whatever additional complexity they add?
>
Absolutely! Again, one of the biggest flaws of the classic JDBC API is the
verbosity it generates in client code. Now many people argue that verbosity
is Java's strength, but that's nonsense when it comes to infrastructure
logic that never changes. E.g. try-with-resources is "convenience API" that
really really helped JDBC, because the proper approach to closing all of
Connection, Statement, ResultSet in the right order and with the right
exception handling really doesn't add any value to anyone's business - au
contraire, it's too easy to get them wrong.
Languages like Kotlin don't have the try-with-resources statement, but
extension methods that can be attached to any (Auto)Closeable, which is the
same thing as *external* convenience API. Without extension methods, that's
not possible in Java, so it is the API maintainer's burden to implement
default methods.
Also, you're not alone. Stream has Stream.collect(supplier, accumulator,
combiner), which is convenience for many cases of Stream.collect(Collector).
JDK 9's Optional has Optional.stream(), which really wasn't "necessary",
but so cool as it allows for flatmapping Stream<Optional<T>> to Stream<T>
through
stream.flatMap(Optional::stream)
instead of
stream.filter(Optional::isPresent).map(Optional::get)
Just two examples. There are many other convenience APIs in the JDK, even
trivial ones like the ArrayList constructors. The only one that is strictly
necessary is the one that accepts a capacity. The others are there for
convenience and improved performance.
Should we add executeAsTransaction? What about other convenience methods?
> Suggestions welcome.
>
Well, since you've added collect(Collector) (still very excited about
that), how about also adding collect(supplier, accumulator, combiner) as a
convenience?
If you're positive that you're moving forward with convenience methods, I'd
be happy to look for more opportunities to contribute both in ADBA and in
JDBC (if that's something you might consider)
Thanks,
Lukas
2017-11-16 17:25 GMT+01:00 Douglas Surber <douglas.surber at oracle.com>:
> ADBA includes a fair number of convenience methods. These are methods that
> encapsulate common code patterns into default methods.
> DataSource.getConnection() is an obvious example.
>
> As default methods these are all implemented by calling other interface
> methods. Clearly they aren’t necessary as the app can make the same
> sequence of calls. On the other hand their use will result in more compact
> and more readable code. Also, some of them handle some not so obvious
> behavior so that the programmer doesn’t have to think about it in the
> common use case. Again DataSource.getConnection is a good example; look at
> Connection.connect. A third benefit and honestly the original motivation
> for many of them is as documentation, how to use the API.
>
> As a general notion, do these methods carry their weight? Are the benefits
> they provide worth whatever additional complexity they add?
>
> Here is a concrete example to consider. This is a method in Connection.
>
> public default CompletionStage<TransactionOutcome>
> executeAsTransaction(BiConsumer<OperationGroup, Transaction>
> action) {
> OperationGroup grp = this.operationGroup();
> Transaction tran = this.transaction();
> action.accept(grp, tran);
> grp.submit();
> return this.commitMaybeRollback(tran); *
> }
>
> Clearly this method is not strictly necessary. It is implemented entirely
> by calling other methods present in ADBA. Any application or framework can
> do exactly the same. On the other hand most applications and frameworks
> would make exactly the same sequence of calls so including this pattern in
> ADBA would be convenient. Finally, this provides an example of how to use
> ADBA to execute a transaction. This is actually the original motivation for
> creating this method.
>
> ADBA is based on some relatively primitive concepts. In some cases there
> is quite a conceptual distance between those primitive concepts and what an
> application wants to do. It is easier to specify, understand, and implement
> just the primitive concepts but that leaves a conceptual gap to what the
> app needs; how to compose the primitives into useful actions. The various
> convenience methods bridge this gap without adding any specification or
> implementation complexity. IMO these convenience methods actually reduce
> the complexity by adding an executable specification of how the primitives
> work together to support a higher level abstraction.
>
> Should we add executeAsTransaction? What about other convenience methods?
> Suggestions welcome.
>
> Douglas
>
>
> * Note: commitMaybeRollback has been corrected to return CompletionStage<TransactionOutcome>
> which is clearly what it should do.
>
>
>
More information about the jdbc-spec-discuss
mailing list