findAll(Pageable pageable) {
21 | return new PageImpl<>(pagedAccountHelper.findAll(pageable.getPageSize(), pageable.getOffset()),
22 | pageable,
23 | pagedAccountHelper.countAll());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/roach-data-jdbc/src/main/java/io/roach/data/jdbc/TransactionHintsAspect.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jdbc;
2 |
3 | import org.aspectj.lang.ProceedingJoinPoint;
4 | import org.aspectj.lang.annotation.Around;
5 | import org.aspectj.lang.annotation.Aspect;
6 | import org.aspectj.lang.annotation.Pointcut;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.core.Ordered;
11 | import org.springframework.core.annotation.Order;
12 | import org.springframework.jdbc.core.JdbcTemplate;
13 | import org.springframework.stereotype.Component;
14 | import org.springframework.transaction.TransactionDefinition;
15 | import org.springframework.transaction.annotation.Transactional;
16 | import org.springframework.transaction.support.TransactionSynchronizationManager;
17 | import org.springframework.util.Assert;
18 |
19 | /**
20 | * Aspect with an around advice that intercepts and sets transaction attributes.
21 | *
22 | * This advice needs to runs in a transactional context, which is after the underlying
23 | * transaction advisor.
24 | */
25 | @Component
26 | @Aspect
27 | // After TX advisor
28 | @Order(Ordered.LOWEST_PRECEDENCE)
29 | public class TransactionHintsAspect {
30 | protected final Logger logger = LoggerFactory.getLogger(getClass());
31 |
32 | @Autowired
33 | private JdbcTemplate jdbcTemplate;
34 |
35 | private String applicationName = "roach-data";
36 |
37 | @Pointcut("execution(* io.roach..*(..)) && @annotation(transactional)")
38 | public void anyTransactionBoundaryOperation(Transactional transactional) {
39 | }
40 |
41 | @Around(value = "anyTransactionBoundaryOperation(transactional)",
42 | argNames = "pjp,transactional")
43 | public Object setTransactionAttributes(ProceedingJoinPoint pjp, Transactional transactional)
44 | throws Throwable {
45 | Assert.isTrue(TransactionSynchronizationManager.isActualTransactionActive(), "TX not active");
46 |
47 | // https://www.cockroachlabs.com/docs/v19.2/set-vars.html
48 | jdbcTemplate.update("SET application_name=?", applicationName);
49 |
50 | if (transactional.timeout() != TransactionDefinition.TIMEOUT_DEFAULT) {
51 | logger.info("Setting statement time {} for {}", transactional.timeout(),
52 | pjp.getSignature().toShortString());
53 | jdbcTemplate.update("SET statement_timeout=?", transactional.timeout() * 1000);
54 | }
55 |
56 | if (transactional.readOnly()) {
57 | logger.info("Setting transaction read only for {}", pjp.getSignature().toShortString());
58 | jdbcTemplate.execute("SET transaction_read_only=true");
59 | }
60 |
61 | return pjp.proceed();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/roach-data-jdbc/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | ########################
2 | # Spring boot properties
3 | # http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
4 | ########################
5 |
6 | spring:
7 | output:
8 | ansi:
9 | enabled: ALWAYS
10 |
11 | liquibase:
12 | change-log: classpath:db/changelog-master.xml
13 | default-schema:
14 | drop-first: false
15 | contexts: crdb
16 | enabled: true
17 |
18 | datasource:
19 | url: jdbc:postgresql://localhost:26257/roach_data?sslmode=disable
20 | driver-class-name: org.postgresql.Driver
21 | username: root
22 | password:
23 | hikari:
24 | maximum-pool-size: 4
25 | connection-init-sql: SELECT 1
26 |
27 | jpa:
28 | open-in-view: false
29 |
30 | management:
31 | endpoints:
32 | web:
33 | exposure:
34 | include: conditions,env,info,health,httptrace,metrics,threaddump,shutdown,configprops,liquibase
35 | endpoint:
36 | health:
37 | show-details: always
38 | health:
39 | defaults:
40 | enabled: true
41 |
42 | server:
43 | port: 9090
44 |
--------------------------------------------------------------------------------
/roach-data-jdbc/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 | ^__^
2 | (oo)\_______
3 | (__)\ )\/\ CockroachDB on Spring Data / JDBC ${application.formatted-version}
4 | ||----w | powered by Spring Boot ${spring-boot.formatted-version}
5 | || ||
6 |
--------------------------------------------------------------------------------
/roach-data-jdbc/src/main/resources/db/changelog-master.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 | ANY
12 |
13 |
14 |
15 |
16 |
17 | 1
18 | Alice
19 |
20 | asset
21 |
22 |
23 | 2
24 | Bob
25 |
26 | expense
27 |
28 |
29 | 3
30 | Bobby Tables
31 |
32 | asset
33 |
34 |
35 | 4
36 | Doris
37 |
38 | expense
39 |
40 |
41 |
--------------------------------------------------------------------------------
/roach-data-jdbc/src/main/resources/db/create.sql:
--------------------------------------------------------------------------------
1 | create table account
2 | (
3 | id int not null primary key default unique_rowid(),
4 | balance numeric(19, 2) not null,
5 | name varchar(128) not null,
6 | type varchar(25) not null,
7 | updated timestamptz not null default clock_timestamp()
8 | );
9 |
10 | -- insert into account (id,balance,name,type)
11 | -- values
12 | -- (1,100.50,'a','expense'),
13 | -- (2,100.50,'b','expense'),
14 | -- (3,100.50,'c','expense'),
15 | -- (4,100.50,'d','expense'),
16 | -- (5,100.50,'e','expense');
17 |
18 | -- select * from account AS OF SYSTEM TIME '-5s';
--------------------------------------------------------------------------------
/roach-data-jdbc/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/roach-data-jdbi/README.md:
--------------------------------------------------------------------------------
1 | # Roach Demo Data :: JDBI
2 |
3 | A CockroachDB demo using JDBI for data access.
4 |
--------------------------------------------------------------------------------
/roach-data-jdbi/pom.xml:
--------------------------------------------------------------------------------
1 |
4 | 4.0.0
5 |
6 | io.roach.data
7 | roach-data-2x-parent
8 | 1.0.0-SNAPSHOT
9 | ../roach-data-2x-parent
10 |
11 |
12 | roach-data-jdbi
13 |
14 |
15 |
16 | org.jdbi
17 | jdbi3-core
18 | 3.37.1
19 |
20 |
21 | org.slf4j
22 | slf4j-api
23 | 1.7.31
24 |
25 |
26 | ch.qos.logback
27 | logback-core
28 | 1.2.3
29 |
30 |
31 | ch.qos.logback
32 | logback-classic
33 | 1.2.3
34 |
35 |
36 |
37 | com.zaxxer
38 | HikariCP
39 | 4.0.3
40 |
41 |
42 | org.postgresql
43 | postgresql
44 |
45 |
46 | net.ttddyy
47 | datasource-proxy
48 | 1.7
49 |
50 |
51 |
52 |
53 | roach-data-jdbc-plain
54 |
55 |
56 | org.springframework.boot
57 | spring-boot-maven-plugin
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/roach-data-jdbi/src/main/java/io/roach/data/jdbi/Account.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jdbi;
2 |
3 | import java.math.BigDecimal;
4 |
5 | public class Account {
6 | String name;
7 |
8 | BigDecimal amount;
9 |
10 | Account(String name, BigDecimal amount) {
11 | this.name = name;
12 | this.amount = amount;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/roach-data-jdbi/src/main/java/io/roach/data/jdbi/DataAccessException.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jdbi;
2 |
3 | public class DataAccessException extends RuntimeException {
4 | public DataAccessException(String message) {
5 | super(message);
6 | }
7 |
8 | public DataAccessException(Throwable cause) {
9 | super(cause);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/roach-data-jdbi/src/main/java/io/roach/data/jdbi/SchemaSupport.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jdbi;
2 |
3 | import org.jdbi.v3.core.Jdbi;
4 |
5 | import java.io.IOException;
6 | import java.net.URISyntaxException;
7 | import java.net.URL;
8 | import java.nio.file.Files;
9 | import java.nio.file.Paths;
10 |
11 | public abstract class SchemaSupport {
12 | private SchemaSupport() {
13 | }
14 |
15 | public static void setupSchema(Jdbi jdbi) {
16 | StringBuilder buffer = new StringBuilder();
17 |
18 | try {
19 | URL sql = JdbiApplication.class.getResource("/db/create.sql");
20 | Files.readAllLines(Paths.get(sql.toURI())).forEach(line -> {
21 | if (!line.startsWith("--") && !line.isEmpty()) {
22 | buffer.append(line);
23 | }
24 | if (line.endsWith(";") && buffer.length() > 0) {
25 | jdbi.useHandle(handle -> {
26 | handle.execute(buffer.toString());
27 | });
28 | buffer.setLength(0);
29 | }
30 | });
31 | } catch (IOException e) {
32 | throw new RuntimeException(e);
33 | } catch (URISyntaxException e) {
34 | throw new RuntimeException(e);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/roach-data-jdbi/src/main/resources/db/create.sql:
--------------------------------------------------------------------------------
1 | drop table if exists account;
2 |
3 | create table if not exists account
4 | (
5 | id int not null primary key,
6 | balance numeric(19, 2) not null,
7 | name varchar(128) not null,
8 | updated timestamptz not null default clock_timestamp()
9 | );
10 |
11 | alter table account
12 | add constraint if not exists check_account_positive_balance check (balance >= 0);
13 |
14 | truncate table account;
15 |
16 | insert into account (id, balance, name)
17 | select i,
18 | 5000.00,
19 | concat('customer:', (i::varchar))
20 | from generate_series(1, 100) as i;
21 |
--------------------------------------------------------------------------------
/roach-data-jdbi/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/roach-data-jooq/README.md:
--------------------------------------------------------------------------------
1 | # Roach Demo Data :: jOOQ
2 |
3 | A CockroachDB Spring Boot Demo using [jOOQ](https://www.jooq.org/) for data access.
4 |
5 | ## Generate jOOQ classes
6 |
7 | First create the DB schema:
8 |
9 | create table account
10 | (
11 | id int not null primary key,
12 | balance numeric(19, 2) not null,
13 | name varchar(128) not null,
14 | type varchar(25) not null
15 | );
16 |
17 | Then generate code by activating a Maven profile called _generate_:
18 |
19 | mvn -P generate clean install
20 |
21 | (Note: this will fail with an error when using CRDB even if classes are generated correctly)
22 |
23 | Finally drop the table
24 |
25 | drop table account cascade;
26 |
27 |
--------------------------------------------------------------------------------
/roach-data-jooq/src/main/java/io/roach/data/jooq/AccountModel.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jooq;
2 |
3 | import java.math.BigDecimal;
4 |
5 | import org.springframework.hateoas.RepresentationModel;
6 | import org.springframework.hateoas.server.core.Relation;
7 |
8 | @Relation(value = "account", collectionRelation = "accounts")
9 | public class AccountModel extends RepresentationModel {
10 | private String name;
11 |
12 | private AccountType type;
13 |
14 | private BigDecimal balance;
15 |
16 | public String getName() {
17 | return name;
18 | }
19 |
20 | public void setName(String name) {
21 | this.name = name;
22 | }
23 |
24 | public AccountType getType() {
25 | return type;
26 | }
27 |
28 | public void setType(AccountType type) {
29 | this.type = type;
30 | }
31 |
32 | public BigDecimal getBalance() {
33 | return balance;
34 | }
35 |
36 | public void setBalance(BigDecimal balance) {
37 | this.balance = balance;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/roach-data-jooq/src/main/java/io/roach/data/jooq/AccountRepository.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jooq;
2 |
3 | import java.math.BigDecimal;
4 |
5 | import org.springframework.data.domain.Page;
6 | import org.springframework.data.domain.Pageable;
7 |
8 | import io.roach.data.jooq.model.tables.records.AccountRecord;
9 |
10 | public interface AccountRepository {
11 | Page findAll(Pageable pageable);
12 |
13 | AccountRecord getOne(Long id);
14 |
15 | BigDecimal getBalance(Long id);
16 |
17 | void updateBalance(Long id, BigDecimal balance);
18 | }
19 |
--------------------------------------------------------------------------------
/roach-data-jooq/src/main/java/io/roach/data/jooq/AccountType.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jooq;
2 |
3 | public enum AccountType {
4 | asset,
5 | expense
6 | }
7 |
--------------------------------------------------------------------------------
/roach-data-jooq/src/main/java/io/roach/data/jooq/JooqAccountRepository.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jooq;
2 |
3 | import java.math.BigDecimal;
4 | import java.util.List;
5 |
6 | import org.jooq.DSLContext;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.data.domain.Page;
9 | import org.springframework.data.domain.PageImpl;
10 | import org.springframework.data.domain.Pageable;
11 | import org.springframework.stereotype.Repository;
12 | import org.springframework.transaction.annotation.Transactional;
13 |
14 | import io.roach.data.jooq.model.tables.records.AccountRecord;
15 |
16 | import static io.roach.data.jooq.model.Tables.ACCOUNT;
17 | import static org.springframework.transaction.annotation.Propagation.MANDATORY;
18 |
19 | @Repository
20 | @Transactional(propagation = MANDATORY)
21 | public class JooqAccountRepository implements AccountRepository {
22 | @Autowired
23 | private DSLContext dsl;
24 |
25 | @Override
26 | public Page findAll(Pageable pageable) {
27 | List accountRecords = dsl.selectFrom(ACCOUNT).limit(pageable.getPageSize())
28 | .offset(pageable.getOffset())
29 | .fetchInto(AccountRecord.class);
30 | long totalRecords = dsl.fetchCount(dsl.select().from(ACCOUNT));
31 | return new PageImpl<>(accountRecords, pageable, totalRecords);
32 | }
33 |
34 | @Override
35 | public AccountRecord getOne(Long id) {
36 | return dsl.selectFrom(ACCOUNT)
37 | .where(ACCOUNT.ID.eq(id))
38 | .fetchOne();
39 | }
40 |
41 | @Override
42 | public BigDecimal getBalance(Long id) {
43 | return dsl.select(ACCOUNT.BALANCE)
44 | .from(ACCOUNT)
45 | .where(ACCOUNT.ID.eq(id))
46 | .forUpdate()
47 | .fetchOne()
48 | .value1();
49 | }
50 |
51 | @Override
52 | public void updateBalance(Long id, BigDecimal balance) {
53 | dsl.update(ACCOUNT)
54 | .set(ACCOUNT.BALANCE, ACCOUNT.BALANCE.plus(balance))
55 | .where(ACCOUNT.ID.eq(id))
56 | .execute();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/roach-data-jooq/src/main/java/io/roach/data/jooq/NegativeBalanceException.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jooq;
2 |
3 | import org.springframework.dao.DataIntegrityViolationException;
4 | import org.springframework.http.HttpStatus;
5 | import org.springframework.web.bind.annotation.ResponseStatus;
6 |
7 | @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Negative balance")
8 | public class NegativeBalanceException extends DataIntegrityViolationException {
9 | public NegativeBalanceException(String message) {
10 | super(message);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/roach-data-jooq/src/main/java/io/roach/data/jooq/model/DefaultCatalog.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is generated by jOOQ.
3 | */
4 | package io.roach.data.jooq.model;
5 |
6 |
7 | import java.util.Arrays;
8 | import java.util.List;
9 |
10 | import org.jooq.Schema;
11 | import org.jooq.impl.CatalogImpl;
12 |
13 |
14 | /**
15 | * This class is generated by jOOQ.
16 | */
17 | @SuppressWarnings({"all", "unchecked", "rawtypes"})
18 | public class DefaultCatalog extends CatalogImpl {
19 |
20 | /**
21 | * The reference instance of DEFAULT_CATALOG
22 | */
23 | public static final DefaultCatalog DEFAULT_CATALOG = new DefaultCatalog();
24 |
25 | private static final long serialVersionUID = 671484435;
26 |
27 | /**
28 | * The schema public
.
29 | */
30 | public final Public PUBLIC = Public.PUBLIC;
31 |
32 | /**
33 | * No further instances allowed
34 | */
35 | private DefaultCatalog() {
36 | super("");
37 | }
38 |
39 | @Override
40 | public final List getSchemas() {
41 | return Arrays.asList(
42 | Public.PUBLIC);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/roach-data-jooq/src/main/java/io/roach/data/jooq/model/Keys.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is generated by jOOQ.
3 | */
4 | package io.roach.data.jooq.model;
5 |
6 |
7 | import org.jooq.TableField;
8 | import org.jooq.UniqueKey;
9 | import org.jooq.impl.Internal;
10 |
11 | import io.roach.data.jooq.model.tables.Account;
12 | import io.roach.data.jooq.model.tables.Databasechangeloglock;
13 | import io.roach.data.jooq.model.tables.records.AccountRecord;
14 | import io.roach.data.jooq.model.tables.records.DatabasechangeloglockRecord;
15 |
16 |
17 | /**
18 | * A class modelling foreign key relationships and constraints of tables of
19 | * the public
schema.
20 | */
21 | @SuppressWarnings({"all", "unchecked", "rawtypes"})
22 | public class Keys {
23 |
24 | // -------------------------------------------------------------------------
25 | // IDENTITY definitions
26 | // -------------------------------------------------------------------------
27 |
28 |
29 | // -------------------------------------------------------------------------
30 | // UNIQUE and PRIMARY KEY definitions
31 | // -------------------------------------------------------------------------
32 |
33 | public static final UniqueKey PRIMARY = UniqueKeys0.PRIMARY;
34 |
35 | public static final UniqueKey DATABASECHANGELOGLOCK_PKEY = UniqueKeys0.DATABASECHANGELOGLOCK_PKEY;
36 |
37 | // -------------------------------------------------------------------------
38 | // FOREIGN KEY definitions
39 | // -------------------------------------------------------------------------
40 |
41 |
42 | // -------------------------------------------------------------------------
43 | // [#1459] distribute members to avoid static initialisers > 64kb
44 | // -------------------------------------------------------------------------
45 |
46 | private static class UniqueKeys0 {
47 | public static final UniqueKey PRIMARY = Internal
48 | .createUniqueKey(Account.ACCOUNT, "primary", new TableField[] {Account.ACCOUNT.ID}, true);
49 |
50 | public static final UniqueKey DATABASECHANGELOGLOCK_PKEY = Internal
51 | .createUniqueKey(Databasechangeloglock.DATABASECHANGELOGLOCK, "databasechangeloglock_pkey",
52 | new TableField[] {Databasechangeloglock.DATABASECHANGELOGLOCK.ID}, true);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/roach-data-jooq/src/main/java/io/roach/data/jooq/model/Public.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is generated by jOOQ.
3 | */
4 | package io.roach.data.jooq.model;
5 |
6 |
7 | import java.util.Arrays;
8 | import java.util.List;
9 |
10 | import org.jooq.Catalog;
11 | import org.jooq.Table;
12 | import org.jooq.impl.SchemaImpl;
13 |
14 | import io.roach.data.jooq.model.tables.Account;
15 | import io.roach.data.jooq.model.tables.Databasechangelog;
16 | import io.roach.data.jooq.model.tables.Databasechangeloglock;
17 |
18 |
19 | /**
20 | * This class is generated by jOOQ.
21 | */
22 | @SuppressWarnings({"all", "unchecked", "rawtypes"})
23 | public class Public extends SchemaImpl {
24 |
25 | /**
26 | * The reference instance of public
27 | */
28 | public static final Public PUBLIC = new Public();
29 |
30 | private static final long serialVersionUID = 243892754;
31 |
32 | /**
33 | * The table public.account
.
34 | */
35 | public final Account ACCOUNT = Account.ACCOUNT;
36 |
37 | /**
38 | * The table public.databasechangelog
.
39 | */
40 | public final Databasechangelog DATABASECHANGELOG = Databasechangelog.DATABASECHANGELOG;
41 |
42 | /**
43 | * The table public.databasechangeloglock
.
44 | */
45 | public final Databasechangeloglock DATABASECHANGELOGLOCK = Databasechangeloglock.DATABASECHANGELOGLOCK;
46 |
47 | /**
48 | * No further instances allowed
49 | */
50 | private Public() {
51 | super("public", null);
52 | }
53 |
54 |
55 | @Override
56 | public Catalog getCatalog() {
57 | return DefaultCatalog.DEFAULT_CATALOG;
58 | }
59 |
60 | @Override
61 | public final List> getTables() {
62 | return Arrays.>asList(
63 | Account.ACCOUNT,
64 | Databasechangelog.DATABASECHANGELOG,
65 | Databasechangeloglock.DATABASECHANGELOGLOCK);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/roach-data-jooq/src/main/java/io/roach/data/jooq/model/Tables.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is generated by jOOQ.
3 | */
4 | package io.roach.data.jooq.model;
5 |
6 |
7 | import io.roach.data.jooq.model.tables.Account;
8 | import io.roach.data.jooq.model.tables.Databasechangelog;
9 | import io.roach.data.jooq.model.tables.Databasechangeloglock;
10 |
11 |
12 | /**
13 | * Convenience access to all tables in public
14 | */
15 | @SuppressWarnings({"all", "unchecked", "rawtypes"})
16 | public class Tables {
17 |
18 | /**
19 | * The table public.account
.
20 | */
21 | public static final Account ACCOUNT = Account.ACCOUNT;
22 |
23 | /**
24 | * The table public.databasechangelog
.
25 | */
26 | public static final Databasechangelog DATABASECHANGELOG = Databasechangelog.DATABASECHANGELOG;
27 |
28 | /**
29 | * The table public.databasechangeloglock
.
30 | */
31 | public static final Databasechangeloglock DATABASECHANGELOGLOCK = Databasechangeloglock.DATABASECHANGELOGLOCK;
32 | }
33 |
--------------------------------------------------------------------------------
/roach-data-jooq/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | ########################
2 | # Spring boot properties
3 | # http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
4 | ########################
5 |
6 | spring:
7 | output:
8 | ansi:
9 | enabled: ALWAYS
10 |
11 | liquibase:
12 | change-log: classpath:db/changelog-master.xml
13 | default-schema:
14 | drop-first: false
15 | contexts: crdb
16 | enabled: true
17 |
18 | datasource:
19 | url: jdbc:postgresql://localhost:26257/roach_data?sslmode=disable
20 | driver-class-name: org.postgresql.Driver
21 | username: root
22 | password:
23 | hikari:
24 | connection-test-query: SELECT 1
25 |
26 | jpa:
27 | open-in-view: false
28 |
29 | server:
30 | port: 9090
31 |
--------------------------------------------------------------------------------
/roach-data-jooq/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 | ^__^
2 | (oo)\_______
3 | (__)\ )\/\ CockroachDB on Spring Data / JooQ ${application.formatted-version}
4 | ||----w | powered by Spring Boot ${spring-boot.formatted-version}
5 | || ||
6 |
--------------------------------------------------------------------------------
/roach-data-jooq/src/main/resources/db/changelog-master.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 | ANY
12 |
13 |
14 |
15 |
16 |
17 | 1
18 | Alice
19 |
20 | asset
21 |
22 |
23 | 2
24 | Bob
25 |
26 | expense
27 |
28 |
29 | 3
30 | Bobby Tables
31 |
32 | asset
33 |
34 |
35 | 4
36 | Doris
37 |
38 | expense
39 |
40 |
41 |
--------------------------------------------------------------------------------
/roach-data-jooq/src/main/resources/db/create.sql:
--------------------------------------------------------------------------------
1 | -- DROP TABLE IF EXISTS account cascade;
2 | -- DROP TABLE IF EXISTS databasechangelog cascade;
3 | -- DROP TABLE IF EXISTS databasechangeloglock cascade;
4 |
5 | create table account
6 | (
7 | id int not null primary key,
8 | balance numeric(19, 2) not null,
9 | name varchar(128) not null,
10 | type varchar(25) not null
11 | );
12 |
13 | -- insert into account (id,balance,name,type) values
14 | -- (1, 500.00,'Alice','asset'),
15 | -- (2, 500.00,'Bob','expense'),
16 | -- (3, 500.00,'Bobby Tables','asset'),
17 | -- (4, 500.00,'Doris','expense');
18 |
19 |
--------------------------------------------------------------------------------
/roach-data-jooq/src/main/resources/jooq_config.properties:
--------------------------------------------------------------------------------
1 | db.driver=org.postgresql.Driver
2 | db.url=jdbc:postgresql://localhost:26257/roach_data?sslmode=disable
3 | db.username=root
4 | db.password=
5 |
--------------------------------------------------------------------------------
/roach-data-jooq/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/roach-data-jpa-orders/README.md:
--------------------------------------------------------------------------------
1 | # JPA/Hibernate Demo for CockroachDB
2 |
3 | A CockroachDB Spring Boot Demo using [Spring Data JPA](https://spring.io/projects/spring-data-jpa)
4 | with Hibernate for data access.
5 |
--------------------------------------------------------------------------------
/roach-data-jpa-orders/pom.xml:
--------------------------------------------------------------------------------
1 |
4 | 4.0.0
5 |
6 | io.roach.data
7 | roach-data-3x-parent
8 | 1.0.0-SNAPSHOT
9 | ../roach-data-3x-parent
10 |
11 |
12 | roach-data-jpa-orders
13 | jar
14 |
15 |
16 | true
17 |
18 |
19 |
20 |
21 | org.springframework.boot
22 | spring-boot-starter-data-jdbc
23 |
24 |
25 | org.springframework.boot
26 | spring-boot-starter-data-jpa
27 |
28 |
29 | org.springframework.data
30 | spring-data-commons
31 |
32 |
33 | net.ttddyy
34 | datasource-proxy
35 | 1.11.0
36 |
37 |
38 | org.fusesource.jansi
39 | jansi
40 | 2.4.1
41 | runtime
42 |
43 |
44 | org.flywaydb
45 | flyway-core
46 |
47 | 9.22.3
48 | runtime
49 |
50 |
51 | org.postgresql
52 | postgresql
53 | runtime
54 |
55 |
56 |
57 |
58 | roach-data-jpa-orders
59 |
60 |
61 | org.springframework.boot
62 | spring-boot-maven-plugin
63 |
64 |
65 |
66 | repackage
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/roach-data-jpa-orders/src/main/java/io/roach/data/jpa/Main.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jpa;
2 |
3 | import org.springframework.boot.WebApplicationType;
4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
5 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
6 | import org.springframework.boot.builder.SpringApplicationBuilder;
7 | import org.springframework.context.annotation.ComponentScan;
8 | import org.springframework.context.annotation.Configuration;
9 |
10 | @Configuration
11 | @EnableAutoConfiguration(exclude = {
12 | DataSourceAutoConfiguration.class
13 | })
14 | @ComponentScan(basePackageClasses = Main.class)
15 | public class Main {
16 | public static void main(String[] args) {
17 | new SpringApplicationBuilder(Main.class)
18 | .web(WebApplicationType.NONE)
19 | .run(args);
20 | }
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/roach-data-jpa-orders/src/main/java/io/roach/data/jpa/OrderSystemClient.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jpa;
2 |
3 | import java.util.List;
4 | import java.util.UUID;
5 |
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.boot.CommandLineRunner;
10 | import org.springframework.stereotype.Component;
11 |
12 | import io.roach.data.jpa.domain.Customer;
13 | import io.roach.data.jpa.domain.Order;
14 | import io.roach.data.jpa.domain.Product;
15 | import io.roach.data.jpa.service.OrderSystem;
16 |
17 | @Component
18 | public class OrderSystemClient implements CommandLineRunner {
19 | private final Logger logger = LoggerFactory.getLogger(getClass());
20 |
21 | @Autowired
22 | private OrderSystem orderSystem;
23 |
24 | @Override
25 | public void run(String... args) {
26 | orderSystem.clearAll();
27 | orderSystem.createProductInventory();
28 | orderSystem.createCustomers();
29 | List ids = orderSystem.createOrders();
30 |
31 | ids.forEach(id -> {
32 | Order o = orderSystem.findOrderById(id);
33 | print(o);
34 | });
35 |
36 | orderSystem.listAllOrders().forEach(this::print);
37 | orderSystem.listAllOrderDetails().forEach(this::print);
38 |
39 | logger.info(">> Find by sku: {}", orderSystem.findProductBySku("CRDB-UL-ED1"));
40 | logger.info(">> Total order price: {}", orderSystem.getTotalOrderPrice());
41 |
42 | orderSystem.removeOrders();
43 | }
44 |
45 | private void print(Order order) {
46 | Customer c = order.getCustomer();
47 | logger.info("""
48 | Order placed by: %s
49 | Total cost: %s
50 | """.formatted(c.getUserName(), order.getTotalPrice()));
51 | }
52 |
53 | private void printDetails(Order order) {
54 | Customer c = order.getCustomer();
55 |
56 | logger.info("""
57 | Order placed by: %s
58 | Total cost: %s
59 | """.formatted(c.getUserName(), order.getTotalPrice()));
60 |
61 | order.getOrderItems().forEach(orderItem -> {
62 | Product p = orderItem.getProduct();
63 |
64 | logger.info("""
65 | Product name: %s
66 | Product price: %s
67 | Product sku: %s
68 | Item qty: %s
69 | Unit price: %s
70 | Total cost: %s
71 | """.formatted(
72 | p.getName(),
73 | p.getPrice(),
74 | p.getSku(),
75 | orderItem.getQuantity(),
76 | orderItem.getUnitPrice(),
77 | orderItem.totalCost()));
78 | });
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/roach-data-jpa-orders/src/main/java/io/roach/data/jpa/config/JpaConfig.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jpa.config;
2 |
3 | import javax.sql.DataSource;
4 |
5 | import org.hibernate.engine.jdbc.internal.FormatStyle;
6 | import org.hibernate.engine.jdbc.internal.Formatter;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
10 | import org.springframework.boot.context.properties.ConfigurationProperties;
11 | import org.springframework.context.annotation.Bean;
12 | import org.springframework.context.annotation.Configuration;
13 | import org.springframework.context.annotation.EnableAspectJAutoProxy;
14 | import org.springframework.context.annotation.Primary;
15 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
16 | import org.springframework.transaction.annotation.EnableTransactionManagement;
17 |
18 | import com.zaxxer.hikari.HikariDataSource;
19 |
20 | import net.ttddyy.dsproxy.listener.logging.DefaultQueryLogEntryCreator;
21 | import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
22 | import net.ttddyy.dsproxy.listener.logging.SLF4JQueryLoggingListener;
23 | import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
24 |
25 | import io.roach.data.jpa.repository.CustomerRepository;
26 |
27 | @Configuration
28 | @EnableTransactionManagement(proxyTargetClass = true)
29 | @EnableAspectJAutoProxy(proxyTargetClass = true)
30 | @EnableJpaRepositories(basePackageClasses = CustomerRepository.class, enableDefaultTransactions = false)
31 | public class JpaConfig {
32 | private final Logger logger = LoggerFactory.getLogger("io.roach.data.jpa.SQL_TRACE");
33 |
34 | @Bean
35 | @ConfigurationProperties("spring.datasource")
36 | public DataSourceProperties dataSourceProperties() {
37 | return new DataSourceProperties();
38 | }
39 |
40 | @Bean
41 | @Primary
42 | public DataSource primaryDataSource() {
43 | return logger.isTraceEnabled() ? loggingProxy(targetDataSource()) : targetDataSource();
44 | }
45 |
46 | @Bean
47 | @ConfigurationProperties("spring.datasource.hikari")
48 | public HikariDataSource targetDataSource() {
49 | HikariDataSource ds = dataSourceProperties()
50 | .initializeDataSourceBuilder()
51 | .type(HikariDataSource.class)
52 | .build();
53 | ds.addDataSourceProperty("reWriteBatchedInserts", true);
54 | ds.addDataSourceProperty("ApplicationName", "jdbc-test");
55 | return ds;
56 | }
57 |
58 | private DataSource loggingProxy(DataSource dataSource) {
59 | final Formatter formatterBasic = FormatStyle.BASIC.getFormatter();
60 | final Formatter formatterHighlight = FormatStyle.HIGHLIGHT.getFormatter();
61 |
62 | DefaultQueryLogEntryCreator creator = new DefaultQueryLogEntryCreator() {
63 | @Override
64 | protected String formatQuery(String query) {
65 | return formatterHighlight.format(formatterBasic.format(query));
66 | }
67 | };
68 | creator.setMultiline(true);
69 |
70 | SLF4JQueryLoggingListener listener = new SLF4JQueryLoggingListener();
71 | listener.setLogger(logger);
72 | listener.setLogLevel(SLF4JLogLevel.TRACE);
73 | listener.setWriteConnectionId(true);
74 | listener.setWriteIsolation(true);
75 | listener.setQueryLogEntryCreator(creator);
76 |
77 | return ProxyDataSourceBuilder
78 | .create(dataSource)
79 | .name("SQL-Trace")
80 | .asJson()
81 | .listener(listener)
82 | .build();
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/roach-data-jpa-orders/src/main/java/io/roach/data/jpa/domain/AbstractEntity.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jpa.domain;
2 |
3 | import java.io.Serializable;
4 |
5 | import jakarta.persistence.MappedSuperclass;
6 | import jakarta.persistence.PostLoad;
7 | import jakarta.persistence.PostPersist;
8 | import jakarta.persistence.Transient;
9 |
10 | import org.springframework.data.domain.Persistable;
11 |
12 | @MappedSuperclass
13 | public abstract class AbstractEntity implements Persistable {
14 | @Transient
15 | private boolean isNew = true;
16 |
17 | @PostPersist
18 | @PostLoad
19 | void markNotNew() {
20 | this.isNew = false;
21 | }
22 |
23 | @Override
24 | public boolean isNew() {
25 | return isNew;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/roach-data-jpa-orders/src/main/java/io/roach/data/jpa/domain/Customer.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jpa.domain;
2 |
3 | import java.util.UUID;
4 |
5 | import jakarta.persistence.Column;
6 | import jakarta.persistence.Entity;
7 | import jakarta.persistence.GeneratedValue;
8 | import jakarta.persistence.GenerationType;
9 | import jakarta.persistence.Id;
10 | import jakarta.persistence.NamedQueries;
11 | import jakarta.persistence.NamedQuery;
12 | import jakarta.persistence.Table;
13 |
14 | @Entity
15 | @Table(name = "customers")
16 | @NamedQueries({
17 | @NamedQuery(
18 | name = "Customer.findByUserName",
19 | query = "from Customer u where u.userName = :userName"
20 | )
21 | })
22 | public class Customer extends AbstractEntity {
23 | public static Builder builder() {
24 | return new Builder();
25 | }
26 |
27 | public static final class Builder {
28 | private String userName;
29 |
30 | private String firstName;
31 |
32 | private String lastName;
33 |
34 | private Builder() {
35 | }
36 |
37 | public Builder withUserName(String userName) {
38 | this.userName = userName;
39 | return this;
40 | }
41 |
42 | public Builder withFirstName(String firstName) {
43 | this.firstName = firstName;
44 | return this;
45 | }
46 |
47 | public Builder withLastName(String lastName) {
48 | this.lastName = lastName;
49 | return this;
50 | }
51 |
52 | public Customer build() {
53 | Customer customer = new Customer();
54 | customer.userName = this.userName;
55 | customer.firstName = this.firstName;
56 | customer.lastName = this.lastName;
57 | return customer;
58 | }
59 | }
60 |
61 | @Id
62 | @GeneratedValue(strategy = GenerationType.AUTO)
63 | private UUID id;
64 |
65 | @Column(name = "user_name", length = 15, nullable = false, unique = true)
66 | private String userName;
67 |
68 | @Column(name = "first_name", length = 45)
69 | private String firstName;
70 |
71 | @Column(name = "last_name", length = 45)
72 | private String lastName;
73 |
74 | @Override
75 | public UUID getId() {
76 | return id;
77 | }
78 |
79 | public String getUserName() {
80 | return userName;
81 | }
82 |
83 | public String getFirstName() {
84 | return firstName;
85 | }
86 |
87 | public String getLastName() {
88 | return lastName;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/roach-data-jpa-orders/src/main/java/io/roach/data/jpa/domain/Order.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jpa.domain;
2 |
3 | import java.math.BigDecimal;
4 | import java.util.ArrayList;
5 | import java.util.Collections;
6 | import java.util.List;
7 | import java.util.UUID;
8 |
9 | import org.hibernate.annotations.Fetch;
10 | import org.hibernate.annotations.FetchMode;
11 |
12 | import jakarta.persistence.Column;
13 | import jakarta.persistence.ElementCollection;
14 | import jakarta.persistence.Entity;
15 | import jakarta.persistence.FetchType;
16 | import jakarta.persistence.GeneratedValue;
17 | import jakarta.persistence.GenerationType;
18 | import jakarta.persistence.Id;
19 | import jakarta.persistence.JoinColumn;
20 | import jakarta.persistence.JoinTable;
21 | import jakarta.persistence.ManyToOne;
22 | import jakarta.persistence.OrderColumn;
23 | import jakarta.persistence.Table;
24 |
25 | @Entity
26 | @Table(name = "orders")
27 | public class Order extends AbstractEntity {
28 | public static Builder builder() {
29 | return new Builder();
30 | }
31 |
32 | public static final class Builder {
33 | private Customer customer;
34 |
35 | private final List orderItems = new ArrayList<>();
36 |
37 | private Builder() {
38 | }
39 |
40 | public Builder withCustomer(Customer customer) {
41 | this.customer = customer;
42 | return this;
43 | }
44 |
45 | public OrderItem.Builder andOrderItem() {
46 | return new OrderItem.Builder(this, orderItems::add);
47 | }
48 |
49 | public Order build() {
50 | if (this.customer == null) {
51 | throw new IllegalStateException("Missing customer");
52 | }
53 | if (this.orderItems.isEmpty()) {
54 | throw new IllegalStateException("Empty order");
55 | }
56 | Order order = new Order();
57 | order.customer = this.customer;
58 | order.orderItems.addAll(this.orderItems);
59 | order.totalPrice = order.subTotal();
60 | return order;
61 | }
62 | }
63 |
64 | @Id
65 | @GeneratedValue(strategy = GenerationType.AUTO)
66 | private UUID id;
67 |
68 | @Column(name = "total_price", nullable = false, updatable = false)
69 | private BigDecimal totalPrice;
70 |
71 | @ManyToOne(fetch = FetchType.LAZY)
72 | @JoinColumn(name = "customer_id", nullable = false, updatable = false)
73 | private Customer customer;
74 |
75 | @ElementCollection(fetch = FetchType.LAZY)
76 | @JoinTable(name = "order_items",
77 | joinColumns = @JoinColumn(name = "order_id"))
78 | @OrderColumn(name = "item_pos")
79 | @Fetch(FetchMode.SUBSELECT)
80 | private List orderItems = new ArrayList<>();
81 |
82 | @Override
83 | public UUID getId() {
84 | return id;
85 | }
86 |
87 | public Order setId(UUID id) {
88 | this.id = id;
89 | return this;
90 | }
91 |
92 | public BigDecimal getTotalPrice() {
93 | return totalPrice;
94 | }
95 |
96 | public Customer getCustomer() {
97 | return customer;
98 | }
99 |
100 | public List getOrderItems() {
101 | return Collections.unmodifiableList(orderItems);
102 | }
103 |
104 | public BigDecimal subTotal() {
105 | BigDecimal subTotal = BigDecimal.ZERO;
106 | for (OrderItem oi : orderItems) {
107 | subTotal = subTotal.add(oi.totalCost());
108 | }
109 | return subTotal;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/roach-data-jpa-orders/src/main/java/io/roach/data/jpa/domain/OrderItem.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jpa.domain;
2 |
3 | import java.math.BigDecimal;
4 | import java.util.function.Consumer;
5 |
6 | import jakarta.persistence.Column;
7 | import jakarta.persistence.Embeddable;
8 | import jakarta.persistence.FetchType;
9 | import jakarta.persistence.JoinColumn;
10 | import jakarta.persistence.ManyToOne;
11 |
12 | @Embeddable
13 | public class OrderItem {
14 | public static final class Builder {
15 | private final Order.Builder parentBuilder;
16 |
17 | private final Consumer callback;
18 |
19 | private int quantity;
20 |
21 | private BigDecimal unitPrice;
22 |
23 | private Product product;
24 |
25 | Builder(Order.Builder parentBuilder, Consumer callback) {
26 | this.parentBuilder = parentBuilder;
27 | this.callback = callback;
28 | }
29 |
30 | public Builder withQuantity(int quantity) {
31 | this.quantity = quantity;
32 | return this;
33 | }
34 |
35 | public Builder withUnitPrice(BigDecimal unitPrice) {
36 | this.unitPrice = unitPrice;
37 | return this;
38 | }
39 |
40 | public Builder withProduct(Product product) {
41 | this.product = product;
42 | return this;
43 | }
44 |
45 | public Order.Builder then() {
46 | if (this.unitPrice == null) {
47 | this.unitPrice = product.getPrice();
48 | }
49 |
50 | OrderItem orderItem = new OrderItem();
51 | orderItem.product = this.product;
52 | orderItem.unitPrice = this.unitPrice;
53 | orderItem.quantity = this.quantity;
54 |
55 | callback.accept(orderItem);
56 |
57 | return parentBuilder;
58 | }
59 | }
60 |
61 | @Column(nullable = false, updatable = false)
62 | private int quantity;
63 |
64 | @Column(name = "unit_price", nullable = false, updatable = false)
65 | private BigDecimal unitPrice;
66 |
67 | @ManyToOne(fetch = FetchType.LAZY) // Default fetch type is EAGER for @ManyToOne
68 | @JoinColumn(name = "product_id", updatable = false)
69 | private Product product;
70 |
71 | public int getQuantity() {
72 | return quantity;
73 | }
74 |
75 | public BigDecimal getUnitPrice() {
76 | return unitPrice;
77 | }
78 |
79 | public Product getProduct() {
80 | return product;
81 | }
82 |
83 | public BigDecimal totalCost() {
84 | if (unitPrice == null) {
85 | throw new IllegalStateException("unitPrice is null");
86 | }
87 | return unitPrice.multiply(new BigDecimal(quantity));
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/roach-data-jpa-orders/src/main/java/io/roach/data/jpa/domain/Product.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jpa.domain;
2 |
3 | import java.math.BigDecimal;
4 | import java.util.UUID;
5 |
6 | import jakarta.persistence.Column;
7 | import jakarta.persistence.Entity;
8 | import jakarta.persistence.GeneratedValue;
9 | import jakarta.persistence.GenerationType;
10 | import jakarta.persistence.Id;
11 | import jakarta.persistence.Table;
12 |
13 | @Entity
14 | @Table(name = "products")
15 | public class Product extends AbstractEntity {
16 | public static Builder builder() {
17 | return new Builder();
18 | }
19 |
20 | public static final class Builder {
21 | private String name;
22 |
23 | private String sku;
24 |
25 | private BigDecimal price;
26 |
27 | private int quantity;
28 |
29 | private Builder() {
30 | }
31 |
32 | public Builder withName(String name) {
33 | this.name = name;
34 | return this;
35 | }
36 |
37 | public Builder withSku(String sku) {
38 | this.sku = sku;
39 | return this;
40 | }
41 |
42 | public Builder withPrice(BigDecimal price) {
43 | this.price = price;
44 | return this;
45 | }
46 |
47 | public Builder withQuantity(int quantity) {
48 | this.quantity = quantity;
49 | return this;
50 | }
51 |
52 |
53 | public Product build() {
54 | Product product = new Product();
55 | product.name = this.name;
56 | product.sku = this.sku;
57 | product.price = this.price;
58 | product.inventory = this.quantity;
59 | return product;
60 | }
61 | }
62 |
63 | @Id
64 | @GeneratedValue(strategy = GenerationType.AUTO)
65 | private UUID id;
66 |
67 | @Column(length = 128, nullable = false)
68 | private String name;
69 |
70 | @Column(length = 128, nullable = false, unique = true)
71 | private String sku;
72 |
73 | @Column(length = 25, nullable = false)
74 | private BigDecimal price;
75 |
76 | @Column(nullable = false)
77 | private int inventory;
78 |
79 | @Override
80 | public UUID getId() {
81 | return id;
82 | }
83 |
84 | public String getName() {
85 | return name;
86 | }
87 |
88 | public BigDecimal getPrice() {
89 | return price;
90 | }
91 |
92 | public String getSku() {
93 | return sku;
94 | }
95 |
96 | public void setPrice(BigDecimal price) {
97 | this.price = price;
98 | }
99 |
100 | public int addInventoryQuantity(int qty) {
101 | this.inventory += qty;
102 | return inventory;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/roach-data-jpa-orders/src/main/java/io/roach/data/jpa/experimental/QueryInterceptor.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jpa.experimental;
2 |
3 | import java.util.ArrayDeque;
4 | import java.util.Arrays;
5 | import java.util.Deque;
6 | import java.util.Map;
7 | import java.util.regex.Matcher;
8 | import java.util.regex.Pattern;
9 |
10 | import org.hibernate.engine.jdbc.internal.FormatStyle;
11 | import org.hibernate.engine.jdbc.internal.Formatter;
12 | import org.hibernate.resource.jdbc.spi.StatementInspector;
13 | import org.slf4j.Logger;
14 | import org.slf4j.LoggerFactory;
15 |
16 | /**
17 | * A statement interceptor that applies various transformations on Hibernate generated SQL
18 | * before passed to the DB. Interception is triggered by semantic tokens detected in SQL
19 | * hint comment prefixes. The comment itself names the token and the transformation to
20 | * apply, or many transformations in a deterministic order.
21 | *
22 | * This is to showcase how JPA/Hibernate SQL rewrites can be done to inject things like
23 | * add optimizer join hints and other elements without changing any entity mappings or
24 | * JPQL queries.
25 | *
26 | * @author Kai Niemi
27 | */
28 | public class QueryInterceptor implements StatementInspector {
29 | @FunctionalInterface
30 | private interface Transformation {
31 | String transform(String sql);
32 | }
33 |
34 | private static final Map transformationMap
35 | = Map.of("replaceJoinWithInnerJoin",
36 | sql -> {
37 | return sql.replaceAll("join", "inner join");
38 | }, "appendForUpdate", sql -> {
39 | return sql + " FOR UPDATE";
40 | }, "appendForShare", sql -> {
41 | return sql + " FOR SHARE";
42 | });
43 |
44 | private static final Logger logger = LoggerFactory.getLogger(QueryInterceptor.class);
45 |
46 | private static final Pattern SQL_COMMENT = Pattern.compile("/\\*(.*?)\\*/\\s*");
47 |
48 | private static final Formatter FORMATTER_BASIC = FormatStyle.BASIC.getFormatter();
49 |
50 | private static final Formatter FORMATTER_HIGHLIGHT = FormatStyle.HIGHLIGHT.getFormatter();
51 |
52 | @Override
53 | public String inspect(String sql) {
54 | Matcher m = SQL_COMMENT.matcher(sql);
55 | if (m.find()) {
56 | // Extract semantic token
57 | String comment = m.group(1).trim();
58 | // Strip the comment prefix
59 | sql = m.replaceAll("");
60 | // Stack of transformations
61 | Deque stack = new ArrayDeque<>();
62 | stack.push(sql);
63 | // Split by comma and transform in-order
64 | Arrays.stream(comment.split(","))
65 | .forEachOrdered(token -> {
66 | if (transformationMap.containsKey(token)) {
67 | String result = transformationMap.get(token).transform(stack.pop());
68 | stack.push(result);
69 | }
70 | });
71 | // Last item is the final SQL
72 | sql = stack.pop();
73 | }
74 |
75 | if (logger.isTraceEnabled()) {
76 | logger.trace(FORMATTER_HIGHLIGHT.format(FORMATTER_BASIC.format(sql)));
77 | }
78 |
79 | return sql;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/roach-data-jpa-orders/src/main/java/io/roach/data/jpa/repository/CustomerRepository.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jpa.repository;
2 |
3 | import java.util.Optional;
4 | import java.util.UUID;
5 |
6 | import org.springframework.data.jpa.repository.JpaRepository;
7 | import org.springframework.stereotype.Repository;
8 |
9 | import io.roach.data.jpa.domain.Customer;
10 |
11 | @Repository
12 | public interface CustomerRepository extends JpaRepository {
13 | Optional findByUserName(String userName);
14 | }
15 |
--------------------------------------------------------------------------------
/roach-data-jpa-orders/src/main/java/io/roach/data/jpa/repository/OrderRepository.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jpa.repository;
2 |
3 | import java.util.List;
4 | import java.util.Optional;
5 | import java.util.UUID;
6 |
7 | import org.springframework.data.jpa.repository.JpaRepository;
8 | import org.springframework.data.jpa.repository.Modifying;
9 | import org.springframework.data.jpa.repository.Query;
10 | import org.springframework.data.jpa.repository.QueryHints;
11 | import org.springframework.data.repository.query.Param;
12 | import org.springframework.stereotype.Repository;
13 |
14 | import jakarta.persistence.QueryHint;
15 |
16 | import io.roach.data.jpa.domain.Order;
17 |
18 | @Repository
19 | public interface OrderRepository extends JpaRepository {
20 | @Modifying
21 | @Query(value = "delete from order_items where 1=1", nativeQuery = true)
22 | void deleteAllOrderItems();
23 |
24 | @Query(value = "from Order o "
25 | + "join fetch o.customer c where o.id=:id")
26 | Optional findOrderById(@Param("id") UUID id);
27 |
28 | @Query(value = "from Order o "
29 | + "join fetch o.customer c")
30 | List findAllOrders();
31 |
32 | @Query(value = "from Order o "
33 | + "join fetch o.customer "
34 | + "join fetch o.orderItems oi "
35 | + "join fetch oi.product")
36 | @QueryHints({
37 | @QueryHint(name = "org.hibernate.comment", value = "replaceJoinWithInnerJoin,appendForShare")
38 | })
39 | List findAllOrderDetails();
40 |
41 | @Query(value = "from Order o "
42 | + "join fetch o.customer c "
43 | + "where c.userName=:userName")
44 | List findOrdersByUserName(@Param("userName") String userName);
45 | }
46 |
--------------------------------------------------------------------------------
/roach-data-jpa-orders/src/main/java/io/roach/data/jpa/repository/ProductRepository.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jpa.repository;
2 |
3 | import java.util.Optional;
4 | import java.util.UUID;
5 |
6 | import org.springframework.data.jpa.repository.JpaRepository;
7 | import org.springframework.data.jpa.repository.Lock;
8 | import org.springframework.data.jpa.repository.Query;
9 | import org.springframework.data.repository.query.Param;
10 | import org.springframework.stereotype.Repository;
11 |
12 | import jakarta.persistence.LockModeType;
13 |
14 | import io.roach.data.jpa.domain.Product;
15 |
16 | @Repository
17 | public interface ProductRepository extends JpaRepository {
18 | @Lock(LockModeType.PESSIMISTIC_WRITE)
19 | Optional findProductBySku(String sku);
20 |
21 | @Query("select p from Product p where p.sku=:sku")
22 | Optional findProductBySkuNoLock(@Param("sku") String sku);
23 | }
24 |
--------------------------------------------------------------------------------
/roach-data-jpa-orders/src/main/java/io/roach/data/jpa/service/OrderSystem.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jpa.service;
2 |
3 | import java.math.BigDecimal;
4 | import java.util.List;
5 | import java.util.UUID;
6 |
7 | import io.roach.data.jpa.domain.Order;
8 | import io.roach.data.jpa.domain.Product;
9 |
10 | public interface OrderSystem {
11 | void clearAll();
12 |
13 | void createProductInventory();
14 |
15 | void createCustomers();
16 |
17 | List createOrders();
18 |
19 | List listAllOrders();
20 |
21 | List listAllOrderDetails();
22 |
23 | Order findOrderById(UUID id);
24 |
25 | Product findProductBySku(String sku);
26 |
27 | BigDecimal getTotalOrderPrice();
28 |
29 | void removeOrders();
30 | }
31 |
--------------------------------------------------------------------------------
/roach-data-jpa-orders/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | #############################
2 | # Spring boot properties
3 | # http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
4 | #############################
5 | spring:
6 | profiles:
7 | active:
8 | output:
9 | ansi:
10 | enabled: ALWAYS
11 | flyway:
12 | enabled: true
13 | connect-retries: 15
14 | baseline-on-migrate: true
15 | clean-disabled: false
16 | jpa:
17 | open-in-view: false
18 | properties:
19 | hibernate:
20 | use_sql_comments: true
21 | connection:
22 | provider_disables_autocommit: false
23 | jdbc:
24 | lob:
25 | non_contextual_creation: true
26 | batch_size: 128
27 | fetch_size: 128
28 | batch_versioned_data: true
29 | order_inserts: true
30 | order_updates: true
31 | hql:
32 | bulk_id_strategy: org.hibernate.hql.spi.id.inline.CteValuesListBulkIdStrategy
33 | cache:
34 | use_minimal_puts: true
35 | use_second_level_cache: false
36 | session_factory:
37 | statement_inspector: io.roach.data.jpa.experimental.QueryInterceptor
38 | datasource:
39 | url: jdbc:postgresql://localhost:26257/defaultdb?sslmode=disable
40 | driver-class-name: org.postgresql.Driver
41 | username: root
42 | password:
43 | hikari:
44 | connection-test-query: SELECT 1
45 | maximum-pool-size: 20
46 | minimum-idle: 0
47 | auto-commit: true
48 | transaction-isolation: TRANSACTION_SERIALIZABLE
49 | initialization-fail-timeout: -1
50 | #############################
51 | logging:
52 | pattern:
53 | console: "%clr(%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) %clr([%logger{39}]){cyan} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}"
54 |
--------------------------------------------------------------------------------
/roach-data-jpa-orders/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 | ^__^
2 | (oo)\_______
3 | (__)\ )\/\ CockroachDB JPA Demo Sandbox ${application.formatted-version}
4 | ||----w | powered by Spring Boot ${spring-boot.formatted-version}
5 | || ||
6 |
--------------------------------------------------------------------------------
/roach-data-jpa-orders/src/main/resources/db/migration/V1_1__create.sql:
--------------------------------------------------------------------------------
1 | create table customers
2 | (
3 | id uuid not null default gen_random_uuid(),
4 | first_name varchar(45),
5 | last_name varchar(45),
6 | user_name varchar(15) not null,
7 | primary key (id)
8 | );
9 |
10 | create table order_items
11 | (
12 | order_id uuid not null,
13 | product_id uuid not null,
14 | quantity int not null,
15 | unit_price numeric(19, 2) not null,
16 | item_pos int not null,
17 | primary key (order_id, item_pos)
18 | );
19 |
20 | create table orders
21 | (
22 | id uuid not null default gen_random_uuid(),
23 | customer_id uuid not null,
24 | total_price numeric(19, 2) not null,
25 | tags string(128) null,
26 |
27 | primary key (id)
28 | );
29 |
30 | create table products
31 | (
32 | id uuid not null default gen_random_uuid(),
33 | inventory int not null,
34 | name string(128) not null,
35 | price numeric(19, 2) not null,
36 | sku string(128) not null,
37 | primary key (id)
38 | );
39 |
40 | alter table products
41 | add constraint check_product_positive_inventory check (products.inventory >= 0);
42 |
43 | alter table if exists customers
44 | add constraint uc_user_name unique (user_name);
45 |
46 | alter table if exists product
47 | add constraint uc_product_sku unique (sku);
48 |
49 | alter table if exists order_items
50 | add constraint fk_order_item_ref_product
51 | foreign key (product_id)
52 | references products;
53 |
54 | alter table if exists order_items
55 | add constraint fk_order_item_ref_order
56 | foreign key (order_id)
57 | references orders;
58 |
59 | alter table if exists orders
60 | add constraint fk_order_ref_customer
61 | foreign key (customer_id)
62 | references customers;
63 |
64 | -- Foreign key indexes
65 | create index fk_order_item_ref_product_idx on order_items (product_id);
66 | create index fk_order_ref_customer_idx on orders (customer_id);
--------------------------------------------------------------------------------
/roach-data-jpa-orders/src/main/resources/logback-spring.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | true
7 |
8 | %d{HH:mm:ss.SSS} [%-15thread] %highlight(%-5level) %cyan(%logger{15}) %msg %n
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/roach-data-jpa/README.md:
--------------------------------------------------------------------------------
1 | # Roach Demo Data :: JPA/Hibernate
2 |
3 | A CockroachDB Spring Boot Demo using [Spring Data JPA](https://spring.io/projects/spring-data-jpa)
4 | with Hibernate for data access.
5 |
6 | Spring Data JPA provides a powerful model for using ORM frameworks such as Hibernate. It's considered a good
7 | fit for data access logic that involve large, complex domain model mapping. Spring Data JPA essentially
8 | removes the need for application code to bind directly to Hibernate APIs. It also unlocks many advanced
9 | features of JPA.
10 |
11 |
12 |
--------------------------------------------------------------------------------
/roach-data-jpa/pom.xml:
--------------------------------------------------------------------------------
1 |
4 | 4.0.0
5 |
6 | io.roach.data
7 | roach-data-2x-parent
8 | 1.0.0-SNAPSHOT
9 | ../roach-data-2x-parent
10 |
11 |
12 | roach-data-jpa
13 |
14 |
15 |
16 | org.springframework.boot
17 | spring-boot-starter-web
18 |
19 |
20 | org.springframework.boot
21 | spring-boot-starter-jetty
22 |
23 |
24 | org.springframework.boot
25 | spring-boot-starter-hateoas
26 |
27 |
28 | org.springframework.boot
29 | spring-boot-starter-jdbc
30 |
31 |
32 | org.springframework.boot
33 | spring-boot-starter-data-jdbc
34 |
35 |
36 | org.springframework.boot
37 | spring-boot-starter-data-jpa
38 |
39 |
40 | org.springframework.data
41 | spring-data-commons
42 |
43 |
44 | org.liquibase
45 | liquibase-core
46 | runtime
47 |
48 |
49 | org.postgresql
50 | postgresql
51 | provided
52 |
53 |
54 |
55 |
56 | roach-data-jpa
57 |
58 |
59 | org.springframework.boot
60 | spring-boot-maven-plugin
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/roach-data-jpa/src/main/java/io/roach/data/jpa/Account.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jpa;
2 |
3 | import java.math.BigDecimal;
4 |
5 | import javax.persistence.*;
6 |
7 | @Entity
8 | @Table(name = "account")
9 | public class Account {
10 | @Id
11 | @Column
12 | @GeneratedValue(strategy = GenerationType.IDENTITY)
13 | private Long id;
14 |
15 | @Column(length = 128, nullable = false, unique = true)
16 | private String name;
17 |
18 | @Column(length = 25, nullable = false)
19 | @Enumerated(EnumType.STRING)
20 | private AccountType type;
21 |
22 | @Column(length = 25, nullable = false)
23 | private BigDecimal balance;
24 |
25 | public Long getId() {
26 | return id;
27 | }
28 |
29 | public String getName() {
30 | return name;
31 | }
32 |
33 | public AccountType getType() {
34 | return type;
35 | }
36 |
37 | public BigDecimal getBalance() {
38 | return balance;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/roach-data-jpa/src/main/java/io/roach/data/jpa/AccountModel.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jpa;
2 |
3 | import java.math.BigDecimal;
4 |
5 | import org.springframework.hateoas.RepresentationModel;
6 | import org.springframework.hateoas.server.core.Relation;
7 |
8 | @Relation(value = "account", collectionRelation = "accounts")
9 | public class AccountModel extends RepresentationModel {
10 | private String name;
11 |
12 | private AccountType type;
13 |
14 | private BigDecimal balance;
15 |
16 | public String getName() {
17 | return name;
18 | }
19 |
20 | public void setName(String name) {
21 | this.name = name;
22 | }
23 |
24 | public AccountType getType() {
25 | return type;
26 | }
27 |
28 | public void setType(AccountType type) {
29 | this.type = type;
30 | }
31 |
32 | public BigDecimal getBalance() {
33 | return balance;
34 | }
35 |
36 | public void setBalance(BigDecimal balance) {
37 | this.balance = balance;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/roach-data-jpa/src/main/java/io/roach/data/jpa/AccountRepository.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jpa;
2 |
3 | import java.math.BigDecimal;
4 |
5 | import javax.persistence.LockModeType;
6 |
7 | import org.springframework.data.jpa.repository.JpaRepository;
8 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
9 | import org.springframework.data.jpa.repository.Lock;
10 | import org.springframework.data.jpa.repository.Modifying;
11 | import org.springframework.data.jpa.repository.Query;
12 | import org.springframework.stereotype.Repository;
13 | import org.springframework.transaction.annotation.Transactional;
14 |
15 | import static org.springframework.transaction.annotation.Propagation.MANDATORY;
16 |
17 | @Repository
18 | @Transactional(propagation = MANDATORY)
19 | public interface AccountRepository extends JpaRepository, JpaSpecificationExecutor {
20 | @Query(value = "select balance from Account where id=?1")
21 | @Lock(LockModeType.PESSIMISTIC_READ)
22 | BigDecimal getBalance(Long id);
23 |
24 | @Modifying
25 | @Query("update Account set balance = balance + ?2 where id=?1")
26 | void updateBalance(Long id, BigDecimal balance);
27 | }
28 |
--------------------------------------------------------------------------------
/roach-data-jpa/src/main/java/io/roach/data/jpa/AccountType.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jpa;
2 |
3 | public enum AccountType {
4 | asset,
5 | expense
6 | }
7 |
--------------------------------------------------------------------------------
/roach-data-jpa/src/main/java/io/roach/data/jpa/NegativeBalanceException.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.jpa;
2 |
3 | import org.springframework.dao.DataIntegrityViolationException;
4 | import org.springframework.http.HttpStatus;
5 | import org.springframework.web.bind.annotation.ResponseStatus;
6 |
7 | @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Negative balance")
8 | public class NegativeBalanceException extends DataIntegrityViolationException {
9 | public NegativeBalanceException(String message) {
10 | super(message);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/roach-data-jpa/src/main/resources/application-dev.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | datasource:
3 | url: jdbc:postgresql://192.168.1.99:26257/roach_data?sslmode=disable
4 | username: root
5 | password:
6 |
--------------------------------------------------------------------------------
/roach-data-jpa/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | ########################
2 | # Spring boot properties
3 | # http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
4 | ########################
5 |
6 | spring:
7 | output:
8 | ansi:
9 | enabled: ALWAYS
10 |
11 | liquibase:
12 | change-log: classpath:db/changelog-master.xml
13 | default-schema:
14 | drop-first: false
15 | contexts: crdb
16 | enabled: true
17 |
18 | datasource:
19 | url: jdbc:postgresql://localhost:26257/roach_data?sslmode=disable
20 | driver-class-name: org.postgresql.Driver
21 | username: root
22 | password:
23 | hikari:
24 | connection-test-query: SELECT 1
25 | maximum-pool-size: 50
26 | minimum-idle: 20
27 |
28 | jpa:
29 | open-in-view: false
30 | properties:
31 | hibernate:
32 | dialect: org.hibernate.dialect.CockroachDB201Dialect
33 | show-sql: false
34 |
35 | server:
36 | port: 9090
--------------------------------------------------------------------------------
/roach-data-jpa/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 | ^__^
2 | (oo)\_______
3 | (__)\ )\/\ CockroachDB on Spring Data / JPA ${application.formatted-version}
4 | ||----w | powered by Spring Boot ${spring-boot.formatted-version}
5 | || ||
6 |
--------------------------------------------------------------------------------
/roach-data-jpa/src/main/resources/db/changelog-master.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 | ANY
12 |
13 |
14 |
15 |
16 |
17 | 1
18 | Alice
19 |
20 | asset
21 |
22 |
23 | 2
24 | Bob
25 |
26 | expense
27 |
28 |
29 | 3
30 | Bobby Tables
31 |
32 | asset
33 |
34 |
35 | 4
36 | Doris
37 |
38 | expense
39 |
40 |
41 |
--------------------------------------------------------------------------------
/roach-data-jpa/src/main/resources/db/create.sql:
--------------------------------------------------------------------------------
1 | create table account
2 | (
3 | id int not null primary key default unique_rowid(),
4 | balance numeric(19, 2) not null,
5 | name varchar(128) not null,
6 | type varchar(25) not null
7 | );
8 |
--------------------------------------------------------------------------------
/roach-data-jpa/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/roach-data-json/README.md:
--------------------------------------------------------------------------------
1 | # Roach Demo Data :: JPA/Hibernate with JSONB
2 |
3 | A CockroachDB Spring Boot Demo using [Spring Data JPA](https://spring.io/projects/spring-data-jpa)
4 | with Hibernate for data access. Demonstrates various JSONB column type mappings with inverted
5 | and computed indexes.
--------------------------------------------------------------------------------
/roach-data-json/pom.xml:
--------------------------------------------------------------------------------
1 |
4 | 4.0.0
5 |
6 | io.roach.data
7 | roach-data-2x-parent
8 | 1.0.0-SNAPSHOT
9 | ../roach-data-2x-parent
10 |
11 |
12 | roach-data-json
13 |
14 |
15 |
16 | org.springframework.boot
17 | spring-boot-starter-web
18 |
19 |
20 | org.springframework.boot
21 | spring-boot-starter-jetty
22 |
23 |
24 | org.springframework.boot
25 | spring-boot-starter-hateoas
26 |
27 |
28 | org.springframework.boot
29 | spring-boot-starter-jdbc
30 |
31 |
32 | org.springframework.boot
33 | spring-boot-starter-data-jdbc
34 |
35 |
36 | org.springframework.boot
37 | spring-boot-starter-data-jpa
38 |
39 |
40 | org.liquibase
41 | liquibase-core
42 | runtime
43 |
44 |
45 | org.springframework.data
46 | spring-data-commons
47 |
48 |
49 | org.postgresql
50 | postgresql
51 | provided
52 |
53 |
54 | net.ttddyy
55 | datasource-proxy
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | org.springframework.boot
64 | spring-boot-starter-test
65 | test
66 |
67 |
68 | org.junit.jupiter
69 | junit-jupiter-engine
70 | test
71 |
72 |
73 |
74 |
75 | roach-data-jpa
76 |
77 |
78 | org.springframework.boot
79 | spring-boot-maven-plugin
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/java/io/roach/data/json/DataSourceConfig.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json;
2 |
3 | import java.util.concurrent.TimeUnit;
4 |
5 | import javax.sql.DataSource;
6 |
7 | import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.Configuration;
10 | import org.springframework.context.annotation.Primary;
11 |
12 | import net.ttddyy.dsproxy.listener.ChainListener;
13 | import net.ttddyy.dsproxy.listener.DataSourceQueryCountListener;
14 | import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
15 | import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
16 |
17 | @Configuration
18 | public class DataSourceConfig {
19 | @Bean
20 | @Primary
21 | public DataSource dataSource(DataSourceProperties properties) {
22 | ChainListener listener = new ChainListener();
23 | listener.addListener(new DataSourceQueryCountListener());
24 | return ProxyDataSourceBuilder
25 | .create(properties.initializeDataSourceBuilder().build())
26 | .name("SQL-Trace")
27 | .listener(listener)
28 | .asJson()
29 | .countQuery()
30 | .logQueryBySlf4j(SLF4JLogLevel.DEBUG, "io.roach.sql_trace")
31 | .logSlowQueryBySlf4j(50, TimeUnit.MILLISECONDS)
32 | // .multiline()
33 | .build();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/java/io/roach/data/json/JsonApplication.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.boot.WebApplicationType;
6 | import org.springframework.boot.autoconfigure.SpringBootApplication;
7 | import org.springframework.boot.builder.SpringApplicationBuilder;
8 | import org.springframework.context.annotation.EnableAspectJAutoProxy;
9 | import org.springframework.core.Ordered;
10 | import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
11 | import org.springframework.hateoas.config.EnableHypermediaSupport;
12 | import org.springframework.transaction.annotation.EnableTransactionManagement;
13 |
14 | @EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
15 | @EnableJdbcRepositories
16 | @EnableAspectJAutoProxy(proxyTargetClass = true)
17 | @EnableTransactionManagement(order = Ordered.LOWEST_PRECEDENCE - 1) // Bump up one level to enable extra advisors
18 | @SpringBootApplication
19 | public class JsonApplication {
20 | protected static final Logger logger = LoggerFactory.getLogger(JsonApplication.class);
21 |
22 | public static void main(String[] args) {
23 | new SpringApplicationBuilder(JsonApplication.class)
24 | .web(WebApplicationType.SERVLET)
25 | .run(args);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/java/io/roach/data/json/chat/ChatHistory.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.chat;
2 |
3 | import java.util.List;
4 | import java.util.UUID;
5 |
6 | import javax.persistence.*;
7 |
8 | import org.hibernate.annotations.Type;
9 | import org.hibernate.annotations.TypeDef;
10 |
11 | import com.fasterxml.jackson.databind.JsonNode;
12 |
13 | import io.roach.data.json.support.AbstractJsonDataType;
14 |
15 | @Entity
16 | @Table(name = "chat_history")
17 | @TypeDef(name = "jsonb-message", typeClass = ChatHistory.StringCollectionJsonType.class)
18 | public class ChatHistory {
19 | public static class StringCollectionJsonType extends AbstractJsonDataType {
20 | @Override
21 | public Class returnedClass() {
22 | return JsonNode.class;
23 | }
24 |
25 | @Override
26 | public boolean isCollectionType() {
27 | return true;
28 | }
29 | }
30 |
31 | @Id
32 | @GeneratedValue(strategy = GenerationType.AUTO)
33 | private UUID id;
34 |
35 | @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
36 | @JoinColumn(name = "parent_id")
37 | private ChatHistory parent;
38 |
39 | @Type(type = "jsonb-message")
40 | @Column(name = "messages")
41 | @Basic(fetch = FetchType.LAZY)
42 | private List messages;
43 |
44 | public UUID getId() {
45 | return id;
46 | }
47 |
48 | public ChatHistory getParent() {
49 | return parent;
50 | }
51 |
52 | public void setParent(ChatHistory parent) {
53 | this.parent = parent;
54 | }
55 |
56 | public List getMessages() {
57 | return messages;
58 | }
59 |
60 | public void setMessages(List messages) {
61 | this.messages = messages;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/java/io/roach/data/json/chat/ChatHistoryController.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.chat;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.data.domain.PageRequest;
5 | import org.springframework.data.domain.Pageable;
6 | import org.springframework.data.domain.Sort;
7 | import org.springframework.data.web.PageableDefault;
8 | import org.springframework.data.web.PagedResourcesAssembler;
9 | import org.springframework.hateoas.CollectionModel;
10 | import org.springframework.hateoas.EntityModel;
11 | import org.springframework.hateoas.PagedModel;
12 | import org.springframework.hateoas.RepresentationModel;
13 | import org.springframework.hateoas.server.SimpleRepresentationModelAssembler;
14 | import org.springframework.http.HttpEntity;
15 | import org.springframework.http.HttpStatus;
16 | import org.springframework.http.ResponseEntity;
17 | import org.springframework.transaction.annotation.Transactional;
18 | import org.springframework.web.bind.annotation.GetMapping;
19 | import org.springframework.web.bind.annotation.RestController;
20 |
21 | import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
22 | import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
23 | import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW;
24 |
25 | @RestController
26 | public class ChatHistoryController {
27 | @Autowired
28 | private ChatHistoryRepository chatHistoryRepository;
29 |
30 | @Autowired
31 | private PagedResourcesAssembler pagedResourcesAssembler;
32 |
33 | @GetMapping
34 | public ResponseEntity> index() {
35 | RepresentationModel> index = new RepresentationModel<>();
36 |
37 | index.add(linkTo(methodOn(ChatHistoryController.class)
38 | .listChats(PageRequest.of(0, 5)))
39 | .withRel("chats"));
40 |
41 | return new ResponseEntity<>(index, HttpStatus.OK);
42 | }
43 |
44 | @GetMapping("/chathistory")
45 | @Transactional(propagation = REQUIRES_NEW)
46 | public HttpEntity>> listChats(
47 | @PageableDefault(size = 5, direction = Sort.Direction.ASC) Pageable page) {
48 | return ResponseEntity
49 | .ok(pagedResourcesAssembler.toModel(chatHistoryRepository.findAll(page), chatModelAssembler()));
50 | }
51 |
52 | private SimpleRepresentationModelAssembler chatModelAssembler() {
53 | return new SimpleRepresentationModelAssembler(){
54 | @Override
55 | public void addLinks(EntityModel resource) {
56 |
57 | }
58 |
59 | @Override
60 | public void addLinks(CollectionModel> resources) {
61 |
62 | }
63 | };
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/java/io/roach/data/json/chat/ChatHistoryRepository.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.chat;
2 |
3 | import java.util.UUID;
4 |
5 | import org.springframework.data.jpa.repository.JpaRepository;
6 | import org.springframework.stereotype.Repository;
7 |
8 | @Repository
9 | public interface ChatHistoryRepository extends JpaRepository {
10 | }
11 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/java/io/roach/data/json/department/Address.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.department;
2 |
3 | public class Address {
4 | public static class Builder {
5 | private final Address adddress = new Address();
6 |
7 | public Builder setAddress1(String address1) {
8 | adddress.address1 = address1;
9 | return this;
10 | }
11 |
12 | public Builder setAddress2(String address2) {
13 | adddress.address2 = address2;
14 | return this;
15 | }
16 |
17 | public Builder setCity(String city) {
18 | adddress.city = city;
19 | return this;
20 | }
21 |
22 | public Builder setPostcode(String postcode) {
23 | adddress.postcode = postcode;
24 | return this;
25 | }
26 |
27 | public Builder setCountry(String country) {
28 | adddress.country = country;
29 | return this;
30 | }
31 |
32 | public Address build() {
33 | return adddress;
34 | }
35 | }
36 |
37 | private String address1;
38 |
39 | private String address2;
40 |
41 | private String city;
42 |
43 | private String postcode;
44 |
45 | private String country;
46 |
47 | protected Address() {
48 | }
49 |
50 | public Address(String address1, String address2, String city, String postcode, String country) {
51 | this.address1 = address1;
52 | this.address2 = address2;
53 | this.city = city;
54 | this.postcode = postcode;
55 | this.country = country;
56 | }
57 |
58 | public String getAddress1() {
59 | return address1;
60 | }
61 |
62 | public String getAddress2() {
63 | return address2;
64 | }
65 |
66 | public String getCity() {
67 | return city;
68 | }
69 |
70 | public String getPostcode() {
71 | return postcode;
72 | }
73 |
74 | public String getCountry() {
75 | return country;
76 | }
77 |
78 | public void setAddress1(String address1) {
79 | this.address1 = address1;
80 | }
81 |
82 | public void setAddress2(String address2) {
83 | this.address2 = address2;
84 | }
85 |
86 | public void setCity(String city) {
87 | this.city = city;
88 | }
89 |
90 | public void setPostcode(String postcode) {
91 | this.postcode = postcode;
92 | }
93 |
94 | public void setCountry(String country) {
95 | this.country = country;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/java/io/roach/data/json/department/Department.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.department;
2 |
3 | import java.util.List;
4 | import java.util.UUID;
5 |
6 | import javax.persistence.*;
7 |
8 | import org.hibernate.annotations.Type;
9 | import org.hibernate.annotations.TypeDef;
10 |
11 | import io.roach.data.json.support.AbstractJsonDataType;
12 |
13 | @Entity
14 | @Table(name = "department")
15 | @TypeDef(name = "jsonb-users", typeClass = Department.UserCollectionJsonType.class, defaultForType = User.class)
16 | public class Department {
17 | public static class UserCollectionJsonType extends AbstractJsonDataType {
18 | @Override
19 | public Class returnedClass() {
20 | return User.class;
21 | }
22 |
23 | @Override
24 | public boolean isCollectionType() {
25 | return true;
26 | }
27 | }
28 |
29 | @Id
30 | @GeneratedValue(strategy = GenerationType.AUTO)
31 | private UUID id;
32 |
33 | @Type(type = "jsonb-users")
34 | @Column(name = "users")
35 | @Basic(fetch = FetchType.LAZY)
36 | private List users;
37 |
38 | public UUID getId() {
39 | return id;
40 | }
41 |
42 | public List getUsers() {
43 | return users;
44 | }
45 |
46 | public void setUsers(List users) {
47 | this.users = users;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/java/io/roach/data/json/department/DepartmentRepository.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.department;
2 |
3 | import java.util.UUID;
4 |
5 | import org.springframework.data.jpa.repository.JpaRepository;
6 | import org.springframework.stereotype.Repository;
7 |
8 | @Repository
9 | public interface DepartmentRepository extends JpaRepository {
10 | }
11 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/java/io/roach/data/json/department/User.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.department;
2 |
3 | public class User {
4 | private String userName;
5 |
6 | private byte[] password;
7 |
8 | private String firstName;
9 |
10 | private String lastName;
11 |
12 | private String telephone;
13 |
14 | private String email;
15 |
16 | private Address address;
17 |
18 | public String getUserName() {
19 | return userName;
20 | }
21 |
22 | public void setUserName(String userName) {
23 | this.userName = userName;
24 | }
25 |
26 | public byte[] getPassword() {
27 | return password;
28 | }
29 |
30 | public void setPassword(byte[] password) {
31 | this.password = password;
32 | }
33 |
34 | public String getFirstName() {
35 | return firstName;
36 | }
37 |
38 | public void setFirstName(String firstName) {
39 | this.firstName = firstName;
40 | }
41 |
42 | public String getLastName() {
43 | return lastName;
44 | }
45 |
46 | public void setLastName(String lastName) {
47 | this.lastName = lastName;
48 | }
49 |
50 | public String getTelephone() {
51 | return telephone;
52 | }
53 |
54 | public void setTelephone(String telephone) {
55 | this.telephone = telephone;
56 | }
57 |
58 | public String getEmail() {
59 | return email;
60 | }
61 |
62 | public void setEmail(String email) {
63 | this.email = email;
64 | }
65 |
66 | public Address getAddress() {
67 | return address;
68 | }
69 |
70 | public void setAddress(Address address) {
71 | this.address = address;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/java/io/roach/data/json/journal/AccountJournal.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.journal;
2 |
3 | import javax.persistence.DiscriminatorValue;
4 | import javax.persistence.Entity;
5 |
6 | import org.hibernate.annotations.TypeDef;
7 |
8 | @Entity
9 | @DiscriminatorValue("ACCOUNT")
10 | @TypeDef(name = "jsonb", typeClass = Account.JsonType.class, defaultForType = Account.class)
11 | public class AccountJournal extends Journal {
12 | }
13 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/java/io/roach/data/json/journal/AccountJournalRepository.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.journal;
2 |
3 | import java.math.BigDecimal;
4 | import java.util.List;
5 | import java.util.UUID;
6 |
7 | import org.springframework.data.jpa.repository.JpaRepository;
8 | import org.springframework.data.jpa.repository.Query;
9 | import org.springframework.data.repository.query.Param;
10 | import org.springframework.stereotype.Repository;
11 |
12 | @Repository
13 | public interface AccountJournalRepository extends JpaRepository {
14 | @Query(value = "SELECT * FROM journal WHERE event_type='ACCOUNT'"
15 | + " AND (payload ->> 'balance')::::decimal BETWEEN :lowerBound AND :upperBound",
16 | nativeQuery = true)
17 | List findWithBalanceBetween(
18 | @Param("lowerBound") BigDecimal lowerBound, @Param("upperBound") BigDecimal upperBound);
19 | }
20 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/java/io/roach/data/json/journal/AccountRepository.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.journal;
2 |
3 | import java.math.BigDecimal;
4 | import java.util.UUID;
5 |
6 | import org.springframework.data.jpa.repository.JpaRepository;
7 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
8 | import org.springframework.data.jpa.repository.Modifying;
9 | import org.springframework.data.jpa.repository.Query;
10 | import org.springframework.stereotype.Repository;
11 |
12 | @Repository
13 | public interface AccountRepository extends JpaRepository, JpaSpecificationExecutor {
14 | Account findByName(String name);
15 |
16 | @Query(value = "select balance from Account where id=?1")
17 | BigDecimal getBalance(UUID id);
18 |
19 | @Modifying
20 | @Query("update Account set balance = balance + ?2 where id=?1")
21 | void updateBalance(UUID id, BigDecimal balance);
22 | }
23 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/java/io/roach/data/json/journal/Journal.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.journal;
2 |
3 | import java.time.LocalDateTime;
4 |
5 | import javax.persistence.*;
6 |
7 | import org.hibernate.annotations.Type;
8 |
9 | @Entity
10 | @Table(name = "journal")
11 | @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
12 | @DiscriminatorColumn(
13 | name = "event_type",
14 | discriminatorType = DiscriminatorType.STRING,
15 | length = 15
16 | )
17 | public abstract class Journal {
18 | @Id
19 | @GeneratedValue(strategy = GenerationType.IDENTITY) // Computed column
20 | private String id;
21 |
22 | @Column(name = "tag", updatable = false)
23 | private String tag;
24 |
25 | @Basic
26 | @Column(name = "updated", updatable = false)
27 | private LocalDateTime updated;
28 |
29 | @Type(type = "jsonb")
30 | @Column(name = "payload", updatable = false)
31 | @Basic(fetch = FetchType.LAZY)
32 | private T event;
33 |
34 | @PrePersist
35 | protected void onCreate() {
36 | if (updated == null) {
37 | updated = LocalDateTime.now();
38 | }
39 | }
40 |
41 | public String getId() {
42 | return id;
43 | }
44 |
45 | public T getEvent() {
46 | return event;
47 | }
48 |
49 | public void setEvent(T payload) {
50 | this.event = payload;
51 | }
52 |
53 | public String getTag() {
54 | return tag;
55 | }
56 |
57 | public void setTag(String origin) {
58 | this.tag = origin;
59 | }
60 |
61 | public LocalDateTime getUpdated() {
62 | return updated;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/java/io/roach/data/json/journal/TransactionJournal.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.journal;
2 |
3 | import javax.persistence.DiscriminatorValue;
4 | import javax.persistence.Entity;
5 |
6 | import org.hibernate.annotations.TypeDef;
7 |
8 | @Entity
9 | @DiscriminatorValue("TRANSACTION")
10 | @TypeDef(name = "jsonb", typeClass = Transaction.JsonType.class, defaultForType = Transaction.class)
11 | public class TransactionJournal extends Journal {
12 | }
13 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/java/io/roach/data/json/journal/TransactionJournalRepository.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.journal;
2 |
3 | import java.math.BigDecimal;
4 | import java.util.List;
5 | import java.util.UUID;
6 |
7 | import org.springframework.data.jpa.repository.JpaRepository;
8 | import org.springframework.data.jpa.repository.Query;
9 | import org.springframework.data.repository.query.Param;
10 | import org.springframework.stereotype.Repository;
11 |
12 | @Repository
13 | public interface TransactionJournalRepository extends JpaRepository {
14 | @Query(value = "SELECT j FROM Journal j WHERE j.tag=:tag")
15 | List findByTag(@Param("tag") String tag);
16 |
17 | @Query(value = "SELECT * FROM journal WHERE event_type='TRANSACTION'"
18 | + " AND payload ->> 'transferDate' BETWEEN :startDate AND :endDate",
19 | nativeQuery = true)
20 | List findBetweenTransferDates(@Param("startDate") String startDate,
21 | @Param("endDate") String endDate);
22 |
23 | @Query(value =
24 | "WITH x AS(SELECT payload from journal where event_type='TRANSACTION' AND tag=:tag),"
25 | + "items AS(SELECT json_array_elements(payload->'items') as y FROM x) "
26 | + "SELECT sum((y->>'amount')::::decimal) FROM items",
27 | nativeQuery = true)
28 | BigDecimal sumTransactionLegAmounts(@Param("tag") String tag);
29 | }
30 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/java/io/roach/data/json/journal/TransactionRepository.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.journal;
2 |
3 | import java.util.UUID;
4 |
5 | import org.springframework.data.jpa.repository.JpaRepository;
6 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
7 | import org.springframework.stereotype.Repository;
8 |
9 | @Repository
10 | public interface TransactionRepository extends JpaRepository, JpaSpecificationExecutor {
11 | }
12 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/java/io/roach/data/json/support/LocalDateDeserializer.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.support;
2 |
3 | import java.io.IOException;
4 | import java.time.LocalDate;
5 |
6 | import com.fasterxml.jackson.core.JsonParser;
7 | import com.fasterxml.jackson.databind.DeserializationContext;
8 | import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
9 |
10 | public class LocalDateDeserializer extends StdDeserializer {
11 | private static final long serialVersionUID = 1L;
12 |
13 | protected LocalDateDeserializer() {
14 | super(LocalDate.class);
15 | }
16 |
17 |
18 | @Override
19 | public LocalDate deserialize(JsonParser jp, DeserializationContext ctxt)
20 | throws IOException {
21 | return LocalDate.parse(jp.readValueAs(String.class));
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/java/io/roach/data/json/support/LocalDateSerializer.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.support;
2 |
3 | import java.io.IOException;
4 | import java.time.LocalDate;
5 | import java.time.format.DateTimeFormatter;
6 |
7 | import com.fasterxml.jackson.core.JsonGenerator;
8 | import com.fasterxml.jackson.databind.SerializerProvider;
9 | import com.fasterxml.jackson.databind.ser.std.StdSerializer;
10 |
11 | public class LocalDateSerializer extends StdSerializer {
12 | private static final long serialVersionUID = 1L;
13 |
14 | public LocalDateSerializer() {
15 | super(LocalDate.class);
16 | }
17 |
18 | @Override
19 | public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider sp) throws IOException {
20 | gen.writeString(value.format(DateTimeFormatter.ISO_LOCAL_DATE));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | ########################
2 | # Spring boot properties
3 | # http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
4 | ########################
5 |
6 | spring:
7 | output:
8 | ansi:
9 | enabled: ALWAYS
10 |
11 | liquibase:
12 | change-log: classpath:db/changelog-master.xml
13 | default-schema:
14 | drop-first: false
15 | enabled: true
16 | contexts: crdb
17 |
18 | datasource:
19 | url: jdbc:postgresql://localhost:26257/roach_data?sslmode=disable
20 | driver-class-name: org.postgresql.Driver
21 | username: root
22 | password:
23 | hikari:
24 | connection-test-query: SELECT 1
25 | auto-commit: true
26 |
27 | jpa:
28 | open-in-view: false
29 | properties:
30 | hibernate:
31 | dialect: org.hibernate.dialect.CockroachDB201Dialect
32 | connection:
33 | provider_disables_autocommit: false
34 |
35 | server:
36 | port: 9090
37 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 | ^__^
2 | (oo)\_______
3 | (__)\ )\/\ CockroachDB on Spring ${application.formatted-version} - Exploring JSONB and inverted indexes
4 | ||----w | powered by Spring Boot ${spring-boot.formatted-version}
5 | || ||
6 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/resources/db/changelog-master.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | ANY
10 |
11 |
12 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/resources/db/create.sql:
--------------------------------------------------------------------------------
1 | -- drop table if exists databasechangelog cascade;
2 | -- drop table if exists databasechangeloglock cascade;
3 | -- drop table if exists account cascade;
4 | -- drop table if exists journal cascade;
5 | -- drop table if exists transaction cascade;
6 | -- drop table if exists transaction_item cascade;
7 |
8 | -- Stores immutable events in json format, mapped to entity types via JPA's single table inheritance model
9 | create table journal
10 | (
11 | id STRING PRIMARY KEY AS (payload->>'id') STORED, -- computed primary index column
12 | event_type varchar(15) not null, -- entity type discriminator
13 | payload json,
14 | tag varchar(64),
15 | updated timestamptz default clock_timestamp(),
16 | INVERTED INDEX event_payload (payload)
17 | );
18 |
19 | -- Event type always used
20 | create index idx_journal_main on journal (event_type, tag);
21 |
22 | create table account
23 | (
24 | id uuid not null default gen_random_uuid(),
25 | account_type varchar(10) not null,
26 | balance numeric(19, 2) not null,
27 | name varchar(64),
28 | updated timestamptz default clock_timestamp(),
29 | primary key (id)
30 | );
31 |
32 | create unique index uidx_account_name on account (name);
33 |
34 | create table transaction
35 | (
36 | id uuid not null default gen_random_uuid(),
37 | booking_date date not null,
38 | transfer_date date not null,
39 | primary key (id)
40 | );
41 |
42 | create table transaction_item
43 | (
44 | id uuid not null default gen_random_uuid(),
45 | amount numeric(19, 2) not null,
46 | running_balance numeric(19, 2) not null,
47 | note varchar(128),
48 | account_id uuid not null,
49 | transaction_id uuid not null,
50 | primary key (id)
51 | );
52 |
53 | alter table if exists transaction_item
54 | add constraint fk_id_ref_account
55 | foreign key (account_id)
56 | references account;
57 |
58 | alter table if exists transaction_item
59 | add constraint fk_id_ref_transaction
60 | foreign key (transaction_id)
61 | references transaction;
62 |
63 | create table department
64 | (
65 | id uuid not null default gen_random_uuid(),
66 | users jsonb
67 | );
68 |
69 | create table chat_history
70 | (
71 | id uuid not null default gen_random_uuid(),
72 | parent_id uuid,
73 | messages jsonb,
74 |
75 | INVERTED INDEX message_payload (messages)
76 | );
77 |
78 | INSERT INTO chat_history (messages) VALUES
79 | ('[
80 | {"title": "Sleeping Beauties", "genres": ["Fiction", "Thriller", "Horror"], "published": false},
81 | {"title": "The Dictator''s Handbook", "genres": ["Law", "Politics"], "authors": ["Bruce Bueno de Mesquita", "Alastair Smith"], "published": true}
82 | ]');
83 |
--------------------------------------------------------------------------------
/roach-data-json/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/roach-data-json/src/test/java/io/roach/data/jpa/AbstractIntegrationTest.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json;
2 |
3 | import org.junit.jupiter.api.MethodOrderer;
4 | import org.junit.jupiter.api.TestInstance;
5 | import org.junit.jupiter.api.TestMethodOrder;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 | import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
9 | import org.springframework.boot.test.context.SpringBootTest;
10 |
11 | @SpringBootTest
12 | @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
13 | @TestInstance(TestInstance.Lifecycle.PER_CLASS)
14 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
15 | public abstract class AbstractIntegrationTest {
16 | protected final Logger logger = LoggerFactory.getLogger(getClass());
17 | }
18 |
--------------------------------------------------------------------------------
/roach-data-json/src/test/java/io/roach/data/jpa/JacksonConfig.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 |
6 | import com.fasterxml.jackson.annotation.JsonInclude;
7 | import com.fasterxml.jackson.databind.DeserializationFeature;
8 | import com.fasterxml.jackson.databind.ObjectMapper;
9 | import com.fasterxml.jackson.databind.SerializationFeature;
10 |
11 | @Configuration
12 | public class JacksonConfig {
13 | @Bean
14 | public ObjectMapper objectMapper() {
15 | return new ObjectMapper()
16 | .enable(SerializationFeature.INDENT_OUTPUT)
17 | .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
18 | .enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
19 | .setSerializationInclusion(JsonInclude.Include.NON_NULL);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/roach-data-json/src/test/java/io/roach/data/jpa/chat/ChatHistoryRepositoryTest.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.chat;
2 |
3 | import java.io.IOException;
4 | import java.io.StringWriter;
5 | import java.util.Arrays;
6 | import java.util.Collections;
7 | import java.util.List;
8 |
9 | import org.junit.jupiter.api.Order;
10 | import org.junit.jupiter.api.Test;
11 | import org.springframework.beans.factory.annotation.Autowired;
12 | import org.springframework.test.annotation.Commit;
13 | import org.springframework.transaction.annotation.Propagation;
14 | import org.springframework.transaction.annotation.Transactional;
15 |
16 | import com.fasterxml.jackson.databind.JsonNode;
17 | import com.fasterxml.jackson.databind.ObjectMapper;
18 |
19 | import io.roach.data.json.AbstractIntegrationTest;
20 |
21 | public class ChatHistoryRepositoryTest extends AbstractIntegrationTest {
22 | @Autowired
23 | private ChatHistoryRepository chatHistoryRepository;
24 |
25 | @Autowired
26 | private ObjectMapper objectMapper;
27 |
28 | @Test
29 | @Transactional(propagation = Propagation.REQUIRES_NEW)
30 | @Commit
31 | @Order(1)
32 | public void whenCreatingChatHistory_thenSuccess() throws Exception {
33 | String data1 = "{\"title\": \"Sleeping Beauties\", \"genres\": [\"Fiction\", \"Thriller\", \"Horror\"], \"published\": false}";
34 | String data2 = "{\"title\": \"The Dictator''s Handbook\", \"genres\": [\"Law\", \"Politics\"], \"authors\": [\"Bruce Bueno de Mesquita\", \"Alastair Smith\"], \"published\": true}";
35 | String data3 = "{\"review\": \"A good book\", \"visitor\": \"alice\"}";
36 | String data4 = "{\"review\": \"A really good book\", \"visitor\": \"bob\"}";
37 |
38 | ChatHistory h1 = new ChatHistory();
39 | h1.setMessages(Arrays.asList(objectMapper.readTree(data1), objectMapper.readTree(data2)));
40 |
41 | ChatHistory h2 = new ChatHistory();
42 | h2.setParent(h1);
43 | h2.setMessages(Collections.singletonList(objectMapper.readTree(data3)));
44 |
45 | ChatHistory h3 = new ChatHistory();
46 | h3.setParent(h1);
47 | h3.setMessages(Collections.singletonList(objectMapper.readTree(data4)));
48 |
49 | chatHistoryRepository.saveAll(Arrays.asList(h1, h2, h3));
50 | }
51 |
52 | @Test
53 | @Transactional(propagation = Propagation.REQUIRES_NEW)
54 | @Commit
55 | @Order(2)
56 | public void whenRetrievingById_thenReturnStuff() {
57 | List chatHistory = chatHistoryRepository.findAll();
58 | chatHistory.forEach(h -> {
59 | List result = h.getMessages();
60 | result.forEach(jsonNode -> {
61 | StringWriter w = new StringWriter();
62 | try {
63 | objectMapper.writeValue(w, jsonNode);
64 | System.out.println(w);
65 | } catch (IOException e) {
66 | e.printStackTrace();
67 | }
68 | });
69 | });
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/roach-data-json/src/test/java/io/roach/data/jpa/department/DepartmentRepositoryTest.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.department;
2 |
3 | import java.nio.charset.StandardCharsets;
4 | import java.security.MessageDigest;
5 | import java.security.NoSuchAlgorithmException;
6 | import java.security.SecureRandom;
7 | import java.util.Collections;
8 | import java.util.List;
9 | import java.util.Locale;
10 | import java.util.Objects;
11 | import java.util.UUID;
12 |
13 | import org.junit.jupiter.api.Order;
14 | import org.junit.jupiter.api.Test;
15 | import org.springframework.beans.factory.annotation.Autowired;
16 | import org.springframework.test.annotation.Commit;
17 | import org.springframework.transaction.annotation.Propagation;
18 | import org.springframework.transaction.annotation.Transactional;
19 |
20 | import io.roach.data.json.AbstractIntegrationTest;
21 |
22 | import static org.junit.jupiter.api.Assertions.assertNotNull;
23 | import static org.junit.jupiter.api.Assertions.assertTrue;
24 |
25 | public class DepartmentRepositoryTest extends AbstractIntegrationTest {
26 | @Autowired
27 | private DepartmentRepository departmentRepository;
28 |
29 | private UUID id;
30 |
31 | public static byte[] hashSecret(String password) {
32 | try {
33 | byte[] salt = new byte[16];
34 | SecureRandom random = new SecureRandom();
35 | random.nextBytes(salt);
36 | MessageDigest md = MessageDigest.getInstance("SHA-512");
37 | md.update(salt);
38 | return md.digest(password.getBytes(StandardCharsets.UTF_8));
39 | } catch (NoSuchAlgorithmException e) {
40 | throw new IllegalStateException(e);
41 | }
42 | }
43 |
44 | @Test
45 | @Transactional(propagation = Propagation.REQUIRES_NEW)
46 | @Commit
47 | @Order(1)
48 | public void whenCreatingDepartmentUser_thenSuccess() {
49 | User customer = new User();
50 | customer.setUserName("alice");
51 | customer.setPassword(hashSecret("password"));
52 | customer.setFirstName("Alice");
53 | customer.setLastName("Alison");
54 | customer.setTelephone("555-5555");
55 | customer.setEmail("alice@roaches.io");
56 |
57 | Address address1 = new Address.Builder()
58 | .setAddress1("street1")
59 | .setAddress2("street2")
60 | .setCity("street2")
61 | .setCountry(Locale.US.getCountry())
62 | .setPostcode("street2").build();
63 | customer.setAddress(address1);
64 |
65 | Department department = new Department();
66 | department.setUsers(Collections.singletonList(customer));
67 | department = departmentRepository.save(department);
68 |
69 | assertNotNull(department);
70 | assertNotNull(department.getId());
71 |
72 | id = department.getId();
73 | }
74 |
75 | @Test
76 | @Transactional(propagation = Propagation.REQUIRES_NEW)
77 | @Commit
78 | @Order(2)
79 | public void whenRetrievingById_thenReturnAlice() {
80 | Department department = departmentRepository.getOne(id);
81 | List result = department.getUsers();
82 | assertTrue(result.size() > 0);
83 | assertTrue(result.stream()
84 | .map(User::getUserName)
85 | .anyMatch(name -> Objects.equals("alice", name)));
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/roach-data-json/src/test/java/io/roach/data/jpa/journal/AccountJournalTest.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.journal;
2 |
3 | import java.math.BigDecimal;
4 | import java.sql.Connection;
5 | import java.util.List;
6 |
7 | import org.junit.jupiter.api.Order;
8 | import org.junit.jupiter.api.Test;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.test.annotation.Commit;
11 | import org.springframework.test.annotation.Repeat;
12 | import org.springframework.transaction.annotation.Propagation;
13 | import org.springframework.transaction.annotation.Transactional;
14 |
15 | import io.roach.data.json.AbstractIntegrationTest;
16 |
17 | import static org.junit.jupiter.api.Assertions.*;
18 |
19 | public class AccountJournalTest extends AbstractIntegrationTest {
20 | @Autowired
21 | private AccountJournalRepository repository;
22 |
23 | @Test
24 | // @Transactional(propagation = Propagation.REQUIRES_NEW)
25 | // @Commit
26 | @Order(1)
27 | public void whenCreatingAccountEventInJournal_thenComputedKeyIsReturnedFromPayload() {
28 | Account account1 = Account.builder()
29 | .withGeneratedId()
30 | .withAccountType("asset")
31 | .withName("abc")
32 | .withBalance(BigDecimal.valueOf(250.00))
33 | .build();
34 | Account account2 = Account.builder()
35 | .withGeneratedId()
36 | .withAccountType("expense")
37 | .withName("def")
38 | .withBalance(BigDecimal.valueOf(500.00))
39 | .build();
40 |
41 | AccountJournal journal1 = new AccountJournal();
42 | journal1.setTag("asset");
43 | journal1.setEvent(account1);
44 | journal1 = repository.save(journal1);
45 |
46 | assertNotNull(journal1);
47 | assertEquals(account1.getId().toString(), journal1.getId());
48 |
49 | AccountJournal journal2 = new AccountJournal();
50 | journal2.setTag("expense");
51 | journal2.setEvent(account2);
52 | journal2 = repository.save(journal2);
53 |
54 | assertNotNull(journal2);
55 | assertEquals(account2.getId().toString(), journal2.getId());
56 | }
57 |
58 | @Test
59 | @Transactional(propagation = Propagation.REQUIRES_NEW)
60 | @Commit
61 | @Order(2)
62 | public void whenFindBalanceBetween_thenAtLeastOneJournalIsReturned() {
63 | List result = repository.findWithBalanceBetween(
64 | BigDecimal.valueOf(250.00), BigDecimal.valueOf(500.00));
65 | assertTrue(result.size() > 0);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/roach-data-json/src/test/java/io/roach/data/jpa/journal/AccountRepositoryTest.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.journal;
2 |
3 | import java.math.BigDecimal;
4 |
5 | import org.junit.jupiter.api.Assertions;
6 | import org.junit.jupiter.api.MethodOrderer;
7 | import org.junit.jupiter.api.Order;
8 | import org.junit.jupiter.api.Test;
9 | import org.junit.jupiter.api.TestMethodOrder;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.test.annotation.Commit;
12 | import org.springframework.transaction.annotation.Propagation;
13 | import org.springframework.transaction.annotation.Transactional;
14 |
15 | import io.roach.data.json.AbstractIntegrationTest;
16 |
17 | public class AccountRepositoryTest extends AbstractIntegrationTest {
18 | @Autowired
19 | private AccountRepository accountRepository;
20 |
21 | @Test
22 | @Transactional(propagation = Propagation.REQUIRES_NEW)
23 | @Commit
24 | @Order(3)
25 | public void testCreateAccount() {
26 | Account a = accountRepository.findByName("A");
27 | if (a != null) {
28 | accountRepository.delete(a);
29 | }
30 | accountRepository.flush();
31 |
32 | a = Account.builder()
33 | .withGeneratedId()
34 | .withName("A")
35 | .withBalance(BigDecimal.TEN)
36 | .withAccountType("X")
37 | .build();
38 | accountRepository.save(a);
39 | logger.info("Created {}", a);
40 | }
41 |
42 | @Test
43 | @Transactional(propagation = Propagation.REQUIRES_NEW)
44 | @Commit
45 | @Order(4)
46 | public void testRetrieveAccount() {
47 | Account a = accountRepository.findByName("A");
48 | Assertions.assertNotNull(a);
49 | Assertions.assertEquals(BigDecimal.TEN.setScale(2), a.getBalance());
50 | }
51 |
52 | @Test
53 | @Transactional(propagation = Propagation.REQUIRES_NEW)
54 | @Commit
55 | @Order(5)
56 | public void testUpdateAccount() {
57 | Account a = accountRepository.findByName("A");
58 | Assertions.assertNotNull(a);
59 | a.setBalance(BigDecimal.ZERO);
60 | }
61 |
62 | @Test
63 | @Transactional(propagation = Propagation.REQUIRES_NEW)
64 | @Commit
65 | @Order(6)
66 | public void testVerifyUpdate() {
67 | Account a = accountRepository.findByName("A");
68 | Assertions.assertEquals(BigDecimal.ZERO.setScale(2), a.getBalance());
69 | }
70 |
71 | @Test
72 | @Transactional(propagation = Propagation.REQUIRES_NEW)
73 | @Commit
74 | @Order(1)
75 | public void testDeleteAccount() {
76 | Account a = accountRepository.findByName("A");
77 | Assertions.assertNotNull(a);
78 | accountRepository.delete(a);
79 | }
80 |
81 | @Test
82 | @Transactional(propagation = Propagation.REQUIRES_NEW)
83 | @Commit
84 | @Order(2)
85 | public void testVerifyDeleted() {
86 | Account a = accountRepository.findByName("A");
87 | Assertions.assertNull(a);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/roach-data-json/src/test/java/io/roach/data/jpa/journal/ObjectMapperFormatTest.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.journal;
2 |
3 | import java.math.BigDecimal;
4 | import java.time.LocalDate;
5 |
6 | import org.junit.jupiter.api.Test;
7 |
8 | import com.fasterxml.jackson.annotation.JsonInclude;
9 | import com.fasterxml.jackson.core.JsonProcessingException;
10 | import com.fasterxml.jackson.databind.DeserializationFeature;
11 | import com.fasterxml.jackson.databind.ObjectMapper;
12 | import com.fasterxml.jackson.databind.SerializationFeature;
13 |
14 | public class ObjectMapperFormatTest {
15 | @Test
16 | public void testSerialisation() {
17 | Transaction transaction = Transaction.builder()
18 | .withGeneratedId()
19 | .withBookingDate(LocalDate.now().minusDays(2))
20 | .withTransferDate(LocalDate.now())
21 | .andItem()
22 | .withAccount(Account.builder()
23 | .withGeneratedId()
24 | .build())
25 | .withAmount(BigDecimal.valueOf(-50.00))
26 | .withNote("debit A")
27 | .then()
28 | .andItem()
29 | .withAccount(Account.builder()
30 | .withGeneratedId()
31 | .build())
32 | .withAmount(BigDecimal.valueOf(50.00))
33 | .withNote("credit A")
34 | .then()
35 | .build();
36 |
37 | ObjectMapper mapper = new ObjectMapper()
38 | .enable(SerializationFeature.INDENT_OUTPUT)
39 | .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
40 | .enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
41 | .setSerializationInclusion(JsonInclude.Include.NON_NULL);
42 | try {
43 | String s = mapper.writeValueAsString(transaction);
44 | System.out.println(s);
45 | } catch (JsonProcessingException e) {
46 | e.printStackTrace();
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/roach-data-json/src/test/java/io/roach/data/jpa/journal/TransactionRepositoryTest.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.json.journal;
2 |
3 | import java.math.BigDecimal;
4 | import java.time.LocalDate;
5 |
6 | import org.junit.jupiter.api.Order;
7 | import org.junit.jupiter.api.Test;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.test.annotation.Commit;
10 | import org.springframework.transaction.annotation.Propagation;
11 | import org.springframework.transaction.annotation.Transactional;
12 |
13 | import io.roach.data.json.AbstractIntegrationTest;
14 |
15 | import static org.junit.jupiter.api.Assertions.assertNotNull;
16 |
17 | public class TransactionRepositoryTest extends AbstractIntegrationTest {
18 | @Autowired
19 | private AccountRepository accountRepository;
20 |
21 | @Autowired
22 | private TransactionRepository transactionRepository;
23 |
24 | @Test
25 | @Transactional(propagation = Propagation.REQUIRES_NEW)
26 | @Commit
27 | @Order(1)
28 | public void cleanUp() {
29 | transactionRepository.deleteAll();
30 | accountRepository.deleteAll();
31 | }
32 |
33 | @Test
34 | @Transactional(propagation = Propagation.REQUIRES_NEW)
35 | @Commit
36 | @Order(2)
37 | public void whenCreatingTransaction_thenSuccess() {
38 | Account a = Account.builder()
39 | .withGeneratedId()
40 | .withName("A")
41 | .withBalance(BigDecimal.valueOf(50.00))
42 | .withAccountType("X")
43 | .build();
44 | a = accountRepository.save(a);
45 |
46 | Account b = Account.builder()
47 | .withGeneratedId()
48 | .withName("B")
49 | .withBalance(BigDecimal.valueOf(0.00))
50 | .withAccountType("X")
51 | .build();
52 | b = accountRepository.save(b);
53 |
54 | Transaction transaction = Transaction.builder()
55 | .withGeneratedId()
56 | .withBookingDate(LocalDate.now().minusDays(1))
57 | .withTransferDate(LocalDate.now())
58 | .andItem()
59 | .withAccount(a)
60 | .withAmount(BigDecimal.valueOf(-50.00))
61 | .withRunningBalance(BigDecimal.valueOf(-0.00))
62 | .withNote("debit A")
63 | .then()
64 | .andItem()
65 | .withAccount(b)
66 | .withAmount(BigDecimal.valueOf(50.00))
67 | .withRunningBalance(BigDecimal.valueOf(-0.00))
68 | .withNote("credit A")
69 | .then()
70 | .build();
71 |
72 | transaction = transactionRepository.save(transaction);
73 |
74 | assertNotNull(transaction);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/roach-data-json/src/test/resources/application.yml:
--------------------------------------------------------------------------------
1 | ########################
2 | # Spring boot properties
3 | # http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
4 | ########################
5 |
6 | spring:
7 | output:
8 | ansi:
9 | enabled: ALWAYS
10 |
11 | liquibase:
12 | change-log: classpath:db/changelog-master.xml
13 | default-schema:
14 | drop-first: false
15 | enabled: true
16 |
17 | datasource:
18 | url: jdbc:postgresql://localhost:26257/roach_data?sslmode=disable
19 | driver-class-name: org.postgresql.Driver
20 | username: root
21 | password:
22 | hikari:
23 | connection-test-query: SELECT 1
24 |
25 | jpa:
26 | open-in-view: false
27 | properties:
28 | hibernate:
29 | dialect: org.hibernate.dialect.CockroachDB201Dialect
30 |
--------------------------------------------------------------------------------
/roach-data-mybatis/README.md:
--------------------------------------------------------------------------------
1 | # Roach Demo Data :: MyBatis
2 |
3 | A CockroachDB Spring Boot Demo using [MyBatis](https://mybatis.org/mybatis-3/)
4 | integrated with [Spring Data JDBC](https://docs.spring.io/spring-data/jdbc/docs/1.1.6.RELEASE/reference/html/#jdbc.mybatis)
5 | for data access.
6 |
--------------------------------------------------------------------------------
/roach-data-mybatis/pom.xml:
--------------------------------------------------------------------------------
1 |
4 | 4.0.0
5 |
6 | io.roach.data
7 | roach-data-2x-parent
8 | 1.0.0-SNAPSHOT
9 | ../roach-data-2x-parent
10 |
11 |
12 | roach-data-mybatis
13 |
14 |
15 |
16 | org.springframework.boot
17 | spring-boot-starter-web
18 |
19 |
20 | org.springframework.boot
21 | spring-boot-starter-jetty
22 |
23 |
24 | org.springframework.boot
25 | spring-boot-starter-hateoas
26 |
27 |
28 | org.springframework.boot
29 | spring-boot-starter-aop
30 |
31 |
32 | org.springframework.boot
33 | spring-boot-starter-data-jdbc
34 |
35 |
36 | org.mybatis.spring.boot
37 | mybatis-spring-boot-starter
38 | 2.1.0
39 |
40 |
41 | org.springframework.data
42 | spring-data-commons
43 |
44 |
45 | org.liquibase
46 | liquibase-core
47 | runtime
48 |
49 |
50 | org.postgresql
51 | postgresql
52 | provided
53 |
54 |
55 | org.projectlombok
56 | lombok
57 | provided
58 |
59 |
60 |
61 |
62 | roach-data-mybatis
63 |
64 |
65 | org.springframework.boot
66 | spring-boot-maven-plugin
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/roach-data-mybatis/src/main/java/io/roach/data/mybatis/Account.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.mybatis;
2 |
3 | import java.math.BigDecimal;
4 |
5 | import org.springframework.data.annotation.Id;
6 |
7 | import lombok.Data;
8 |
9 | @Data
10 | public class Account {
11 | @Id
12 | private Long id;
13 |
14 | private String name;
15 |
16 | private AccountType type;
17 |
18 | private BigDecimal balance;
19 |
20 | public Long getId() {
21 | return id;
22 | }
23 |
24 | public String getName() {
25 | return name;
26 | }
27 |
28 | public AccountType getType() {
29 | return type;
30 | }
31 |
32 | public BigDecimal getBalance() {
33 | return balance;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/roach-data-mybatis/src/main/java/io/roach/data/mybatis/AccountModel.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.mybatis;
2 |
3 | import java.math.BigDecimal;
4 |
5 | import org.springframework.hateoas.RepresentationModel;
6 | import org.springframework.hateoas.server.core.Relation;
7 |
8 | @Relation(value = "account", collectionRelation = "accounts")
9 | public class AccountModel extends RepresentationModel {
10 | private String name;
11 |
12 | private AccountType type;
13 |
14 | private BigDecimal balance;
15 |
16 | public String getName() {
17 | return name;
18 | }
19 |
20 | public void setName(String name) {
21 | this.name = name;
22 | }
23 |
24 | public AccountType getType() {
25 | return type;
26 | }
27 |
28 | public void setType(AccountType type) {
29 | this.type = type;
30 | }
31 |
32 | public BigDecimal getBalance() {
33 | return balance;
34 | }
35 |
36 | public void setBalance(BigDecimal balance) {
37 | this.balance = balance;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/roach-data-mybatis/src/main/java/io/roach/data/mybatis/AccountRepository.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.mybatis;
2 |
3 | import java.math.BigDecimal;
4 |
5 | import org.apache.ibatis.annotations.Param;
6 | import org.springframework.data.jdbc.repository.query.Modifying;
7 | import org.springframework.data.jdbc.repository.query.Query;
8 | import org.springframework.data.repository.CrudRepository;
9 | import org.springframework.stereotype.Repository;
10 | import org.springframework.transaction.annotation.Transactional;
11 |
12 | import static org.springframework.transaction.annotation.Propagation.MANDATORY;
13 |
14 | @Repository
15 | @Transactional(propagation = MANDATORY)
16 | interface AccountRepository extends CrudRepository, PagedAccountRepository {
17 | @Query("SELECT balance FROM account WHERE id = :id FOR UPDATE")
18 | BigDecimal getBalance(@Param("id") Long id);
19 |
20 | @Query("UPDATE account SET balance = balance + :balance WHERE id=:id")
21 | @Modifying
22 | void updateBalance(@Param("id") Long id, @Param("balance") BigDecimal balance);
23 | }
24 |
--------------------------------------------------------------------------------
/roach-data-mybatis/src/main/java/io/roach/data/mybatis/AccountType.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.mybatis;
2 |
3 | public enum AccountType {
4 | asset,
5 | expense
6 | }
7 |
--------------------------------------------------------------------------------
/roach-data-mybatis/src/main/java/io/roach/data/mybatis/IndexModel.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.mybatis;
2 |
3 | import org.springframework.hateoas.RepresentationModel;
4 |
5 | public class IndexModel extends RepresentationModel {
6 | private String message;
7 |
8 | public IndexModel(String message) {
9 | this.message = message;
10 | }
11 |
12 | public String getMessage() {
13 | return message;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/roach-data-mybatis/src/main/java/io/roach/data/mybatis/NegativeBalanceException.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.mybatis;
2 |
3 | import org.springframework.dao.DataIntegrityViolationException;
4 | import org.springframework.http.HttpStatus;
5 | import org.springframework.web.bind.annotation.ResponseStatus;
6 |
7 | @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Negative balance")
8 | public class NegativeBalanceException extends DataIntegrityViolationException {
9 | public NegativeBalanceException(String message) {
10 | super(message);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/roach-data-mybatis/src/main/java/io/roach/data/mybatis/PagedAccountMapper.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.mybatis;
2 |
3 | import java.util.List;
4 |
5 | import org.apache.ibatis.annotations.Mapper;
6 | import org.apache.ibatis.annotations.Param;
7 | import org.apache.ibatis.annotations.Select;
8 | import org.springframework.data.domain.Pageable;
9 |
10 | @Mapper
11 | public interface PagedAccountMapper {
12 | @Select("SELECT * FROM account LIMIT #{pageable.pageSize} OFFSET #{pageable.offset}")
13 | List findAll(@Param("pageable") Pageable pageable);
14 |
15 | @Select("SELECT count(id) from account")
16 | long countAll();
17 | }
18 |
--------------------------------------------------------------------------------
/roach-data-mybatis/src/main/java/io/roach/data/mybatis/PagedAccountRepository.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.mybatis;
2 |
3 | import org.springframework.data.domain.Page;
4 | import org.springframework.data.domain.Pageable;
5 |
6 | public interface PagedAccountRepository {
7 | Page findAll(Pageable pageable);
8 | }
9 |
--------------------------------------------------------------------------------
/roach-data-mybatis/src/main/java/io/roach/data/mybatis/PagedAccountRepositoryImpl.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.mybatis;
2 |
3 | import java.util.List;
4 |
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.data.domain.Page;
7 | import org.springframework.data.domain.PageImpl;
8 | import org.springframework.data.domain.Pageable;
9 | import org.springframework.stereotype.Repository;
10 | import org.springframework.transaction.annotation.Transactional;
11 |
12 | import static org.springframework.transaction.annotation.Propagation.MANDATORY;
13 |
14 | @Repository
15 | @Transactional(propagation = MANDATORY)
16 | public class PagedAccountRepositoryImpl implements PagedAccountRepository {
17 | @Autowired
18 | private PagedAccountMapper pagedAccountMapper;
19 |
20 | @Override
21 | public Page findAll(Pageable pageable) {
22 | List accounts = pagedAccountMapper.findAll(pageable);
23 | long totalRecords = pagedAccountMapper.countAll();
24 | return new PageImpl<>(accounts, pageable, totalRecords);
25 |
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/roach-data-mybatis/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | ########################
2 | # Spring boot properties
3 | # http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
4 | ########################
5 |
6 | spring:
7 | output:
8 | ansi:
9 | enabled: ALWAYS
10 |
11 | liquibase:
12 | change-log: classpath:db/changelog-master.xml
13 | default-schema:
14 | drop-first: false
15 | contexts: crdb
16 | enabled: true
17 |
18 | datasource:
19 | url: jdbc:postgresql://localhost:26257/roach_data?sslmode=disable
20 | driver-class-name: org.postgresql.Driver
21 | username: root
22 | password:
23 | hikari:
24 | connection-test-query: SELECT 1
25 |
26 | jpa:
27 | open-in-view: false
28 |
29 | server:
30 | port: 9090
31 |
--------------------------------------------------------------------------------
/roach-data-mybatis/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 | ^__^
2 | (oo)\_______
3 | (__)\ )\/\ CockroachDB on Spring Data / MyBatis ${application.formatted-version}
4 | ||----w | powered by Spring Boot ${spring-boot.formatted-version}
5 | || ||
6 |
--------------------------------------------------------------------------------
/roach-data-mybatis/src/main/resources/db/changelog-master.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 | ANY
12 |
13 |
14 |
15 |
16 |
17 | 1
18 | Alice
19 |
20 | asset
21 |
22 |
23 | 2
24 | Bob
25 |
26 | expense
27 |
28 |
29 | 3
30 | Bobby Tables
31 |
32 | asset
33 |
34 |
35 | 4
36 | Doris
37 |
38 | expense
39 |
40 |
41 |
--------------------------------------------------------------------------------
/roach-data-mybatis/src/main/resources/db/create.sql:
--------------------------------------------------------------------------------
1 | -- DROP TABLE IF EXISTS account cascade;
2 | -- DROP TABLE IF EXISTS databasechangelog cascade;
3 | -- DROP TABLE IF EXISTS databasechangeloglock cascade;
4 |
5 | create table account
6 | (
7 | id int not null primary key default unique_rowid(),
8 | balance numeric(19, 2) not null,
9 | name varchar(128) not null,
10 | type varchar(25) not null
11 | );
12 |
13 | -- insert into account (id,balance,name,type) values
14 | -- (1, 500.00,'Alice','asset'),
15 | -- (2, 500.00,'Bob','expense'),
16 | -- (3, 500.00,'Bobby Tables','asset'),
17 | -- (4, 500.00,'Doris','expense');
18 |
--------------------------------------------------------------------------------
/roach-data-mybatis/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/roach-data-parallel/pom.xml:
--------------------------------------------------------------------------------
1 |
4 | 4.0.0
5 |
6 | io.roach.data
7 | roach-data-2x-parent
8 | 1.0.0-SNAPSHOT
9 | ../roach-data-2x-parent
10 |
11 |
12 | roach-data-parallel
13 |
14 |
15 | true
16 |
17 |
18 |
19 |
20 |
21 | org.slf4j
22 | slf4j-api
23 |
24 |
25 | ch.qos.logback
26 | logback-core
27 |
28 |
29 | ch.qos.logback
30 | logback-classic
31 |
32 |
33 |
34 | com.zaxxer
35 | HikariCP
36 |
37 |
38 | org.postgresql
39 | postgresql
40 |
41 |
42 | net.ttddyy
43 | datasource-proxy
44 |
45 |
46 |
47 | org.junit.jupiter
48 | junit-jupiter-engine
49 | test
50 |
51 |
52 | org.junit.jupiter
53 | junit-jupiter-params
54 | test
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/roach-data-parallel/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/roach-data-reactive-2x/README.md:
--------------------------------------------------------------------------------
1 | # Roach Demo Data :: Reactive 2x
2 |
3 | A CockroachDB Spring Boot 2.x Demo using [Spring Data R2DBC](https://spring.io/projects/spring-data-r2dbc)
4 | for data access combined with WebFlux.
5 |
6 |
--------------------------------------------------------------------------------
/roach-data-reactive-2x/pom.xml:
--------------------------------------------------------------------------------
1 |
4 | 4.0.0
5 |
6 |
7 | io.roach.data
8 | roach-data-2x-parent
9 | 1.0.0-SNAPSHOT
10 | ../roach-data-2x-parent
11 |
12 |
13 | roach-data-reactive-2x
14 | jar
15 |
16 |
17 | true
18 |
19 |
20 |
21 |
22 | org.springframework.boot
23 | spring-boot-starter-webflux
24 |
25 |
26 | org.springframework.boot
27 | spring-boot-starter-web
28 |
29 |
30 | org.springframework.boot
31 | spring-boot-starter-jetty
32 |
33 |
34 | org.springframework.boot
35 | spring-boot-starter-hateoas
36 |
37 |
38 | org.springframework.boot
39 | spring-boot-starter-aop
40 |
41 |
42 | org.springframework.boot
43 | spring-boot-starter-data-r2dbc
44 |
45 |
46 | io.r2dbc
47 | r2dbc-postgresql
48 | 0.8.13.RELEASE
49 |
50 |
51 |
52 |
53 | roach-data-reactive
54 |
55 |
56 | org.springframework.boot
57 | spring-boot-maven-plugin
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/roach-data-reactive-2x/src/main/java/io/roach/data/reactive/Account.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.reactive;
2 |
3 | import java.math.BigDecimal;
4 |
5 | import org.springframework.data.annotation.Id;
6 | import org.springframework.data.relational.core.mapping.Table;
7 |
8 | @Table("account")
9 | public class Account {
10 | @Id
11 | private Long id;
12 |
13 | private String name;
14 |
15 | private AccountType type;
16 |
17 | private BigDecimal balance;
18 |
19 | public Long getId() {
20 | return id;
21 | }
22 |
23 | public String getName() {
24 | return name;
25 | }
26 |
27 | public AccountType getType() {
28 | return type;
29 | }
30 |
31 | public BigDecimal getBalance() {
32 | return balance;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/roach-data-reactive-2x/src/main/java/io/roach/data/reactive/AccountRepository.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.reactive;
2 |
3 | import java.math.BigDecimal;
4 |
5 | import org.springframework.data.domain.Pageable;
6 | import org.springframework.data.r2dbc.repository.Modifying;
7 | import org.springframework.data.r2dbc.repository.Query;
8 | import org.springframework.data.repository.reactive.ReactiveCrudRepository;
9 | import org.springframework.stereotype.Repository;
10 | import org.springframework.transaction.annotation.Transactional;
11 |
12 | import reactor.core.publisher.Flux;
13 | import reactor.core.publisher.Mono;
14 |
15 | import static org.springframework.transaction.annotation.Propagation.MANDATORY;
16 |
17 | @Repository
18 | @Transactional(propagation = MANDATORY)
19 | public interface AccountRepository extends ReactiveCrudRepository {
20 | Flux findAllBy(Pageable pageable);
21 |
22 | @Query(value = "select balance from Account where id=:id")
23 | Mono getBalance(Long id);
24 |
25 | @Modifying
26 | @Query("update Account set balance = balance + :balance where id=:id")
27 | Mono updateBalance(Long id, BigDecimal balance);
28 | }
29 |
--------------------------------------------------------------------------------
/roach-data-reactive-2x/src/main/java/io/roach/data/reactive/AccountType.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.reactive;
2 |
3 | public enum AccountType {
4 | asset,
5 | expense
6 | }
7 |
--------------------------------------------------------------------------------
/roach-data-reactive-2x/src/main/java/io/roach/data/reactive/NegativeBalanceException.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.reactive;
2 |
3 | import org.springframework.dao.DataIntegrityViolationException;
4 | import org.springframework.http.HttpStatus;
5 | import org.springframework.web.bind.annotation.ResponseStatus;
6 |
7 | @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Negative balance")
8 | public class NegativeBalanceException extends DataIntegrityViolationException {
9 | public NegativeBalanceException(String message) {
10 | super(message);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/roach-data-reactive-2x/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | ########################
2 | # Spring boot properties
3 | # http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
4 | ########################
5 | spring:
6 | output:
7 | ansi:
8 | enabled: ALWAYS
9 | r2dbc:
10 | url: r2dbc:postgresql://kai-odin-11257.8nj.cockroachlabs.cloud:26257/kai-odin-11257.defaultdb?sslmode=verify-full
11 | username: guest
12 | password:
13 | pool:
14 | initial-size: 1
15 | max-size: 10
16 | max-idle-time: 30m
17 | server:
18 | port: 9090
--------------------------------------------------------------------------------
/roach-data-reactive-2x/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 | ___
2 | /() \ CockroachDB on Spring Data / Reactive ${application.formatted-version}
3 | _|_____|_ powered by Spring Boot ${spring-boot.formatted-version}
4 | | | === | |
5 | |_| 0 |_|
6 | || 0 ||
7 | ||__*__||
8 | |~ \___/ ~|
9 | [_]_[_]_[_]
10 |
--------------------------------------------------------------------------------
/roach-data-reactive-2x/src/main/resources/db/create.sql:
--------------------------------------------------------------------------------
1 | drop table if exists account;
2 |
3 | create table if not exists account
4 | (
5 | id int not null primary key default unique_rowid(),
6 | balance numeric(19, 2) not null,
7 | name varchar(128) not null,
8 | type varchar(25) not null default 'expense'
9 | );
10 |
11 | insert into account (id, balance, name)
12 | select id::float8,
13 | 5000.00::decimal,
14 | md5(random()::text)
15 | from generate_series(1, 10000) as id;
--------------------------------------------------------------------------------
/roach-data-reactive-2x/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/roach-data-reactive/README.md:
--------------------------------------------------------------------------------
1 | # Roach Demo Data :: Reactive
2 |
3 | A CockroachDB Spring Boot 3.x Demo using [Spring Data R2DBC](https://spring.io/projects/spring-data-r2dbc)
4 | for data access combined with WebFlux.
5 |
6 |
--------------------------------------------------------------------------------
/roach-data-reactive/pom.xml:
--------------------------------------------------------------------------------
1 |
4 | 4.0.0
5 |
6 |
7 | io.roach.data
8 | roach-data-3x-parent
9 | 1.0.0-SNAPSHOT
10 | ../roach-data-3x-parent
11 |
12 |
13 | roach-data-reactive
14 | jar
15 |
16 |
17 | true
18 | 5.0.0
19 |
20 |
21 |
22 |
23 | org.springframework.boot
24 | spring-boot-starter-webflux
25 |
26 |
27 | org.springframework.boot
28 | spring-boot-starter-web
29 |
30 |
31 | org.springframework.boot
32 | spring-boot-starter-jetty
33 |
34 |
35 | org.springframework.boot
36 | spring-boot-starter-hateoas
37 |
38 |
39 | org.springframework.boot
40 | spring-boot-starter-aop
41 |
42 |
43 | org.springframework.boot
44 | spring-boot-starter-data-r2dbc
45 |
46 |
47 | org.postgresql
48 | r2dbc-postgresql
49 | 1.0.2.RELEASE
50 |
51 |
52 |
53 |
54 | roach-data-reactive
55 |
56 |
57 | org.springframework.boot
58 | spring-boot-maven-plugin
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/roach-data-reactive/src/main/java/io/roach/data/reactive/Account.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.reactive;
2 |
3 | import java.math.BigDecimal;
4 |
5 | import org.springframework.data.annotation.Id;
6 | import org.springframework.data.relational.core.mapping.Table;
7 |
8 | @Table("account")
9 | public class Account {
10 | @Id
11 | private Long id;
12 |
13 | private String name;
14 |
15 | private AccountType type;
16 |
17 | private BigDecimal balance;
18 |
19 | public Long getId() {
20 | return id;
21 | }
22 |
23 | public String getName() {
24 | return name;
25 | }
26 |
27 | public AccountType getType() {
28 | return type;
29 | }
30 |
31 | public BigDecimal getBalance() {
32 | return balance;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/roach-data-reactive/src/main/java/io/roach/data/reactive/AccountRepository.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.reactive;
2 |
3 | import java.math.BigDecimal;
4 |
5 | import org.springframework.data.domain.Pageable;
6 | import org.springframework.data.r2dbc.repository.Modifying;
7 | import org.springframework.data.r2dbc.repository.Query;
8 | import org.springframework.data.repository.reactive.ReactiveCrudRepository;
9 | import org.springframework.stereotype.Repository;
10 | import org.springframework.transaction.annotation.Transactional;
11 |
12 | import reactor.core.publisher.Flux;
13 | import reactor.core.publisher.Mono;
14 |
15 | import static org.springframework.transaction.annotation.Propagation.MANDATORY;
16 |
17 | @Repository
18 | @Transactional(propagation = MANDATORY)
19 | public interface AccountRepository extends ReactiveCrudRepository {
20 | Flux findAllBy(Pageable pageable);
21 |
22 | @Query(value = "select balance from Account where id=:id")
23 | Mono getBalance(Long id);
24 |
25 | @Modifying
26 | @Query("update Account set balance = balance + :balance where id=:id")
27 | Mono updateBalance(Long id, BigDecimal balance);
28 | }
29 |
--------------------------------------------------------------------------------
/roach-data-reactive/src/main/java/io/roach/data/reactive/AccountType.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.reactive;
2 |
3 | public enum AccountType {
4 | asset,
5 | expense
6 | }
7 |
--------------------------------------------------------------------------------
/roach-data-reactive/src/main/java/io/roach/data/reactive/NegativeBalanceException.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.reactive;
2 |
3 | import org.springframework.dao.DataIntegrityViolationException;
4 | import org.springframework.http.HttpStatus;
5 | import org.springframework.web.bind.annotation.ResponseStatus;
6 |
7 | @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Negative balance")
8 | public class NegativeBalanceException extends DataIntegrityViolationException {
9 | public NegativeBalanceException(String message) {
10 | super(message);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/roach-data-reactive/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | ########################
2 | # Spring boot properties
3 | # http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
4 | ########################
5 | spring:
6 | output:
7 | ansi:
8 | enabled: ALWAYS
9 | r2dbc:
10 | url: r2dbc:postgresql://kai-odin-11257.8nj.cockroachlabs.cloud:26257/kai-odin-11257.defaultdb?sslmode=verify-full
11 | username: guest
12 | password:
13 | pool:
14 | initial-size: 1
15 | max-size: 10
16 | max-idle-time: 30m
17 | server:
18 | port: 9090
--------------------------------------------------------------------------------
/roach-data-reactive/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 | ___
2 | /() \ CockroachDB on Spring Data / Reactive ${application.formatted-version}
3 | _|_____|_ powered by Spring Boot ${spring-boot.formatted-version}
4 | | | === | |
5 | |_| 0 |_|
6 | || 0 ||
7 | ||__*__||
8 | |~ \___/ ~|
9 | [_]_[_]_[_]
10 |
--------------------------------------------------------------------------------
/roach-data-reactive/src/main/resources/db/create.sql:
--------------------------------------------------------------------------------
1 | drop table if exists account;
2 |
3 | create table if not exists account
4 | (
5 | id int not null primary key default unique_rowid(),
6 | balance numeric(19, 2) not null,
7 | name varchar(128) not null,
8 | type varchar(25) not null default 'expense'
9 | );
10 |
11 | insert into account (id, balance, name)
12 | select id::float8,
13 | 5000.00::decimal,
14 | md5(random()::text)
15 | from generate_series(1, 10000) as id;
--------------------------------------------------------------------------------
/roach-data-reactive/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/roach-data-relational/README.md:
--------------------------------------------------------------------------------
1 | # Roach Demo Data :: JDBC / Relational
2 |
3 | A CockroachDB Spring Boot Demo using [Spring Data JDBC](https://spring.io/projects/spring-data-jdbc) for data access.
4 |
5 |
6 |
--------------------------------------------------------------------------------
/roach-data-relational/pom.xml:
--------------------------------------------------------------------------------
1 |
4 | 4.0.0
5 |
6 | io.roach.data
7 | roach-data-3x-parent
8 | 1.0.0-SNAPSHOT
9 | ../roach-data-3x-parent
10 |
11 |
12 | roach-data-relational
13 |
14 |
15 |
16 | org.springframework.boot
17 | spring-boot-starter-data-jdbc
18 |
19 |
20 |
21 | org.flywaydb
22 | flyway-core
23 | runtime
24 |
25 |
26 | net.ttddyy
27 | datasource-proxy
28 | 1.9
29 |
30 |
31 | org.postgresql
32 | postgresql
33 | compile
34 |
35 |
36 |
37 |
38 | roach-data-jdbc-orders
39 |
40 |
41 | org.springframework.boot
42 | spring-boot-maven-plugin
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/roach-data-relational/src/main/java/io/roach/data/relational/JdbcConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.relational;
2 |
3 | import com.zaxxer.hikari.HikariDataSource;
4 | import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
5 | import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
6 | import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
7 | import org.springframework.boot.context.properties.ConfigurationProperties;
8 | import org.springframework.context.annotation.Bean;
9 | import org.springframework.context.annotation.Configuration;
10 | import org.springframework.context.annotation.Primary;
11 | import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
12 | import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration;
13 | import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
14 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
15 | import org.springframework.jdbc.datasource.DataSourceTransactionManager;
16 | import org.springframework.transaction.PlatformTransactionManager;
17 | import org.springframework.transaction.annotation.EnableTransactionManagement;
18 |
19 | import javax.sql.DataSource;
20 |
21 | @Configuration
22 | @EnableTransactionManagement
23 | @EnableJdbcRepositories(basePackages = {"io.roach"})
24 | public class JdbcConfiguration extends AbstractJdbcConfiguration {
25 | public static final String SQL_TRACE_LOGGER = "io.roach.SQL_TRACE";
26 |
27 | @Bean
28 | @Primary
29 | @ConfigurationProperties("spring.datasource")
30 | public DataSourceProperties dataSourceProperties() {
31 | return new DataSourceProperties();
32 | }
33 |
34 | @Bean
35 | @Primary
36 | public DataSource primaryDataSource() {
37 | return loggingProxy(targetDataSource());
38 | }
39 |
40 | @Bean
41 | @ConfigurationProperties("spring.datasource.hikari")
42 | public HikariDataSource targetDataSource() {
43 | HikariDataSource ds = dataSourceProperties()
44 | .initializeDataSourceBuilder()
45 | .type(HikariDataSource.class)
46 | .build();
47 | ds.addDataSourceProperty("reWriteBatchedInserts", true);
48 | ds.addDataSourceProperty("ApplicationName", "jdbc-test");
49 | return ds;
50 | }
51 |
52 | private DataSource loggingProxy(DataSource dataSource) {
53 | return ProxyDataSourceBuilder
54 | .create(dataSource)
55 | .name("SQL-Trace")
56 | .asJson()
57 | .countQuery()
58 | .logQueryBySlf4j(SLF4JLogLevel.TRACE, SQL_TRACE_LOGGER)
59 | .multiline()
60 | .build();
61 | }
62 |
63 | @Bean
64 | public NamedParameterJdbcTemplate namedParameterJdbcTemplate() {
65 | return new NamedParameterJdbcTemplate(primaryDataSource());
66 | }
67 |
68 | @Bean
69 | public PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() {
70 | return new PersistenceExceptionTranslationPostProcessor();
71 | }
72 |
73 | @Bean
74 | public PlatformTransactionManager transactionManager() {
75 | return new DataSourceTransactionManager(primaryDataSource());
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/roach-data-relational/src/main/java/io/roach/data/relational/OrderSystemClient.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.relational;
2 |
3 | import io.roach.data.relational.domain.Customer;
4 | import io.roach.data.relational.domain.Product;
5 | import io.roach.data.relational.service.OrderSystem;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.boot.CommandLineRunner;
10 | import org.springframework.stereotype.Component;
11 |
12 | @Component
13 | public class OrderSystemClient implements CommandLineRunner {
14 | protected final Logger logger = LoggerFactory.getLogger(getClass());
15 |
16 | @Autowired
17 | private OrderSystem orderSystem;
18 |
19 | @Override
20 | public void run(String... args) {
21 | logger.info("Removing everything...");
22 | orderSystem.clearAll();
23 |
24 | logger.info("Creating products...");
25 | orderSystem.createProductInventory();
26 |
27 | logger.info("Creating customers...");
28 | orderSystem.createCustomers();
29 |
30 | logger.info("Creating orders...");
31 | orderSystem.createOrders();
32 |
33 | logger.info("Listing orders...");
34 | orderSystem.findOrders().forEach(order -> {
35 | Customer c = orderSystem.findCustomerById(order.getCustomerRef().getId());
36 | logger.info("Order placed by {}", c.getUserName());
37 | logger.info("\tOrder total price: {}", order.getTotalPrice());
38 | logger.info("\tOrder items:");
39 |
40 | order.getOrderItems().forEach(orderItem -> {
41 | Product p = orderSystem.findProductById(orderItem.getProductRef().getId());
42 | logger.info("\t\t{} price: {} sku: {} qty: {} unit price: {} cost: {}",
43 | p.getName(),
44 | p.getPrice(),
45 | p.getSku(),
46 | orderItem.getQuantity(),
47 | orderItem.getUnitPrice(),
48 | orderItem.totalCost()
49 | );
50 | });
51 | });
52 |
53 |
54 | logger.info("Find by sku: {}", orderSystem.findProductBySku("CRDB-UL-ED1"));
55 |
56 | logger.info("Listing customer orders...");
57 | orderSystem.findCustomers().forEach(customer -> {
58 | logger.info("Customer {} orders:", customer.getId());
59 | orderSystem.findOrders(customer).forEach(order -> {
60 | logger.info("\t{}:", order.toString());
61 | });
62 | });
63 |
64 | orderSystem.getTotalOrderCost((iterated, aggregated) -> {
65 | logger.info("Total order cost iterated: {}", iterated);
66 | logger.info("Total order cost aggregated: {}", aggregated);
67 | });
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/roach-data-relational/src/main/java/io/roach/data/relational/RelationalApplication.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.relational;
2 |
3 | import org.springframework.boot.WebApplicationType;
4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
5 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
6 | import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
7 | import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration;
8 | import org.springframework.boot.builder.SpringApplicationBuilder;
9 | import org.springframework.context.annotation.ComponentScan;
10 | import org.springframework.context.annotation.Configuration;
11 |
12 | @Configuration
13 | @EnableAutoConfiguration(exclude = {
14 | TransactionAutoConfiguration.class,
15 | DataSourceTransactionManagerAutoConfiguration.class,
16 | DataSourceAutoConfiguration.class
17 | })
18 | @ComponentScan(basePackages = "io.roach.data.relational")
19 | public class RelationalApplication {
20 | public static void main(String[] args) {
21 | new SpringApplicationBuilder(RelationalApplication.class)
22 | .web(WebApplicationType.NONE)
23 | .run(args);
24 | }
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/roach-data-relational/src/main/java/io/roach/data/relational/domain/AbstractEntity.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.relational.domain;
2 |
3 | import java.io.Serializable;
4 |
5 |
6 | import org.springframework.data.annotation.Transient;
7 | import org.springframework.data.domain.Persistable;
8 |
9 | public abstract class AbstractEntity implements Persistable {
10 | @Transient
11 | private boolean isNew = true;
12 |
13 | protected void markNotNew() {
14 | this.isNew = false;
15 | }
16 |
17 | @Override
18 | public boolean isNew() {
19 | return isNew;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/roach-data-relational/src/main/java/io/roach/data/relational/domain/Customer.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.relational.domain;
2 |
3 | import org.springframework.data.annotation.Id;
4 | import org.springframework.data.relational.core.mapping.Column;
5 | import org.springframework.data.relational.core.mapping.Table;
6 |
7 | import java.util.UUID;
8 |
9 | @Table(name = "customer")
10 | public class Customer extends AbstractEntity {
11 | public static Builder builder() {
12 | return new Builder();
13 | }
14 |
15 | public static final class Builder {
16 | private String userName;
17 |
18 | private String firstName;
19 |
20 | private String lastName;
21 |
22 | private Builder() {
23 | }
24 |
25 | public Builder withUserName(String userName) {
26 | this.userName = userName;
27 | return this;
28 | }
29 |
30 | public Builder withFirstName(String firstName) {
31 | this.firstName = firstName;
32 | return this;
33 | }
34 |
35 | public Builder withLastName(String lastName) {
36 | this.lastName = lastName;
37 | return this;
38 | }
39 |
40 | public Customer build() {
41 | Customer customer = new Customer();
42 | customer.userName = this.userName;
43 | customer.firstName = this.firstName;
44 | customer.lastName = this.lastName;
45 | return customer;
46 | }
47 | }
48 |
49 | @Id
50 | private UUID id;
51 |
52 | @Column(value = "user_name")
53 | private String userName;
54 |
55 | @Column(value = "first_name")
56 | private String firstName;
57 |
58 | @Column(value = "last_name")
59 | private String lastName;
60 |
61 | @Override
62 | public UUID getId() {
63 | return id;
64 | }
65 |
66 | public String getUserName() {
67 | return userName;
68 | }
69 |
70 | public String getFirstName() {
71 | return firstName;
72 | }
73 |
74 | public String getLastName() {
75 | return lastName;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/roach-data-relational/src/main/java/io/roach/data/relational/domain/Order.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.relational.domain;
2 |
3 | import org.springframework.data.annotation.Id;
4 | import org.springframework.data.jdbc.core.mapping.AggregateReference;
5 | import org.springframework.data.relational.core.mapping.Column;
6 | import org.springframework.data.relational.core.mapping.MappedCollection;
7 | import org.springframework.data.relational.core.mapping.Table;
8 |
9 | import java.math.BigDecimal;
10 | import java.util.*;
11 |
12 | @Table(name = "orders")
13 | public class Order extends AbstractEntity {
14 | public static Builder builder() {
15 | return new Builder();
16 | }
17 |
18 | public static final class Builder {
19 | private Customer customer;
20 |
21 | private final List orderItems = new ArrayList<>();
22 |
23 | private Builder() {
24 | }
25 |
26 | public Builder withCustomer(Customer customer) {
27 | this.customer = customer;
28 | return this;
29 | }
30 |
31 | public OrderItem.Builder andOrderItem() {
32 | return new OrderItem.Builder(this, orderItems::add);
33 | }
34 |
35 | public Order build() {
36 | if (this.customer == null) {
37 | throw new IllegalStateException("Missing customer");
38 | }
39 | if (this.orderItems.isEmpty()) {
40 | throw new IllegalStateException("Empty order");
41 | }
42 | Order order = new Order();
43 | order.customer = AggregateReference.to(this.customer.getId());
44 | order.orderItems.addAll(this.orderItems);
45 | order.totalPrice = order.subTotal();
46 | return order;
47 | }
48 | }
49 |
50 | @Id
51 | private UUID id;
52 |
53 | @Column(value = "total_price")
54 | private BigDecimal totalPrice;
55 |
56 | @Column(value = "customer_id")
57 | private AggregateReference customer;
58 |
59 | @MappedCollection(idColumn = "order_id", keyColumn = "order_id")
60 | private Set orderItems = new HashSet<>();
61 |
62 | @Override
63 | public UUID getId() {
64 | return id;
65 | }
66 |
67 | public Order setId(UUID id) {
68 | this.id = id;
69 | return this;
70 | }
71 |
72 | public BigDecimal getTotalPrice() {
73 | return totalPrice;
74 | }
75 |
76 | public AggregateReference getCustomerRef() {
77 | return customer;
78 | }
79 |
80 | public Collection getOrderItems() {
81 | return Collections.unmodifiableSet(orderItems);
82 | }
83 |
84 | public BigDecimal subTotal() {
85 | BigDecimal subTotal = BigDecimal.ZERO;
86 | for (OrderItem oi : orderItems) {
87 | subTotal = subTotal.add(oi.totalCost());
88 | }
89 | return subTotal;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/roach-data-relational/src/main/java/io/roach/data/relational/domain/OrderItem.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.relational.domain;
2 |
3 | import org.springframework.data.jdbc.core.mapping.AggregateReference;
4 | import org.springframework.data.relational.core.mapping.Column;
5 | import org.springframework.data.relational.core.mapping.Table;
6 |
7 | import java.math.BigDecimal;
8 | import java.util.UUID;
9 | import java.util.function.Consumer;
10 |
11 | @Table("order_item")
12 | public class OrderItem {
13 | public static final class Builder {
14 | private final Order.Builder parentBuilder;
15 |
16 | private final Consumer callback;
17 |
18 | private int quantity;
19 |
20 | private BigDecimal unitPrice;
21 |
22 | private Product product;
23 |
24 | Builder(Order.Builder parentBuilder, Consumer callback) {
25 | this.parentBuilder = parentBuilder;
26 | this.callback = callback;
27 | }
28 |
29 | public Builder withQuantity(int quantity) {
30 | this.quantity = quantity;
31 | return this;
32 | }
33 |
34 | public Builder withUnitPrice(BigDecimal unitPrice) {
35 | this.unitPrice = unitPrice;
36 | return this;
37 | }
38 |
39 | public Builder withProduct(Product product) {
40 | this.product = product;
41 | return this;
42 | }
43 |
44 | public Order.Builder then() {
45 | if (this.unitPrice == null) {
46 | this.unitPrice = product.getPrice();
47 | }
48 |
49 | OrderItem orderItem = new OrderItem();
50 | orderItem.unitPrice = this.unitPrice;
51 | orderItem.quantity = this.quantity;
52 | orderItem.product = AggregateReference.to(this.product.getId());
53 | orderItem.order = AggregateReference.to(this.product.getId());
54 |
55 | callback.accept(orderItem);
56 |
57 | return parentBuilder;
58 | }
59 |
60 | }
61 |
62 | @Column(value = "product_id")
63 | private AggregateReference product;
64 |
65 | @Column(value = "order_id")
66 | private AggregateReference order;
67 |
68 | @Column
69 | private int quantity;
70 |
71 | @Column(value = "unit_price")
72 | private BigDecimal unitPrice;
73 |
74 | public int getQuantity() {
75 | return quantity;
76 | }
77 |
78 | public BigDecimal getUnitPrice() {
79 | return unitPrice;
80 | }
81 |
82 | public AggregateReference getProductRef() {
83 | return product;
84 | }
85 |
86 | public BigDecimal totalCost() {
87 | if (unitPrice == null) {
88 | throw new IllegalStateException("unitPrice is null");
89 | }
90 | return unitPrice.multiply(new BigDecimal(quantity));
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/roach-data-relational/src/main/java/io/roach/data/relational/domain/Product.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.relational.domain;
2 |
3 | import org.springframework.data.annotation.Id;
4 | import org.springframework.data.relational.core.mapping.Column;
5 | import org.springframework.data.relational.core.mapping.Table;
6 |
7 | import java.math.BigDecimal;
8 | import java.util.UUID;
9 |
10 | @Table(name = "product")
11 | public class Product extends AbstractEntity {
12 | public static Builder builder() {
13 | return new Builder();
14 | }
15 |
16 | public static final class Builder {
17 | private String name;
18 |
19 | private String sku;
20 |
21 | private BigDecimal price;
22 |
23 | private int quantity;
24 |
25 | private Builder() {
26 | }
27 |
28 | public Builder withName(String name) {
29 | this.name = name;
30 | return this;
31 | }
32 |
33 | public Builder withSku(String sku) {
34 | this.sku = sku;
35 | return this;
36 | }
37 |
38 | public Builder withPrice(BigDecimal price) {
39 | this.price = price;
40 | return this;
41 | }
42 |
43 | public Builder withQuantity(int quantity) {
44 | this.quantity = quantity;
45 | return this;
46 | }
47 |
48 |
49 | public Product build() {
50 | Product product = new Product();
51 | product.name = this.name;
52 | product.sku = this.sku;
53 | product.price = this.price;
54 | product.inventory = this.quantity;
55 | return product;
56 | }
57 | }
58 |
59 | @Id
60 | private UUID id;
61 |
62 | @Column
63 | private String name;
64 |
65 | @Column
66 | private String sku;
67 |
68 | @Column
69 | private BigDecimal price;
70 |
71 | @Column
72 | private int inventory;
73 |
74 | @Override
75 | public UUID getId() {
76 | return id;
77 | }
78 |
79 | public String getName() {
80 | return name;
81 | }
82 |
83 | public BigDecimal getPrice() {
84 | return price;
85 | }
86 |
87 | public String getSku() {
88 | return sku;
89 | }
90 |
91 | public void setPrice(BigDecimal price) {
92 | this.price = price;
93 | }
94 |
95 | public int addInventoryQuantity(int qty) {
96 | this.inventory += qty;
97 | return inventory;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/roach-data-relational/src/main/java/io/roach/data/relational/repository/CustomerRepository.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.relational.repository;
2 |
3 | import io.roach.data.relational.domain.Customer;
4 | import org.springframework.data.repository.CrudRepository;
5 | import org.springframework.stereotype.Repository;
6 |
7 | import java.util.Optional;
8 | import java.util.UUID;
9 |
10 | @Repository
11 | public interface CustomerRepository extends CrudRepository {
12 | Optional findByUserName(String userName);
13 | }
14 |
--------------------------------------------------------------------------------
/roach-data-relational/src/main/java/io/roach/data/relational/repository/OrderRepository.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.relational.repository;
2 |
3 | import io.roach.data.relational.domain.Order;
4 | import org.springframework.data.jdbc.repository.query.Query;
5 | import org.springframework.data.repository.CrudRepository;
6 | import org.springframework.data.repository.query.Param;
7 | import org.springframework.stereotype.Repository;
8 |
9 | import java.math.BigDecimal;
10 | import java.util.UUID;
11 |
12 | @Repository
13 | public interface OrderRepository extends CrudRepository {
14 | @Query(value = "select * from orders")
15 | Iterable findOrders();
16 |
17 | @Query(value = "select o.* from orders o "
18 | + "join customer c on o.customer_id = c.id "
19 | + "where c.id=:customerId")
20 | Iterable findOrdersByCustomerId(@Param("customerId") UUID customerId);
21 |
22 | @Query(value = "select sum(o.total_price) from orders o")
23 | BigDecimal totalOrderCost();
24 | }
25 |
--------------------------------------------------------------------------------
/roach-data-relational/src/main/java/io/roach/data/relational/repository/ProductRepository.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.relational.repository;
2 |
3 | import io.roach.data.relational.domain.Product;
4 | import org.springframework.data.jdbc.repository.query.Query;
5 | import org.springframework.data.relational.core.sql.LockMode;
6 | import org.springframework.data.relational.repository.Lock;
7 | import org.springframework.data.repository.CrudRepository;
8 | import org.springframework.data.repository.query.Param;
9 | import org.springframework.stereotype.Repository;
10 |
11 | import java.util.Optional;
12 | import java.util.UUID;
13 |
14 | @Repository
15 | public interface ProductRepository extends CrudRepository {
16 | @Lock(LockMode.PESSIMISTIC_READ)
17 | Optional findProductBySku(String sku);
18 |
19 | @Query("select p from product p where p.sku=:sku")
20 | Optional findProductBySkuNoLock(@Param("sku") String sku);
21 | }
22 |
--------------------------------------------------------------------------------
/roach-data-relational/src/main/java/io/roach/data/relational/service/OrderService.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.relational.service;
2 |
3 | import io.roach.data.relational.domain.Order;
4 | import io.roach.data.relational.repository.OrderRepository;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.stereotype.Service;
7 | import org.springframework.transaction.annotation.Propagation;
8 | import org.springframework.transaction.annotation.Transactional;
9 |
10 | import java.math.BigDecimal;
11 |
12 | @Service
13 | public class OrderService {
14 | @Autowired
15 | private OrderRepository orderRepository;
16 |
17 | @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
18 | public BigDecimal getTotalOrderCost() {
19 | BigDecimal price = BigDecimal.ZERO;
20 | // N+1 query
21 | Iterable orders = orderRepository.findOrders();
22 | for (Order order : orders) {
23 | price = price.add(order.getTotalPrice());
24 | }
25 | return price;
26 | }
27 |
28 | @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
29 | public BigDecimal getTotalOrderCostAggregate() {
30 | return orderRepository.totalOrderCost();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/roach-data-relational/src/main/java/io/roach/data/relational/service/OrderSystem.java:
--------------------------------------------------------------------------------
1 | package io.roach.data.relational.service;
2 |
3 | import io.roach.data.relational.domain.Customer;
4 | import io.roach.data.relational.domain.Order;
5 | import io.roach.data.relational.domain.Product;
6 |
7 | import java.math.BigDecimal;
8 | import java.util.List;
9 | import java.util.UUID;
10 | import java.util.function.BiConsumer;
11 |
12 | /**
13 | * Main order system business facade.
14 | */
15 | public interface OrderSystem {
16 | List createProductInventory();
17 |
18 | List createCustomers();
19 |
20 | List createOrders();
21 |
22 | Iterable findOrders();
23 |
24 | Iterable findOrders(Customer customer);
25 |
26 | Iterable findCustomers();
27 |
28 | Product findProductBySku(String sku);
29 |
30 | Product findProductById(UUID id);
31 |
32 | Customer findCustomerById(UUID id);
33 |
34 | void getTotalOrderCost(BiConsumer result);
35 |
36 | void clearAll();
37 | }
38 |
--------------------------------------------------------------------------------
/roach-data-relational/src/main/resources/application-dev.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | datasource:
3 | url: jdbc:postgresql://192.168.1.99:26257/roach_data?sslmode=disable
4 |
--------------------------------------------------------------------------------
/roach-data-relational/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | ########################
2 | # Spring boot properties
3 | # http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
4 | ########################
5 |
6 | spring:
7 | profiles:
8 | active:
9 | output:
10 | ansi:
11 | enabled: ALWAYS
12 | flyway:
13 | enabled: true
14 | clean-on-validation-error: true
15 | baseline-on-migrate: true
16 | clean-disabled: false
17 | datasource:
18 | url: jdbc:postgresql://localhost:26257/roach_data?sslmode=disable
19 | driver-class-name: org.postgresql.Driver
20 | username: root
21 | password:
22 | hikari:
23 | connection-test-query: SELECT 1
24 | maximum-pool-size: 20
25 | minimum-idle: 20
26 | auto-commit: false
27 | #############################
28 | logging:
29 | pattern:
30 | console: "%clr(%d{${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:%5p}) %clr([%logger{39}]){cyan} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}"
31 |
--------------------------------------------------------------------------------
/roach-data-relational/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 | ^__^
2 | (oo)\_______
3 | (__)\ )\/\ CockroachDB on Spring Data JDBC / Relational ${application.formatted-version}
4 | ||----w | powered by Spring Boot ${spring-boot.formatted-version}
5 | || ||
6 |
--------------------------------------------------------------------------------
/roach-data-relational/src/main/resources/db/migration/V1_1__create.sql:
--------------------------------------------------------------------------------
1 | create table customer
2 | (
3 | id uuid not null default gen_random_uuid(),
4 | first_name varchar(45),
5 | last_name varchar(45),
6 | user_name varchar(15) not null,
7 |
8 | primary key (id)
9 | );
10 |
11 | create table orders
12 | (
13 | id uuid not null default gen_random_uuid(),
14 | customer_id uuid not null,
15 | total_price numeric(19, 2) not null,
16 | tags string null,
17 |
18 | primary key (id)
19 | );
20 |
21 | create table product
22 | (
23 | id uuid not null default gen_random_uuid(),
24 | inventory int not null,
25 | name string not null,
26 | description jsonb null,
27 | price numeric(19, 2) not null,
28 | sku string not null,
29 |
30 | primary key (id)
31 | );
32 |
33 | create table order_item
34 | (
35 | order_id uuid not null,
36 | product_id uuid not null,
37 | quantity int not null,
38 | unit_price numeric(19, 2) not null,
39 |
40 | primary key (order_id, product_id)
41 | );
42 |
43 | alter table product
44 | add constraint check_product_positive_inventory check (product.inventory >= 0);
45 |
46 | alter table if exists customer
47 | add constraint uc_user_name unique (user_name);
48 |
49 | alter table if exists product
50 | add constraint uc_product_sku unique (sku);
51 |
52 | alter table if exists order_item
53 | add constraint fk_order_item_ref_product
54 | foreign key (product_id)
55 | references product;
56 |
57 | alter table if exists order_item
58 | add constraint fk_order_item_ref_order
59 | foreign key (order_id)
60 | references orders;
61 |
62 | alter table if exists orders
63 | add constraint fk_order_ref_customer
64 | foreign key (customer_id)
65 | references customer;
66 |
67 | create index fk_order_item_ref_product_idx on order_item (product_id);
68 |
69 | create index fk_order_ref_customer_idx on orders (customer_id);
--------------------------------------------------------------------------------
/roach-data-relational/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------