@@ -106,6 +106,9 @@ public static void main(String[] args) {
106106 testClient ();
107107 }
108108
109+ /**
110+ * Client logic that submits money transfer requests to the REST API.
111+ */
109112 public static void testClient () {
110113 logger .info ("Lets move some $$ around!" );
111114
@@ -164,6 +167,9 @@ enum AccountType {
164167 expense
165168}
166169
170+ /**
171+ * Domain entity mapped to the account table.
172+ */
167173class Account {
168174 @ Id
169175 private Long id ;
@@ -191,6 +197,9 @@ public BigDecimal getBalance() {
191197 }
192198}
193199
200+ /**
201+ * Account resource represented in HAL+JSON via REST API.
202+ */
194203@ Relation (value = "account" , collectionRelation = "accounts" )
195204class AccountModel extends RepresentationModel <AccountModel > {
196205 private String name ;
@@ -225,27 +234,24 @@ public void setBalance(BigDecimal balance) {
225234}
226235
227236/**
228- * Pagination is not available in spring-data-jdbc yet so we create a separate
229- * repository to provide basic limit+offset pagination of accounts.
237+ * Pagination is not available in spring-data-jdbc ( yet) so we create a separate
238+ * repository to provide basic limit+offset pagination queries for accounts.
230239 */
231240interface PagedAccountRepository {
232241 Page <Account > findAll (Pageable pageable );
233242}
234243
235244@ Repository
236245interface PagedAccountHelper extends org .springframework .data .repository .Repository <Account , Long > {
237- /**
238- * Selects a page of accounts using follower reads.
239- */
240246 @ Query ("SELECT * FROM account LIMIT :pageSize OFFSET :offset" )
241247 List <Account > findAll (@ Param ("pageSize" ) int pageSize , @ Param ("offset" ) long offset );
242248
243- @ Query ("SELECT count(id) from account" )
249+ @ Query ("SELECT count(id) FROM account" )
244250 long countAll ();
245251}
246252
247253@ Repository
248- // @Transactional is not needed, only here for clarity since we want the repo to always be called from a tx context
254+ // @Transactional is not needed but here for clarity since we want repos to always be called from a tx context
249255@ Transactional (propagation = MANDATORY )
250256class PagedAccountRepositoryImpl implements PagedAccountRepository {
251257 @ Autowired
@@ -262,15 +268,17 @@ public Page<Account> findAll(Pageable pageable) {
262268/**
263269 * The main account repository, notice there's no implementation needed since its auto-proxied by
264270 * spring-data.
271+ * <p>
272+ * Should have extended PagingAndSortingRepository in normal cases.
265273 */
266274@ Repository
267275@ Transactional (propagation = MANDATORY )
268276interface AccountRepository extends CrudRepository <Account , Long >, PagedAccountRepository {
269- @ Query (value = "select balance from account where id=:id" )
277+ @ Query (value = "SELECT balance FROM account WHERE id=:id" )
270278 BigDecimal getBalance (@ Param ("id" ) Long id );
271279
272280 @ Modifying
273- @ Query ("update account set balance = balance + :balance where id=:id" )
281+ @ Query ("UPDATE account SET balance = balance + :balance WHERE id=:id" )
274282 void updateBalance (@ Param ("id" ) Long id , @ Param ("balance" ) BigDecimal balance );
275283}
276284
@@ -285,7 +293,7 @@ public NegativeBalanceException(String message) {
285293}
286294
287295/**
288- * Annotation marking a transaction boundary to use time travel.
296+ * Annotation marking a transaction boundary to use follower reads ( time travel) .
289297 * See https://www.cockroachlabs.com/docs/stable/follower-reads.html
290298 */
291299@ Inherited
@@ -299,12 +307,16 @@ public NegativeBalanceException(String message) {
299307/**
300308 * Main remoting and transaction boundary in the form of a REST controller. The discipline
301309 * when following the entity-control-boundary (ECB) pattern is that only service boundaries
302- * are allowed to start and end transactions. That is enforced by the REQUIRES_NEW propagation
303- * attribute of transactional controller methods. Between the web container's HTTP listener
304- * and the transaction proxy there's also another transparent proxy in the form of a
305- * retry loop advice with exponential backoff. It takes care of retrying transactions that
306- * are aborted by transient SQL errors, rather than having these propagate all the way
307- * over the wire to the HTTP client. See RetryableTransactionAspect below.
310+ * are allowed to start and end transactions. A service boundary can be a controller, business
311+ * service facade or service activator (JMS/Kafka listener).
312+ * <p>
313+ * This is enforced by the REQUIRES_NEW propagation attribute of @Transactional annotated
314+ * controller methods. Between the web container's HTTP listener and the transaction proxy,
315+ * there's yet another transparent proxy in the form of a retry loop advice with exponential
316+ * backoff. It takes care of retrying transactions that are aborted by transient SQL errors,
317+ * rather than having these propagate all the way over the wire to the client / user agent.
318+ *
319+ * @see RetryableTransactionAspect
308320 */
309321@ RestController
310322class AccountController {
@@ -325,10 +337,11 @@ public ResponseEntity<RepresentationModel> index() {
325337 // Type-safe way to generate URLs bound to controller methods
326338 index .add (linkTo (methodOn (AccountController .class )
327339 .listAccounts (PageRequest .of (0 , 5 )))
328- .withRel ("accounts" ));
340+ .withRel ("accounts" )); // Lets skip curies and affordances for now
329341
330- // This essentially informs the client that a POST to a href with this rel
331- // and value parameters will transfer funds between accounts.
342+ // This rel essentially informs the client that a POST to its href with
343+ // form parameters will transfer funds between referenced accounts.
344+ // (its only a demo)
332345 index .add (linkTo (AccountController .class )
333346 .slash ("transfer{?fromId,toId,amount}" )
334347 .withRel ("transfer" ));
@@ -350,7 +363,7 @@ public ResponseEntity<RepresentationModel> index() {
350363 */
351364 @ GetMapping ("/account" )
352365 @ Transactional (propagation = REQUIRES_NEW )
353- @ TimeTravel
366+ @ TimeTravel // We dont need the result to be authoritative, so any follower replica can service the read
354367 public HttpEntity <PagedModel <AccountModel >> listAccounts (
355368 @ PageableDefault (size = 5 , direction = Sort .Direction .ASC ) Pageable page ) {
356369 return ResponseEntity
@@ -361,7 +374,7 @@ public HttpEntity<PagedModel<AccountModel>> listAccounts(
361374 * Provides a point lookup of a given account.
362375 */
363376 @ GetMapping (value = "/account/{id}" )
364- @ Transactional (propagation = REQUIRES_NEW , readOnly = true )
377+ @ Transactional (propagation = REQUIRES_NEW , readOnly = true ) // Notice its marked read-only
365378 public HttpEntity <AccountModel > getAccount (@ PathVariable ("id" ) Long accountId ) {
366379 return new ResponseEntity <>(accountModelAssembler ().toModel (
367380 accountRepository .findById (accountId )
0 commit comments