Monitoring database interactions is critical for performance tuning, debugging, and security. With Oracle AI Database, you can attach client metadata to connections; such as client identifiers, module and action names. Client metadata can be queried from views like V$SESSION to see where your application is interacting with the database.
In this article, we’ll build a sample Spring Boot application that integrates client info into database connections. The sample includes a REST API for managing books that shows both global and per-operation client info settings.
Prerequisites:
- Java 21+
- Maven
- Docker-compatible environment (for docker-compose)
You can view the full sample app here: Spring Boot Database Client Info.
Project Dependencies
We use Spring starters for JDBC and web interfaces, as well as the Oracle Spring Boot starter for UCP (Universal Connection Pool):
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.oracle.database.spring</groupId>
<artifactId>oracle-spring-boot-starter-ucp</artifactId>
<version>${oracle.starters.version}</version>
</dependency>
<!-- Include if using Oracle Database Wallet -->
<!-- <dependency>-->
<!-- <groupId>com.oracle.database.spring</groupId>-->
<!-- <artifactId>oracle-spring-boot-starter-wallet</artifactId>-->
<!-- <version>${oracle.starters.version}</version>-->
<!-- </dependency>-->
</dependencies>In the Spring application properties, we configure the Java DataSource to use UCP:
spring:
application:
name: MyApp
datasource:
username: testuser
password: testpwd
url: jdbc:oracle:thin:@localhost:1525/freepdb1
driver-class-name: oracle.jdbc.OracleDriver
type: oracle.ucp.jdbc.PoolDataSource
oracleucp:
initial-pool-size: 1
min-pool-size: 1
max-pool-size: 30
connection-pool-name: ${spring.application.name}
connection-factory-class-name: oracle.jdbc.pool.OracleDataSourceSet app and host name
To inject global database client info we wrap a Java DataSource with a Properties object (ClientInfoDataSource):
package com.example.clientinfo;
import org.springframework.jdbc.datasource.DelegatingDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class ClientInfoDataSource extends DelegatingDataSource {
private final Properties clientInfo;
public ClientInfoDataSource(DataSource original, Properties clientInfo) {
super(original);
this.clientInfo = clientInfo;
}
@Override
public Connection getConnection() throws SQLException {
Connection connection = super.getConnection();
connection.setClientInfo(clientInfo);
return connection;
}
}Then, we configure the properties object with the app and host name using the OCSID.CLIENTID property (Configuration class):
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
InetAddress address;
try {
address = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
if(bean instanceof DataSource ds) {
Properties props = new Properties();
String clientId = "%s@%s".formatted(appName, address.getHostName());
props.setProperty("OCSID.CLIENTID", clientId);
return new ClientInfoDataSource(ds, props);
}
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}Now, each DataSource connection uses the OCSID.CLIENTID=MyApp@<hostname> property. You can extend this for any global client info.
Set module and action for a REST controller
The BooksController exposes a CRUD API for books. In each operation, we configure the OCSID.MODULE and OCSID.ACTION client info properties:
private Connection getConnectionWithClientInfo(String action) throws SQLException {
Connection conn = dataSource.getConnection();
conn.setClientInfo("OCSID.MODULE", "Books");
conn.setClientInfo("OCSID.ACTION", action);
return conn;
}
```
// Example usage in getAllBooks():
try (Connection conn = getConnectionWithClientInfo("getAllBooks");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM books")) {
// ...
}The BooksController uses similar code for POST, GET, and DELETE methods.
Run the app
Clone the spring-boot-database-client-info project and start the Oracle Database Free container:
docker compose up -dThis runs grant_permissions.sql to create a test user (testuser) and the books table:
create table testuser.books (
id number generated always as identity primary key,
title varchar2(255) not null,
author varchar2(255) not null,
isbn varchar2(20),
published_date date
);Then run the app, which starts on port 8080:
mvn spring-boot:runTesting client info
Create a book:
curl -X POST http://localhost:8080/books \
-H "Content-Type: application/json" \
-d '{"title":"Test Book","author":"John Doe","isbn":"1234567890","publishedDate":"2023-01-01"}'Get all books:
curl http://localhost:8080/booksObserve client info from the V$SESSION view:
SELECT sid, client_identifier, module, action FROM v$session WHERE username = 'TESTUSER';This shows entries like CLIENT_IDENTIFIER=”MyApp@hostname”, MODULE=”Books”, ACTION=”createBook”:

You can take this a step further and view historical session data from V$ACTIVE_SESSION_HISTORY:
SELECT
ash.sample_time,
ash.session_state,
ash.event,
ash.sql_id
FROM v$active_session_history ash
WHERE ash.MODULE = 'Books'
ORDER BY ash.sample_time DESC;References
Looking for more observability tools? Check out these resources:

Leave a Reply