JDBC Next: A New Asynchronous API for Connecting to a Database CON1491

Douglas Surber JDBC Architect Oracle Database Server Technologies

October 3, 2017

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 3 Safe Harbor Statement

The following is intended to outline our general product direction. It is intended for information purposes only, and may not be incorporated into any contract. It is not a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions. The development, release, and timing of any features or functionality described for Oracle’s products remains at the sole discretion of Oracle.

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | Program Agenda

1 Overview

2 Code

3 Wrap-up

4 Q&A

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | Confidential – Oracle Internal/Restricted/Highly Restricted 5 Overview

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 6 Asynchronous Database Access

standard database access API that never blocks user threads • Developed by the JDBC Expert Group with community input • Targeted for a near future release, Java 10 or equivalent • Asynchronous apps have better throughput – Fewer threads means less thread scheduling, less thread contention – Database access is slow so blocked threads leave resources idle for a long time – Simultaneous access to multiple databases, e.g. map/reduce, sharded databases – Fire and forget, e.g. DMS, stored procedures

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 7 Goals • No user thread blocks ever – Minimize the number of threads used for database access • Alternate API for database access – Not an extension of the standard JDBC API – Not a replacement for the standard JDBC API • Target high throughput apps – Not a completely general purpose database access API – The initial version will have a limited feature set • Build on the Java SE class library

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 8 Design Choices

• Minimal or no reference to java.sql • Rigorous use of types • Builder pattern • Fluent API • Immutable after initialization • One way to do something is enough • Avoid SQL processing • Avoid callback hell

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 9 What About … ? There are already multiple asynchronous/non-blocking database access APIs • Streams – Java streams are inherently synchronous • ReactiveStreams – Not integrated into the Java SE class library • NodeJS • ADBCJ

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 10 Code

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 11 Trivial Insert public void trivialInsert(DataSource ds) { String sql = "insert into tab values (:id, :name, :answer)"; try (Connection conn = ds.getConnection()) { conn.countOperation(sql) .set("id", 1, JdbcType.NUMERIC) .set("name", "Deep Thought", JdbcType.VARCHAR) .set("answer", 42, JdbcType.NUMERIC) .submit(); } }

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 12 All SQL is Vendor Specific

• No escape sequences • No specified parameter markers • Non vendor specific syntax requires processing by the driver – Adds overhead – Increases code complexity – Minimal benefit as most apps are tied to a specific database regardless

Note: Code examples use parameter markers from a variety of databases including DB2 (:foo), MySQL (?), Oracle Database(:foo), PostgresSQL($1), and SQL Server (@foo).

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 13 Trivial Select public void trivialSelect(DataSource ds, List result) { String sql = "select id, name, answer " + "from tab where answer = $1"; try (Connection conn = ds.getConnection()) { conn.>rowOperation(sql) .set("1", 42, JdbcType.NUMERIC) .rowAggregator( (ignore, row) -> { result.add(row.get("id", Integer.class)); return null; } ) .submit(); } }

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 14 Execution Model Everything is an Operation • Operations consist of – SQL or other database operation – Parameter assignments – Result handling – Submission and CompletableFuture • User thread creates and submits Operations – Creating and submitting never blocks; user thread never blocks • Implementation executes those Operations asynchronously – Performs round trip(s) to the database – Executes result handling – Completes CompletableFutures

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 15 Getting a DataSource public DataSource getDataSource(String url, String user, String pass) { return DataSourceFactory.forName("Oracle Database") .builder() .url("oracle:database:@//javaone.oracle.com:5521/javaone") .username("scott") .password("tiger") .connectionProperty(JdbcConnectionProperty.TRANSACTION_ISOLATION, TransactionIsolation.SERIALIZABLE) .build(); }

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 16 Getting a Connection in interface DataSource: public default Connection getConnection() { return builder().build().connect(); } in interface Connection: public default Connection connect() { holdForMoreMembers(); submit(); connectOperation().submit(); return this; }

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 17 Basic SELECT public Future selectIdForAnswer(DataSource ds, int answer) { String sql = "select id, name, answer from tab " + "where answer = @target"; try (Connection conn = ds.getConnection()) { return conn.>rowOperation(sql) .set("target", 42, JdbcType.NUMERIC) .initialValue( () -> new ArrayList<>() ) .rowAggregator( (list, row) -> { list.add(row.get("id", Integer.class)); return list; } ) .submit() .toCompletableFuture() .thenApply( l -> l.get(0) ); } }

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 18 POJOs public static class Item { protected int id; protected String name; protected int answer;

@SqlColumns( { “ID”, “USER”, “RESPONSE” } ) public Item(int id, String name, int answer) { this.id = id; this.name = name; this.answer = answer; }

(cont.)

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 19 POJOs (cont.)

@SqlParameter(marker=”id”, sqlType="NUMERIC") public int getId() { return id; }

@SqlParameter(marker=”name”, sqlType="VARCHAR") public String getName() { return name; }

@SqlParameter(marker=”answer”, sqlType="NUMERIC") public int getAnswer() { return answer; } }

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 20 Batch Insert public void insertListIndependent(List list, DataSource ds) { String sql = "insert into tab values " + "(:elem_id, :elem_name, :elem_answer)"; try (Connection conn = ds.getConnection()) { BatchCountOperation batch = conn.batchCountOperation(sql); list.forEach( (elem) -> { batch.countOperation() .set("elem_", elem) .submit(); }); batch.submit(); } }

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 21 OperationGroup Operations can be grouped • OperationGroup has its own result handling and CompletableFuture • Members submitted to group. OperationGroup is submitted as a unit • Order of execution – Sequential in order submitted – Parallel, any order • Error response – Dependent: remaining group members skipped – Independent: remaining group members unaffected • Conditional or unconditional • Connection is an OperationGroup – Sequential, dependent, unconditional by default

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 22 Independent INSERT public void insertListHold(List list, DataSource ds) { String sql = "insert into tab " + "values (@elem_id, @elem_name, @elem_answer)"; try (Connection conn = ds.getConnection()) { OperationGroup group = conn.operationGroup() .holdForMoreMembers() .independent(); group.submit();

(cont.)

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 23 Independent Insert (cont.)

for (Item elem : list) { group.countOperation(sql) .set("elem_", elem) .submit() .toCompletableFuture() .exceptionally( t -> { System.out.println(elem.getId()); return null; }); } group.releaseProhibitingMoreMembers(); } }

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 24 Close in interface Connection public default void close() { closeOperation().submit(); releaseProhibitingMoreMembers(); }

Note: A CloseOperation is never skipped.

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 25 Connection Pooling in interface Connection public Connection activate(); public Connection deactivate(); public registerLifecycleListener(LifecycleListener listener);

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 26 ConnectionProperties public DataSource getDataSource(String url, String user, String pass) { return DataSourceFactory.forName("Oracle Database") .builder() .url("oracle:database:@//javaone.oracle.com:5521/javaone") .username("scott") .password("tiger") .connectionProperty(JdbcConnectionProperty.TRANSACTION_ISOLATION, TransactionIsolation.SERIALIZABLE) .connectionProperty(NLS_LANGUAGE, “French”) .build(); }

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 27 ConnectionProperty public static class NlsLanguageProperty implements ConnectionProperty { public static final ConnectionProperty NLS_LANGUAGE = new NlsLanguageProperty(); private NlsLanguageProperty() {} public String name() { return “NLS_LANGUAGE”; } public Class range() { return String.class; } public String defaultValue() { return “American”; } public boolean isSensitive() { return false; } public Operation configureOperation(OperationGroup group, Object value) { String sql = “ALTER SESSION SET NLS_LANGUAGE TO “ + value; return group.operation(sql); }

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 28 ARRAYs and STRUCTs

@SqlArray(elementSqlTypeName=”STUDENT”) public class Roster extends LinkedList { public Roster() {}; }

@SqlStruct(sqlTypeName=”STUDENT”, fields={ @Field(sqlFieldName=”NAME”, sqlTypeName=”VARCHAR(100)”, javaFieldName=”name”), @Field(sqlFieldName=”EMAIL”, sqlTypeName=”VARCHAR(100)”, javaFieldName=”email”)}) public class Student { String name; String email; public void setName(String n) {name = n;} public void setEmail(String e) {email = e;} public String getName() {return name;} public String getEmail() {return email;} }

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 29 Transactions public void transfer(Connection conn, double amount, int fromAccount, int toAccount) { String sql = “update account set balance=balance+@amount where id = @account”; conn.countOperation(sql) .set(“amount”, -amount, JdbcType.DECIMAL) .set(“account”, fromAccount, JdbcType.INTEGER) .submit(); conn.countOperation(sql) .set(“amount”, amount, JdbcType.DECIMAL) .set(“account”, toAccount, JdbcType.INTEGER) .submit(); conn.commit(); }

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 30 Transactions (cont.) public void transfer(Connection conn, double amount, int fromAccount, int toAccount) { String sql = “update account set balance=balance+@amount where id = @account”; final Transaction tran = conn.getTransaction(); conn.countOperation(sql) .set(“amount”, -amount, JdbcType.DECIMAL) .set(“account”, fromAccount, JdbcType.INTEGER) .onError( e -> tran.setRollbackOnly() ) .submit(); conn.countOperation(sql) .set(“amount”, amount, JdbcType.DECIMAL) .set(“account”, toAccount, JdbcType.INTEGER) .onError( e -> tran.setRollbackOnly() ) .submit(); conn.commit(); }

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 31 Local Operations public void paycheck(Connection conn, Employee emp) { String sql = “call generate_paycheck(?, ?)”; CompletableFuture details = conn.outOperation(sql) .set(“1”, emp, JdbcType.STRUCT) .outParameter(“2”, JdbcType.STRUCT) .resultProcessor( m -> m.get(“2”, CheckDetail.class) ) .submit() .toCompletableFuture(); conn.localOperation() .onExecution( () -> { printPaycheck(details); return null; } ) .submit() .toCompletableFuture() .thenRun( () -> reportPrintSuccess(details) ); .exceptionally( t -> {reportPrintFailure(details); return null;} ); }

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 32 Future Parameters public void deposit(Connection conn, int accountId, double amount) { String selectSql = “select balance from account where id = $1”; CompletableFuture newBalanceF = conn rowOperation(selectSql) .set(“1”, accountId, JdbcType.INTEGER) .rowAggregator( (p, row) -> row.get(“balance”, Double.class) ) .submit() .toCompletableFuture() .thenApply( b -> b + amount ); String updateSql = “update account set balance=$2 where id = $1”; conn.countOperation(updateSql) .set(“1”, accountId, JdbcType.INTEGER) .set(“2”, newBalanceF, JdbcType.DOUBLE) .submit(); }

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 33 Wrap-up

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 34 Status • Everything is subject to change • Prototype Oracle Database driver implements much of the API – Uses nio Selector so non-blocking all the way down • Developed by the JDBC Expert Group through the • Targeted for a near future release, Java 10 or equivalent • The API is available for download from OpenJDK at http://oracle.com/goto/java-async-db • Send feedback to jdbc-spec-discuss@.java.net

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 35 Q&A

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 36 Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | 37

public DataSource getDataSource_2(String url, String user, String pass) { /* conn.>rowOperation(sql) return DataSourceFactory.forName("Oracle Database") * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. .set("1", 42, JdbcType.NUMERIC) .builder() * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. .rowAggregator((ignore, row) -> { .url("oracle:database:@//javaone.oracle.com:5521/javaone") * result.add(row.get("id", Integer.class)); .username("scott") * This code is free software; you can redistribute it and/or modify it return null; .password("tiger") * under the terms of the GNU General Public License version 2 only, as }) .connectionProperty(JdbcConnectionProperty.TRANSACTION_ISOLATION, * published by the Free Software Foundation. Oracle designates this .submit(); TransactionIsolation.SERIALIZABLE) * particular file as subject to the "Classpath" exception as provided } .connectionProperty(NlsLanguageProperty.NLS_LANGUAGE, "French") * by Oracle in the LICENSE file that accompanied this code. } * .build(); } * This code is distributed in the hope that it will be useful, but WITHOUT public DataSource getDataSource(String url, String user, String pass) { * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or return DataSourceFactory.forName("Oracle Database") public void transfer(Connection conn, * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License .builder() double amount, int fromAccount, int toAccount) { * version 2 for more details (a copy is included in the LICENSE file that .url("oracle:database:@//javaone.oracle.com:5521/javaone") String sql = "update account set balance=balance+@amount where id = * accompanied this code). .username("scott") @account"; * .password("tiger") conn.countOperation(sql) * You should have received a copy of the GNU General Public License version .connectionProperty(JdbcConnectionProperty.TRANSACTION_ISOLATION, .set("amount", -amount, JdbcType.DECIMAL) * 2 along with this work; if not, write to the Free Software Foundation, TransactionIsolation.SERIALIZABLE) .set("account", fromAccount, JdbcType.INTEGER) * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. .build(); .submit(); * } * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA conn.countOperation(sql) .set("amount", amount, JdbcType.DECIMAL) * or visit www.oracle.com if you need additional information or have any public Future selectIdForAnswer(DataSource ds, int answer) { .set("account", toAccount, JdbcType.INTEGER) * questions. String sql = "select id, name, answer from tab " .submit(); */ + "where answer = @target"; conn.commit(); package test; try (Connection conn = ds.getConnection()) { } return conn.>rowOperation(sql) import java.sql2.BatchCountOperation; .set("target", 42, JdbcType.NUMERIC) public void transfer_2(Connection conn, import java.sql2.Connection; .initialValue(() -> new ArrayList<>()) double amount, int fromAccount, int toAccount) { import java.sql2.ConnectionProperty; .rowAggregator((list, row) -> { String sql = "update account set balance=balance+@amount where id = import java.sql2.DataSource; list.add(row.get("id", Integer.class)); @account"; import java.sql2.DataSourceFactory; return list; final Transaction tran = conn.getTransaction(); import java.sql2.JdbcConnectionProperty; }) conn.countOperation(sql) import java.sql2.JdbcConnectionProperty.TransactionIsolation; .submit() .set("amount", -amount, JdbcType.DECIMAL) import java.sql2.JdbcType; .toCompletableFuture() .set("account", fromAccount, JdbcType.INTEGER) import java.sql2.Operation; .thenApply(l -> l.get(0)); .onError(e -> tran.setRollbackOnly()) import java.sql2.OperationGroup; } .submit(); import java.sql2.SqlArray; } import java.sql2.SqlColumns; conn.countOperation(sql) .set("amount", amount, JdbcType.DECIMAL) import java.sql2.SqlParameter; public void insertListIndependent(List list, DataSource ds) { .set("account", toAccount, JdbcType.INTEGER) import java.sql2.SqlStruct; String sql = "insert into tab values " .onError(e -> tran.setRollbackOnly()) import java.sql2.SqlStruct.Field; + "(:elem_id, :elem_name, :elem_answer)"; .submit(); import java.sql2.Transaction; try (Connection conn = ds.getConnection()) { conn.commit(); import java.util.ArrayList; BatchCountOperation batch = conn.batchCountOperation(sql); } import java.util.LinkedList; list.forEach((elem) -> { import java.util.List; batch.countOperation() public void paycheck(Connection conn, Employee emp) { import java.util.concurrent.CompletableFuture; .set("elem_", elem) String sql = "call generate_paycheck(?, ?)"; import java.util.concurrent.Future; .submit(); CompletableFuture details = import java.util.function.Consumer; }); conn.outOperation(sql) batch.submit(); .set("1", emp, JdbcType.STRUCT) /** } .outParameter("2", JdbcType.STRUCT) * } * Code from JavaOne 2017 slides: CON-1491 .resultProcessor(m -> m.get("2", CheckDetail.class)) .submit() * JDBC Next: A New Asynchronous API for Connecting to a Database public void insertListHold(List list, DataSource ds) { .toCompletableFuture(); * String sql = "insert into tab " conn.localOperation() */ + "values (@elem_id, @elem_name, @elem_answer)"; .onExecution(() -> { public class JavaOne { try (Connection conn = ds.getConnection()) { printPaycheck(details); OperationGroup group = conn.operationGroup() return null; public void trivialInsert(DataSource ds) { .holdForMoreMembers() }) String sql = "insert into tab values (:id, :name, :answer)"; .independent(); .submit() try (Connection conn = ds.getConnection()) { group.submit(); .toCompletableFuture() conn.countOperation(sql) for (Item elem : list) { .thenRun(() -> reportPrintSuccess(details)) .set("id", 1, JdbcType.NUMERIC) group.countOperation(sql) .exceptionally(t -> { .set("name", "Deep Thought", JdbcType.VARCHAR) .set("elem_", elem) reportPrintFailure(details); .set("answer", 42, JdbcType.NUMERIC) .submit() return null; .submit(); .toCompletableFuture() }); } .exceptionally(t -> { } } System.out.println(elem.getId()); return null; public void trivialSelect(DataSource ds, List result) { }); String sql = "select id, name, answer " } + "from tab where answer = $1"; group.releaseProhibitingMoreMembers(); try (Connection conn = ds.getConnection()) { } } Copyright © 2017, Oracle and/or its affiliates. All rights reserved. | @SqlArray(elementSqlTypeName = "STUDENT") public void deposit(Connection conn, int accountId, double amount) { public static class NlsLanguageProperty implements ConnectionProperty { String selectSql = "select balance from account where id = $1"; public static class Roster extends LinkedList { CompletableFuture newBalanceF = public static final ConnectionProperty NLS_LANGUAGE public Roster() { conn.rowOperation(selectSql) = new NlsLanguageProperty(); .set("1", accountId, JdbcType.INTEGER) } .rowAggregator((p, row) -> row.get("balance", Double.class)) private NlsLanguageProperty() { } .submit() } .toCompletableFuture() @SqlStruct(sqlTypeName = "STUDENT", fields = { .thenApply(b -> b + amount); @Override @Field(sqlFieldName = "NAME", sqlTypeName = "VARCHAR(100)", javaFieldName = "name"), String updateSql = "update account set balance=$2 where id = $1"; public String name() { @Field(sqlFieldName = "EMAIL", sqlTypeName = "VARCHAR(100)", javaFieldName = "email")}) conn.countOperation(updateSql) return "NLS_LANGUAGE"; public class Student { .set("1", accountId, JdbcType.INTEGER) } .set("2", newBalanceF, JdbcType.DOUBLE) String name; .submit(); @Override String email; } public Class range() { return String.class; public void setName(String n) { public static class Item { } name = n; } protected int id; @Override protected String name; public String defaultValue() { public void setEmail(String e) { protected int answer; return "American"; email = e; } @SqlColumns({"ID", "USER", "RESPONSE"}) } public Item(int id, String name, int answer) { @Override public String getName() { this.id = id; public boolean isSensitive() { return name; this.name = name; return false; } this.answer = answer; } } public String getEmail() { @Override return email; @SqlParameter(marker = "id", sqlType = "NUMERIC") public Operation configureOperation(OperationGroup group, Object value) { } public int getId() { String sql = "ALTER SESSION SET NLS_LANGUAGE TO " + value; } return id; return group.operation(sql); } } public class Employee { } @SqlParameter(marker = "name", sqlType = "VARCHAR") } public String getName() { return name; public class CheckDetail { } }

@SqlParameter(marker = "answer", sqlType = "NUMERIC") private void printPaycheck(Future checkF) { public int getAnswer() { throw new UnsupportedOperationException("Not supported yet."); //To change body of return answer; generated methods, choose Tools | Templates. } } } private Consumer reportPrintSuccess(Future checkF) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. }

private CompletableFuture reportPrintFailure(Future checkF) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. }

}

Copyright © 2017, Oracle and/or its affiliates. All rights reserved. |