You can create a CockroachDB Serverless cluster using either the CockroachDB Cloud Console, a web-based graphical user interface (GUI) tool, or ccloud, a command-line interface (CLI) tool.
On the Create your cluster page, select Serverless.
Click Create cluster.
Your cluster will be created in a few seconds and the Create SQL user dialog will display.
Create a SQL user
The Create SQL user dialog allows you to create a new SQL user and password.
Enter a username in the SQL user field or use the one provided by default.
Click Generate & save password.
Copy the generated password and save it in a secure location.
Click Next.
By default, all new SQL users are created with full privileges. For more information and to change the default settings, refer to Manage SQL users on a cluster.
Get the connection string
The Connect to cluster dialog shows information about how to connect to your cluster.
Select Java from the Select option/language dropdown.
Select JDBC from the Select tool dropdown.
Copy the command provided to set the JDBC_DATABASE_URL environment variable.
Note:
The JDBC connection URL is pre-populated with your username, password, cluster name, and other details. Your password, in particular, will be provided only once. Save it in a secure place (Cockroach Labs recommends a password manager) to connect to your cluster in the future. If you forget your password, you can reset it by going to the SQL Users page for the cluster, found at https://cockroachlabs.cloud/cluster/<CLUSTER ID>/users.
Follow these steps to create a CockroachDB Serverless cluster using the ccloud CLI tool.
Note:
The ccloud CLI tool is in Preview.
Install ccloud
Choose your OS:
You can install ccloud using either Homebrew or by downloading the binary.
In a PowerShell window, enter the following command to download and extract the ccloud binary and add it to your PATH:
$ErrorActionPreference="Stop";[Net.ServicePointManager]::SecurityProtocol =[Net.SecurityProtocolType]::Tls12;$ProgressPreference='SilentlyContinue';$null= New-Item -Type Directory -Force$env:appdata/ccloud; Invoke-WebRequest -Uri https://binaries.cockroachdb.com/ccloud/ccloud_windows-amd64_0.6.12.zip -OutFile ccloud.zip; Expand-Archive -Force-Path ccloud.zip; Copy-Item -Force ccloud/ccloud.exe -Destination$env:appdata/ccloud;$Env:PATH +=";$env:appdata/ccloud";# We recommend adding ";$env:appdata/ccloud" to the Path variable for your system environment. See https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_environment_variables#saving-changes-to-environment-variables for more information.
Run ccloud quickstart to create a new cluster, create a SQL user, and retrieve the connection string.
The easiest way of getting started with CockroachDB Cloud is to use ccloud quickstart. The ccloud quickstart command guides you through logging in to CockroachDB Cloud, creating a new CockroachDB Basic cluster, and connecting to the new cluster. Run ccloud quickstart and follow the instructions:
ccloud quickstart
The ccloud quickstart command will open a browser window to log you in to CockroachDB Cloud. If you are new to CockroachDB Cloud, you can register using one of the single sign-on (SSO) options, or create a new account using an email address.
The ccloud quickstart command will prompt you for the cluster name, cloud provider, and cloud provider region, then ask if you want to connect to the cluster. Each prompt has default values that you can select, or change if you want a different option.
Select General connection string, then copy the connection string displayed and save it in a secure location. The connection string is the line starting postgresql://.
? How would you like to connect? General connection string
Retrieving cluster info: succeeded
Downloading cluster cert to /Users/maxroach/.postgresql/root.crt: succeeded
postgresql://maxroach:ThisIsNotAGoodPassword@blue-dog-147.6wr.cockroachlabs.cloud:26257/defaultdb?sslmode=verify-full&sslrootcert=%2FUsers%2Fmaxroach%2F.postgresql%2Froot.crt
The dbinit.sql file initializes the database schema that the application uses:
404:NotFound
The BasicExample.java file contains the code for INSERT, SELECT, and UPDATE SQL operations. The file also contains the main method of the program.
packagecom.cockroachlabs;importjava.math.BigDecimal;importjava.sql.Connection;importjava.sql.PreparedStatement;importjava.sql.ResultSet;importjava.sql.ResultSetMetaData;importjava.sql.SQLException;importjava.time.LocalTime;importjava.util.HashMap;importjava.util.Map;importjava.util.Random;importjava.util.UUID;importjavax.sql.DataSource;importorg.postgresql.ds.PGSimpleDataSource;/**
* Main class for the basic JDBC example.
**/publicclassBasicExample{publicstaticvoidmain(String[]args){// Configure the database connection.PGSimpleDataSourceds=newPGSimpleDataSource();ds.setApplicationName("docs_simplecrud_jdbc");ds.setUrl(System.getenv("JDBC_DATABASE_URL"));// Create DAO.BasicExampleDAOdao=newBasicExampleDAO(ds);// Test our retry handling logic if FORCE_RETRY is true. This// method is only used to test the retry logic. It is not// necessary in production code.dao.testRetryHandling();// Create the accounts table if it doesn't exist dao.createAccountsTable();// Insert a few accounts "by hand", using INSERTs on the backend.Map<String,String>balances=newHashMap<>();UUIDid1=UUID.randomUUID();UUIDid2=UUID.randomUUID();balances.put(id1.toString(),"1000");balances.put(id2.toString(),"250");intupdatedAccounts=dao.updateAccounts(balances);System.out.printf("BasicExampleDAO.updateAccounts:\n => %s total updated accounts\n",updatedAccounts);// How much money is in these accounts?BigDecimalbalance1=dao.getAccountBalance(id1);BigDecimalbalance2=dao.getAccountBalance(id2);System.out.printf("main:\n => Account balances at time '%s':\n ID %s => $%s\n ID %s => $%s\n",LocalTime.now(),1,balance1,2,balance2);// Transfer $100 from account 1 to account 2UUIDfromAccount=UUID.randomUUID();UUIDtoAccount=UUID.randomUUID();BigDecimaltransferAmount=BigDecimal.valueOf(100);inttransferredAccounts=dao.transferFunds(fromAccount,toAccount,transferAmount);if(transferredAccounts!=-1){System.out.printf("BasicExampleDAO.transferFunds:\n => $%s transferred between accounts %s and %s, %s rows updated\n",transferAmount,fromAccount,toAccount,transferredAccounts);}balance1=dao.getAccountBalance(id1);balance2=dao.getAccountBalance(id2);System.out.printf("main:\n => Account balances at time '%s':\n ID %s => $%s\n ID %s => $%s\n",LocalTime.now(),1,balance1,2,balance2);// Bulk insertion example using JDBC's batching support.inttotalRowsInserted=dao.bulkInsertRandomAccountData();System.out.printf("\nBasicExampleDAO.bulkInsertRandomAccountData:\n => finished, %s total rows inserted\n",totalRowsInserted);// Print out 10 account values.intaccountsRead=dao.readAccounts(10);}}/**
* Data access object used by 'BasicExample'. Abstraction over some
* common CockroachDB operations, including:
*
* - Auto-handling transaction retries in the 'runSQL' method
*
* - Example of bulk inserts in the 'bulkInsertRandomAccountData'
* method
*/classBasicExampleDAO{privatestaticfinalintMAX_RETRY_COUNT=3;privatestaticfinalStringRETRY_SQL_STATE="40001";privatestaticfinalbooleanFORCE_RETRY=false;privatefinalDataSourceds;privatefinalRandomrand=newRandom();BasicExampleDAO(DataSourceds){this.ds=ds;}/**
Used to test the retry logic in 'runSQL'. It is not necessary
in production code.
*/voidtestRetryHandling(){if(BasicExampleDAO.FORCE_RETRY){runSQL("SELECT crdb_internal.force_retry('1s':::INTERVAL)");}}/**
* Run SQL code in a way that automatically handles the
* transaction retry logic so we don't have to duplicate it in
* various places.
*
* @param sqlCode a String containing the SQL code you want to
* execute. Can have placeholders, e.g., "INSERT INTO accounts
* (id, balance) VALUES (?, ?)".
*
* @param args String Varargs to fill in the SQL code's
* placeholders.
* @return Integer Number of rows updated, or -1 if an error is thrown.
*/publicIntegerrunSQL(StringsqlCode,String...args){// This block is only used to emit class and method names in// the program output. It is not necessary in production// code.StackTraceElement[]stacktrace=Thread.currentThread().getStackTrace();StackTraceElementelem=stacktrace[2];StringcallerClass=elem.getClassName();StringcallerMethod=elem.getMethodName();intrv=0;try(Connectionconnection=ds.getConnection()){// We're managing the commit lifecycle ourselves so we can// automatically issue transaction retries.connection.setAutoCommit(false);intretryCount=0;while(retryCount<=MAX_RETRY_COUNT){if(retryCount==MAX_RETRY_COUNT){Stringerr=String.format("hit max of %s retries, aborting",MAX_RETRY_COUNT);thrownewRuntimeException(err);}// This block is only used to test the retry logic.// It is not necessary in production code. See also// the method 'testRetryHandling()'.if(FORCE_RETRY){forceRetry(connection);// SELECT 1}try(PreparedStatementpstmt=connection.prepareStatement(sqlCode)){// Loop over the args and insert them into the// prepared statement based on their types. In// this simple example we classify the argument// types as "integers" and "everything else"// (a.k.a. strings).for(inti=0;i<args.length;i++){intplace=i+1;Stringarg=args[i];try{intval=Integer.parseInt(arg);pstmt.setInt(place,val);}catch(NumberFormatExceptione){pstmt.setString(place,arg);}}if(pstmt.execute()){// We know that `pstmt.getResultSet()` will// not return `null` if `pstmt.execute()` was// trueResultSetrs=pstmt.getResultSet();ResultSetMetaDatarsmeta=rs.getMetaData();intcolCount=rsmeta.getColumnCount();// This printed output is for debugging and/or demonstration// purposes only. It would not be necessary in production code.System.out.printf("\n%s.%s:\n '%s'\n",callerClass,callerMethod,pstmt);while(rs.next()){for(inti=1;i<=colCount;i++){Stringname=rsmeta.getColumnName(i);Stringtype=rsmeta.getColumnTypeName(i);// In this "bank account" example we know we are only handling// integer values (technically 64-bit INT8s, the CockroachDB// default). This code could be made into a switch statement// to handle the various SQL types needed by the application.if("int8".equals(type)){intval=rs.getInt(name);// This printed output is for debugging and/or demonstration// purposes only. It would not be necessary in production code.System.out.printf(" %-8s => %10s\n",name,val);}}}}else{intupdateCount=pstmt.getUpdateCount();rv+=updateCount;// This printed output is for debugging and/or demonstration// purposes only. It would not be necessary in production code.System.out.printf("\n%s.%s:\n '%s'\n",callerClass,callerMethod,pstmt);}connection.commit();break;}catch(SQLExceptione){if(RETRY_SQL_STATE.equals(e.getSQLState())){// Since this is a transaction retry error, we// roll back the transaction and sleep a// little before trying again. Each time// through the loop we sleep for a little// longer than the last time// (A.K.A. exponential backoff).System.out.printf("retryable exception occurred:\n sql state = [%s]\n message = [%s]\n retry counter = %s\n",e.getSQLState(),e.getMessage(),retryCount);connection.rollback();retryCount++;intsleepMillis=(int)(Math.pow(2,retryCount)*100)+rand.nextInt(100);System.out.printf("Hit 40001 transaction retry error, sleeping %s milliseconds\n",sleepMillis);try{Thread.sleep(sleepMillis);}catch(InterruptedExceptionignored){// Necessary to allow the Thread.sleep()// above so the retry loop can continue.}rv=-1;}else{rv=-1;throwe;}}}}catch(SQLExceptione){System.out.printf("BasicExampleDAO.runSQL ERROR: { state => %s, cause => %s, message => %s }\n",e.getSQLState(),e.getCause(),e.getMessage());rv=-1;}returnrv;}/**
* Helper method called by 'testRetryHandling'. It simply issues
* a "SELECT 1" inside the transaction to force a retry. This is
* necessary to take the connection's session out of the AutoRetry
* state, since otherwise the other statements in the session will
* be retried automatically, and the client (us) will not see a
* retry error. Note that this information is taken from the
* following test:
* https://github.com/cockroachdb/cockroach/blob/master/pkg/sql/logictest/testdata/logic_test/manual_retry
*
* @param connection Connection
*/privatevoidforceRetry(Connectionconnection)throwsSQLException{try(PreparedStatementstatement=connection.prepareStatement("SELECT 1")){statement.executeQuery();}}/**
* Update accounts by passing in a Map of (ID, Balance) pairs.
*
* @param accounts (Map)
* @return The number of updated accounts (int)
*/publicintupdateAccounts(Map<String,String>accounts){introws=0;for(Map.Entry<String,String>account:accounts.entrySet()){Stringk=account.getKey();Stringv=account.getValue();String[]args={k,v};rows+=runSQL("INSERT INTO accounts (id, balance) VALUES (?, ?)",args);}returnrows;}/**
* Transfer funds between one account and another. Handles
* transaction retries in case of conflict automatically on the
* backend.
* @param fromId (UUID)
* @param toId (UUID)
* @param amount (int)
* @return The number of updated accounts (int)
*/publicinttransferFunds(UUIDfromId,UUIDtoId,BigDecimalamount){StringsFromId=fromId.toString();StringsToId=toId.toString();StringsAmount=amount.toPlainString();// We have omitted explicit BEGIN/COMMIT statements for// brevity. Individual statements are treated as implicit// transactions by CockroachDB (see// https://www.cockroachlabs.com/docs/v22.1/transactions.html#individual-statements).StringsqlCode="UPSERT INTO accounts (id, balance) VALUES"+"(?, ((SELECT balance FROM accounts WHERE id = ?) - ?)),"+"(?, ((SELECT balance FROM accounts WHERE id = ?) + ?))";returnrunSQL(sqlCode,sFromId,sFromId,sAmount,sToId,sToId,sAmount);}/**
* Get the account balance for one account.
*
* We skip using the retry logic in 'runSQL()' here for the
* following reasons:
*
* 1. Since this is a single read ("SELECT"), we don't expect any
* transaction conflicts to handle
*
* 2. We need to return the balance as an integer
*
* @param id (UUID)
* @return balance (int)
*/publicBigDecimalgetAccountBalance(UUIDid){BigDecimalbalance=BigDecimal.valueOf(0);try(Connectionconnection=ds.getConnection()){// Check the current balance.ResultSetres=connection.createStatement().executeQuery(String.format("SELECT balance FROM accounts WHERE id = '%s'",id.toString()));if(!res.next()){System.out.printf("No users in the table with id %d",id);}else{balance=res.getBigDecimal("balance");}}catch(SQLExceptione){System.out.printf("BasicExampleDAO.getAccountBalance ERROR: { state => %s, cause => %s, message => %s }\n",e.getSQLState(),e.getCause(),e.getMessage());}returnbalance;}/**
* Insert randomized account data (ID, balance) using the JDBC
* fast path for bulk inserts. The fastest way to get data into
* CockroachDB is the IMPORT statement. However, if you must bulk
* ingest from the application using INSERT statements, the best
* option is the method shown here. It will require the following:
*
* 1. Add `rewriteBatchedInserts=true` to your JDBC connection
* settings (see the connection info in 'BasicExample.main').
*
* 2. Inserting in batches of 128 rows, as used inside this method
* (see BATCH_SIZE), since the PGJDBC driver's logic works best
* with powers of two, such that a batch of size 128 can be 6x
* faster than a batch of size 250.
* @return The number of new accounts inserted (int)
*/publicintbulkInsertRandomAccountData(){Randomrandom=newRandom();intBATCH_SIZE=128;inttotalNewAccounts=0;try(Connectionconnection=ds.getConnection()){// We're managing the commit lifecycle ourselves so we can// control the size of our batch inserts.connection.setAutoCommit(false);// In this example we are adding 500 rows to the database,// but it could be any number. What's important is that// the batch size is 128.try(PreparedStatementpstmt=connection.prepareStatement("INSERT INTO accounts (id, balance) VALUES (?, ?)")){for(inti=0;i<=(500/BATCH_SIZE);i++){for(intj=0;j<BATCH_SIZE;j++){Stringid=UUID.randomUUID().toString();BigDecimalbalance=BigDecimal.valueOf(random.nextInt(1000000000));pstmt.setString(1,id);pstmt.setBigDecimal(2,balance);pstmt.addBatch();}int[]count=pstmt.executeBatch();totalNewAccounts+=count.length;System.out.printf("\nBasicExampleDAO.bulkInsertRandomAccountData:\n '%s'\n",pstmt.toString());System.out.printf(" => %s row(s) updated in this batch\n",count.length);}connection.commit();}catch(SQLExceptione){System.out.printf("BasicExampleDAO.bulkInsertRandomAccountData ERROR: { state => %s, cause => %s, message => %s }\n",e.getSQLState(),e.getCause(),e.getMessage());}}catch(SQLExceptione){System.out.printf("BasicExampleDAO.bulkInsertRandomAccountData ERROR: { state => %s, cause => %s, message => %s }\n",e.getSQLState(),e.getCause(),e.getMessage());}returntotalNewAccounts;}/**
* Read out a subset of accounts from the data store.
*
* @param limit (int)
* @return Number of accounts read (int)
*/publicintreadAccounts(intlimit){returnrunSQL("SELECT id, balance FROM accounts LIMIT ?",Integer.toString(limit));}/**
* Create the accounts table if it doesn't already exist.
*
*/publicvoidcreateAccountsTable(){runSQL("CREATE TABLE IF NOT EXISTS accounts (id UUID PRIMARY KEY, balance int8)");}}
The sample app uses JDBC and the Data Access Object (DAO) pattern to map Java methods to SQL operations. It consists of two classes:
BasicExample, which is where the application logic lives.
BasicExampleDAO, which is used by the application to access the data store (in this case CockroachDB). This class also includes a helper function (runSql) that runs SQL statements inside a transaction, retrying statements as needed.
The main method of the app performs the following steps which roughly correspond to method calls in the BasicExample class.
Step
Method
1. Insert account data using a Map that corresponds to the input to INSERT on the backend
BasicExampleDAO.updateAccounts(Map balance)
2. Transfer money from one account to another, printing out account balances before and after the transfer
BasicExampleDAO.transferFunds(UUID from, UUID to, BigDecimal amount)
3. Insert random account data using JDBC's bulk insertion support
BasicExampleDAO.bulkInsertRandomAccountData()
4. Print out some account data
BasicExampleDAO.readAccounts(int limit)
It does all of the above using the practices we recommend for using JDBC with CockroachDB, which are listed in the Recommended Practices section below.
Step 3. Update the connection configuration
Navigate to the example-app-java-jdbc directory:
$ cd example-app-java-jdbc
Set the JDBC_DATABASE_URL environment variable to a JDBC-compatible connection string:
$ cockroach cert create-client max --certs-dir=certs --ca-key=my-safe-directory/ca.key --also-generate-pkcs8-key
The generated PKCS8 key will be named client.max.key.pk8.
Note:
CockroachDB Cloud does not yet support certificate-based user authentication.
Use IMPORT to read in large data sets
If you are trying to get a large data set into CockroachDB all at once (a bulk import), avoid writing client-side code altogether and use the IMPORT statement instead. It is much faster and more efficient than making a series of INSERTs and UPDATEs. It bypasses the SQL layer altogether and writes directly to the storage layer of the database.
This will change batch inserts from insert into foo (col1, col2, col3) values (1,2,3) into insert into foo (col1, col2, col3) values (1,2,3), (4,5,6) this provides 2-3x performance improvement
Use a batch size of 128
PGJDBC's batching support only works with powers of two, and will split batches of other sizes up into multiple sub-batches. This means that a batch of size 128 can be 6x faster than a batch of size 250.
The code snippet below shows a pattern for using a batch size of 128, and is taken from the longer example above (specifically, the BasicExampleDAO.bulkInsertRandomAccountData() method).
Specifically, it does the following:
Turn off auto-commit so you can manage the transaction lifecycle and thus the size of the batch inserts.
Given an overall update size of 500 rows (for example), split it into batches of size 128 and execute each batch in turn.
Finally, commit the batches of statements you've just executed.
intBATCH_SIZE=128;connection.setAutoCommit(false);try(PreparedStatementpstmt=connection.prepareStatement("INSERT INTO accounts (id, balance) VALUES (?, ?)")){for(inti=0;i<=(500/BATCH_SIZE);i++){for(intj=0;j<BATCH_SIZE;j++){intid=random.nextInt(1000000000);BigDecimalbalance=BigDecimal.valueOf(random.nextInt(1000000000));pstmt.setInt(1,id);pstmt.setBigDecimal(2,balance);pstmt.addBatch();}int[]count=pstmt.executeBatch();System.out.printf(" => %s row(s) updated in this batch\n",count.length);// Verifying 128 rows in the batch}connection.commit();}
Retrieve large data sets in chunks using cursors
CockroachDB now supports the PostgreSQL wire-protocol cursors for implicit transactions and explicit transactions executed to completion. This means the PGJDBC driver can use this protocol to stream queries with large result sets. This is much faster than paginating through results in SQL using LIMIT .. OFFSET.
Note that interleaved execution (partial execution of multiple statements within the same connection and transaction) is not supported when Statement.setFetchSize() is used.