SuperAPI AI Directory

persistence-hana

Deployment & operations

View repository

Updated 10-16 13:16

About this skill

Persistence (Database)

Configure HANA Cloud database connectivity for Cloud Foundry.

Purpose

Replace Neo's javax.sql.DataSource JNDI resource reference with Cloud Foundry's HANA schema binding using the SAP Java Buildpack's TomcatDataSourceFactory.

Detection

This skill applies if any of these patterns are found:

In web.xml

<resource-ref>
    <res-ref-name>jdbc/DefaultDB</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
</resource-ref>

In Java source files

import javax.sql.DataSource;
import javax.annotation.Resource;

@Resource(name = "jdbc/DefaultDB")
private DataSource dataSource;

// OR JNDI lookup
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/DefaultDB");

Prerequisites

Working directory: This skill must run inside the -cf-migration copy of your app, created by jakarta-java25-migration or neo-to-cf-migration-orchestrator. If your current directory does not end in -cf-migration, switch to it before proceeding.

Before invoking this skill, ensure you have invoked:

  1. sdk-replacement - Use the sdk-replacement skill
    • Sets up SAP Cloud SDK
    • REQUIRED before this skill

Also required:

  • HANA Cloud instance available in your subaccount

Transformation Steps

Step 0: Detect existing JSON conventions

Before generating any code, scan for existing JSON library usage:

# JSON libraries
grep -E "fasterxml\.jackson|com\.google\.code\.gson|org\.json|jakarta\.json" pom.xml
grep -rl "ObjectMapper\|new Gson()\|new JSONObject\|Json\.create" --include="*.java" src/main/java/ 2>/dev/null | head -5

Rule: Reuse what is found. Default if nothing found: jakarta.json (JSON-P) — already in cf-tomcat-bom, no extra dependency needed.

Step 1: Remove Resource Reference from web.xml

Remove this from web.xml:

<resource-ref>
    <res-ref-name>jdbc/DefaultDB</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
</resource-ref>

Step 2: Create context.xml

Create src/main/webapp/META-INF/context.xml - see assets/context.xml:

<?xml version='1.0' encoding='utf-8'?>
<Context>
    <Resource name="jdbc/DefaultDB"
              auth="Container"
              type="javax.sql.DataSource"
              factory="com.sap.xs.jdbc.datasource.tomcat.TomcatDataSourceFactory"
              service="${service_name_for_DefaultDB}"/>
</Context>

Note: The ${service_name_for_DefaultDB} placeholder is replaced at runtime via the JBP_CONFIG_RESOURCE_CONFIGURATION environment variable.

Step 3: Create resource_configuration.yml

Create src/main/webapp/META-INF/sap_java_buildpack/config/resource_configuration.yml - see assets/resource_configuration.yml:

---
tomcat/webapps/ROOT/META-INF/context.xml:
  service_name_for_DefaultDB: ${app-name}-hana

Note: Replace ${app-name}-hana with your actual HANA service instance name defined in mtad.yaml.

Step 4: Update MTA Descriptor

Add HANA schema resource and configuration to mtad.yaml:

_schema-version: "3.2"
ID: ${app-name}
version: 0.0.1

modules:
  - name: ${app-name}
    type: java.tomcat
    path: target/<artifactId>.war
    parameters:
      buildpack: sap_java_buildpack_jakarta
      disk-quota: 1024M
      memory: 1024M
    properties:
      ENABLE_SECURITY_JAVA_API_V2: true
      SET_LOGGING_LEVEL: 'ROOT: INFO'
      # Resource configuration - MUST use YAML list format
      JBP_CONFIG_RESOURCE_CONFIGURATION:
        - tomcat/webapps/ROOT/META-INF/context.xml:
            service_name_for_DefaultDB: ${app-name}-hana
    requires:
      - name: ${app-name}-hana

resources:
  - name: ${app-name}-hana
    type: com.sap.xs.hana-schema

CRITICAL — do NOT add service: hana or service-plan: hdi-shared under a com.sap.xs.hana-schema resource.

❌ Anti-pattern subagents sometimes emit (it breaks deploy):

- name: ${app-name}-hana
  type: com.sap.xs.hana-schema
  parameters:
    service: hana              # ← WRONG for hana-schema
    service-plan: hdi-shared   # ← WRONG for hana-schema

Those two keys belong on the GENERIC type: org.cloudfoundry.managed-service (xsuaa, destination, credstore, sdm, …). The dedicated com.sap.xs.hana-schema type already implies the service and the plan — adding them makes the controller reject the request at deploy time with:

CF-UnprocessableEntity(10008): Invalid service plan. ... Ensure that the service plan is visible in your current space ...

✅ Correct form:

- name: ${app-name}-hana
  type: com.sap.xs.hana-schema

Step 5: Java Code (No Changes Required)

The Java code using DataSource via JNDI lookup continues to work:

import javax.sql.DataSource;
import javax.naming.Context;
import javax.naming.InitialContext;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class PersistenceServlet extends HttpServlet {

    private DataSource getDataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/DefaultDB");
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        try (Connection conn = getDataSource().getConnection()) {

            // Create table if not exists
            String createTable = "CREATE TABLE IF NOT EXISTS PERSONS " +
                "(ID INTEGER PRIMARY KEY, NAME VARCHAR(255))";
            try (PreparedStatement stmt = conn.prepareStatement(createTable)) {
                stmt.execute();
            }

            // Query data
            String query = "SELECT * FROM PERSONS";
            try (PreparedStatement stmt = conn.prepareStatement(query);
                 ResultSet rs = stmt.executeQuery()) {

                response.setContentType("application/json");
                response.getWriter().println("[");

                boolean first = true;
                while (rs.next()) {
                    if (!first) response.getWriter().println(",");
                    response.getWriter().printf("{\"id\": %d, \"name\": \"%s\"}",
                        rs.getInt("ID"), rs.getString("NAME"));
                    first = false;
                }

                response.getWriter().println("]");
            }

        } catch (Exception e) {
            throw new ServletException("Database error: " + e.getMessage(), e);
        }
    }
}

Step 6: Alternative - Using @Resource Annotation

If using @Resource annotation, it continues to work:

import javax.annotation.Resource;
import javax.sql.DataSource;

public class PersistenceServlet extends HttpServlet {

    @Resource(name = "jdbc/DefaultDB")
    private DataSource dataSource;

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        try (Connection conn = dataSource.getConnection()) {
            // ... use connection
        } catch (SQLException e) {
            throw new ServletException(e);
        }
    }
}

Directory Structure

After applying this skill, your project should have:

src/main/webapp/
├── META-INF/
│   ├── context.xml                           # DataSource configuration
│   └── sap_java_buildpack/
│       └── config/
│           └── resource_configuration.yml    # Service name mapping
└── WEB-INF/
    └── web.xml                               # (resource-ref removed)

Configuration Files

File Location Purpose
context.xml src/main/webapp/META-INF/ Defines JNDI DataSource
resource_configuration.yml src/main/webapp/META-INF/sap_java_buildpack/config/ Maps service name placeholder

CF Services

Service Type Purpose
${app-name}-hana com.sap.xs.hana-schema HANA Cloud schema binding

Creating HANA Cloud Instance

If you don't have a HANA Cloud instance:

Option 1: Via BTP Cockpit

  1. Navigate to your subaccount
  2. Go to Service Marketplace
  3. Find "SAP HANA Cloud"
  4. Create an instance
  5. Create a schema service key

Option 2: Via CF CLI

# List available plans
cf marketplace -s hana

# Create HANA schema
cf create-service hana schema ${app-name}-hana

Verification

1. Verify Directory Structure

ls -la src/main/webapp/META-INF/
# Should show: context.xml

ls -la src/main/webapp/META-INF/sap_java_buildpack/config/
# Should show: resource_configuration.yml

2. Build and Deploy

mvn clean package
cf deploy . -f

3. Verify Service Binding

cf services
# Should show ${app-name}-hana bound to ${app-name}

cf env ${app-name} | grep -A 20 "hana"
# Should show HANA credentials

4. Test Database Connectivity

curl "https://${app-url}/persistence"
# Should return database query results

5. Check Logs

cf logs ${app-name} --recent | grep -i "datasource\|hana\|jdbc"

Common Issues

Issue: "Cannot create JDBC driver" error

Cause: HANA JDBC driver not available. Solution: The SAP Java Buildpack includes HANA drivers. Ensure you're using sap_java_buildpack_jakarta.

Issue: "Service not found" during deployment

Cause: HANA schema service doesn't exist. Solution:

  1. Check HANA Cloud entitlement in subaccount
  2. Create the hana-schema service manually first:
    cf create-service hana schema ${app-name}-hana
    

Issue: Connection refused

Cause: HANA Cloud instance not running or IP allowlist. Solution:

  1. Check HANA Cloud instance is started
  2. Verify IP allowlist includes CF runtime IPs

Issue: "Invalid object name" SQL error

Cause: Schema not set correctly or table doesn't exist. Solution: HANA schema services automatically set the current schema. Verify table names are correct.

Issue: JBP_CONFIG_RESOURCE_CONFIGURATION not working

Cause: YAML syntax error or wrong path. Solution:

  1. Validate YAML syntax
  2. Ensure path matches: tomcat/webapps/ROOT/META-INF/context.xml
  3. Check indentation in mtad.yaml

Using JPA (Optional)

If your application uses JPA, update persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence
                                 https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
             version="3.0">

    <persistence-unit name="default" transaction-type="RESOURCE_LOCAL">
        <non-jta-data-source>java:comp/env/jdbc/DefaultDB</non-jta-data-source>

        <properties>
            <property name="jakarta.persistence.schema-generation.database.action"
                      value="create"/>
            <property name="eclipselink.logging.level" value="INFO"/>
        </properties>
    </persistence-unit>
</persistence>

Next Steps

After completing this skill, proceed to other applicable skills: