Build a Java App with CockroachDB

This tutorial shows you how build a simple Java application with CockroachDB using a PostgreSQL-compatible driver or ORM.

We have tested the Java JDBC driver and the Hibernate ORM enough to claim beta-level support, so those are featured here. If you encounter problems, please open an issue with details to help us make progress toward full support.

Before you begin

Warning:

The examples on this page assume you are using a Java version <= 9. They do not work with Java 10.

Step 1. Install the Java JDBC driver

Download and set up the Java JDBC driver as described in the official documentation.

Step 2. Create the maxroach user and bank database

Start the built-in SQL client:

  1. $ cockroach sql --certs-dir=certs

In the SQL shell, issue the following statements to create the maxroach user and bank database:

  1. > CREATE USER IF NOT EXISTS maxroach;
  1. > CREATE DATABASE bank;

Give the maxroach user the necessary permissions:

  1. > GRANT ALL ON DATABASE bank TO maxroach;

Exit the SQL shell:

  1. > \q

Step 3. Generate a certificate for the maxroach user

Create a certificate and key for the maxroach user by running the following command. The code samples will run as this user.

New in v19.1: You can pass the —also-generate-pkcs8-key flag to generate a key in PKCS#8 format, which is the standard key encoding format in Java. In this case, the generated PKCS8 key will be named client.maxroach.key.pk8.

  1. $ cockroach cert create-client maxroach --certs-dir=certs --ca-key=my-safe-directory/ca.key --also-generate-pkcs8-key

Step 4. Run the Java code

Now that you have created a database and set up encryption keys, in this section you will:

Basic example

First, use the following code to connect as the maxroach user and execute some basic SQL statements: create a table, insert rows, and read and print the rows.

To run it:

  1. $ javac -classpath .:/path/to/postgresql.jar BasicSample.java
  1. $ java -classpath .:/path/to/postgresql.jar BasicSample

The output should be:

  1. Initial balances:
  2. account 1: 1000
  3. account 2: 250

The contents of BasicSample.java:

  1. import java.sql.*;
  2. import java.util.Properties;
  3. /*
  4. Download the Postgres JDBC driver jar from https://jdbc.postgresql.org.
  5. Then, compile and run this example like so:
  6. $ export CLASSPATH=.:/path/to/postgresql.jar
  7. $ javac BasicSample.java && java BasicSample
  8. */
  9. public class BasicSample {
  10. public static void main(String[] args)
  11. throws ClassNotFoundException, SQLException {
  12. // Load the Postgres JDBC driver.
  13. Class.forName("org.postgresql.Driver");
  14. // Connect to the "bank" database.
  15. Properties props = new Properties();
  16. props.setProperty("user", "maxroach");
  17. props.setProperty("sslmode", "require");
  18. props.setProperty("sslrootcert", "certs/ca.crt");
  19. props.setProperty("sslkey", "certs/client.maxroach.key.pk8");
  20. props.setProperty("sslcert", "certs/client.maxroach.crt");
  21. props.setProperty("ApplicationName", "roachtest");
  22. Connection db = DriverManager
  23. .getConnection("jdbc:postgresql://127.0.0.1:26257/bank", props);
  24. try {
  25. // Create the "accounts" table.
  26. db.createStatement()
  27. .execute("CREATE TABLE IF NOT EXISTS accounts (id INT PRIMARY KEY, balance INT)");
  28. // Insert two rows into the "accounts" table.
  29. db.createStatement()
  30. .execute("INSERT INTO accounts (id, balance) VALUES (1, 1000), (2, 250)");
  31. // Print out the balances.
  32. System.out.println("Initial balances:");
  33. ResultSet res = db.createStatement()
  34. .executeQuery("SELECT id, balance FROM accounts");
  35. while (res.next()) {
  36. System.out.printf("\taccount %s: %s\n",
  37. res.getInt("id"),
  38. res.getInt("balance"));
  39. }
  40. } finally {
  41. // Close the database connection.
  42. db.close();
  43. }
  44. }
  45. }

Transaction example (with retry logic)

Next, use the following code to execute a batch of statements as a transaction to transfer funds from one account to another.

To run it:

  • Download TxnSample.java, or create the file yourself and copy the code below. Note the use of SQLException.getSQLState() instead of getErrorCode().
  • Compile and run the code (again adding the PostgreSQL JDBC driver to your classpath):
  1. $ javac -classpath .:/path/to/postgresql.jar TxnSample.java
  1. $ java -classpath .:/path/to/postgresql.jar TxnSample

The output should be:

  1. account 1: 900
  2. account 2: 350

Note:

With the default SERIALIZABLE isolation level, CockroachDB may require the client to retry a transaction in case of read/write contention. CockroachDB provides a generic retry function that runs inside a transaction and retries it as needed. The code sample below shows how it is used.

  1. import java.sql.*;
  2. import java.util.Properties;
  3. /*
  4. Download the Postgres JDBC driver jar from https://jdbc.postgresql.org.
  5. Then, compile and run this example like so:
  6. $ export CLASSPATH=.:/path/to/postgresql.jar
  7. $ javac TxnSample.java && java TxnSample
  8. */
  9. // Ambiguous whether the transaction committed or not.
  10. class AmbiguousCommitException extends SQLException{
  11. public AmbiguousCommitException(Throwable cause) {
  12. super(cause);
  13. }
  14. }
  15. class InsufficientBalanceException extends Exception {}
  16. class AccountNotFoundException extends Exception {
  17. public int account;
  18. public AccountNotFoundException(int account) {
  19. this.account = account;
  20. }
  21. }
  22. // A simple interface that provides a retryable lambda expression.
  23. interface RetryableTransaction {
  24. public void run(Connection conn)
  25. throws SQLException, InsufficientBalanceException,
  26. AccountNotFoundException, AmbiguousCommitException;
  27. }
  28. public class TxnSample {
  29. public static RetryableTransaction transferFunds(int from, int to, int amount) {
  30. return new RetryableTransaction() {
  31. public void run(Connection conn)
  32. throws SQLException, InsufficientBalanceException,
  33. AccountNotFoundException, AmbiguousCommitException {
  34. // Check the current balance.
  35. ResultSet res = conn.createStatement()
  36. .executeQuery("SELECT balance FROM accounts WHERE id = "
  37. + from);
  38. if(!res.next()) {
  39. throw new AccountNotFoundException(from);
  40. }
  41. int balance = res.getInt("balance");
  42. if(balance < from) {
  43. throw new InsufficientBalanceException();
  44. }
  45. // Perform the transfer.
  46. conn.createStatement()
  47. .executeUpdate("UPDATE accounts SET balance = balance - "
  48. + amount + " where id = " + from);
  49. conn.createStatement()
  50. .executeUpdate("UPDATE accounts SET balance = balance + "
  51. + amount + " where id = " + to);
  52. }
  53. };
  54. }
  55. public static void retryTransaction(Connection conn, RetryableTransaction tx)
  56. throws SQLException, InsufficientBalanceException,
  57. AccountNotFoundException, AmbiguousCommitException {
  58. Savepoint sp = conn.setSavepoint("cockroach_restart");
  59. while(true) {
  60. boolean releaseAttempted = false;
  61. try {
  62. tx.run(conn);
  63. releaseAttempted = true;
  64. conn.releaseSavepoint(sp);
  65. break;
  66. }
  67. catch(SQLException e) {
  68. String sqlState = e.getSQLState();
  69. // Check if the error code indicates a SERIALIZATION_FAILURE.
  70. if(sqlState.equals("40001")) {
  71. // Signal the database that we will attempt a retry.
  72. conn.rollback(sp);
  73. } else if(releaseAttempted) {
  74. throw new AmbiguousCommitException(e);
  75. } else {
  76. throw e;
  77. }
  78. }
  79. }
  80. conn.commit();
  81. }
  82. public static void main(String[] args)
  83. throws ClassNotFoundException, SQLException {
  84. // Load the Postgres JDBC driver.
  85. Class.forName("org.postgresql.Driver");
  86. // Connect to the 'bank' database.
  87. Properties props = new Properties();
  88. props.setProperty("user", "maxroach");
  89. props.setProperty("sslmode", "require");
  90. props.setProperty("sslrootcert", "certs/ca.crt");
  91. props.setProperty("sslkey", "certs/client.maxroach.key.pk8");
  92. props.setProperty("sslcert", "certs/client.maxroach.crt");
  93. props.setProperty("ApplicationName", "roachtest");
  94. Connection db = DriverManager
  95. .getConnection("jdbc:postgresql://127.0.0.1:26257/bank", props);
  96. try {
  97. // We need to turn off autocommit mode to allow for
  98. // multi-statement transactions.
  99. db.setAutoCommit(false);
  100. // Perform the transfer. This assumes the 'accounts'
  101. // table has already been created in the database.
  102. RetryableTransaction transfer = transferFunds(1, 2, 100);
  103. retryTransaction(db, transfer);
  104. // Check balances after transfer.
  105. db.setAutoCommit(true);
  106. ResultSet res = db.createStatement()
  107. .executeQuery("SELECT id, balance FROM accounts");
  108. while (res.next()) {
  109. System.out.printf("\taccount %s: %s\n", res.getInt("id"),
  110. res.getInt("balance"));
  111. }
  112. } catch(InsufficientBalanceException e) {
  113. System.out.println("Insufficient balance");
  114. } catch(AccountNotFoundException e) {
  115. System.out.println("No users in the table with id " + e.account);
  116. } catch(AmbiguousCommitException e) {
  117. System.out.println("Ambiguous result encountered: " + e);
  118. } catch(SQLException e) {
  119. System.out.println("SQLException encountered:" + e);
  120. } finally {
  121. // Close the database connection.
  122. db.close();
  123. }
  124. }
  125. }

To verify that funds were transferred from one account to another, start the built-in SQL client:

  1. $ cockroach sql --certs-dir=certs --database=bank

To check the account balances, issue the following statement:

  1. > SELECT id, balance FROM accounts;
  1. +----+---------+
  2. | id | balance |
  3. +----+---------+
  4. | 1 | 900 |
  5. | 2 | 350 |
  6. +----+---------+
  7. (2 rows)

Step 2. Create the maxroach user and bank database

Start the built-in SQL client:

  1. $ cockroach sql --insecure

In the SQL shell, issue the following statements to create the maxroach user and bank database:

  1. > CREATE USER IF NOT EXISTS maxroach;
  1. > CREATE DATABASE bank;

Give the maxroach user the necessary permissions:

  1. > GRANT ALL ON DATABASE bank TO maxroach;

Exit the SQL shell:

  1. > \q

Step 3. Run the Java code

Now that you have created a database, in this section you will:

Basic example

First, use the following code to connect as the maxroach user and execute some basic SQL statements, creating a table, inserting rows, and reading and printing the rows.

To run it:

  1. $ javac -classpath .:/path/to/postgresql.jar BasicSample.java
  1. $ java -classpath .:/path/to/postgresql.jar BasicSample

The contents of BasicSample.java:

  1. import java.sql.*;
  2. import java.util.Properties;
  3. /*
  4. Download the Postgres JDBC driver jar from https://jdbc.postgresql.org.
  5. Then, compile and run this example like so:
  6. $ export CLASSPATH=.:/path/to/postgresql.jar
  7. $ javac BasicSample.java && java BasicSample
  8. */
  9. public class BasicSample {
  10. public static void main(String[] args)
  11. throws ClassNotFoundException, SQLException {
  12. // Load the Postgres JDBC driver.
  13. Class.forName("org.postgresql.Driver");
  14. // Connect to the "bank" database.
  15. Properties props = new Properties();
  16. props.setProperty("user", "maxroach");
  17. props.setProperty("sslmode", "disable");
  18. Connection db = DriverManager
  19. .getConnection("jdbc:postgresql://127.0.0.1:26257/bank", props);
  20. try {
  21. // Create the "accounts" table.
  22. db.createStatement()
  23. .execute("CREATE TABLE IF NOT EXISTS accounts (id INT PRIMARY KEY, balance INT)");
  24. // Insert two rows into the "accounts" table.
  25. db.createStatement()
  26. .execute("INSERT INTO accounts (id, balance) VALUES (1, 1000), (2, 250)");
  27. // Print out the balances.
  28. System.out.println("Initial balances:");
  29. ResultSet res = db.createStatement()
  30. .executeQuery("SELECT id, balance FROM accounts");
  31. while (res.next()) {
  32. System.out.printf("\taccount %s: %s\n",
  33. res.getInt("id"),
  34. res.getInt("balance"));
  35. }
  36. } finally {
  37. // Close the database connection.
  38. db.close();
  39. }
  40. }
  41. }

Transaction example (with retry logic)

Next, use the following code to execute a batch of statements as a transaction to transfer funds from one account to another.

To run it:

  • Download TxnSample.java, or create the file yourself and copy the code below. Note the use of SQLException.getSQLState() instead of getErrorCode().
  • Compile and run the code (again adding the PostgreSQL JDBC driver to your classpath):
  1. $ javac -classpath .:/path/to/postgresql.jar TxnSample.java
  1. $ java -classpath .:/path/to/postgresql.jar TxnSample

Note:

With the default SERIALIZABLE isolation level, CockroachDB may require the client to retry a transaction in case of read/write contention. CockroachDB provides a generic retry function that runs inside a transaction and retries it as needed. The code sample below shows how it is used.

  1. import java.sql.*;
  2. import java.util.Properties;
  3. /*
  4. Download the Postgres JDBC driver jar from https://jdbc.postgresql.org.
  5. Then, compile and run this example like so:
  6. $ export CLASSPATH=.:/path/to/postgresql.jar
  7. $ javac TxnSample.java && java TxnSample
  8. */
  9. // Ambiguous whether the transaction committed or not.
  10. class AmbiguousCommitException extends SQLException{
  11. public AmbiguousCommitException(Throwable cause) {
  12. super(cause);
  13. }
  14. }
  15. class InsufficientBalanceException extends Exception {}
  16. class AccountNotFoundException extends Exception {
  17. public int account;
  18. public AccountNotFoundException(int account) {
  19. this.account = account;
  20. }
  21. }
  22. // A simple interface that provides a retryable lambda expression.
  23. interface RetryableTransaction {
  24. public void run(Connection conn)
  25. throws SQLException, InsufficientBalanceException,
  26. AccountNotFoundException, AmbiguousCommitException;
  27. }
  28. public class TxnSample {
  29. public static RetryableTransaction transferFunds(int from, int to, int amount) {
  30. return new RetryableTransaction() {
  31. public void run(Connection conn)
  32. throws SQLException, InsufficientBalanceException,
  33. AccountNotFoundException, AmbiguousCommitException {
  34. // Check the current balance.
  35. ResultSet res = conn.createStatement()
  36. .executeQuery("SELECT balance FROM accounts WHERE id = "
  37. + from);
  38. if(!res.next()) {
  39. throw new AccountNotFoundException(from);
  40. }
  41. int balance = res.getInt("balance");
  42. if(balance < from) {
  43. throw new InsufficientBalanceException();
  44. }
  45. // Perform the transfer.
  46. conn.createStatement()
  47. .executeUpdate("UPDATE accounts SET balance = balance - "
  48. + amount + " where id = " + from);
  49. conn.createStatement()
  50. .executeUpdate("UPDATE accounts SET balance = balance + "
  51. + amount + " where id = " + to);
  52. }
  53. };
  54. }
  55. public static void retryTransaction(Connection conn, RetryableTransaction tx)
  56. throws SQLException, InsufficientBalanceException,
  57. AccountNotFoundException, AmbiguousCommitException {
  58. Savepoint sp = conn.setSavepoint("cockroach_restart");
  59. while(true) {
  60. boolean releaseAttempted = false;
  61. try {
  62. tx.run(conn);
  63. releaseAttempted = true;
  64. conn.releaseSavepoint(sp);
  65. }
  66. catch(SQLException e) {
  67. String sqlState = e.getSQLState();
  68. // Check if the error code indicates a SERIALIZATION_FAILURE.
  69. if(sqlState.equals("40001")) {
  70. // Signal the database that we will attempt a retry.
  71. conn.rollback(sp);
  72. continue;
  73. } else if(releaseAttempted) {
  74. throw new AmbiguousCommitException(e);
  75. } else {
  76. throw e;
  77. }
  78. }
  79. break;
  80. }
  81. conn.commit();
  82. }
  83. public static void main(String[] args)
  84. throws ClassNotFoundException, SQLException {
  85. // Load the Postgres JDBC driver.
  86. Class.forName("org.postgresql.Driver");
  87. // Connect to the 'bank' database.
  88. Properties props = new Properties();
  89. props.setProperty("user", "maxroach");
  90. props.setProperty("sslmode", "disable");
  91. Connection db = DriverManager
  92. .getConnection("jdbc:postgresql://127.0.0.1:26257/bank", props);
  93. try {
  94. // We need to turn off autocommit mode to allow for
  95. // multi-statement transactions.
  96. db.setAutoCommit(false);
  97. // Perform the transfer. This assumes the 'accounts'
  98. // table has already been created in the database.
  99. RetryableTransaction transfer = transferFunds(1, 2, 100);
  100. retryTransaction(db, transfer);
  101. // Check balances after transfer.
  102. db.setAutoCommit(true);
  103. ResultSet res = db.createStatement()
  104. .executeQuery("SELECT id, balance FROM accounts");
  105. while (res.next()) {
  106. System.out.printf("\taccount %s: %s\n", res.getInt("id"),
  107. res.getInt("balance"));
  108. }
  109. } catch(InsufficientBalanceException e) {
  110. System.out.println("Insufficient balance");
  111. } catch(AccountNotFoundException e) {
  112. System.out.println("No users in the table with id " + e.account);
  113. } catch(AmbiguousCommitException e) {
  114. System.out.println("Ambiguous result encountered: " + e);
  115. } catch(SQLException e) {
  116. System.out.println("SQLException encountered:" + e);
  117. } finally {
  118. // Close the database connection.
  119. db.close();
  120. }
  121. }
  122. }

To verify that funds were transferred from one account to another, start the built-in SQL client:

  1. $ cockroach sql --insecure --database=bank

To check the account balances, issue the following statement:

  1. > SELECT id, balance FROM accounts;
  1. +----+---------+
  2. | id | balance |
  3. +----+---------+
  4. | 1 | 900 |
  5. | 2 | 350 |
  6. +----+---------+
  7. (2 rows)

What's next?

Read more about using the Java JDBC driver.

You might also be interested in using a local cluster to explore the following CockroachDB benefits:

Was this page helpful?
YesNo