Develop React + GraphQL + Spring Data JPA + UCP + Oracle​

Paul Parkinson
Oracle Developers
Published in
6 min readSep 19, 2022

This blog is written to give a succinct description and example of a modern full stack microservice app including a React frontend service that conducts GraphQL queries against a Spring Boot Data JPA backend service that in turn maps to an Oracle database.

As such I’ll start right off with a link to the app source: https://github.com/paulparkinson/react-graphql-springboot-jpa-oracle-ucp-example

It should take only a couple minutes to build and run using the simple instructions here:

  1. cd spring-data-jpa-graphql-ucp-oracle
  2. Modify src/main/resources/application.properties to set values for spring.datasource.url, spring.datasource.username, and spring.datasource.password
  3. Run mvn clean install
  4. Run java -jar target/spring-data-jpa-graphql-oracle-0.0.1-SNAPSHOT.jar
  5. (In a separate terminal/console) cd react-graphql
  6. Run yarn add @apollo/client graphql (this is only necessary once for the project)
  7. Run npm run build
  8. Run npm start

A browser window will open to http://localhost:3000/ which is a React app that will use Apollo to make a GraphQL query against a Spring Boot service running on localhost:8080 which in turn uses JPA to query an Oracle database via a connection obtained from UCP.

There are quite a few pieces written on the advantages and details of GraphQL particularly as they fit well in many microservices architectures. I will attempt to illustrate this via the actual application source and will just briefly state here that GraphQL queries allow the client to dynamically specify exactly what is required in a query (whether that be read or write) while the server provides this outcome using the most appropriate and efficient means possible and in doing so can cut down the number of requests required and improve performance.

Some details working from back to front…

The first thing you will do is obtain an Oracle database and configure the Spring Boot service to connect to it using UCP.

Any Oracle database (on-prem, in-container, cloud, …) will do. The Simplify Microservices with Converged Oracle Database workshop is one very convenient way to create a free Oracle cloud database (and also sets up a full microservices environment, though that is not necessary for this simple example of course).

Spring Boot currently uses the Hikari connection pool by default, however, Oracle’s Universal Connection Pool (UCP) provides a number of advantages, including performance, and features such as labelling, request boundaries, application continuity, RAC failover, sharding, diagnostics, and monitoring with many more coming in future releases. In order to use UCP instead of Hikari, specific datasource configuration properties must be set and the appropriate dependencies need to be added.

Example snippet of configuration properties in application.properties file:

spring.datasource.url=jdbc:oracle:thin:@someServiceName_tp?TNS_ADMIN=/someLocation/Wallet_someWallet
spring.datasource.username=someUser
spring.datasource.password=somePassword
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.type=oracle.ucp.jdbc.PoolDataSource
spring.datasource.oracleucp.connection-factory-class-name=oracle.jdbc.replay.OracleDataSourceImpl
spring.datasource.oracleucp.database-name=oracleADBForGraphQL
spring.datasource.oracleucp.data-source-name=oracleADBForGraphQLDataSource
spring.datasource.oracleucp.description="Oracle ADB Used For GraphQL"

* See src repos for additional optional UCP properties that can be set including pooling and logging settings.

The Oracle driver and UCP library dependencies can be set in a few ways.

For example, the production pom can be used as shown in this pom.xml snippet:

<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc11-production</artifactId>
<version>21.1.0.0</version>
<type>pom</type>
</dependency>

Or individual dependency entries can be used as shown in this pom.xml snippet:

<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc11</artifactId>
<version>21.1.0.0</version>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ucp</artifactId>
<version>21.1.0.0</version>
</dependency>
<dependency>
<groupId>com.oracle.database.ha</groupId>
<artifactId>ons</artifactId>
<version>21.1.0.0</version>
</dependency>
<dependency>
<groupId>com.oracle.database.security</groupId>
<artifactId>oraclepki</artifactId>
<version>21.1.0.0</version>
</dependency>
<dependency>
<groupId>com.oracle.database.security</groupId>
<artifactId>osdt_core</artifactId>
<version>21.1.0.0</version>
</dependency>
<dependency>
<groupId>com.oracle.database.security</groupId>
<artifactId>osdt_cert</artifactId>
<version>21.1.0.0</version>
</dependency>

Next notice the basics of Spring Data JPA are unchanged.

Notice no special, additional logic exists in the Spring Data JPA source for either UCP or GraphQL logic as far as the model and repository abstractions (Account and Bank in this case).

Then notice the basics of serverside GraphQL in Spring Boot

GraphQL includes the concept of schemas, queries, and mutations.
Schemas describe what data is available to be queried and manipulated. Such as in
src/main/resources/graphql/account.graphqls we see:

type Account {
id: ID!
balance: BigDecimal!
description: String
bank: Bank
}

Queries, as you would expect, describe the information that can be read/queried. Again in account.graphqls we see:

extend type Query {
findAllAccounts: [Account]!
countAccounts: Long!
}

Mutations describe the information that can be created, deleted, and updated. Again in account.graphqls we see:

extend type Mutation {
createAccount(balance: BigDecimal!, description: String, bank: ID!): Account!
updateAccount(id: ID!, balance: BigDecimal, description: String): Account!
deleteAccount(id: ID!): Boolean
}

The logic to map between GraphQL and Spring Data JPA is in the resolver package with the implementations of GraphQLQueryResolver (Query), GraphQLResolver<Account> (AccountResolver), and GraphQLMutationResolver (Mutation)
All are direct and straightforward mediations to and from. Here are a few source snippets to give an example:

Query.class:

public Iterable<Account> findAllAccounts() {
return accountRepository.findAll();
}

AccountResolver.class:

public Bank getBank(Account account) {
return bankRepository.findById(account.getBank().getId()).orElseThrow(null);
}

Mutation.class:

public Account updateAccount(Long id, BigDecimal balance, String description) throws Exception {
Optional<Account> optionalAccount = accountRepository.findById(id);
if (optionalAccount.isPresent()) {
Account account = optionalAccount.get();
if (balance != null)
account.setBalance(balance);
if (description != null)
account.setDescription(description);
accountRepository.save(account);
return account;
}
throw new Exception("No account found to update.");
}

*Note that “Spring for GraphQL is the successor of the GraphQL Java Spring project from the GraphQL Java team” that we use here and it “aims to be the foundation for all Spring, GraphQL applications.”. Therefore, I will likely publish a new version/branch of this application using that technology (which provides convenience annotations for the graphqls functionality, etc.), however, this new feature only reached version 1.0 in May 2022 and so I am using the original, more widely used approach here.

Finally, try out the app to see the behavior.

Postman is a handy and simple testing utility so we’ll use that to create one or more banks but using a GraphQL POST with gql (Graph Query Language — a language intentionally similar to SQL) such as this one:

The sample application has spring.jpa.show-sql: true in the application.properties file so that the related SQL JPA issues against the database can be seen in logs. In this createBank case we see:

Hibernate: select hibernate_sequence.nextval from dual
Hibernate: insert into bank (name, routing, id) values (?, ?, ?)

We then go ahead and create one more accounts for the bank(s)/bankid(s) created:

Finally, we conduct a findAllAccounts query using the following gql:

This time in the Spring Boot log we see:

Hibernate: select bank0_.id as id1_1_0_, bank0_.name as name2_1_0_, bank0_.routing as routing3_1_0_ from bank bank0_ where bank0_.id=?

Now let’s look at the frontend React client and it’s use of Apollo to make GraphQL queries against the Spring Boot service.

Apollo is the most popular library for conducting GraphQL in ReactJS and can be installed by running

yarn add @apollo/client graphql

In index.tsx we see the creation of a client that points to the Spring Data JPA service as well as the code to render the reply:

const client = new ApolloClient({
uri: 'http://localhost:8080/apis/graphql',
cache: new InMemoryCache()
});
render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root'),
);

And in App.tsx we see the same pql we issued in Postman earlier for the findAllAccounts query:

const ACCOUNT_INFORMATION_QUERY = gql`
{findAllAccounts {
id
balance
description
bank {
id
name
}
}}
`;
function AccountInformation() {
const {loading, error, data} = useQuery(ACCOUNT_INFORMATION_QUERY);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return data.findAllAccounts.map( (account:any) =>
<div key={account.id}>
bank id: {account.bank.id} , bank name: {account.bank.name} , account id: {account.id}, account description: {account.description}, account balance: {account.balance}
</div>
);
}
function App() {
return (
<div>
<h2>Bank Account(s) Information...</h2>
<AccountInformation/>
</div>
);
}

Finally, when we run the React application, we see the expected same query outcome.

Oracle is extremely focused on robust and simple support for the React, GraphQL, and Spring Boot Data communities and has additional functionality and innovation planned in an upcoming release. More on that later.

Please feel free to contact me with any comments or questions and thanks for reading!

--

--