Skip to content

Commit d133ffd

Browse files
committed
Initial commit
0 parents  commit d133ffd

File tree

5 files changed

+290
-0
lines changed

5 files changed

+290
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*~
2+
.gradle
3+
build/
4+
.DS_Store

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# hello-world-java-hibernate
2+
3+
This repo has a "Hello World" Java application that uses the [Hibernate ORM](http://hibernate.org/) to talk to [CockroachDB](https://www.cockroachlabs.com/docs/stable/).
4+
5+
To run the code:
6+
7+
1. Start a [local, insecure CockroachDB cluster](https://www.cockroachlabs.com/docs/stable/start-a-local-cluster.html).
8+
9+
2. Create a `bank` database and `maxroach` user as described in [Build a Java app with CockroachDB and Hibernate](https://www.cockroachlabs.com/docs/stable/build-a-java-app-with-cockroachdb-hibernate.html#insecure).
10+
11+
3. From the [SQL client](https://www.cockroachlabs.com/docs/stable/cockroach-sql.html): `GRANT ALL ON DATABASE bank TO maxroach`
12+
13+
4. In your terminal, from this directory: `gradle run`

build.gradle

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
group 'com.cockroachlabs'
2+
version '1.0'
3+
4+
apply plugin: 'java'
5+
apply plugin: 'application'
6+
7+
mainClassName = 'com.cockroachlabs.Sample'
8+
9+
repositories {
10+
mavenCentral()
11+
}
12+
13+
dependencies {
14+
compile 'org.hibernate:hibernate-core:5.2.4.Final'
15+
compile 'org.postgresql:postgresql:42.2.2.jre7'
16+
}
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
package com.cockroachlabs;
2+
3+
import org.hibernate.Session;
4+
import org.hibernate.SessionFactory;
5+
import org.hibernate.Transaction;
6+
import org.hibernate.JDBCException;
7+
import org.hibernate.cfg.Configuration;
8+
9+
import java.util.*;
10+
import java.util.function.Function;
11+
12+
import javax.persistence.Column;
13+
import javax.persistence.Entity;
14+
import javax.persistence.Id;
15+
import javax.persistence.Table;
16+
17+
public class Sample {
18+
19+
private static final Random RAND = new Random();
20+
private static final boolean FORCE_RETRY = false;
21+
private static final String RETRY_SQL_STATE = "40001";
22+
private static final int MAX_ATTEMPT_COUNT = 6;
23+
24+
// Account is our model, which corresponds to the "accounts" database table.
25+
@Entity
26+
@Table(name="accounts")
27+
public static class Account {
28+
@Id
29+
@Column(name="id")
30+
public long id;
31+
32+
public long getId() {
33+
return id;
34+
}
35+
36+
@Column(name="balance")
37+
public long balance;
38+
public long getBalance() {
39+
return balance;
40+
}
41+
public void setBalance(long newBalance) {
42+
this.balance = newBalance;
43+
}
44+
45+
// Convenience constructor.
46+
public Account(int id, int balance) {
47+
this.id = id;
48+
this.balance = balance;
49+
}
50+
51+
// Hibernate needs a default (no-arg) constructor to create model objects.
52+
public Account() {}
53+
}
54+
55+
private static Function<Session, Long> addAccounts() throws JDBCException{
56+
Function<Session, Long> f = s -> {
57+
long rv = 0;
58+
try {
59+
s.save(new Account(1, 1000));
60+
s.save(new Account(2, 250));
61+
s.save(new Account(3, 314159));
62+
rv = 1;
63+
System.out.printf("APP: addAccounts() --> %d\n", rv);
64+
} catch (JDBCException e) {
65+
throw e;
66+
}
67+
return rv;
68+
};
69+
return f;
70+
}
71+
72+
private static Function<Session, Long> transferFunds(long fromId, long toId, long amount) throws JDBCException{
73+
Function<Session, Long> f = s -> {
74+
long rv = 0;
75+
try {
76+
Account fromAccount = (Account) s.get(Account.class, fromId);
77+
Account toAccount = (Account) s.get(Account.class, toId);
78+
if (!(amount > fromAccount.getBalance())) {
79+
fromAccount.balance -= amount;
80+
toAccount.balance += amount;
81+
s.save(fromAccount);
82+
s.save(toAccount);
83+
rv = amount;
84+
System.out.printf("APP: transferFunds(%d, %d, %d) --> %d\n", fromId, toId, amount, rv);
85+
}
86+
} catch (JDBCException e) {
87+
throw e;
88+
}
89+
return rv;
90+
};
91+
return f;
92+
}
93+
94+
// Test our retry handling logic if FORCE_RETRY is true. This
95+
// method is only used to test the retry logic. It is not
96+
// intended for production code.
97+
private static Function<Session, Long> forceRetryLogic() throws JDBCException {
98+
Function<Session, Long> f = s -> {
99+
long rv = -1;
100+
try {
101+
System.out.printf("APP: testRetryLogic: BEFORE EXCEPTION\n");
102+
s.createNativeQuery("SELECT crdb_internal.force_retry('1s')").executeUpdate();
103+
} catch (JDBCException e) {
104+
System.out.printf("APP: testRetryLogic: AFTER EXCEPTION\n");
105+
throw e;
106+
}
107+
return rv;
108+
};
109+
return f;
110+
}
111+
112+
private static Function<Session, Long> getAccountBalance(long id) throws JDBCException{
113+
Function<Session, Long> f = s -> {
114+
long balance;
115+
try {
116+
Account account = s.get(Account.class, id);
117+
balance = account.getBalance();
118+
System.out.printf("APP: getAccountBalance(%d) --> %d\n", id, balance);
119+
} catch (JDBCException e) {
120+
throw e;
121+
}
122+
return balance;
123+
};
124+
return f;
125+
}
126+
127+
// Run SQL code in a way that automatically handles the
128+
// transaction retry logic so we don't have to duplicate it in
129+
// various places.
130+
private static long runTransaction(Session session, Function<Session, Long> fn) {
131+
long rv = 0;
132+
int attemptCount = 0;
133+
134+
while (attemptCount < MAX_ATTEMPT_COUNT) {
135+
attemptCount++;
136+
137+
if (attemptCount > 1) {
138+
System.out.printf("APP: Entering retry loop again, iteration %d\n", attemptCount);
139+
}
140+
141+
Transaction txn = session.beginTransaction();
142+
System.out.printf("APP: BEGIN;\n");
143+
144+
if (attemptCount == MAX_ATTEMPT_COUNT) {
145+
String err = String.format("hit max of %s attempts, aborting", MAX_ATTEMPT_COUNT);
146+
throw new RuntimeException(err);
147+
}
148+
149+
// This block is only used to test the retry logic.
150+
// It is not necessary in production code. See also
151+
// the method 'testRetryLogic()'.
152+
if (FORCE_RETRY) {
153+
session.createNativeQuery("SELECT now()").list();
154+
}
155+
156+
try {
157+
rv = fn.apply(session);
158+
if (rv != -1) {
159+
txn.commit();
160+
System.out.printf("APP: COMMIT;\n");
161+
break;
162+
}
163+
} catch (JDBCException e) {
164+
if (RETRY_SQL_STATE.equals(e.getSQLState())) {
165+
// Since this is a transaction retry error, we
166+
// roll back the transaction and sleep a little
167+
// before trying again. Each time through the
168+
// loop we sleep for a little longer than the last
169+
// time (A.K.A. exponential backoff).
170+
System.out.printf("APP: retryable exception occurred:\n sql state = [%s]\n message = [%s]\n retry counter = %s\n", e.getSQLState(), e.getMessage(), attemptCount);
171+
System.out.printf("APP: ROLLBACK;\n");
172+
txn.rollback();
173+
int sleepMillis = (int)(Math.pow(2, attemptCount) * 100) + RAND.nextInt(100);
174+
System.out.printf("APP: Hit 40001 transaction retry error, sleeping %s milliseconds\n", sleepMillis);
175+
try {
176+
Thread.sleep(sleepMillis);
177+
} catch (InterruptedException ignored) {
178+
// no-op
179+
}
180+
rv = -1;
181+
} else {
182+
throw e;
183+
}
184+
}
185+
}
186+
return rv;
187+
}
188+
189+
public static void main(String[] args) {
190+
// Create a SessionFactory based on our hibernate.cfg.xml configuration
191+
// file, which defines how to connect to the database.
192+
SessionFactory sessionFactory =
193+
new Configuration()
194+
.configure("hibernate.cfg.xml")
195+
.addAnnotatedClass(Account.class)
196+
.buildSessionFactory();
197+
198+
try (Session session = sessionFactory.openSession()) {
199+
long fromAccountId = 1;
200+
long toAccountId = 2;
201+
long transferAmount = 100;
202+
203+
if (FORCE_RETRY) {
204+
System.out.printf("APP: About to test retry logic in 'runTransaction'\n");
205+
runTransaction(session, forceRetryLogic());
206+
} else {
207+
208+
runTransaction(session, addAccounts());
209+
long fromBalance = runTransaction(session, getAccountBalance(fromAccountId));
210+
long toBalance = runTransaction(session, getAccountBalance(toAccountId));
211+
if (fromBalance != -1 && toBalance != -1) {
212+
// Success!
213+
System.out.printf("APP: getAccountBalance(%d) --> %d\n", fromAccountId, fromBalance);
214+
System.out.printf("APP: getAccountBalance(%d) --> %d\n", toAccountId, toBalance);
215+
}
216+
217+
// Transfer $100 from account 1 to account 2
218+
long transferResult = runTransaction(session, transferFunds(fromAccountId, toAccountId, transferAmount));
219+
if (transferResult != -1) {
220+
// Success!
221+
System.out.printf("APP: transferFunds(%d, %d, %d) --> %d \n", fromAccountId, toAccountId, transferAmount, transferResult);
222+
223+
long fromBalanceAfter = runTransaction(session, getAccountBalance(fromAccountId));
224+
long toBalanceAfter = runTransaction(session, getAccountBalance(toAccountId));
225+
if (fromBalanceAfter != -1 && toBalanceAfter != -1) {
226+
// Success!
227+
System.out.printf("APP: getAccountBalance(%d) --> %d\n", fromAccountId, fromBalanceAfter);
228+
System.out.printf("APP: getAccountBalance(%d) --> %d\n", toAccountId, toBalanceAfter);
229+
}
230+
}
231+
}
232+
} finally {
233+
sessionFactory.close();
234+
}
235+
}
236+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version='1.0' encoding='utf-8'?>
2+
<!DOCTYPE hibernate-configuration PUBLIC
3+
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
4+
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
5+
<hibernate-configuration>
6+
<session-factory>
7+
<!-- Database connection settings -->
8+
<property name="connection.driver_class">org.postgresql.Driver</property>
9+
<property name="dialect">org.hibernate.dialect.PostgreSQL95Dialect</property>
10+
<property name="connection.url">jdbc:postgresql://127.0.0.1:26257/bank?sslmode=disable</property>
11+
<property name="connection.username">maxroach</property>
12+
<property name="connection.password"></property>
13+
14+
<!-- Required so a table can be created from the 'Account' class in Sample.java -->
15+
<property name="hibernate.hbm2ddl.auto">create</property>
16+
17+
<!-- Optional: Show SQL output for debugging -->
18+
<!-- <property name="hibernate.show_sql">true</property> -->
19+
<!-- <property name="hibernate.format_sql">true</property> -->
20+
</session-factory>
21+
</hibernate-configuration>

0 commit comments

Comments
 (0)