Wednesday 31 July 2019

Develop a Spring Boot (Java) application with HANA database on SAP Cloud Platform (Cloud Foundry) – PART 1

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.

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.

SAP HANA Study Materials, SAP HANA Certifications, SAP HANA Online Exam, SAP HANA Guides

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.

No comments:

Post a Comment