From e7091beba7369435429cd9c11a2ca5bf4a0bd5aa Mon Sep 17 00:00:00 2001 From: Rich Loveland Date: Thu, 31 Oct 2019 16:13:44 -0400 Subject: [PATCH] Update Hibernate example with app-level retry loop Fixes #5537. Relates to #1775, #4810, #5035. --- .../app/hibernate-basic-sample/Sample.java | 220 ++++++++++++++-- .../hibernate-basic-sample.tgz | Bin 1716 -> 3291 bytes .../hibernate-basic-sample/Sample.java | 220 ++++++++++++++-- .../hibernate-basic-sample.tgz | Bin 1664 -> 3236 bytes _includes/v19.1/app/see-also-links.md | 2 +- .../app/hibernate-basic-sample/Sample.java | 220 ++++++++++++++-- .../hibernate-basic-sample.tgz | Bin 1716 -> 3291 bytes .../hibernate-basic-sample/Sample.java | 220 ++++++++++++++-- .../hibernate-basic-sample.tgz | Bin 1664 -> 3238 bytes .../hibernate-basic-sample/hibernate.cfg.xml | 4 +- _includes/v19.2/app/see-also-links.md | 2 +- .../app/hibernate-basic-sample/Sample.java | 220 ++++++++++++++-- .../hibernate-basic-sample.tgz | Bin 1716 -> 3291 bytes .../hibernate-basic-sample/Sample.java | 220 ++++++++++++++-- .../hibernate-basic-sample.tgz | Bin 1664 -> 3236 bytes _includes/v20.1/app/see-also-links.md | 2 +- ...d-a-java-app-with-cockroachdb-hibernate.md | 240 ++++++++++------- ...d-a-java-app-with-cockroachdb-hibernate.md | 244 ++++++++++------- ...d-a-java-app-with-cockroachdb-hibernate.md | 246 +++++++++++------- 19 files changed, 1626 insertions(+), 434 deletions(-) diff --git a/_includes/v19.1/app/hibernate-basic-sample/Sample.java b/_includes/v19.1/app/hibernate-basic-sample/Sample.java index ed36ae15ad3..58d28f37a4b 100644 --- a/_includes/v19.1/app/hibernate-basic-sample/Sample.java +++ b/_includes/v19.1/app/hibernate-basic-sample/Sample.java @@ -2,22 +2,24 @@ import org.hibernate.Session; import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.JDBCException; import org.hibernate.cfg.Configuration; +import java.util.*; +import java.util.function.Function; + import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; -import javax.persistence.criteria.CriteriaQuery; public class Sample { - // Create a SessionFactory based on our hibernate.cfg.xml configuration - // file, which defines how to connect to the database. - private static final SessionFactory sessionFactory = - new Configuration() - .configure("hibernate.cfg.xml") - .addAnnotatedClass(Account.class) - .buildSessionFactory(); + + private static final Random RAND = new Random(); + private static final boolean FORCE_RETRY = false; + private static final String RETRY_SQL_STATE = "40001"; + private static final int MAX_ATTEMPT_COUNT = 6; // Account is our model, which corresponds to the "accounts" database table. @Entity @@ -27,8 +29,18 @@ public static class Account { @Column(name="id") public long id; + public long getId() { + return id; + } + @Column(name="balance") public long balance; + public long getBalance() { + return balance; + } + public void setBalance(long newBalance) { + this.balance = newBalance; + } // Convenience constructor. public Account(int id, int balance) { @@ -40,24 +52,184 @@ public Account(int id, int balance) { public Account() {} } - public static void main(String[] args) throws Exception { - Session session = sessionFactory.openSession(); - - try { - // Insert two rows into the "accounts" table. - session.beginTransaction(); - session.save(new Account(1, 1000)); - session.save(new Account(2, 250)); - session.getTransaction().commit(); - - // Print out the balances. - CriteriaQuery query = session.getCriteriaBuilder().createQuery(Account.class); - query.select(query.from(Account.class)); - for (Account account : session.createQuery(query).getResultList()) { - System.out.printf("%d %d\n", account.id, account.balance); + private static Function addAccounts() throws JDBCException{ + Function f = s -> { + long rv = 0; + try { + s.save(new Account(1, 1000)); + s.save(new Account(2, 250)); + s.save(new Account(3, 314159)); + rv = 1; + System.out.printf("APP: addAccounts() --> %d\n", rv); + } catch (JDBCException e) { + throw e; + } + return rv; + }; + return f; + } + + private static Function transferFunds(long fromId, long toId, long amount) throws JDBCException{ + Function f = s -> { + long rv = 0; + try { + Account fromAccount = (Account) s.get(Account.class, fromId); + Account toAccount = (Account) s.get(Account.class, toId); + if (!(amount > fromAccount.getBalance())) { + fromAccount.balance -= amount; + toAccount.balance += amount; + s.save(fromAccount); + s.save(toAccount); + rv = amount; + System.out.printf("APP: transferFunds(%d, %d, %d) --> %d\n", fromId, toId, amount, rv); + } + } catch (JDBCException e) { + throw e; + } + return rv; + }; + return f; + } + + // Test our retry handling logic if FORCE_RETRY is true. This + // method is only used to test the retry logic. It is not + // intended for production code. + private static Function forceRetryLogic() throws JDBCException { + Function f = s -> { + long rv = -1; + try { + System.out.printf("APP: testRetryLogic: BEFORE EXCEPTION\n"); + s.createNativeQuery("SELECT crdb_internal.force_retry('1s')").executeUpdate(); + } catch (JDBCException e) { + System.out.printf("APP: testRetryLogic: AFTER EXCEPTION\n"); + throw e; + } + return rv; + }; + return f; + } + + private static Function getAccountBalance(long id) throws JDBCException{ + Function f = s -> { + long balance; + try { + Account account = s.get(Account.class, id); + balance = account.getBalance(); + System.out.printf("APP: getAccountBalance(%d) --> %d\n", id, balance); + } catch (JDBCException e) { + throw e; + } + return balance; + }; + return f; + } + + // Run SQL code in a way that automatically handles the + // transaction retry logic so we don't have to duplicate it in + // various places. + private static long runTransaction(Session session, Function fn) { + long rv = 0; + int attemptCount = 0; + + while (attemptCount < MAX_ATTEMPT_COUNT) { + attemptCount++; + + if (attemptCount > 1) { + System.out.printf("APP: Entering retry loop again, iteration %d\n", attemptCount); + } + + Transaction txn = session.beginTransaction(); + System.out.printf("APP: BEGIN;\n"); + + if (attemptCount == MAX_ATTEMPT_COUNT) { + String err = String.format("hit max of %s attempts, aborting", MAX_ATTEMPT_COUNT); + throw new RuntimeException(err); + } + + // This block is only used to test the retry logic. + // It is not necessary in production code. See also + // the method 'testRetryLogic()'. + if (FORCE_RETRY) { + session.createNativeQuery("SELECT now()").list(); + } + + try { + rv = fn.apply(session); + if (rv != -1) { + txn.commit(); + System.out.printf("APP: COMMIT;\n"); + break; + } + } catch (JDBCException e) { + 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("APP: retryable exception occurred:\n sql state = [%s]\n message = [%s]\n retry counter = %s\n", e.getSQLState(), e.getMessage(), attemptCount); + System.out.printf("APP: ROLLBACK;\n"); + txn.rollback(); + int sleepMillis = (int)(Math.pow(2, attemptCount) * 100) + RAND.nextInt(100); + System.out.printf("APP: Hit 40001 transaction retry error, sleeping %s milliseconds\n", sleepMillis); + try { + Thread.sleep(sleepMillis); + } catch (InterruptedException ignored) { + // no-op + } + rv = -1; + } else { + throw e; + } + } + } + return rv; + } + + public static void main(String[] args) { + // Create a SessionFactory based on our hibernate.cfg.xml configuration + // file, which defines how to connect to the database. + SessionFactory sessionFactory = + new Configuration() + .configure("hibernate.cfg.xml") + .addAnnotatedClass(Account.class) + .buildSessionFactory(); + + try (Session session = sessionFactory.openSession()) { + long fromAccountId = 1; + long toAccountId = 2; + long transferAmount = 100; + + if (FORCE_RETRY) { + System.out.printf("APP: About to test retry logic in 'runTransaction'\n"); + runTransaction(session, forceRetryLogic()); + } else { + + runTransaction(session, addAccounts()); + long fromBalance = runTransaction(session, getAccountBalance(fromAccountId)); + long toBalance = runTransaction(session, getAccountBalance(toAccountId)); + if (fromBalance != -1 && toBalance != -1) { + // Success! + System.out.printf("APP: getAccountBalance(%d) --> %d\n", fromAccountId, fromBalance); + System.out.printf("APP: getAccountBalance(%d) --> %d\n", toAccountId, toBalance); + } + + // Transfer $100 from account 1 to account 2 + long transferResult = runTransaction(session, transferFunds(fromAccountId, toAccountId, transferAmount)); + if (transferResult != -1) { + // Success! + System.out.printf("APP: transferFunds(%d, %d, %d) --> %d \n", fromAccountId, toAccountId, transferAmount, transferResult); + + long fromBalanceAfter = runTransaction(session, getAccountBalance(fromAccountId)); + long toBalanceAfter = runTransaction(session, getAccountBalance(toAccountId)); + if (fromBalanceAfter != -1 && toBalanceAfter != -1) { + // Success! + System.out.printf("APP: getAccountBalance(%d) --> %d\n", fromAccountId, fromBalanceAfter); + System.out.printf("APP: getAccountBalance(%d) --> %d\n", toAccountId, toBalanceAfter); + } + } } } finally { - session.close(); sessionFactory.close(); } } diff --git a/_includes/v19.1/app/hibernate-basic-sample/hibernate-basic-sample.tgz b/_includes/v19.1/app/hibernate-basic-sample/hibernate-basic-sample.tgz index 355dba55713c4c540e2a23401433523872feb90a..3e729bf439e7079e36b7c865cc8da37bfe6e9253 100644 GIT binary patch literal 3291 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 literal 1716 zcmV;l221%LiwFQT!?aug1ML}WZ`wL^pZOIY(k>ts2T}rC3v3UEnRy%-Hr2sJbc~{`y4W3V=6h(v^geGgU~kn^Bglt833k)X<{kRIXXcz`yU+`uQb zV}AXg!0h|+A4kmh@Kh4dr}vft{^)i(r@7T_^(}n%N>vT8!|D(VS@i+DRAW3kS^S|S?A^tt5?d7o2f4cvE zbn^Gn0m`@5e8Q%YjQf3b^w;aRhoedwQ&eqQ)}I;b;tq`pMEfWEXdAaI>)>5=K|0eq z+_9{Si;G1|kD*hyJndN}@|(a0!+I5TEkPN^68efNV{3ClT$nemhLySC{?ld??GsI) z6e!FHoKrVeQ&FwK9||>_!v%IIMM$N-LLBCsK{Zz&b4u(<2EP5`nlY$4EKVDEKVIxx z8KZT0dyM$NJK?qR8XqOxk3YP9;p`{x?#4f-dnEnpu>bVaXn*fy@6)L_cJuR*W%?VSQyA5!Ysyx@;|m14Iy51@n}0!GKy*2E-&!feepzu3 zf8`1M=fYb3zbk_K%m8=q|J!zZ)BimRtR4TtA9r`lhV|ober$;UcCWXY{~rZ@F#fr7 z>~i|Ltvvf49}^Z2JGlfRMOnWNNzIrT^x4D-{-mLTXg(G>?;0d-ulG zH_4G+f@J2$!W^)WWfJNVhh`j68s-oRh<+3-#H}J6aLx494G4$c4T~qHgS%nfl}uB~ z2p*+3A0lcDtAx?mXKvcrCFPKQldvo_irX}35R3jmX0Y%OOcXFvqm_Xg6`G!m*M#|W z3th~ZJA(#p!Z=mP^mbZgBZ|2-{ic_WE;uF)VY)C%K;mjpbYi}KEbF6+kArz%XMod- z;<#3C6xK7qncB2oU9qWJLPI=nk8`0-#~gnos;A8|jy+mJ6@Qj2+E8!oRx($mED%$% zgU$TmXRdRFgux{g+*6sqswBZz)w%1Ejy`eVt0c3M`#4YPNs~J#OSDWgA@y^T5Sb?5 z=61X~^cMF?Qm5>gmvd6e%oot4GjF$2LNg{lf%GgPOjmQ%CMN|yr<|FrHi@I4DK`$p z%N$bG>&!UP+d}4=GHm4RF`#EmVdkMhtm?{2N(IYvg|hYQf@+Ju2RNk=A7T=nM7~CK zE}BG68+nc6%ufm|jxZAAQ>X$}Y6+-)UCBu;2JBpb1?H@)A8QG-(WlP{B&r73AjO43 z2bV65r>dOsX$ofb0~Qu53B#nrQ>1x<#(se+EBX-3KjbigLKR;K6iWn;uOw(LFEdC* z;xUDh?nDx4Vh9I9wtVZZICDh$zA^SJQ0k(yz?Hk$*LMsl1TG3C;>QdB)=h7~n`lhl(3 zGrTX+ED@POI>SI|fY8odp}g#vWy9$Bkt#^%w{WV|8_QJ`C)5?3HvtBh;Ltc{Qm;Pq z&@=BJUfs%sU{j6ReO8f0-L4k}mTdU`a=Ojc^o61&nUW>(3Qkra)I diff --git a/_includes/v19.1/app/insecure/hibernate-basic-sample/Sample.java b/_includes/v19.1/app/insecure/hibernate-basic-sample/Sample.java index ed36ae15ad3..58d28f37a4b 100644 --- a/_includes/v19.1/app/insecure/hibernate-basic-sample/Sample.java +++ b/_includes/v19.1/app/insecure/hibernate-basic-sample/Sample.java @@ -2,22 +2,24 @@ import org.hibernate.Session; import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.JDBCException; import org.hibernate.cfg.Configuration; +import java.util.*; +import java.util.function.Function; + import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; -import javax.persistence.criteria.CriteriaQuery; public class Sample { - // Create a SessionFactory based on our hibernate.cfg.xml configuration - // file, which defines how to connect to the database. - private static final SessionFactory sessionFactory = - new Configuration() - .configure("hibernate.cfg.xml") - .addAnnotatedClass(Account.class) - .buildSessionFactory(); + + private static final Random RAND = new Random(); + private static final boolean FORCE_RETRY = false; + private static final String RETRY_SQL_STATE = "40001"; + private static final int MAX_ATTEMPT_COUNT = 6; // Account is our model, which corresponds to the "accounts" database table. @Entity @@ -27,8 +29,18 @@ public static class Account { @Column(name="id") public long id; + public long getId() { + return id; + } + @Column(name="balance") public long balance; + public long getBalance() { + return balance; + } + public void setBalance(long newBalance) { + this.balance = newBalance; + } // Convenience constructor. public Account(int id, int balance) { @@ -40,24 +52,184 @@ public Account(int id, int balance) { public Account() {} } - public static void main(String[] args) throws Exception { - Session session = sessionFactory.openSession(); - - try { - // Insert two rows into the "accounts" table. - session.beginTransaction(); - session.save(new Account(1, 1000)); - session.save(new Account(2, 250)); - session.getTransaction().commit(); - - // Print out the balances. - CriteriaQuery query = session.getCriteriaBuilder().createQuery(Account.class); - query.select(query.from(Account.class)); - for (Account account : session.createQuery(query).getResultList()) { - System.out.printf("%d %d\n", account.id, account.balance); + private static Function addAccounts() throws JDBCException{ + Function f = s -> { + long rv = 0; + try { + s.save(new Account(1, 1000)); + s.save(new Account(2, 250)); + s.save(new Account(3, 314159)); + rv = 1; + System.out.printf("APP: addAccounts() --> %d\n", rv); + } catch (JDBCException e) { + throw e; + } + return rv; + }; + return f; + } + + private static Function transferFunds(long fromId, long toId, long amount) throws JDBCException{ + Function f = s -> { + long rv = 0; + try { + Account fromAccount = (Account) s.get(Account.class, fromId); + Account toAccount = (Account) s.get(Account.class, toId); + if (!(amount > fromAccount.getBalance())) { + fromAccount.balance -= amount; + toAccount.balance += amount; + s.save(fromAccount); + s.save(toAccount); + rv = amount; + System.out.printf("APP: transferFunds(%d, %d, %d) --> %d\n", fromId, toId, amount, rv); + } + } catch (JDBCException e) { + throw e; + } + return rv; + }; + return f; + } + + // Test our retry handling logic if FORCE_RETRY is true. This + // method is only used to test the retry logic. It is not + // intended for production code. + private static Function forceRetryLogic() throws JDBCException { + Function f = s -> { + long rv = -1; + try { + System.out.printf("APP: testRetryLogic: BEFORE EXCEPTION\n"); + s.createNativeQuery("SELECT crdb_internal.force_retry('1s')").executeUpdate(); + } catch (JDBCException e) { + System.out.printf("APP: testRetryLogic: AFTER EXCEPTION\n"); + throw e; + } + return rv; + }; + return f; + } + + private static Function getAccountBalance(long id) throws JDBCException{ + Function f = s -> { + long balance; + try { + Account account = s.get(Account.class, id); + balance = account.getBalance(); + System.out.printf("APP: getAccountBalance(%d) --> %d\n", id, balance); + } catch (JDBCException e) { + throw e; + } + return balance; + }; + return f; + } + + // Run SQL code in a way that automatically handles the + // transaction retry logic so we don't have to duplicate it in + // various places. + private static long runTransaction(Session session, Function fn) { + long rv = 0; + int attemptCount = 0; + + while (attemptCount < MAX_ATTEMPT_COUNT) { + attemptCount++; + + if (attemptCount > 1) { + System.out.printf("APP: Entering retry loop again, iteration %d\n", attemptCount); + } + + Transaction txn = session.beginTransaction(); + System.out.printf("APP: BEGIN;\n"); + + if (attemptCount == MAX_ATTEMPT_COUNT) { + String err = String.format("hit max of %s attempts, aborting", MAX_ATTEMPT_COUNT); + throw new RuntimeException(err); + } + + // This block is only used to test the retry logic. + // It is not necessary in production code. See also + // the method 'testRetryLogic()'. + if (FORCE_RETRY) { + session.createNativeQuery("SELECT now()").list(); + } + + try { + rv = fn.apply(session); + if (rv != -1) { + txn.commit(); + System.out.printf("APP: COMMIT;\n"); + break; + } + } catch (JDBCException e) { + 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("APP: retryable exception occurred:\n sql state = [%s]\n message = [%s]\n retry counter = %s\n", e.getSQLState(), e.getMessage(), attemptCount); + System.out.printf("APP: ROLLBACK;\n"); + txn.rollback(); + int sleepMillis = (int)(Math.pow(2, attemptCount) * 100) + RAND.nextInt(100); + System.out.printf("APP: Hit 40001 transaction retry error, sleeping %s milliseconds\n", sleepMillis); + try { + Thread.sleep(sleepMillis); + } catch (InterruptedException ignored) { + // no-op + } + rv = -1; + } else { + throw e; + } + } + } + return rv; + } + + public static void main(String[] args) { + // Create a SessionFactory based on our hibernate.cfg.xml configuration + // file, which defines how to connect to the database. + SessionFactory sessionFactory = + new Configuration() + .configure("hibernate.cfg.xml") + .addAnnotatedClass(Account.class) + .buildSessionFactory(); + + try (Session session = sessionFactory.openSession()) { + long fromAccountId = 1; + long toAccountId = 2; + long transferAmount = 100; + + if (FORCE_RETRY) { + System.out.printf("APP: About to test retry logic in 'runTransaction'\n"); + runTransaction(session, forceRetryLogic()); + } else { + + runTransaction(session, addAccounts()); + long fromBalance = runTransaction(session, getAccountBalance(fromAccountId)); + long toBalance = runTransaction(session, getAccountBalance(toAccountId)); + if (fromBalance != -1 && toBalance != -1) { + // Success! + System.out.printf("APP: getAccountBalance(%d) --> %d\n", fromAccountId, fromBalance); + System.out.printf("APP: getAccountBalance(%d) --> %d\n", toAccountId, toBalance); + } + + // Transfer $100 from account 1 to account 2 + long transferResult = runTransaction(session, transferFunds(fromAccountId, toAccountId, transferAmount)); + if (transferResult != -1) { + // Success! + System.out.printf("APP: transferFunds(%d, %d, %d) --> %d \n", fromAccountId, toAccountId, transferAmount, transferResult); + + long fromBalanceAfter = runTransaction(session, getAccountBalance(fromAccountId)); + long toBalanceAfter = runTransaction(session, getAccountBalance(toAccountId)); + if (fromBalanceAfter != -1 && toBalanceAfter != -1) { + // Success! + System.out.printf("APP: getAccountBalance(%d) --> %d\n", fromAccountId, fromBalanceAfter); + System.out.printf("APP: getAccountBalance(%d) --> %d\n", toAccountId, toBalanceAfter); + } + } } } finally { - session.close(); sessionFactory.close(); } } diff --git a/_includes/v19.1/app/insecure/hibernate-basic-sample/hibernate-basic-sample.tgz b/_includes/v19.1/app/insecure/hibernate-basic-sample/hibernate-basic-sample.tgz index 9581b3a52df25f34edf09f9e27682b63f418140c..8205b37922935f23acf8bb72875bea56090e9b59 100644 GIT binary patch literal 3236 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 literal 1664 zcmV-`27mb`rd)M7vheTEW`_1|VFw_aT?4UWY?Tfd*AV#E_?u^)AoA3-V@|KsudNKC`}}o@>$4$@M=C_8;@_< zC--B1{h!eE`}rRwEDZ2Ok|3lH)&cS8b~@Gh?{vI!{$0oIwx1y95&XN8e)9ZJq)1{^ z_eF$#;eU~W_|uS#RNcCyQZd0%y^Wo^Wsx`zuTdN(6UO_f{+V2o`s$D7EAxp4F_4G| z<3}M;>K%zFI{c0je4bVt@JbpB#k7!&D)h}lKoPm5{D^WbN!VywH z#w?_$F60EyYq`IuYF|j&_ppmQ_zmMEtgnlKW<8NqeGU6dw=Y1V?K;ovp!vT}(!WR* z{ja3|c+Y^1&;O3=?R#A~|GQpycm8jMZppvt-$yBe`#k?UUK{e?^BixN|83A+`8VhM zLyO>E{on1G=YMCvyUYJJ=&t<3!%!sBr|JPkaKHYC^WSZIZfBSOZP4cVpL1e=Ji%8{ zxUr6^`5$HEI_-8T|H$olUT-)5Z-WLeA(JSd^$*ioAHjqz0>&qY^+bt zt>eCJ&t|j5NRMGsw*wv675ZDS4Tkj^*jm9dOf?J?RmRuWnD~G4a*P%AE%20yAZG5 zF<>Nw9kU%4fyjTxXW5?TAKt$3j%azM25BD=V0V7;~v{?yFG}dgWG+#>v}zV zMEIANN`;XK=wZMV8HMz|nv^j5DU6q6#;HPP^3EDuN9~#XAs|Z!T1!5`< zumLP#5jtN;064MWfhyuvBN@M{%|n;rjVyq#vdYTt(>iHnBluh_vof!QJkD7`6qbCO z`|;{DSlnktoo~m2r6A>-g#sW13l6LbnGp#IlxKxtzPcoBYEtk^%9*KZQ#cABg9#vB z-61!>{ znxX>OoO+oThbntlI&TNL2}AVNd|kxbXe~Hjo5O)hqdBdPRC485DVv^dh7~pMP8!)4 z^J-y5W<|)r{DK4J2}1q3LCd<+Et|&1k5oZ9zlEntqq*8eX+d4X^CrR&5*(Z0j2pFQ z0eTkv$!mLs5^TD$xGy@gY}?JE!LkECTrIb`nW0d$qEd query = session.getCriteriaBuilder().createQuery(Account.class); - query.select(query.from(Account.class)); - for (Account account : session.createQuery(query).getResultList()) { - System.out.printf("%d %d\n", account.id, account.balance); + private static Function addAccounts() throws JDBCException{ + Function f = s -> { + long rv = 0; + try { + s.save(new Account(1, 1000)); + s.save(new Account(2, 250)); + s.save(new Account(3, 314159)); + rv = 1; + System.out.printf("APP: addAccounts() --> %d\n", rv); + } catch (JDBCException e) { + throw e; + } + return rv; + }; + return f; + } + + private static Function transferFunds(long fromId, long toId, long amount) throws JDBCException{ + Function f = s -> { + long rv = 0; + try { + Account fromAccount = (Account) s.get(Account.class, fromId); + Account toAccount = (Account) s.get(Account.class, toId); + if (!(amount > fromAccount.getBalance())) { + fromAccount.balance -= amount; + toAccount.balance += amount; + s.save(fromAccount); + s.save(toAccount); + rv = amount; + System.out.printf("APP: transferFunds(%d, %d, %d) --> %d\n", fromId, toId, amount, rv); + } + } catch (JDBCException e) { + throw e; + } + return rv; + }; + return f; + } + + // Test our retry handling logic if FORCE_RETRY is true. This + // method is only used to test the retry logic. It is not + // intended for production code. + private static Function forceRetryLogic() throws JDBCException { + Function f = s -> { + long rv = -1; + try { + System.out.printf("APP: testRetryLogic: BEFORE EXCEPTION\n"); + s.createNativeQuery("SELECT crdb_internal.force_retry('1s')").executeUpdate(); + } catch (JDBCException e) { + System.out.printf("APP: testRetryLogic: AFTER EXCEPTION\n"); + throw e; + } + return rv; + }; + return f; + } + + private static Function getAccountBalance(long id) throws JDBCException{ + Function f = s -> { + long balance; + try { + Account account = s.get(Account.class, id); + balance = account.getBalance(); + System.out.printf("APP: getAccountBalance(%d) --> %d\n", id, balance); + } catch (JDBCException e) { + throw e; + } + return balance; + }; + return f; + } + + // Run SQL code in a way that automatically handles the + // transaction retry logic so we don't have to duplicate it in + // various places. + private static long runTransaction(Session session, Function fn) { + long rv = 0; + int attemptCount = 0; + + while (attemptCount < MAX_ATTEMPT_COUNT) { + attemptCount++; + + if (attemptCount > 1) { + System.out.printf("APP: Entering retry loop again, iteration %d\n", attemptCount); + } + + Transaction txn = session.beginTransaction(); + System.out.printf("APP: BEGIN;\n"); + + if (attemptCount == MAX_ATTEMPT_COUNT) { + String err = String.format("hit max of %s attempts, aborting", MAX_ATTEMPT_COUNT); + throw new RuntimeException(err); + } + + // This block is only used to test the retry logic. + // It is not necessary in production code. See also + // the method 'testRetryLogic()'. + if (FORCE_RETRY) { + session.createNativeQuery("SELECT now()").list(); + } + + try { + rv = fn.apply(session); + if (rv != -1) { + txn.commit(); + System.out.printf("APP: COMMIT;\n"); + break; + } + } catch (JDBCException e) { + 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("APP: retryable exception occurred:\n sql state = [%s]\n message = [%s]\n retry counter = %s\n", e.getSQLState(), e.getMessage(), attemptCount); + System.out.printf("APP: ROLLBACK;\n"); + txn.rollback(); + int sleepMillis = (int)(Math.pow(2, attemptCount) * 100) + RAND.nextInt(100); + System.out.printf("APP: Hit 40001 transaction retry error, sleeping %s milliseconds\n", sleepMillis); + try { + Thread.sleep(sleepMillis); + } catch (InterruptedException ignored) { + // no-op + } + rv = -1; + } else { + throw e; + } + } + } + return rv; + } + + public static void main(String[] args) { + // Create a SessionFactory based on our hibernate.cfg.xml configuration + // file, which defines how to connect to the database. + SessionFactory sessionFactory = + new Configuration() + .configure("hibernate.cfg.xml") + .addAnnotatedClass(Account.class) + .buildSessionFactory(); + + try (Session session = sessionFactory.openSession()) { + long fromAccountId = 1; + long toAccountId = 2; + long transferAmount = 100; + + if (FORCE_RETRY) { + System.out.printf("APP: About to test retry logic in 'runTransaction'\n"); + runTransaction(session, forceRetryLogic()); + } else { + + runTransaction(session, addAccounts()); + long fromBalance = runTransaction(session, getAccountBalance(fromAccountId)); + long toBalance = runTransaction(session, getAccountBalance(toAccountId)); + if (fromBalance != -1 && toBalance != -1) { + // Success! + System.out.printf("APP: getAccountBalance(%d) --> %d\n", fromAccountId, fromBalance); + System.out.printf("APP: getAccountBalance(%d) --> %d\n", toAccountId, toBalance); + } + + // Transfer $100 from account 1 to account 2 + long transferResult = runTransaction(session, transferFunds(fromAccountId, toAccountId, transferAmount)); + if (transferResult != -1) { + // Success! + System.out.printf("APP: transferFunds(%d, %d, %d) --> %d \n", fromAccountId, toAccountId, transferAmount, transferResult); + + long fromBalanceAfter = runTransaction(session, getAccountBalance(fromAccountId)); + long toBalanceAfter = runTransaction(session, getAccountBalance(toAccountId)); + if (fromBalanceAfter != -1 && toBalanceAfter != -1) { + // Success! + System.out.printf("APP: getAccountBalance(%d) --> %d\n", fromAccountId, fromBalanceAfter); + System.out.printf("APP: getAccountBalance(%d) --> %d\n", toAccountId, toBalanceAfter); + } + } } } finally { - session.close(); sessionFactory.close(); } } diff --git a/_includes/v19.2/app/hibernate-basic-sample/hibernate-basic-sample.tgz b/_includes/v19.2/app/hibernate-basic-sample/hibernate-basic-sample.tgz index 355dba55713c4c540e2a23401433523872feb90a..3e729bf439e7079e36b7c865cc8da37bfe6e9253 100644 GIT binary patch literal 3291 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 literal 1716 zcmV;l221%LiwFQT!?aug1ML}WZ`wL^pZOIY(k>ts2T}rC3v3UEnRy%-Hr2sJbc~{`y4W3V=6h(v^geGgU~kn^Bglt833k)X<{kRIXXcz`yU+`uQb zV}AXg!0h|+A4kmh@Kh4dr}vft{^)i(r@7T_^(}n%N>vT8!|D(VS@i+DRAW3kS^S|S?A^tt5?d7o2f4cvE zbn^Gn0m`@5e8Q%YjQf3b^w;aRhoedwQ&eqQ)}I;b;tq`pMEfWEXdAaI>)>5=K|0eq z+_9{Si;G1|kD*hyJndN}@|(a0!+I5TEkPN^68efNV{3ClT$nemhLySC{?ld??GsI) z6e!FHoKrVeQ&FwK9||>_!v%IIMM$N-LLBCsK{Zz&b4u(<2EP5`nlY$4EKVDEKVIxx z8KZT0dyM$NJK?qR8XqOxk3YP9;p`{x?#4f-dnEnpu>bVaXn*fy@6)L_cJuR*W%?VSQyA5!Ysyx@;|m14Iy51@n}0!GKy*2E-&!feepzu3 zf8`1M=fYb3zbk_K%m8=q|J!zZ)BimRtR4TtA9r`lhV|ober$;UcCWXY{~rZ@F#fr7 z>~i|Ltvvf49}^Z2JGlfRMOnWNNzIrT^x4D-{-mLTXg(G>?;0d-ulG zH_4G+f@J2$!W^)WWfJNVhh`j68s-oRh<+3-#H}J6aLx494G4$c4T~qHgS%nfl}uB~ z2p*+3A0lcDtAx?mXKvcrCFPKQldvo_irX}35R3jmX0Y%OOcXFvqm_Xg6`G!m*M#|W z3th~ZJA(#p!Z=mP^mbZgBZ|2-{ic_WE;uF)VY)C%K;mjpbYi}KEbF6+kArz%XMod- z;<#3C6xK7qncB2oU9qWJLPI=nk8`0-#~gnos;A8|jy+mJ6@Qj2+E8!oRx($mED%$% zgU$TmXRdRFgux{g+*6sqswBZz)w%1Ejy`eVt0c3M`#4YPNs~J#OSDWgA@y^T5Sb?5 z=61X~^cMF?Qm5>gmvd6e%oot4GjF$2LNg{lf%GgPOjmQ%CMN|yr<|FrHi@I4DK`$p z%N$bG>&!UP+d}4=GHm4RF`#EmVdkMhtm?{2N(IYvg|hYQf@+Ju2RNk=A7T=nM7~CK zE}BG68+nc6%ufm|jxZAAQ>X$}Y6+-)UCBu;2JBpb1?H@)A8QG-(WlP{B&r73AjO43 z2bV65r>dOsX$ofb0~Qu53B#nrQ>1x<#(se+EBX-3KjbigLKR;K6iWn;uOw(LFEdC* z;xUDh?nDx4Vh9I9wtVZZICDh$zA^SJQ0k(yz?Hk$*LMsl1TG3C;>QdB)=h7~n`lhl(3 zGrTX+ED@POI>SI|fY8odp}g#vWy9$Bkt#^%w{WV|8_QJ`C)5?3HvtBh;Ltc{Qm;Pq z&@=BJUfs%sU{j6ReO8f0-L4k}mTdU`a=Ojc^o61&nUW>(3Qkra)I diff --git a/_includes/v19.2/app/insecure/hibernate-basic-sample/Sample.java b/_includes/v19.2/app/insecure/hibernate-basic-sample/Sample.java index ed36ae15ad3..58d28f37a4b 100644 --- a/_includes/v19.2/app/insecure/hibernate-basic-sample/Sample.java +++ b/_includes/v19.2/app/insecure/hibernate-basic-sample/Sample.java @@ -2,22 +2,24 @@ import org.hibernate.Session; import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.JDBCException; import org.hibernate.cfg.Configuration; +import java.util.*; +import java.util.function.Function; + import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; -import javax.persistence.criteria.CriteriaQuery; public class Sample { - // Create a SessionFactory based on our hibernate.cfg.xml configuration - // file, which defines how to connect to the database. - private static final SessionFactory sessionFactory = - new Configuration() - .configure("hibernate.cfg.xml") - .addAnnotatedClass(Account.class) - .buildSessionFactory(); + + private static final Random RAND = new Random(); + private static final boolean FORCE_RETRY = false; + private static final String RETRY_SQL_STATE = "40001"; + private static final int MAX_ATTEMPT_COUNT = 6; // Account is our model, which corresponds to the "accounts" database table. @Entity @@ -27,8 +29,18 @@ public static class Account { @Column(name="id") public long id; + public long getId() { + return id; + } + @Column(name="balance") public long balance; + public long getBalance() { + return balance; + } + public void setBalance(long newBalance) { + this.balance = newBalance; + } // Convenience constructor. public Account(int id, int balance) { @@ -40,24 +52,184 @@ public Account(int id, int balance) { public Account() {} } - public static void main(String[] args) throws Exception { - Session session = sessionFactory.openSession(); - - try { - // Insert two rows into the "accounts" table. - session.beginTransaction(); - session.save(new Account(1, 1000)); - session.save(new Account(2, 250)); - session.getTransaction().commit(); - - // Print out the balances. - CriteriaQuery query = session.getCriteriaBuilder().createQuery(Account.class); - query.select(query.from(Account.class)); - for (Account account : session.createQuery(query).getResultList()) { - System.out.printf("%d %d\n", account.id, account.balance); + private static Function addAccounts() throws JDBCException{ + Function f = s -> { + long rv = 0; + try { + s.save(new Account(1, 1000)); + s.save(new Account(2, 250)); + s.save(new Account(3, 314159)); + rv = 1; + System.out.printf("APP: addAccounts() --> %d\n", rv); + } catch (JDBCException e) { + throw e; + } + return rv; + }; + return f; + } + + private static Function transferFunds(long fromId, long toId, long amount) throws JDBCException{ + Function f = s -> { + long rv = 0; + try { + Account fromAccount = (Account) s.get(Account.class, fromId); + Account toAccount = (Account) s.get(Account.class, toId); + if (!(amount > fromAccount.getBalance())) { + fromAccount.balance -= amount; + toAccount.balance += amount; + s.save(fromAccount); + s.save(toAccount); + rv = amount; + System.out.printf("APP: transferFunds(%d, %d, %d) --> %d\n", fromId, toId, amount, rv); + } + } catch (JDBCException e) { + throw e; + } + return rv; + }; + return f; + } + + // Test our retry handling logic if FORCE_RETRY is true. This + // method is only used to test the retry logic. It is not + // intended for production code. + private static Function forceRetryLogic() throws JDBCException { + Function f = s -> { + long rv = -1; + try { + System.out.printf("APP: testRetryLogic: BEFORE EXCEPTION\n"); + s.createNativeQuery("SELECT crdb_internal.force_retry('1s')").executeUpdate(); + } catch (JDBCException e) { + System.out.printf("APP: testRetryLogic: AFTER EXCEPTION\n"); + throw e; + } + return rv; + }; + return f; + } + + private static Function getAccountBalance(long id) throws JDBCException{ + Function f = s -> { + long balance; + try { + Account account = s.get(Account.class, id); + balance = account.getBalance(); + System.out.printf("APP: getAccountBalance(%d) --> %d\n", id, balance); + } catch (JDBCException e) { + throw e; + } + return balance; + }; + return f; + } + + // Run SQL code in a way that automatically handles the + // transaction retry logic so we don't have to duplicate it in + // various places. + private static long runTransaction(Session session, Function fn) { + long rv = 0; + int attemptCount = 0; + + while (attemptCount < MAX_ATTEMPT_COUNT) { + attemptCount++; + + if (attemptCount > 1) { + System.out.printf("APP: Entering retry loop again, iteration %d\n", attemptCount); + } + + Transaction txn = session.beginTransaction(); + System.out.printf("APP: BEGIN;\n"); + + if (attemptCount == MAX_ATTEMPT_COUNT) { + String err = String.format("hit max of %s attempts, aborting", MAX_ATTEMPT_COUNT); + throw new RuntimeException(err); + } + + // This block is only used to test the retry logic. + // It is not necessary in production code. See also + // the method 'testRetryLogic()'. + if (FORCE_RETRY) { + session.createNativeQuery("SELECT now()").list(); + } + + try { + rv = fn.apply(session); + if (rv != -1) { + txn.commit(); + System.out.printf("APP: COMMIT;\n"); + break; + } + } catch (JDBCException e) { + 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("APP: retryable exception occurred:\n sql state = [%s]\n message = [%s]\n retry counter = %s\n", e.getSQLState(), e.getMessage(), attemptCount); + System.out.printf("APP: ROLLBACK;\n"); + txn.rollback(); + int sleepMillis = (int)(Math.pow(2, attemptCount) * 100) + RAND.nextInt(100); + System.out.printf("APP: Hit 40001 transaction retry error, sleeping %s milliseconds\n", sleepMillis); + try { + Thread.sleep(sleepMillis); + } catch (InterruptedException ignored) { + // no-op + } + rv = -1; + } else { + throw e; + } + } + } + return rv; + } + + public static void main(String[] args) { + // Create a SessionFactory based on our hibernate.cfg.xml configuration + // file, which defines how to connect to the database. + SessionFactory sessionFactory = + new Configuration() + .configure("hibernate.cfg.xml") + .addAnnotatedClass(Account.class) + .buildSessionFactory(); + + try (Session session = sessionFactory.openSession()) { + long fromAccountId = 1; + long toAccountId = 2; + long transferAmount = 100; + + if (FORCE_RETRY) { + System.out.printf("APP: About to test retry logic in 'runTransaction'\n"); + runTransaction(session, forceRetryLogic()); + } else { + + runTransaction(session, addAccounts()); + long fromBalance = runTransaction(session, getAccountBalance(fromAccountId)); + long toBalance = runTransaction(session, getAccountBalance(toAccountId)); + if (fromBalance != -1 && toBalance != -1) { + // Success! + System.out.printf("APP: getAccountBalance(%d) --> %d\n", fromAccountId, fromBalance); + System.out.printf("APP: getAccountBalance(%d) --> %d\n", toAccountId, toBalance); + } + + // Transfer $100 from account 1 to account 2 + long transferResult = runTransaction(session, transferFunds(fromAccountId, toAccountId, transferAmount)); + if (transferResult != -1) { + // Success! + System.out.printf("APP: transferFunds(%d, %d, %d) --> %d \n", fromAccountId, toAccountId, transferAmount, transferResult); + + long fromBalanceAfter = runTransaction(session, getAccountBalance(fromAccountId)); + long toBalanceAfter = runTransaction(session, getAccountBalance(toAccountId)); + if (fromBalanceAfter != -1 && toBalanceAfter != -1) { + // Success! + System.out.printf("APP: getAccountBalance(%d) --> %d\n", fromAccountId, fromBalanceAfter); + System.out.printf("APP: getAccountBalance(%d) --> %d\n", toAccountId, toBalanceAfter); + } + } } } finally { - session.close(); sessionFactory.close(); } } diff --git a/_includes/v19.2/app/insecure/hibernate-basic-sample/hibernate-basic-sample.tgz b/_includes/v19.2/app/insecure/hibernate-basic-sample/hibernate-basic-sample.tgz index 9581b3a52df25f34edf09f9e27682b63f418140c..0ef2d046434a7c6a50c705600deab883b24cbf1d 100644 GIT binary patch literal 3238 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 - true - true + + diff --git a/_includes/v19.2/app/see-also-links.md b/_includes/v19.2/app/see-also-links.md index 90f06751e13..e5dd6173c99 100644 --- a/_includes/v19.2/app/see-also-links.md +++ b/_includes/v19.2/app/see-also-links.md @@ -1,4 +1,4 @@ -You might also be interested in using a local cluster to explore the following CockroachDB benefits: +You might also be interested in the following pages: - [Client Connection Parameters](connection-parameters.html) - [Data Replication](demo-data-replication.html) diff --git a/_includes/v20.1/app/hibernate-basic-sample/Sample.java b/_includes/v20.1/app/hibernate-basic-sample/Sample.java index ed36ae15ad3..58d28f37a4b 100644 --- a/_includes/v20.1/app/hibernate-basic-sample/Sample.java +++ b/_includes/v20.1/app/hibernate-basic-sample/Sample.java @@ -2,22 +2,24 @@ import org.hibernate.Session; import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.JDBCException; import org.hibernate.cfg.Configuration; +import java.util.*; +import java.util.function.Function; + import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; -import javax.persistence.criteria.CriteriaQuery; public class Sample { - // Create a SessionFactory based on our hibernate.cfg.xml configuration - // file, which defines how to connect to the database. - private static final SessionFactory sessionFactory = - new Configuration() - .configure("hibernate.cfg.xml") - .addAnnotatedClass(Account.class) - .buildSessionFactory(); + + private static final Random RAND = new Random(); + private static final boolean FORCE_RETRY = false; + private static final String RETRY_SQL_STATE = "40001"; + private static final int MAX_ATTEMPT_COUNT = 6; // Account is our model, which corresponds to the "accounts" database table. @Entity @@ -27,8 +29,18 @@ public static class Account { @Column(name="id") public long id; + public long getId() { + return id; + } + @Column(name="balance") public long balance; + public long getBalance() { + return balance; + } + public void setBalance(long newBalance) { + this.balance = newBalance; + } // Convenience constructor. public Account(int id, int balance) { @@ -40,24 +52,184 @@ public Account(int id, int balance) { public Account() {} } - public static void main(String[] args) throws Exception { - Session session = sessionFactory.openSession(); - - try { - // Insert two rows into the "accounts" table. - session.beginTransaction(); - session.save(new Account(1, 1000)); - session.save(new Account(2, 250)); - session.getTransaction().commit(); - - // Print out the balances. - CriteriaQuery query = session.getCriteriaBuilder().createQuery(Account.class); - query.select(query.from(Account.class)); - for (Account account : session.createQuery(query).getResultList()) { - System.out.printf("%d %d\n", account.id, account.balance); + private static Function addAccounts() throws JDBCException{ + Function f = s -> { + long rv = 0; + try { + s.save(new Account(1, 1000)); + s.save(new Account(2, 250)); + s.save(new Account(3, 314159)); + rv = 1; + System.out.printf("APP: addAccounts() --> %d\n", rv); + } catch (JDBCException e) { + throw e; + } + return rv; + }; + return f; + } + + private static Function transferFunds(long fromId, long toId, long amount) throws JDBCException{ + Function f = s -> { + long rv = 0; + try { + Account fromAccount = (Account) s.get(Account.class, fromId); + Account toAccount = (Account) s.get(Account.class, toId); + if (!(amount > fromAccount.getBalance())) { + fromAccount.balance -= amount; + toAccount.balance += amount; + s.save(fromAccount); + s.save(toAccount); + rv = amount; + System.out.printf("APP: transferFunds(%d, %d, %d) --> %d\n", fromId, toId, amount, rv); + } + } catch (JDBCException e) { + throw e; + } + return rv; + }; + return f; + } + + // Test our retry handling logic if FORCE_RETRY is true. This + // method is only used to test the retry logic. It is not + // intended for production code. + private static Function forceRetryLogic() throws JDBCException { + Function f = s -> { + long rv = -1; + try { + System.out.printf("APP: testRetryLogic: BEFORE EXCEPTION\n"); + s.createNativeQuery("SELECT crdb_internal.force_retry('1s')").executeUpdate(); + } catch (JDBCException e) { + System.out.printf("APP: testRetryLogic: AFTER EXCEPTION\n"); + throw e; + } + return rv; + }; + return f; + } + + private static Function getAccountBalance(long id) throws JDBCException{ + Function f = s -> { + long balance; + try { + Account account = s.get(Account.class, id); + balance = account.getBalance(); + System.out.printf("APP: getAccountBalance(%d) --> %d\n", id, balance); + } catch (JDBCException e) { + throw e; + } + return balance; + }; + return f; + } + + // Run SQL code in a way that automatically handles the + // transaction retry logic so we don't have to duplicate it in + // various places. + private static long runTransaction(Session session, Function fn) { + long rv = 0; + int attemptCount = 0; + + while (attemptCount < MAX_ATTEMPT_COUNT) { + attemptCount++; + + if (attemptCount > 1) { + System.out.printf("APP: Entering retry loop again, iteration %d\n", attemptCount); + } + + Transaction txn = session.beginTransaction(); + System.out.printf("APP: BEGIN;\n"); + + if (attemptCount == MAX_ATTEMPT_COUNT) { + String err = String.format("hit max of %s attempts, aborting", MAX_ATTEMPT_COUNT); + throw new RuntimeException(err); + } + + // This block is only used to test the retry logic. + // It is not necessary in production code. See also + // the method 'testRetryLogic()'. + if (FORCE_RETRY) { + session.createNativeQuery("SELECT now()").list(); + } + + try { + rv = fn.apply(session); + if (rv != -1) { + txn.commit(); + System.out.printf("APP: COMMIT;\n"); + break; + } + } catch (JDBCException e) { + 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("APP: retryable exception occurred:\n sql state = [%s]\n message = [%s]\n retry counter = %s\n", e.getSQLState(), e.getMessage(), attemptCount); + System.out.printf("APP: ROLLBACK;\n"); + txn.rollback(); + int sleepMillis = (int)(Math.pow(2, attemptCount) * 100) + RAND.nextInt(100); + System.out.printf("APP: Hit 40001 transaction retry error, sleeping %s milliseconds\n", sleepMillis); + try { + Thread.sleep(sleepMillis); + } catch (InterruptedException ignored) { + // no-op + } + rv = -1; + } else { + throw e; + } + } + } + return rv; + } + + public static void main(String[] args) { + // Create a SessionFactory based on our hibernate.cfg.xml configuration + // file, which defines how to connect to the database. + SessionFactory sessionFactory = + new Configuration() + .configure("hibernate.cfg.xml") + .addAnnotatedClass(Account.class) + .buildSessionFactory(); + + try (Session session = sessionFactory.openSession()) { + long fromAccountId = 1; + long toAccountId = 2; + long transferAmount = 100; + + if (FORCE_RETRY) { + System.out.printf("APP: About to test retry logic in 'runTransaction'\n"); + runTransaction(session, forceRetryLogic()); + } else { + + runTransaction(session, addAccounts()); + long fromBalance = runTransaction(session, getAccountBalance(fromAccountId)); + long toBalance = runTransaction(session, getAccountBalance(toAccountId)); + if (fromBalance != -1 && toBalance != -1) { + // Success! + System.out.printf("APP: getAccountBalance(%d) --> %d\n", fromAccountId, fromBalance); + System.out.printf("APP: getAccountBalance(%d) --> %d\n", toAccountId, toBalance); + } + + // Transfer $100 from account 1 to account 2 + long transferResult = runTransaction(session, transferFunds(fromAccountId, toAccountId, transferAmount)); + if (transferResult != -1) { + // Success! + System.out.printf("APP: transferFunds(%d, %d, %d) --> %d \n", fromAccountId, toAccountId, transferAmount, transferResult); + + long fromBalanceAfter = runTransaction(session, getAccountBalance(fromAccountId)); + long toBalanceAfter = runTransaction(session, getAccountBalance(toAccountId)); + if (fromBalanceAfter != -1 && toBalanceAfter != -1) { + // Success! + System.out.printf("APP: getAccountBalance(%d) --> %d\n", fromAccountId, fromBalanceAfter); + System.out.printf("APP: getAccountBalance(%d) --> %d\n", toAccountId, toBalanceAfter); + } + } } } finally { - session.close(); sessionFactory.close(); } } diff --git a/_includes/v20.1/app/hibernate-basic-sample/hibernate-basic-sample.tgz b/_includes/v20.1/app/hibernate-basic-sample/hibernate-basic-sample.tgz index 355dba55713c4c540e2a23401433523872feb90a..3e729bf439e7079e36b7c865cc8da37bfe6e9253 100644 GIT binary patch literal 3291 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 literal 1716 zcmV;l221%LiwFQT!?aug1ML}WZ`wL^pZOIY(k>ts2T}rC3v3UEnRy%-Hr2sJbc~{`y4W3V=6h(v^geGgU~kn^Bglt833k)X<{kRIXXcz`yU+`uQb zV}AXg!0h|+A4kmh@Kh4dr}vft{^)i(r@7T_^(}n%N>vT8!|D(VS@i+DRAW3kS^S|S?A^tt5?d7o2f4cvE zbn^Gn0m`@5e8Q%YjQf3b^w;aRhoedwQ&eqQ)}I;b;tq`pMEfWEXdAaI>)>5=K|0eq z+_9{Si;G1|kD*hyJndN}@|(a0!+I5TEkPN^68efNV{3ClT$nemhLySC{?ld??GsI) z6e!FHoKrVeQ&FwK9||>_!v%IIMM$N-LLBCsK{Zz&b4u(<2EP5`nlY$4EKVDEKVIxx z8KZT0dyM$NJK?qR8XqOxk3YP9;p`{x?#4f-dnEnpu>bVaXn*fy@6)L_cJuR*W%?VSQyA5!Ysyx@;|m14Iy51@n}0!GKy*2E-&!feepzu3 zf8`1M=fYb3zbk_K%m8=q|J!zZ)BimRtR4TtA9r`lhV|ober$;UcCWXY{~rZ@F#fr7 z>~i|Ltvvf49}^Z2JGlfRMOnWNNzIrT^x4D-{-mLTXg(G>?;0d-ulG zH_4G+f@J2$!W^)WWfJNVhh`j68s-oRh<+3-#H}J6aLx494G4$c4T~qHgS%nfl}uB~ z2p*+3A0lcDtAx?mXKvcrCFPKQldvo_irX}35R3jmX0Y%OOcXFvqm_Xg6`G!m*M#|W z3th~ZJA(#p!Z=mP^mbZgBZ|2-{ic_WE;uF)VY)C%K;mjpbYi}KEbF6+kArz%XMod- z;<#3C6xK7qncB2oU9qWJLPI=nk8`0-#~gnos;A8|jy+mJ6@Qj2+E8!oRx($mED%$% zgU$TmXRdRFgux{g+*6sqswBZz)w%1Ejy`eVt0c3M`#4YPNs~J#OSDWgA@y^T5Sb?5 z=61X~^cMF?Qm5>gmvd6e%oot4GjF$2LNg{lf%GgPOjmQ%CMN|yr<|FrHi@I4DK`$p z%N$bG>&!UP+d}4=GHm4RF`#EmVdkMhtm?{2N(IYvg|hYQf@+Ju2RNk=A7T=nM7~CK zE}BG68+nc6%ufm|jxZAAQ>X$}Y6+-)UCBu;2JBpb1?H@)A8QG-(WlP{B&r73AjO43 z2bV65r>dOsX$ofb0~Qu53B#nrQ>1x<#(se+EBX-3KjbigLKR;K6iWn;uOw(LFEdC* z;xUDh?nDx4Vh9I9wtVZZICDh$zA^SJQ0k(yz?Hk$*LMsl1TG3C;>QdB)=h7~n`lhl(3 zGrTX+ED@POI>SI|fY8odp}g#vWy9$Bkt#^%w{WV|8_QJ`C)5?3HvtBh;Ltc{Qm;Pq z&@=BJUfs%sU{j6ReO8f0-L4k}mTdU`a=Ojc^o61&nUW>(3Qkra)I diff --git a/_includes/v20.1/app/insecure/hibernate-basic-sample/Sample.java b/_includes/v20.1/app/insecure/hibernate-basic-sample/Sample.java index ed36ae15ad3..58d28f37a4b 100644 --- a/_includes/v20.1/app/insecure/hibernate-basic-sample/Sample.java +++ b/_includes/v20.1/app/insecure/hibernate-basic-sample/Sample.java @@ -2,22 +2,24 @@ import org.hibernate.Session; import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.JDBCException; import org.hibernate.cfg.Configuration; +import java.util.*; +import java.util.function.Function; + import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; -import javax.persistence.criteria.CriteriaQuery; public class Sample { - // Create a SessionFactory based on our hibernate.cfg.xml configuration - // file, which defines how to connect to the database. - private static final SessionFactory sessionFactory = - new Configuration() - .configure("hibernate.cfg.xml") - .addAnnotatedClass(Account.class) - .buildSessionFactory(); + + private static final Random RAND = new Random(); + private static final boolean FORCE_RETRY = false; + private static final String RETRY_SQL_STATE = "40001"; + private static final int MAX_ATTEMPT_COUNT = 6; // Account is our model, which corresponds to the "accounts" database table. @Entity @@ -27,8 +29,18 @@ public static class Account { @Column(name="id") public long id; + public long getId() { + return id; + } + @Column(name="balance") public long balance; + public long getBalance() { + return balance; + } + public void setBalance(long newBalance) { + this.balance = newBalance; + } // Convenience constructor. public Account(int id, int balance) { @@ -40,24 +52,184 @@ public Account(int id, int balance) { public Account() {} } - public static void main(String[] args) throws Exception { - Session session = sessionFactory.openSession(); - - try { - // Insert two rows into the "accounts" table. - session.beginTransaction(); - session.save(new Account(1, 1000)); - session.save(new Account(2, 250)); - session.getTransaction().commit(); - - // Print out the balances. - CriteriaQuery query = session.getCriteriaBuilder().createQuery(Account.class); - query.select(query.from(Account.class)); - for (Account account : session.createQuery(query).getResultList()) { - System.out.printf("%d %d\n", account.id, account.balance); + private static Function addAccounts() throws JDBCException{ + Function f = s -> { + long rv = 0; + try { + s.save(new Account(1, 1000)); + s.save(new Account(2, 250)); + s.save(new Account(3, 314159)); + rv = 1; + System.out.printf("APP: addAccounts() --> %d\n", rv); + } catch (JDBCException e) { + throw e; + } + return rv; + }; + return f; + } + + private static Function transferFunds(long fromId, long toId, long amount) throws JDBCException{ + Function f = s -> { + long rv = 0; + try { + Account fromAccount = (Account) s.get(Account.class, fromId); + Account toAccount = (Account) s.get(Account.class, toId); + if (!(amount > fromAccount.getBalance())) { + fromAccount.balance -= amount; + toAccount.balance += amount; + s.save(fromAccount); + s.save(toAccount); + rv = amount; + System.out.printf("APP: transferFunds(%d, %d, %d) --> %d\n", fromId, toId, amount, rv); + } + } catch (JDBCException e) { + throw e; + } + return rv; + }; + return f; + } + + // Test our retry handling logic if FORCE_RETRY is true. This + // method is only used to test the retry logic. It is not + // intended for production code. + private static Function forceRetryLogic() throws JDBCException { + Function f = s -> { + long rv = -1; + try { + System.out.printf("APP: testRetryLogic: BEFORE EXCEPTION\n"); + s.createNativeQuery("SELECT crdb_internal.force_retry('1s')").executeUpdate(); + } catch (JDBCException e) { + System.out.printf("APP: testRetryLogic: AFTER EXCEPTION\n"); + throw e; + } + return rv; + }; + return f; + } + + private static Function getAccountBalance(long id) throws JDBCException{ + Function f = s -> { + long balance; + try { + Account account = s.get(Account.class, id); + balance = account.getBalance(); + System.out.printf("APP: getAccountBalance(%d) --> %d\n", id, balance); + } catch (JDBCException e) { + throw e; + } + return balance; + }; + return f; + } + + // Run SQL code in a way that automatically handles the + // transaction retry logic so we don't have to duplicate it in + // various places. + private static long runTransaction(Session session, Function fn) { + long rv = 0; + int attemptCount = 0; + + while (attemptCount < MAX_ATTEMPT_COUNT) { + attemptCount++; + + if (attemptCount > 1) { + System.out.printf("APP: Entering retry loop again, iteration %d\n", attemptCount); + } + + Transaction txn = session.beginTransaction(); + System.out.printf("APP: BEGIN;\n"); + + if (attemptCount == MAX_ATTEMPT_COUNT) { + String err = String.format("hit max of %s attempts, aborting", MAX_ATTEMPT_COUNT); + throw new RuntimeException(err); + } + + // This block is only used to test the retry logic. + // It is not necessary in production code. See also + // the method 'testRetryLogic()'. + if (FORCE_RETRY) { + session.createNativeQuery("SELECT now()").list(); + } + + try { + rv = fn.apply(session); + if (rv != -1) { + txn.commit(); + System.out.printf("APP: COMMIT;\n"); + break; + } + } catch (JDBCException e) { + 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("APP: retryable exception occurred:\n sql state = [%s]\n message = [%s]\n retry counter = %s\n", e.getSQLState(), e.getMessage(), attemptCount); + System.out.printf("APP: ROLLBACK;\n"); + txn.rollback(); + int sleepMillis = (int)(Math.pow(2, attemptCount) * 100) + RAND.nextInt(100); + System.out.printf("APP: Hit 40001 transaction retry error, sleeping %s milliseconds\n", sleepMillis); + try { + Thread.sleep(sleepMillis); + } catch (InterruptedException ignored) { + // no-op + } + rv = -1; + } else { + throw e; + } + } + } + return rv; + } + + public static void main(String[] args) { + // Create a SessionFactory based on our hibernate.cfg.xml configuration + // file, which defines how to connect to the database. + SessionFactory sessionFactory = + new Configuration() + .configure("hibernate.cfg.xml") + .addAnnotatedClass(Account.class) + .buildSessionFactory(); + + try (Session session = sessionFactory.openSession()) { + long fromAccountId = 1; + long toAccountId = 2; + long transferAmount = 100; + + if (FORCE_RETRY) { + System.out.printf("APP: About to test retry logic in 'runTransaction'\n"); + runTransaction(session, forceRetryLogic()); + } else { + + runTransaction(session, addAccounts()); + long fromBalance = runTransaction(session, getAccountBalance(fromAccountId)); + long toBalance = runTransaction(session, getAccountBalance(toAccountId)); + if (fromBalance != -1 && toBalance != -1) { + // Success! + System.out.printf("APP: getAccountBalance(%d) --> %d\n", fromAccountId, fromBalance); + System.out.printf("APP: getAccountBalance(%d) --> %d\n", toAccountId, toBalance); + } + + // Transfer $100 from account 1 to account 2 + long transferResult = runTransaction(session, transferFunds(fromAccountId, toAccountId, transferAmount)); + if (transferResult != -1) { + // Success! + System.out.printf("APP: transferFunds(%d, %d, %d) --> %d \n", fromAccountId, toAccountId, transferAmount, transferResult); + + long fromBalanceAfter = runTransaction(session, getAccountBalance(fromAccountId)); + long toBalanceAfter = runTransaction(session, getAccountBalance(toAccountId)); + if (fromBalanceAfter != -1 && toBalanceAfter != -1) { + // Success! + System.out.printf("APP: getAccountBalance(%d) --> %d\n", fromAccountId, fromBalanceAfter); + System.out.printf("APP: getAccountBalance(%d) --> %d\n", toAccountId, toBalanceAfter); + } + } } } finally { - session.close(); sessionFactory.close(); } } diff --git a/_includes/v20.1/app/insecure/hibernate-basic-sample/hibernate-basic-sample.tgz b/_includes/v20.1/app/insecure/hibernate-basic-sample/hibernate-basic-sample.tgz index 9581b3a52df25f34edf09f9e27682b63f418140c..8205b37922935f23acf8bb72875bea56090e9b59 100644 GIT binary patch literal 3236 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 literal 1664 zcmV-`27mb`rd)M7vheTEW`_1|VFw_aT?4UWY?Tfd*AV#E_?u^)AoA3-V@|KsudNKC`}}o@>$4$@M=C_8;@_< zC--B1{h!eE`}rRwEDZ2Ok|3lH)&cS8b~@Gh?{vI!{$0oIwx1y95&XN8e)9ZJq)1{^ z_eF$#;eU~W_|uS#RNcCyQZd0%y^Wo^Wsx`zuTdN(6UO_f{+V2o`s$D7EAxp4F_4G| z<3}M;>K%zFI{c0je4bVt@JbpB#k7!&D)h}lKoPm5{D^WbN!VywH z#w?_$F60EyYq`IuYF|j&_ppmQ_zmMEtgnlKW<8NqeGU6dw=Y1V?K;ovp!vT}(!WR* z{ja3|c+Y^1&;O3=?R#A~|GQpycm8jMZppvt-$yBe`#k?UUK{e?^BixN|83A+`8VhM zLyO>E{on1G=YMCvyUYJJ=&t<3!%!sBr|JPkaKHYC^WSZIZfBSOZP4cVpL1e=Ji%8{ zxUr6^`5$HEI_-8T|H$olUT-)5Z-WLeA(JSd^$*ioAHjqz0>&qY^+bt zt>eCJ&t|j5NRMGsw*wv675ZDS4Tkj^*jm9dOf?J?RmRuWnD~G4a*P%AE%20yAZG5 zF<>Nw9kU%4fyjTxXW5?TAKt$3j%azM25BD=V0V7;~v{?yFG}dgWG+#>v}zV zMEIANN`;XK=wZMV8HMz|nv^j5DU6q6#;HPP^3EDuN9~#XAs|Z!T1!5`< zumLP#5jtN;064MWfhyuvBN@M{%|n;rjVyq#vdYTt(>iHnBluh_vof!QJkD7`6qbCO z`|;{DSlnktoo~m2r6A>-g#sW13l6LbnGp#IlxKxtzPcoBYEtk^%9*KZQ#cABg9#vB z-61!>{ znxX>OoO+oThbntlI&TNL2}AVNd|kxbXe~Hjo5O)hqdBdPRC485DVv^dh7~pMP8!)4 z^J-y5W<|)r{DK4J2}1q3LCd<+Et|&1k5oZ9zlEntqq*8eX+d4X^CrR&5*(Z0j2pFQ z0eTkv$!mLs5^TD$xGy@gY}?JE!LkECTrIb`nW0d$qEdNew in v19.1: You can pass the [`--also-generate-pkcs8-key` flag](create-security-certificates.html#flag-pkcs8) to generate a key in [PKCS#8 format](https://tools.ietf.org/html/rfc5208), which is the standard key encoding format in Java. In this case, the generated PKCS8 key will be named `client.maxroach.key.pk8`. +You can pass the [`--also-generate-pkcs8-key` flag](create-security-certificates.html#flag-pkcs8) to generate a key in [PKCS#8 format](https://tools.ietf.org/html/rfc5208), which is the standard key encoding format in Java. In this case, the generated PKCS8 key will be named `client.maxroach.key.pk8`. {% include copy-clipboard.html %} ~~~ shell @@ -72,29 +72,63 @@ $ cockroach cert create-client maxroach --certs-dir=certs --ca-key=my-safe-direc ## Step 4. Run the Java code -Download and extract [hibernate-basic-sample.tgz](https://github.com/cockroachdb/docs/raw/master/_includes/v19.1/app/hibernate-basic-sample/hibernate-basic-sample.tgz), which contains a Java project that includes the following files: +The code below uses Hibernate to map Java methods to SQL operations. It perform the following steps which roughly correspond to method calls in the `Sample` class. -File | Description ------|------------ -[`Sample.java`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/v19.1/app/hibernate-basic-sample/Sample.java) | Uses [Hibernate](http://hibernate.org/orm/) to map Java object state to SQL operations. For more information, see [Sample.java](#sample-java). -[`hibernate.cfg.xml`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/v19.1/app/hibernate-basic-sample/hibernate.cfg.xml) | Specifies how to connect to the database and that the database schema will be deleted and recreated each time the app is run. For more information, see [hibernate.cfg.xml](#hibernate-cfg-xml). -[`build.gradle`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/v19.1/app/hibernate-basic-sample/build.gradle) | Used to build and run your app. For more information, see [build.gradle](#build-gradle). +1. Create an `accounts` table in the `bank` database as specified by the Hibernate `Account` class. +2. Inserts rows into the table using `session.save(new Account(int id, int balance))` (see `Sample.addAccounts()`). +3. Transfer money from one account to another, printing out account balances before and after the transfer (see `transferFunds(long fromId, long toId, long amount)`). +4. Print out account balances before and after the transfer (see `Sample.getAccountBalance(long id)`). -In the `hibernate-basic-sample` directory, build and run the application: +In addition, the code shows a pattern for automatically handling [transaction retries](transactions.html#client-side-intervention-example) by wrapping transactions in a higher-order function `Sample.runTransaction()`. It also includes a method for testing the retry handling logic (`Sample.forceRetryLogic()`), which will be run if you set the `FORCE_RETRY` variable to `true`. + +It does all of the above using the practices we recommend for using Hibernate (and the underlying JDBC connection) with CockroachDB, which are listed in the [Recommended Practices](#recommended-practices) section below. + +To run it: + +1. Download and extract [hibernate-basic-sample.tgz](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/hibernate-basic-sample/hibernate-basic-sample.tgz). The settings in [`hibernate.cfg.xml`](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/hibernate-basic-sample/hibernate.cfg.xml) specify how to connect to the database. +2. Compile and run the code using [`build.gradle`](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/hibernate-basic-sample/build.gradle), which will also download the dependencies. + + {% include copy-clipboard.html %} + ~~~ shell + $ gradle run + ~~~ + +The contents of [`Sample.java`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/{{page.version.version}}/app/hibernate-basic-sample/Sample.java): {% include copy-clipboard.html %} -~~~ shell -$ gradle run +~~~ java +{% include {{page.version.version}}/app/hibernate-basic-sample/Sample.java %} ~~~ Toward the end of the output, you should see: ~~~ -1 1000 -2 250 +APP: BEGIN; +APP: addAccounts() --> 1 +APP: COMMIT; +APP: BEGIN; +APP: getAccountBalance(1) --> 1000 +APP: COMMIT; +APP: BEGIN; +APP: getAccountBalance(2) --> 250 +APP: COMMIT; +APP: getAccountBalance(1) --> 1000 +APP: getAccountBalance(2) --> 250 +APP: BEGIN; +APP: transferFunds(1, 2, 100) --> 100 +APP: COMMIT; +APP: transferFunds(1, 2, 100) --> 100 +APP: BEGIN; +APP: getAccountBalance(1) --> 900 +APP: COMMIT; +APP: BEGIN; +APP: getAccountBalance(2) --> 350 +APP: COMMIT; +APP: getAccountBalance(1) --> 900 +APP: getAccountBalance(2) --> 350 ~~~ -To verify that the table and rows were created successfully, start the [built-in SQL client](use-the-built-in-sql-client.html): +To verify that the account balances were updated successfully, start the [built-in SQL client](use-the-built-in-sql-client.html): {% include copy-clipboard.html %} ~~~ shell @@ -105,50 +139,16 @@ To check the account balances, issue the following statement: {% include copy-clipboard.html %} ~~~ sql -> SELECT id, balance FROM accounts; +SELECT id, balance FROM accounts; ~~~ ~~~ + id | balance +----+---------+ -| id | balance | -+----+---------+ -| 1 | 1000 | -| 2 | 250 | -+----+---------+ -(2 rows) -~~~ - -### Sample.java - -The Java code shown below uses the [Hibernate ORM](http://hibernate.org/orm/) to map Java object state to SQL operations. Specifically, this code: - -- Creates an `accounts` table in the database based on the `Account` class. - -- Inserts rows into the table using `session.save(new Account())`. - -- Defines the SQL query for selecting from the table so that balances can be printed using the `CriteriaQuery query` object. - -{% include copy-clipboard.html %} -~~~ java -{% include {{page.version.version}}/app/hibernate-basic-sample/Sample.java %} -~~~ - -### hibernate.cfg.xml - -The Hibernate config (in `hibernate.cfg.xml`, shown below) specifies how to connect to the database. Note the [connection URL](connection-parameters.html#connect-using-a-url) that turns on SSL and specifies the location of the security certificates. - -{% include copy-clipboard.html %} -~~~ xml -{% include {{page.version.version}}/app/hibernate-basic-sample/hibernate.cfg.xml %} -~~~ - -### build.gradle - -The Gradle build file specifies the dependencies (in this case the Postgres JDBC driver and Hibernate): - -{% include copy-clipboard.html %} -~~~ groovy -{% include {{page.version.version}}/app/hibernate-basic-sample/build.gradle %} + 1 | 900 + 2 | 350 + 3 | 314159 +(3 rows) ~~~ @@ -161,29 +161,63 @@ The Gradle build file specifies the dependencies (in this case the Postgres JDBC ## Step 3. Run the Java code -Download and extract [hibernate-basic-sample.tgz](https://github.com/cockroachdb/docs/raw/master/_includes/v19.1/app/insecure/hibernate-basic-sample/hibernate-basic-sample.tgz), which contains a Java project that includes the following files: +The code below uses Hibernate to map Java methods to SQL operations. It perform the following steps which roughly correspond to method calls in the `Sample` class. + +1. Create an `accounts` table in the `bank` database as specified by the Hibernate `Account` class. +2. Inserts rows into the table using `session.save(new Account(int id, int balance))` (see `Sample.addAccounts()`). +3. Transfer money from one account to another, printing out account balances before and after the transfer (see `transferFunds(long fromId, long toId, long amount)`). +4. Print out account balances before and after the transfer (see `Sample.getAccountBalance(long id)`). + +In addition, the code shows a pattern for automatically handling [transaction retries](transactions.html#client-side-intervention-example) by wrapping transactions in a higher-order function `Sample.runTransaction()`. It also includes a method for testing the retry handling logic (`Sample.forceRetryLogic()`), which will be run if you set the `FORCE_RETRY` variable to `true`. + +It does all of the above using the practices we recommend for using Hibernate (and the underlying JDBC connection) with CockroachDB, which are listed in the [Recommended Practices](#recommended-practices) section below. + +To run it: -File | Description ------|------------ -[`Sample.java`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/v19.1/app/insecure/hibernate-basic-sample/Sample.java) | Uses [Hibernate](http://hibernate.org/orm/) to map Java object state to SQL operations. For more information, see [Sample.java](#sample-java). -[`hibernate.cfg.xml`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/v19.1/app/insecure/hibernate-basic-sample/hibernate.cfg.xml) | Specifies how to connect to the database and that the database schema will be deleted and recreated each time the app is run. For more information, see [hibernate.cfg.xml](#hibernate-cfg-xml). -[`build.gradle`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/v19.1/app/insecure/hibernate-basic-sample/build.gradle) | Used to build and run your app. For more information, see [build.gradle](#build-gradle). +1. Download and extract [hibernate-basic-sample.tgz](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/insecure/hibernate-basic-sample/hibernate-basic-sample.tgz). The settings in [`hibernate.cfg.xml`](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/insecure/hibernate-basic-sample/hibernate.cfg.xml) specify how to connect to the database. +2. Compile and run the code using [`build.gradle`](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/insecure/hibernate-basic-sample/build.gradle), which will also download the dependencies. -In the `hibernate-basic-sample` directory, build and run the application: + {% include copy-clipboard.html %} + ~~~ shell + $ gradle run + ~~~ + +The contents of [`Sample.java`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/{{page.version.version}}/app/insecure/hibernate-basic-sample/Sample.java): {% include copy-clipboard.html %} -~~~ shell -$ gradle run +~~~ java +{% include {{page.version.version}}/app/insecure/hibernate-basic-sample/Sample.java %} ~~~ Toward the end of the output, you should see: ~~~ -1 1000 -2 250 +APP: BEGIN; +APP: addAccounts() --> 1 +APP: COMMIT; +APP: BEGIN; +APP: getAccountBalance(1) --> 1000 +APP: COMMIT; +APP: BEGIN; +APP: getAccountBalance(2) --> 250 +APP: COMMIT; +APP: getAccountBalance(1) --> 1000 +APP: getAccountBalance(2) --> 250 +APP: BEGIN; +APP: transferFunds(1, 2, 100) --> 100 +APP: COMMIT; +APP: transferFunds(1, 2, 100) --> 100 +APP: BEGIN; +APP: getAccountBalance(1) --> 900 +APP: COMMIT; +APP: BEGIN; +APP: getAccountBalance(2) --> 350 +APP: COMMIT; +APP: getAccountBalance(1) --> 900 +APP: getAccountBalance(2) --> 350 ~~~ -To verify that the table and rows were created successfully, start the [built-in SQL client](use-the-built-in-sql-client.html): +To verify that the account balances were updated successfully, start the [built-in SQL client](use-the-built-in-sql-client.html): {% include copy-clipboard.html %} ~~~ shell @@ -194,53 +228,67 @@ To check the account balances, issue the following statement: {% include copy-clipboard.html %} ~~~ sql -> SELECT id, balance FROM accounts; +SELECT id, balance FROM accounts; ~~~ ~~~ + id | balance +----+---------+ -| id | balance | -+----+---------+ -| 1 | 1000 | -| 2 | 250 | -+----+---------+ -(2 rows) + 1 | 900 + 2 | 350 + 3 | 314159 +(3 rows) ~~~ -### Sample.java + -The Java code shown below uses the [Hibernate ORM](http://hibernate.org/orm/) to map Java object state to SQL operations. Specifically, this code: +## Recommended Practices -- Creates an `accounts` table in the database based on the `Account` class. +### Use `IMPORT` to read in large data sets -- Inserts rows into the table using `session.save(new Account())`. +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`](import.html) statement instead. It is much faster and more efficient than making a series of [`INSERT`s](insert.html) and [`UPDATE`s](update.html). It bypasses the [SQL layer](architecture/sql-layer.html) altogether and writes directly to the [storage layer](architecture/storage-layer.html) of the database. -- Defines the SQL query for selecting from the table so that balances can be printed using the `CriteriaQuery query` object. +For more information about importing data from Postgres, see [Migrate from Postgres](migrate-from-postgres.html). -{% include copy-clipboard.html %} -~~~ java -{% include {{page.version.version}}/app/insecure/hibernate-basic-sample/Sample.java %} -~~~ +For more information about importing data from MySQL, see [Migrate from MySQL](migrate-from-mysql.html). -### hibernate.cfg.xml +### Use `rewriteBatchedInserts` for increased speed -The Hibernate config (in `hibernate.cfg.xml`, shown below) specifies how to connect to the database. Note the [connection URL](connection-parameters.html#connect-using-a-url) that turns on SSL and specifies the location of the security certificates. +We strongly recommend setting `rewriteBatchedInserts=true`; we have seen 2-3x performance improvements with it enabled. From [the JDBC connection parameters documentation](https://jdbc.postgresql.org/documentation/head/connect.html#connection-parameters): -{% include copy-clipboard.html %} -~~~ xml -{% include {{page.version.version}}/app/insecure/hibernate-basic-sample/hibernate.cfg.xml %} -~~~ +> 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 -### build.gradle +### Use a batch size of 128 -The Gradle build file specifies the dependencies (in this case the Postgres JDBC driver and Hibernate): +PGJDBC's batching support only works with [powers of two](https://github.com/pgjdbc/pgjdbc/blob/7b52b0c9e5b9aa9a9c655bb68f23bf4ec57fd51c/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java#L1597), 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. -{% include copy-clipboard.html %} -~~~ groovy -{% include {{page.version.version}}/app/insecure/hibernate-basic-sample/build.gradle %} -~~~ +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: + +1. Turn off auto-commit so you can manage the transaction lifecycle and thus the size of the batch inserts. +2. Given an overall update size of 500 rows (for example), split it into batches of size 128 and execute each batch in turn. +3. Finally, commit the batches of statements you've just executed. + +~~~ java +int BATCH_SIZE = 128; +connection.setAutoCommit(false); + +try (PreparedStatement pstmt = connection.prepareStatement("INSERT INTO accounts (id, balance) VALUES (?, ?)")) { + for (int i=0; i<=(500/BATCH_SIZE);i++) { + for (int j=0; j %s row(s) updated in this batch\n", count.length); // Verifying 128 rows in the batch + } + connection.commit(); +} +~~~ ## What's next? diff --git a/v19.2/build-a-java-app-with-cockroachdb-hibernate.md b/v19.2/build-a-java-app-with-cockroachdb-hibernate.md index 9483d1e66aa..67344aeaf93 100644 --- a/v19.2/build-a-java-app-with-cockroachdb-hibernate.md +++ b/v19.2/build-a-java-app-with-cockroachdb-hibernate.md @@ -15,7 +15,7 @@ This tutorial shows you how build a simple Java application with CockroachDB usi We have tested the [Java JDBC driver](https://jdbc.postgresql.org/) and the [Hibernate ORM](http://hibernate.org/) enough to claim **beta-level** support, so those are featured here. If you encounter problems, please [open an issue](https://github.com/cockroachdb/cockroach/issues/new) with details to help us make progress toward full support. {{site.data.alerts.callout_success}} -For a more realistic use of Hibernate with CockroachDB, see our [`examples-orms`](https://github.com/cockroachdb/examples-orms) repository. +For another use of Hibernate with CockroachDB, see our [`examples-orms`](https://github.com/cockroachdb/examples-orms) repository. {{site.data.alerts.end}} ## Before you begin @@ -61,7 +61,7 @@ For other ways to install Gradle, see [its official documentation](https://gradl ## 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. +Create a certificate and key for the `maxroach` user by running the following command. The code samples will run as this user. You can pass the [`--also-generate-pkcs8-key` flag](create-security-certificates.html#flag-pkcs8) to generate a key in [PKCS#8 format](https://tools.ietf.org/html/rfc5208), which is the standard key encoding format in Java. In this case, the generated PKCS8 key will be named `client.maxroach.key.pk8`. @@ -72,29 +72,63 @@ $ cockroach cert create-client maxroach --certs-dir=certs --ca-key=my-safe-direc ## Step 4. Run the Java code -Download and extract [hibernate-basic-sample.tgz](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/hibernate-basic-sample/hibernate-basic-sample.tgz), which contains a Java project that includes the following files: +The code below uses Hibernate to map Java methods to SQL operations. It perform the following steps which roughly correspond to method calls in the `Sample` class. -File | Description ------|------------ -[`Sample.java`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/{{ page.version.version }}/app/hibernate-basic-sample/Sample.java) | Uses [Hibernate](http://hibernate.org/orm/) to map Java object state to SQL operations. For more information, see [Sample.java](#sample-java). -[`hibernate.cfg.xml`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/{{ page.version.version }}/app/hibernate-basic-sample/hibernate.cfg.xml) | Specifies how to connect to the database and that the database schema will be deleted and recreated each time the app is run. For more information, see [hibernate.cfg.xml](#hibernate-cfg-xml). -[`build.gradle`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/{{ page.version.version }}/app/hibernate-basic-sample/build.gradle) | Used to build and run your app. For more information, see [build.gradle](#build-gradle). +1. Create an `accounts` table in the `bank` database as specified by the Hibernate `Account` class. +2. Inserts rows into the table using `session.save(new Account(int id, int balance))` (see `Sample.addAccounts()`). +3. Transfer money from one account to another, printing out account balances before and after the transfer (see `transferFunds(long fromId, long toId, long amount)`). +4. Print out account balances before and after the transfer (see `Sample.getAccountBalance(long id)`). -In the `hibernate-basic-sample` directory, build and run the application: +In addition, the code shows a pattern for automatically handling [transaction retries](transactions.html#client-side-intervention-example) by wrapping transactions in a higher-order function `Sample.runTransaction()`. It also includes a method for testing the retry handling logic (`Sample.forceRetryLogic()`), which will be run if you set the `FORCE_RETRY` variable to `true`. + +It does all of the above using the practices we recommend for using Hibernate (and the underlying JDBC connection) with CockroachDB, which are listed in the [Recommended Practices](#recommended-practices) section below. + +To run it: + +1. Download and extract [hibernate-basic-sample.tgz](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/hibernate-basic-sample/hibernate-basic-sample.tgz). The settings in [`hibernate.cfg.xml`](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/hibernate-basic-sample/hibernate.cfg.xml) specify how to connect to the database. +2. Compile and run the code using [`build.gradle`](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/hibernate-basic-sample/build.gradle), which will also download the dependencies. + + {% include copy-clipboard.html %} + ~~~ shell + $ gradle run + ~~~ + +The contents of [`Sample.java`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/{{page.version.version}}/app/hibernate-basic-sample/Sample.java): {% include copy-clipboard.html %} -~~~ shell -$ gradle run +~~~ java +{% include {{page.version.version}}/app/hibernate-basic-sample/Sample.java %} ~~~ Toward the end of the output, you should see: ~~~ -1 1000 -2 250 +APP: BEGIN; +APP: addAccounts() --> 1 +APP: COMMIT; +APP: BEGIN; +APP: getAccountBalance(1) --> 1000 +APP: COMMIT; +APP: BEGIN; +APP: getAccountBalance(2) --> 250 +APP: COMMIT; +APP: getAccountBalance(1) --> 1000 +APP: getAccountBalance(2) --> 250 +APP: BEGIN; +APP: transferFunds(1, 2, 100) --> 100 +APP: COMMIT; +APP: transferFunds(1, 2, 100) --> 100 +APP: BEGIN; +APP: getAccountBalance(1) --> 900 +APP: COMMIT; +APP: BEGIN; +APP: getAccountBalance(2) --> 350 +APP: COMMIT; +APP: getAccountBalance(1) --> 900 +APP: getAccountBalance(2) --> 350 ~~~ -To verify that the table and rows were created successfully, start the [built-in SQL client](use-the-built-in-sql-client.html): +To verify that the account balances were updated successfully, start the [built-in SQL client](use-the-built-in-sql-client.html): {% include copy-clipboard.html %} ~~~ shell @@ -105,50 +139,16 @@ To check the account balances, issue the following statement: {% include copy-clipboard.html %} ~~~ sql -> SELECT id, balance FROM accounts; +SELECT id, balance FROM accounts; ~~~ ~~~ + id | balance +----+---------+ -| id | balance | -+----+---------+ -| 1 | 1000 | -| 2 | 250 | -+----+---------+ -(2 rows) -~~~ - -### Sample.java - -The Java code shown below uses the [Hibernate ORM](http://hibernate.org/orm/) to map Java object state to SQL operations. Specifically, this code: - -- Creates an `accounts` table in the database based on the `Account` class. - -- Inserts rows into the table using `session.save(new Account())`. - -- Defines the SQL query for selecting from the table so that balances can be printed using the `CriteriaQuery query` object. - -{% include copy-clipboard.html %} -~~~ java -{% include {{page.version.version}}/app/hibernate-basic-sample/Sample.java %} -~~~ - -### hibernate.cfg.xml - -The Hibernate config (in `hibernate.cfg.xml`, shown below) specifies how to connect to the database. Note the [connection URL](connection-parameters.html#connect-using-a-url) that turns on SSL and specifies the location of the security certificates. - -{% include copy-clipboard.html %} -~~~ xml -{% include {{page.version.version}}/app/hibernate-basic-sample/hibernate.cfg.xml %} -~~~ - -### build.gradle - -The Gradle build file specifies the dependencies (in this case the Postgres JDBC driver and Hibernate): - -{% include copy-clipboard.html %} -~~~ groovy -{% include {{page.version.version}}/app/hibernate-basic-sample/build.gradle %} + 1 | 900 + 2 | 350 + 3 | 314159 +(3 rows) ~~~ @@ -161,29 +161,63 @@ The Gradle build file specifies the dependencies (in this case the Postgres JDBC ## Step 3. Run the Java code -Download and extract [hibernate-basic-sample.tgz](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/insecure/hibernate-basic-sample/hibernate-basic-sample.tgz), which contains a Java project that includes the following files: +The code below uses Hibernate to map Java methods to SQL operations. It perform the following steps which roughly correspond to method calls in the `Sample` class. + +1. Create an `accounts` table in the `bank` database as specified by the Hibernate `Account` class. +2. Inserts rows into the table using `session.save(new Account(int id, int balance))` (see `Sample.addAccounts()`). +3. Transfer money from one account to another, printing out account balances before and after the transfer (see `transferFunds(long fromId, long toId, long amount)`). +4. Print out account balances before and after the transfer (see `Sample.getAccountBalance(long id)`). + +In addition, the code shows a pattern for automatically handling [transaction retries](transactions.html#client-side-intervention-example) by wrapping transactions in a higher-order function `Sample.runTransaction()`. It also includes a method for testing the retry handling logic (`Sample.forceRetryLogic()`), which will be run if you set the `FORCE_RETRY` variable to `true`. + +It does all of the above using the practices we recommend for using Hibernate (and the underlying JDBC connection) with CockroachDB, which are listed in the [Recommended Practices](#recommended-practices) section below. + +To run it: + +1. Download and extract [hibernate-basic-sample.tgz](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/insecure/hibernate-basic-sample/hibernate-basic-sample.tgz). The settings in [`hibernate.cfg.xml`](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/insecure/hibernate-basic-sample/hibernate.cfg.xml) specify how to connect to the database. +2. Compile and run the code using [`build.gradle`](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/insecure/hibernate-basic-sample/build.gradle), which will also download the dependencies. -File | Description ------|------------ -[`Sample.java`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/{{ page.version.version }}/app/insecure/hibernate-basic-sample/Sample.java) | Uses [Hibernate](http://hibernate.org/orm/) to map Java object state to SQL operations. For more information, see [Sample.java](#sample-java). -[`hibernate.cfg.xml`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/{{ page.version.version }}/app/insecure/hibernate-basic-sample/hibernate.cfg.xml) | Specifies how to connect to the database and that the database schema will be deleted and recreated each time the app is run. For more information, see [hibernate.cfg.xml](#hibernate-cfg-xml). -[`build.gradle`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/{{ page.version.version }}/app/insecure/hibernate-basic-sample/build.gradle) | Used to build and run your app. For more information, see [build.gradle](#build-gradle). + {% include copy-clipboard.html %} + ~~~ shell + $ gradle run + ~~~ -In the `hibernate-basic-sample` directory, build and run the application: +The contents of [`Sample.java`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/{{page.version.version}}/app/insecure/hibernate-basic-sample/Sample.java): {% include copy-clipboard.html %} -~~~ shell -$ gradle run +~~~ java +{% include {{page.version.version}}/app/insecure/hibernate-basic-sample/Sample.java %} ~~~ Toward the end of the output, you should see: ~~~ -1 1000 -2 250 +APP: BEGIN; +APP: addAccounts() --> 1 +APP: COMMIT; +APP: BEGIN; +APP: getAccountBalance(1) --> 1000 +APP: COMMIT; +APP: BEGIN; +APP: getAccountBalance(2) --> 250 +APP: COMMIT; +APP: getAccountBalance(1) --> 1000 +APP: getAccountBalance(2) --> 250 +APP: BEGIN; +APP: transferFunds(1, 2, 100) --> 100 +APP: COMMIT; +APP: transferFunds(1, 2, 100) --> 100 +APP: BEGIN; +APP: getAccountBalance(1) --> 900 +APP: COMMIT; +APP: BEGIN; +APP: getAccountBalance(2) --> 350 +APP: COMMIT; +APP: getAccountBalance(1) --> 900 +APP: getAccountBalance(2) --> 350 ~~~ -To verify that the table and rows were created successfully, start the [built-in SQL client](use-the-built-in-sql-client.html): +To verify that the account balances were updated successfully, start the [built-in SQL client](use-the-built-in-sql-client.html): {% include copy-clipboard.html %} ~~~ shell @@ -194,53 +228,75 @@ To check the account balances, issue the following statement: {% include copy-clipboard.html %} ~~~ sql -> SELECT id, balance FROM accounts; +SELECT id, balance FROM accounts; ~~~ ~~~ + id | balance +----+---------+ -| id | balance | -+----+---------+ -| 1 | 1000 | -| 2 | 250 | -+----+---------+ -(2 rows) + 1 | 900 + 2 | 350 + 3 | 314159 +(3 rows) ~~~ -### Sample.java + -The Java code shown below uses the [Hibernate ORM](http://hibernate.org/orm/) to map Java object state to SQL operations. Specifically, this code: +## Recommended Practices -- Creates an `accounts` table in the database based on the `Account` class. +### Use `IMPORT` to read in large data sets -- Inserts rows into the table using `session.save(new Account())`. +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`](import.html) statement instead. It is much faster and more efficient than making a series of [`INSERT`s](insert.html) and [`UPDATE`s](update.html). It bypasses the [SQL layer](architecture/sql-layer.html) altogether and writes directly to the [storage layer](architecture/storage-layer.html) of the database. -- Defines the SQL query for selecting from the table so that balances can be printed using the `CriteriaQuery query` object. +For more information about importing data from Postgres, see [Migrate from Postgres](migrate-from-postgres.html). -{% include copy-clipboard.html %} -~~~ java -{% include {{page.version.version}}/app/insecure/hibernate-basic-sample/Sample.java %} -~~~ +For more information about importing data from MySQL, see [Migrate from MySQL](migrate-from-mysql.html). -### hibernate.cfg.xml +### Use `rewriteBatchedInserts` for increased speed -The Hibernate config (in `hibernate.cfg.xml`, shown below) specifies how to connect to the database. Note the [connection URL](connection-parameters.html#connect-using-a-url) that turns on SSL and specifies the location of the security certificates. +We strongly recommend setting `rewriteBatchedInserts=true`; we have seen 2-3x performance improvements with it enabled. From [the JDBC connection parameters documentation](https://jdbc.postgresql.org/documentation/head/connect.html#connection-parameters): -{% include copy-clipboard.html %} -~~~ xml -{% include {{page.version.version}}/app/insecure/hibernate-basic-sample/hibernate.cfg.xml %} -~~~ +> 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 -### build.gradle +### Use a batch size of 128 -The Gradle build file specifies the dependencies (in this case the Postgres JDBC driver and Hibernate): +PGJDBC's batching support only works with [powers of two](https://github.com/pgjdbc/pgjdbc/blob/7b52b0c9e5b9aa9a9c655bb68f23bf4ec57fd51c/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java#L1597), 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. -{% include copy-clipboard.html %} -~~~ groovy -{% include {{page.version.version}}/app/insecure/hibernate-basic-sample/build.gradle %} +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: + +1. Turn off auto-commit so you can manage the transaction lifecycle and thus the size of the batch inserts. +2. Given an overall update size of 500 rows (for example), split it into batches of size 128 and execute each batch in turn. +3. Finally, commit the batches of statements you've just executed. + +~~~ java +int BATCH_SIZE = 128; +connection.setAutoCommit(false); + +try (PreparedStatement pstmt = connection.prepareStatement("INSERT INTO accounts (id, balance) VALUES (?, ?)")) { + for (int i=0; i<=(500/BATCH_SIZE);i++) { + for (int j=0; j %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 + +New in v19.2: CockroachDB now supports the Postgres wire-protocol cursors for implicit transactions and explicit transactions executed to completion. This means the [PGJDBC driver](https://jdbc.postgresql.org) can use this protocol to stream queries with large result sets. This is much faster than [paginating through results in SQL using `LIMIT .. OFFSET`](selection-queries.html#paginate-through-limited-results). + +For instructions showing how to use cursors in your Java code, see [Getting results based on a cursor](https://jdbc.postgresql.org/documentation/head/query.html#query-with-cursor) from the PGJDBC documentation. + +Note that interleaved execution (partial execution of multiple statements within the same connection and transaction) is not supported when [`Statement.setFetchSize()`](https://docs.oracle.com/javase/8/docs/api/java/sql/Statement.html#setFetchSize-int-) is used. ## What's next? diff --git a/v20.1/build-a-java-app-with-cockroachdb-hibernate.md b/v20.1/build-a-java-app-with-cockroachdb-hibernate.md index 9483d1e66aa..3d1168acfdd 100644 --- a/v20.1/build-a-java-app-with-cockroachdb-hibernate.md +++ b/v20.1/build-a-java-app-with-cockroachdb-hibernate.md @@ -15,7 +15,7 @@ This tutorial shows you how build a simple Java application with CockroachDB usi We have tested the [Java JDBC driver](https://jdbc.postgresql.org/) and the [Hibernate ORM](http://hibernate.org/) enough to claim **beta-level** support, so those are featured here. If you encounter problems, please [open an issue](https://github.com/cockroachdb/cockroach/issues/new) with details to help us make progress toward full support. {{site.data.alerts.callout_success}} -For a more realistic use of Hibernate with CockroachDB, see our [`examples-orms`](https://github.com/cockroachdb/examples-orms) repository. +For another use of Hibernate with CockroachDB, see our [`examples-orms`](https://github.com/cockroachdb/examples-orms) repository. {{site.data.alerts.end}} ## Before you begin @@ -61,7 +61,7 @@ For other ways to install Gradle, see [its official documentation](https://gradl ## 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. +Create a certificate and key for the `maxroach` user by running the following command. The code samples will run as this user. You can pass the [`--also-generate-pkcs8-key` flag](create-security-certificates.html#flag-pkcs8) to generate a key in [PKCS#8 format](https://tools.ietf.org/html/rfc5208), which is the standard key encoding format in Java. In this case, the generated PKCS8 key will be named `client.maxroach.key.pk8`. @@ -72,83 +72,83 @@ $ cockroach cert create-client maxroach --certs-dir=certs --ca-key=my-safe-direc ## Step 4. Run the Java code -Download and extract [hibernate-basic-sample.tgz](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/hibernate-basic-sample/hibernate-basic-sample.tgz), which contains a Java project that includes the following files: +The code below uses Hibernate to map Java methods to SQL operations. It perform the following steps which roughly correspond to method calls in the `Sample` class. -File | Description ------|------------ -[`Sample.java`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/{{ page.version.version }}/app/hibernate-basic-sample/Sample.java) | Uses [Hibernate](http://hibernate.org/orm/) to map Java object state to SQL operations. For more information, see [Sample.java](#sample-java). -[`hibernate.cfg.xml`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/{{ page.version.version }}/app/hibernate-basic-sample/hibernate.cfg.xml) | Specifies how to connect to the database and that the database schema will be deleted and recreated each time the app is run. For more information, see [hibernate.cfg.xml](#hibernate-cfg-xml). -[`build.gradle`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/{{ page.version.version }}/app/hibernate-basic-sample/build.gradle) | Used to build and run your app. For more information, see [build.gradle](#build-gradle). +1. Create an `accounts` table in the `bank` database as specified by the Hibernate `Account` class. +2. Inserts rows into the table using `session.save(new Account(int id, int balance))` (see `Sample.addAccounts()`). +3. Transfer money from one account to another, printing out account balances before and after the transfer (see `transferFunds(long fromId, long toId, long amount)`). +4. Print out account balances before and after the transfer (see `Sample.getAccountBalance(long id)`). -In the `hibernate-basic-sample` directory, build and run the application: +In addition, the code shows a pattern for automatically handling [transaction retries](transactions.html#client-side-intervention-example) by wrapping transactions in a higher-order function `Sample.runTransaction()`. It also includes a method for testing the retry handling logic (`Sample.forceRetryLogic()`), which will be run if you set the `FORCE_RETRY` variable to `true`. + +It does all of the above using the practices we recommend for using Hibernate (and the underlying JDBC connection) with CockroachDB, which are listed in the [Recommended Practices](#recommended-practices) section below. + +To run it: + +1. Download and extract [hibernate-basic-sample.tgz](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/hibernate-basic-sample/hibernate-basic-sample.tgz). The settings in [`hibernate.cfg.xml`](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/hibernate-basic-sample/hibernate.cfg.xml) specify how to connect to the database. +2. Compile and run the code using [`build.gradle`](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/hibernate-basic-sample/build.gradle), which will also download the dependencies. + + {% include copy-clipboard.html %} + ~~~ shell + $ gradle run + ~~~ + +The contents of [`Sample.java`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/{{page.version.version}}/app/hibernate-basic-sample/Sample.java): {% include copy-clipboard.html %} -~~~ shell -$ gradle run +~~~ java +{% include {{page.version.version}}/app/hibernate-basic-sample/Sample.java %} ~~~ Toward the end of the output, you should see: ~~~ -1 1000 -2 250 +APP: BEGIN; +APP: addAccounts() --> 1 +APP: COMMIT; +APP: BEGIN; +APP: getAccountBalance(1) --> 1000 +APP: COMMIT; +APP: BEGIN; +APP: getAccountBalance(2) --> 250 +APP: COMMIT; +APP: getAccountBalance(1) --> 1000 +APP: getAccountBalance(2) --> 250 +APP: BEGIN; +APP: transferFunds(1, 2, 100) --> 100 +APP: COMMIT; +APP: transferFunds(1, 2, 100) --> 100 +APP: BEGIN; +APP: getAccountBalance(1) --> 900 +APP: COMMIT; +APP: BEGIN; +APP: getAccountBalance(2) --> 350 +APP: COMMIT; +APP: getAccountBalance(1) --> 900 +APP: getAccountBalance(2) --> 350 ~~~ -To verify that the table and rows were created successfully, start the [built-in SQL client](use-the-built-in-sql-client.html): +To verify that the account balances were updated successfully, start the [built-in SQL client](use-the-built-in-sql-client.html): {% include copy-clipboard.html %} ~~~ shell -$ cockroach sql --certs-dir=certs --database=bank +$ cockroach sql --certs-dir=certs ~~~ To check the account balances, issue the following statement: {% include copy-clipboard.html %} ~~~ sql -> SELECT id, balance FROM accounts; +SELECT id, balance FROM accounts; ~~~ ~~~ + id | balance +----+---------+ -| id | balance | -+----+---------+ -| 1 | 1000 | -| 2 | 250 | -+----+---------+ -(2 rows) -~~~ - -### Sample.java - -The Java code shown below uses the [Hibernate ORM](http://hibernate.org/orm/) to map Java object state to SQL operations. Specifically, this code: - -- Creates an `accounts` table in the database based on the `Account` class. - -- Inserts rows into the table using `session.save(new Account())`. - -- Defines the SQL query for selecting from the table so that balances can be printed using the `CriteriaQuery query` object. - -{% include copy-clipboard.html %} -~~~ java -{% include {{page.version.version}}/app/hibernate-basic-sample/Sample.java %} -~~~ - -### hibernate.cfg.xml - -The Hibernate config (in `hibernate.cfg.xml`, shown below) specifies how to connect to the database. Note the [connection URL](connection-parameters.html#connect-using-a-url) that turns on SSL and specifies the location of the security certificates. - -{% include copy-clipboard.html %} -~~~ xml -{% include {{page.version.version}}/app/hibernate-basic-sample/hibernate.cfg.xml %} -~~~ - -### build.gradle - -The Gradle build file specifies the dependencies (in this case the Postgres JDBC driver and Hibernate): - -{% include copy-clipboard.html %} -~~~ groovy -{% include {{page.version.version}}/app/hibernate-basic-sample/build.gradle %} + 1 | 900 + 2 | 350 + 3 | 314159 +(3 rows) ~~~ @@ -161,29 +161,63 @@ The Gradle build file specifies the dependencies (in this case the Postgres JDBC ## Step 3. Run the Java code -Download and extract [hibernate-basic-sample.tgz](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/insecure/hibernate-basic-sample/hibernate-basic-sample.tgz), which contains a Java project that includes the following files: +The code below uses Hibernate to map Java methods to SQL operations. It perform the following steps which roughly correspond to method calls in the `Sample` class. + +1. Create an `accounts` table in the `bank` database as specified by the Hibernate `Account` class. +2. Inserts rows into the table using `session.save(new Account(int id, int balance))` (see `Sample.addAccounts()`). +3. Transfer money from one account to another, printing out account balances before and after the transfer (see `transferFunds(long fromId, long toId, long amount)`). +4. Print out account balances before and after the transfer (see `Sample.getAccountBalance(long id)`). + +In addition, the code shows a pattern for automatically handling [transaction retries](transactions.html#client-side-intervention-example) by wrapping transactions in a higher-order function `Sample.runTransaction()`. It also includes a method for testing the retry handling logic (`Sample.forceRetryLogic()`), which will be run if you set the `FORCE_RETRY` variable to `true`. + +It does all of the above using the practices we recommend for using Hibernate (and the underlying JDBC connection) with CockroachDB, which are listed in the [Recommended Practices](#recommended-practices) section below. + +To run it: + +1. Download and extract [hibernate-basic-sample.tgz](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/insecure/hibernate-basic-sample/hibernate-basic-sample.tgz). The settings in [`hibernate.cfg.xml`](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/insecure/hibernate-basic-sample/hibernate.cfg.xml) specify how to connect to the database. +2. Compile and run the code using [`build.gradle`](https://github.com/cockroachdb/docs/raw/master/_includes/{{ page.version.version }}/app/insecure/hibernate-basic-sample/build.gradle), which will also download the dependencies. -File | Description ------|------------ -[`Sample.java`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/{{ page.version.version }}/app/insecure/hibernate-basic-sample/Sample.java) | Uses [Hibernate](http://hibernate.org/orm/) to map Java object state to SQL operations. For more information, see [Sample.java](#sample-java). -[`hibernate.cfg.xml`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/{{ page.version.version }}/app/insecure/hibernate-basic-sample/hibernate.cfg.xml) | Specifies how to connect to the database and that the database schema will be deleted and recreated each time the app is run. For more information, see [hibernate.cfg.xml](#hibernate-cfg-xml). -[`build.gradle`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/{{ page.version.version }}/app/insecure/hibernate-basic-sample/build.gradle) | Used to build and run your app. For more information, see [build.gradle](#build-gradle). + {% include copy-clipboard.html %} + ~~~ shell + $ gradle run + ~~~ -In the `hibernate-basic-sample` directory, build and run the application: +The contents of [`Sample.java`](https://raw.githubusercontent.com/cockroachdb/docs/master/_includes/{{page.version.version}}/app/insecure/hibernate-basic-sample/Sample.java): {% include copy-clipboard.html %} -~~~ shell -$ gradle run +~~~ java +{% include {{page.version.version}}/app/insecure/hibernate-basic-sample/Sample.java %} ~~~ Toward the end of the output, you should see: ~~~ -1 1000 -2 250 +APP: BEGIN; +APP: addAccounts() --> 1 +APP: COMMIT; +APP: BEGIN; +APP: getAccountBalance(1) --> 1000 +APP: COMMIT; +APP: BEGIN; +APP: getAccountBalance(2) --> 250 +APP: COMMIT; +APP: getAccountBalance(1) --> 1000 +APP: getAccountBalance(2) --> 250 +APP: BEGIN; +APP: transferFunds(1, 2, 100) --> 100 +APP: COMMIT; +APP: transferFunds(1, 2, 100) --> 100 +APP: BEGIN; +APP: getAccountBalance(1) --> 900 +APP: COMMIT; +APP: BEGIN; +APP: getAccountBalance(2) --> 350 +APP: COMMIT; +APP: getAccountBalance(1) --> 900 +APP: getAccountBalance(2) --> 350 ~~~ -To verify that the table and rows were created successfully, start the [built-in SQL client](use-the-built-in-sql-client.html): +To verify that the account balances were updated successfully, start the [built-in SQL client](use-the-built-in-sql-client.html): {% include copy-clipboard.html %} ~~~ shell @@ -194,53 +228,75 @@ To check the account balances, issue the following statement: {% include copy-clipboard.html %} ~~~ sql -> SELECT id, balance FROM accounts; +SELECT id, balance FROM accounts; ~~~ ~~~ + id | balance +----+---------+ -| id | balance | -+----+---------+ -| 1 | 1000 | -| 2 | 250 | -+----+---------+ -(2 rows) + 1 | 900 + 2 | 350 + 3 | 314159 +(3 rows) ~~~ -### Sample.java + -The Java code shown below uses the [Hibernate ORM](http://hibernate.org/orm/) to map Java object state to SQL operations. Specifically, this code: +## Recommended Practices -- Creates an `accounts` table in the database based on the `Account` class. +### Use `IMPORT` to read in large data sets -- Inserts rows into the table using `session.save(new Account())`. +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`](import.html) statement instead. It is much faster and more efficient than making a series of [`INSERT`s](insert.html) and [`UPDATE`s](update.html). It bypasses the [SQL layer](architecture/sql-layer.html) altogether and writes directly to the [storage layer](architecture/storage-layer.html) of the database. -- Defines the SQL query for selecting from the table so that balances can be printed using the `CriteriaQuery query` object. +For more information about importing data from Postgres, see [Migrate from Postgres](migrate-from-postgres.html). -{% include copy-clipboard.html %} -~~~ java -{% include {{page.version.version}}/app/insecure/hibernate-basic-sample/Sample.java %} -~~~ +For more information about importing data from MySQL, see [Migrate from MySQL](migrate-from-mysql.html). -### hibernate.cfg.xml +### Use `rewriteBatchedInserts` for increased speed -The Hibernate config (in `hibernate.cfg.xml`, shown below) specifies how to connect to the database. Note the [connection URL](connection-parameters.html#connect-using-a-url) that turns on SSL and specifies the location of the security certificates. +We strongly recommend setting `rewriteBatchedInserts=true`; we have seen 2-3x performance improvements with it enabled. From [the JDBC connection parameters documentation](https://jdbc.postgresql.org/documentation/head/connect.html#connection-parameters): -{% include copy-clipboard.html %} -~~~ xml -{% include {{page.version.version}}/app/insecure/hibernate-basic-sample/hibernate.cfg.xml %} -~~~ +> 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 -### build.gradle +### Use a batch size of 128 -The Gradle build file specifies the dependencies (in this case the Postgres JDBC driver and Hibernate): +PGJDBC's batching support only works with [powers of two](https://github.com/pgjdbc/pgjdbc/blob/7b52b0c9e5b9aa9a9c655bb68f23bf4ec57fd51c/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java#L1597), 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. -{% include copy-clipboard.html %} -~~~ groovy -{% include {{page.version.version}}/app/insecure/hibernate-basic-sample/build.gradle %} +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: + +1. Turn off auto-commit so you can manage the transaction lifecycle and thus the size of the batch inserts. +2. Given an overall update size of 500 rows (for example), split it into batches of size 128 and execute each batch in turn. +3. Finally, commit the batches of statements you've just executed. + +~~~ java +int BATCH_SIZE = 128; +connection.setAutoCommit(false); + +try (PreparedStatement pstmt = connection.prepareStatement("INSERT INTO accounts (id, balance) VALUES (?, ?)")) { + for (int i=0; i<=(500/BATCH_SIZE);i++) { + for (int j=0; j %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 Postgres wire-protocol cursors for implicit transactions and explicit transactions executed to completion. This means the [PGJDBC driver](https://jdbc.postgresql.org) can use this protocol to stream queries with large result sets. This is much faster than [paginating through results in SQL using `LIMIT .. OFFSET`](selection-queries.html#paginate-through-limited-results). + +For instructions showing how to use cursors in your Java code, see [Getting results based on a cursor](https://jdbc.postgresql.org/documentation/head/query.html#query-with-cursor) from the PGJDBC documentation. + +Note that interleaved execution (partial execution of multiple statements within the same connection and transaction) is not supported when [`Statement.setFetchSize()`](https://docs.oracle.com/javase/8/docs/api/java/sql/Statement.html#setFetchSize-int-) is used. ## What's next?