Develop a Simple App: React, GraphQL, Spring Data JPA, UCP, and Oracle
Develop a simple app in minutes with a React frontend that makes GraphQL calls against a Spring Boot Data JPA service backed by Oracle database and accessed via UCP.
Join the DZone community and get the full member experience.
Join For FreeThis blog is written to give a succinct description and example of a modern full stack microservice app including a React front-end service that conducts GraphQL queries against a Spring Boot Data JPA back-end service that in turn maps to an Oracle database.
As such, I'll start right off with a link to the app source.
It should take only a couple of minutes to build and run using the simple instructions here:
cd spring-data-jpa-graphql-ucp-oracle
- Modify
src/main/resources/application.properties
to set values forspring.datasource.url
,spring.datasource.username
, andspring.datasource.password
. - Run
mvn clean install
. - Run
java -jar target/spring-data-jpa-graphql-oracle-0.0.1-SNAPSHOT.jar
. - (In a separate terminal/console)
cd react-graphql
- Run
yarn add @apollo/client graphql
(this is only necessary once for the project). - Run
npm run build
. - Run
npm start
.
A browser window will open to http://localhost:3000/. This 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. In doing so, it can cut down the number of requests required and improve performance.
Some details working from back to front:
1. Obtain an Oracle Database and Configure the Spring Boot Service
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, etc.) 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 labeling, 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.
Below is an 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>
2. Basics of Spring Boot Data JPA
Next, notice the basics of Spring Boot 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).
3. Basics of Serverside GraphQL in Spring Boot
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. For example, 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 mediation 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: "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, so I am using the original, more widely used approach here.
4. Try the App
Finally, try out the app to see the behavior.
Postman is a handy and simple testing utility. 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 account 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=?
5. Front End React Client
Now let's look at the front end React client and its 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 the following:
yarn add @apollo/client graphql
In index.tsx, we see the creation of a client that points to the Spring Boot 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'), );
Also, 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.
Additional functionality and innovation are planned in an upcoming release. More on that later.
Please feel free to contact me with any comments or questions, and thanks for reading!
Published at DZone with permission of Paul Parkinson. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments