Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
boudhayan-dev
Employee
Employee

In this blog series, we will develop a Spring Boot application and deploy it on SAP Cloud Platform (Cloud Foundry). For persistence, we will make use of SAP HANA service on SCP. The goal of this project is to show how we can make use of HANA database in Cloud foundry. The application itself is very simple - REST endpoints supporting CRUD operations on Employee entity.


The code used in this blog can be found here - GitHub

So, let's get started.

STEP 1 : Create the Spring boot application.



1.1 Create Spring Boot project using Spring Starter project in Eclipse


Install Spring Tools 4 for Eclipse.

Select New project -> Spring -> Create new Spring Boot project.

Group ID - spring-hana-cloud-foundry
Artifact ID - 
spring-hana-cloud-foundry
Name -
spring- hana-cloud-foundry
Description -
Sample Spring Boot application to use SAP HANA Service

1.2 Add the following dependencies, profiles and plugins in pom.xml


pom.xml -
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>spring-hana-cloud-foundry</groupId>
<artifactId>spring-hana-cloud-foundry</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>spring-hana-cloud-foundry</name>
<description>Sample Spring Boot application to use SAP HANA Service</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-cloudfoundry-connector</artifactId>
<version>1.2.2.RELEASE</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.sap.hana.cloud</groupId>
<artifactId>spring-cloud-cloudfoundry-hana-service-connector</artifactId>
<version>1.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-spring-service-connector</artifactId>
<version>1.2.2.RELEASE</version>
</dependency>

<dependency>
<groupId>com.sap.db.jdbc</groupId>
<artifactId>ngdbc</artifactId>
<version>2.3.55</version>
</dependency>

</dependencies>

<profiles>
<profile>
<id>local</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<activatedProperties>local</activatedProperties>
</properties>
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
</profile>

<profile>
<id>cf</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<properties>
<activatedProperties>cf</activatedProperties>
</properties>

</profile>
</profiles>


<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

 

  • spring-boot-starter-data-jpa​ - Starter for using Spring Data JPA with Hibernate

  • lombok - Provides automatic getter, setters and other convenient annotations for your POJOs.

  • h2 - In-memory database used for local testing


The important dependencies are -

  • spring-cloud-cloudfoundry-connector - It simplifies the process of connecting to services in cloud environments like Cloud Foundry.

  • spring-cloud-spring-service-connector - This library provides data source implementations for spring data connector.

  • spring-cloud-cloudfoundry-hana-service-connector - Hana connector for Spring boot.

  • ngdbc - HANA driver.


 

1.3 Create the Models, Repository, Service and Controller for the application


Create model - Employee.java
package com.sap.springhanacloudfoundry.models;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.Getter;
import lombok.AllArgsConstructor;

@Entity
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@ToString
public class Employee {
@Id
@Column(name ="id", unique=true)
private long id;
@Column(name ="firstName")
private String firstName;
@Column(name ="lastName")
private String lastName;
@Column(name ="email", unique=true)
private String email;
@Column(name ="contact",unique=true)
private String contact;
}

 

Create corresponding repository for the model class - EmployeeRepository.java
package com.sap.springhanacloudfoundry.repository;

import org.springframework.data.repository.CrudRepository;
import com.sap.springhanacloudfoundry.models.Employee;

public interface EmployeeRepository extends CrudRepository<Employee, Long>{
}

Create corresponding service for the repository - EmployeeService.java
package com.sap.springhanacloudfoundry.services;

import java.util.ArrayList;
import java.util.List;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.sap.springhanacloudfoundry.models.Employee;
import com.sap.springhanacloudfoundry.repository.EmployeeRepository;

@Service
public class EmployeeService {

@Autowired
private EmployeeRepository employeeRepository;

public long getCount() {
long count = employeeRepository.count();
return count;
}

public List<Employee> findAllEmployee(){
List<Employee> employee = new ArrayList<>();
employeeRepository.findAll().forEach(employee::add);
return employee;
}

public boolean insertEmployee(Employee employee) {
try {
employeeRepository.save(employee);
return true;
}
catch (Exception e) {
return false;
}
}

public Employee findEmployeeById(Long id) {
Employee employee = employeeRepository.findById(id).orElse(null);
return employee;
}

public boolean deleteEmployee(long id) {
Employee employee = employeeRepository.findById(id).orElse(null);
if(employee!=null) {
employeeRepository.delete(employee);
return true;
}
return false;
}
}

 

Create the controller to handle all the requests - EmployeeController.java
package com.sap.springhanacloudfoundry.controller;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.sap.springhanacloudfoundry.models.Employee;
import com.sap.springhanacloudfoundry.services.EmployeeService;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class EmployeeController {

Logger log = LoggerFactory.getLogger(getClass());

@Autowired
private EmployeeService employeeService;

@RequestMapping("/employee/count")
public long count() {
log.info("Search total number of employees");
return employeeService.getCount();
}

@RequestMapping("/employee/all")
public List<Employee> getAllEmployees(){
log.info("Searching all employees");
return employeeService.findAllEmployee();
}

@RequestMapping(method=RequestMethod.POST, value = "/employee/add")
public boolean addEmployee(@RequestBody Employee employee) {

log.info("Creation/Updating Employee - "+employee.toString());
return employeeService.insertEmployee(employee);
}

@RequestMapping("/employee/id/{id}" )
public Employee findById(@PathVariable long id) {
log.info("Searching employee with ID - "+ id);
return employeeService.findEmployeeById(id);
}

@RequestMapping(method=RequestMethod.DELETE, value="/employee/delete/{id}")
public boolean deleteEmployee(@PathVariable long id) {
return employeeService.deleteEmployee(id);
}

}

 

And finally we come to most important bit of code, the configuration of the datasource.

We will bind the datasource to the application on runtime. The datasource details are available as environment variables in CF  also known as - VCAP_SERVICES. At the time of writing this blog, the datasource could not be bound to the application directly. The url, username and password needed to be injected manually into the config class due to an issue with the ngdbc driver - Issue

 

Create a config class for the datasource as follows  -  CloudDatabaseConfig.java
package com.sap.springhanacloudfoundry.config;

import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.cloud.config.java.AbstractCloudConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import com.zaxxer.hikari.HikariDataSource;

@Configuration
@Profile("cloud")
public class CloudDatabaseConfig extends AbstractCloudConfig {

@Bean
public DataSource dataSource(@Value("${hana.url}")final String url,
@Value("${hana.user}")final String user,
@Value("${hana.password}")final String password) {


return DataSourceBuilder.create()
.type(HikariDataSource.class)
.driverClassName(com.sap.db.jdbc.Driver.class.getName())
.url(url)
.username(user)
.password(password)
.build();

}
}

 

1.4 Create application properties


 

With that we are done with our application logic. The final bit is the application configuration which would define how we connect to the database in cloud as well as in local.

For cloud , create properties file as - application-cf.properties
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.naming_strategy=org.hibernate.cfg.EJB3NamingStrategy
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.HANAColumnStoreDialect
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true

hana.url = ${vcap.services.hana_migration.credentials.url}
hana.user = ${vcap.services.hana_migration.credentials.user}
hana.password = ${vcap.services.hana_migration.credentials.password}

 

hana_migration is the name of the HANA service instance bound to the application (We will cover this topic in the next part).

And for local testing , create properties file as - application-local.properties
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.hibernate.naming_strategy=org.hibernate.cfg.EJB3NamingStrategy
spring.h2.console.enabled=true

 

And in application.properties add -
spring.profiles.active=@activatedProperties@

 

With that, we are ready with our application code. The next step would be to deploy the application on cloud foundry and test it.

 

Additionally, if you want to run the application in local and test, you can run it as a Spring Boot app from Eclipse. The following endpoints are available for testing -

  1. GET /employee/count -> Returns count of total employees in database.

  2. GET /employee/all -> Returns all employees in database.

  3. GET /employee/id/{id} -> Returns the employee instance corresponding to the ID.

  4. POST /employee/add -> Add a new employee in database. Use the following payload in body -
    {
    "id":"1",
    "firstName":"Boudhayan",
    "lastName":"Dev",
    "email":"email@example.com",
    "contact":"12121212"
    }


  5. DELETE /employee/delete/{id} -> Delete particular employee from database.


 

See you in the next part where we configure the HANA instance in Cloud platform.

Cheers.

 

Find the remaining parts here -



  1. Develop the Spring Boot Application (PART 1) - Develop a Spring Boot (Java) application with HANA database on SAP Cloud Platform (Cloud Foundry) - ...

  2. Create instance of HANA service (PART 2) - Develop a Spring Boot (Java) application with HANA database on SAP Cloud Platform (Cloud Foundry) - ...

  3. Deploy and Test (PART 3) - Develop a Spring Boot (Java) application with HANA database on SAP Cloud Platform (Cloud Foundry) – ...

15 Comments
mark_devries
Explorer
An even easier way to automatically get the credentials wired up is using CFJavaEnv

Assuming you are using spring boot 2.x

simply add
		<dependency>
<groupId>io.pivotal.cfenv</groupId>
<artifactId>java-cfenv-boot</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>

to your pom and add the wiring:
	@Bean
@Primary
@Profile("cloud")
public DataSourceProperties dataSourceProperties() {
CfJdbcEnv cfJdbcEnv = new CfJdbcEnv();
DataSourceProperties properties = new DataSourceProperties();
CfCredentials hanaCredentials = cfJdbcEnv.findCredentialsByTag("hana");

if (hanaCredentials != null) {

String uri = hanaCredentials.getUri("hana");
properties.setUrl(uri);
properties.setUsername(hanaCredentials.getUsername());
properties.setPassword(hanaCredentials.getPassword());
}

return properties;
}

This is enough to wire up the datasource.
boudhayan-dev
Employee
Employee
0 Kudos
Thanks


mark.devries




This library will be very helpful in parsing CF environment variables. I always wondered why don't we have a parser library in Java when something similar already existed in Node (@SAP/xsenv and cfenv). I guess that problem is solved.


Regards
0 Kudos
How looks the application.properties now with this dependecy ?
0 Kudos

Hi Guys,
I get this Error on my pom.xml file. Can you help please ?

I am using this Dependecy:

<dependency>
<groupId>com.sap.db.jdbc</groupId>
<artifactId>ngdbc</artifactId>
<version>2.3.55</version>
</dependency>
</dependencies>

 

Error: Missing artifact com.sap.db.jdbc:ngdbc:jar:2.3.55

The Driver is not found: com.sap.db.jdbc.Driver.class.getName()

0 Kudos
Hello,

The group Id is different here. In pom.xml make the following change.

<groupId>com.sap.cloud.db.jdbc</groupId>

 

Thanks!

 
kayur_goyal
Advisor
Advisor
0 Kudos

Hi,

I am getting the following error when I try and push the application to Cloud Foundry,

I am using Cloud Foundry Trial.

Below is my pom.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>spring-hana-cloud-foundry</groupId>
<artifactId>spring-hana-cloud-foundry</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>spring-hana-cloud-foundry</name>
<description>Sample Spring Boot application to use SAP HANA Service</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>



<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-cloudfoundry-connector</artifactId>
<version>1.2.2.RELEASE</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.sap.hana.cloud</groupId>
<artifactId>spring-cloud-cloudfoundry-hana-service-connector</artifactId>
<version>1.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-spring-service-connector</artifactId>
<version>1.2.2.RELEASE</version>
</dependency>

<dependency>
<groupId>com.sap.cloud.db.jdbc</groupId>
<artifactId>ngdbc</artifactId>
<version>2.3.55</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/ngdbc-2.3.55.jar</systemPath>
</dependency>
<dependency>
<groupId>io.pivotal.cfenv</groupId>
<artifactId>java-cfenv-boot</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
</dependencies>

<profiles>
<profile>
<id>local</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<activatedProperties>local</activatedProperties>
</properties>
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
</profile>

<profile>
<id>cf</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<properties>
<activatedProperties>cf</activatedProperties>
</properties>

</profile>
</profiles>


<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
dhdbob26
Explorer
0 Kudos
Were you able to figure it out?
0 Kudos
i added that

<groupId>com.sap.cloud.db.jdbc</groupId>

but i got same error
0 Kudos
Hi Boudhayan,

 
DataSourceBuilder.create()
.type(HikariDataSource.class)
.driverClassName(com.sap.db.jdbc.Driver.class.getName())
.url(url)
.username(user)
.password(password)
.build();

 

What is the significance of HikariDataSource In data source builder??

 

Without  HikariDataSource as type connection is working fine for me.

 

Regards,

Lekhak Patil
boudhayan-dev
Employee
Employee
HikariDataSource is the connection pool implementation. You have other alternatives - c3p0, dbcp2, tomcat etc.

It is optional to specify in the config as far as I know. As by default spring data jpa comes bundled with HikariCP dependency.
0 Kudos
Hi Boudhayan,

 

During application initialization app is able to connect to Hana schema and container instance with provided url, username and password.

Now we want to try to connect to Hana schema and container instance during runtime, Rather than application initialization time.

We are working on the multi-tenant scenario and want to connect to different Hana schema and container instances depending on from which tenant request is coming.

Any help is appreciated.

 

Regards,

Lekhak Patil
0 Kudos
you can do just like this:

pom.xml
<!-- https://mvnrepository.com/artifact/com.sap.cloud.db.jdbc/ngdbc -->
<dependency>
<groupId>com.sap.cloud.db.jdbc</groupId>
<artifactId>ngdbc</artifactId>
<version>2.9.16</version>
</dependency>

and CloudDatabaseConfig.java
@Configuration
@Profile("cloud")
public class CloudDatabaseConfig extends AbstractCloudConfig {

@Bean
public DataSource dataSource(@Value("${hana.url}") final String url,
@Value("${hana.user}") final String user,
@Value("${hana.password}") final String password) {

return DataSourceBuilder.create()
.type(HikariDataSource.class)
.driverClassName(com.sap.db.jdbc.Driver.class.getName())
.url(url)
.username(user)
.password(password)
.build();
}
}

0 Kudos
I am getting exactly same error. Are you able resolve this? Thanks -Vijay Gorambekar
0 Kudos
I just found out that, the CloudDatabaseConfig is using profile "cloud"
@Profile("cloud")

 

But you have activated profile is "cf"

so if you change, the @Profile annotation to "cf", it will resolve your vcap hana.url
@Profile("cf")

Please try this.