SAP Java Entwicklung in der Cloud Platform lernen – Teil 2

SAP Java Entwicklung in der Cloud Platform lernen – Teil 2

Java Entwicklung in der SAP Cloud Platform – Teil 2

Lernen Sie mit dem Team der CloudDNA, alles über SAP Java Entwicklung in der SAP Cloud Platform. Wir erklären Ihnen alles was sie darüber wissen müssen.

Zugriff auf SAP HANA in der NEO Umgebung- Java Entwicklung

Im ersten Teil der Blogserie habe ich Sie in die Java Entwicklung auf Basis des Spring Frameworks eingeführt. Anhand eines einfachen RestControllers wurden die Grundlagen gezeigt.

Was wäre eine Applikation ohne Zugriff auf eine Datenbank. Im SAP Cloud Kontext ist es natürlich naheliegend dass SAP HANA als Datenbank verwendet wird. In diesem Teil der Blogserie zeige ich Ihnen wie Sie auf die Datenbank in der NEO Umgebung zugreifen können.

JNDI – Die gelben Seiten- Java Entwicklung

Der Zugriff auf die HANA Datenbank erfolgt über JNDI, aus meiner Sicht eine der spannendsten Technologien im Java Umfeld. Sie werden es kennenlernen und gerne damit arbeiten. Mit einem JNDI Lookup können Sie von der Laufzeitumgebung verwaltete Resourcen laden. Die Idee dahinter ist sehr einfach – die Laufzeitumgebung kümmert sich um das Instanzieren der benötigten Klassen und stellt diese der Applikation bereit.

Damit die hier vorgestellte Applikation JNDI unterstützt müssen Sie einige Tätigkeiten durchführen. Im ersten Schritt ist es erforderlich dass Sie im Verzeichnis src > main eine Unterverzeichnis namens webapp und darin in Unterverzeichnis namens WEB-INF anlegen. Nun müssen Sie im Verzeichnis src > main > webapp > WEB-INF eine Datei namens web.xml anlegen.

Verzeichnisstruktur

Verzeichnisstruktur für web.xml

Jene Resourcen die mittels JNDI verwaltet werden, müssen Sie  in der Datei web.xml als Resource Reference (resource-ref) anführen. Darin sollten Sie den Name (res-ref-name) definieren, unter dem die Resource angesprochen wird. Sowie die dahinterliegende Java Klasse (res-type) auf die typisiert wird.

 

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
 version="3.0">
   <resource-ref>
     <res-ref-name>jdbc/DefaultDB</res-ref-name>
     <res-type>javax.sql.DataSource</res-type>
   </resource-ref>
</web-app>

Spring Data Dependency deklarieren

Damit haben Sie den ersten Schritt bereits erledigt. Nachdem wir den Zugriff auf die Datenbank nicht mittels SQL Kommandos selbst implementieren, sondern auf Spring Data zurückgreifen, müssen Sie im pom.xml noch eine entsprechende Dependency definieren.

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

Nachdem Spring Data eine Vielzahl an Datenbanken unterstützt, müssen Sie der Laufzeitumgebung mitteilen welche Datenbank verwendet werden soll. Das passiert über die Datei application.properties. Falls diese noch nicht existiert müssen Sie diese im Verzeichnis src > main > resources anlegen. Der vollqualifizierte Klassenname des HANA Treibers lautet com.sap.db.jdbc.Driver

spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.HANAColumnStoreDialect
spring.jpa.properties.hibernate.connection.pool_size = 10
spring.datasource.driverClassName=com.sap.db.jdbc.Driver

@Configuration- SAP Java Entwicklung

Spring @Configuration-Annotation ist Teil des Spring Core Frameworks. Die Spring-Annotation weist Sie darauf hin, dass die Klasse über eine @Bean-Definitionsmethoden verfügt. Der Spring-Container kann dadurch die Klasse verarbeiten und Spring Beans zur Laufzeit generieren, die Sie in der Anwendung verwenden können.

Für die Verwendung im Neo Stack müssen Sie die Datasource mittels JNDI laden. Dazu sollten Sie eine entsprechende Klasse erstellen. In der nachfolgenden Code-Snippet wird die vollständige Klasse dargestellt.

Neo Datasource Configuration

package at.clouddna.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import javax.sql.DataSource;
import java.sql.SQLException;
@Configuration
@Profile({"neo"})
public class NeoConfig 
{	
	@Bean(destroyMethod="")
	public DataSource jndiDataSource() throws IllegalArgumentException, SQLException
	{
		JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
		DataSource ds = dataSourceLookup.getDataSource("java:comp/env/jdbc/DefaultDB");
		return ds;
	}
}

Der JNDI-Lookup zielt auf den Namen java:comp/env/jdbc/DefaultDB ab. Der Prefix java:comp/env/ ist in der SAP Cloud Platform immer derselbe. Der dahinter definierte Name jdbc/DefaultDB entspricht dem res-ref-name in der web.xml

Entwicklung einer Entity-Klasse

Die Verwendung von Spring-Data ermöglicht Ihnen eine sehr effiziente Entwicklung der Persistenzschicht. Spring-Data basiert auf dem Hibernate Framework. Sobald wir eine Klasse anlegen und diese mit der Annotation @Entity versehen, wird auf der darunterliegenden Datenbank eine zugehörige Tabelle angelegt. Im nachfolgende Code-Snippet zeige ich Ihnen eine einfach User Klasse.

User.java

package at.clouddna.demo.model;
import javax.persistence.*;
@Entity
public class User {
    @Column(nullable = false)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Long id;
    private String firstname;
    private String lastname;
    public Long getId() {
        return this.id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }
    public String getFirstname() {
        return this.firstname;
    }
    public void setLastname(String lastname) {
        this.lastname = lastname;
    }
    public String getLastname() {
        return this.lastname;
    }
}

CRUD Methoden

Das geniale an Spring-Data ist die Out-of-the-box Verfügbarkeit von CRUD Methoden. Dazu müssen sie lediglich ein Interface erstellen, dass vom JpaRespository erbt. Dies wird für die User Entity im nachfolgenden Code-Snippet dargestellt.

package at.clouddna.demo.repository;
import at.clouddna.demo.model.User;
public interface IUserRepository extends JpaRepository<User, Long> {
}

Das Respository können Sie  nun direkt im Controller verwenden, indem über Autowiring darauf zugegriffen wird. Mein Unternehmen sieht davon jedoch ab. Wir erstellen für jede Entity-Klasse immer ein zugehöriges DTO (Data Transfer Object) und erstellen zusätzlich eine Serivce-Klasse, die mit der @Service Annotation versehen wird, welche die Verwendung des Repository kapselt. Die Service-Klasse kann im Controller über die @Autowired Annotation injiziert werden.

Selbstverständlich zeige ich Ihnen wie das funktioniert.

ServiceBase Klasse und Model Mapper

Das Mapping der Entity-Klasse auf das DTO und umgekehrt führen wir nicht manuell sondern mittels ModelMapper durch. Den Modelmapper müssen Sie ins pom.xml als Dependency aufnehmen.

<dependency>
   <groupId>org.modelmapper</groupId>
   <artifactId>modelmapper</artifactId>
   <version>2.3.5</version>
</dependency>

ServiceBase.java

package at.clouddna.codegenerator.service.da;
import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public abstract class ServiceBase {
    private ModelMapper modelMapper;
    public ServiceBase(){
        this.modelMapper = new ModelMapper();
        this.modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STANDARD);
    }
    public <D, T> D map(T entity, Class<D> outClass) {
        return modelMapper.map(entity, outClass);
    }
    public <D, T> List<D> mapAll(Collection<T> entityList, Class<D> outCLass) {
        return entityList.stream()
                .map(entity -> map(entity, outCLass))
                .collect(Collectors.toList());
    }
    protected void writeToFile(String fileName, String content) throws IOException {
        FileOutputStream outputStream = new FileOutputStream(fileName);
        byte[] strToBytes = content.getBytes();
        outputStream.write(strToBytes);
        outputStream.close();
    }
    protected void writeToFile(String fileName, byte[] content) throws IOException {
        FileOutputStream outputStream = new FileOutputStream(fileName);
        outputStream.write(content);
        outputStream.close();
    }
}

Service Klasse

Die Service Klasse erbt von der ServiceBase Klasse und kapselt den Zugriff auf die Datenbank. Nachfolgendes Code-Snippet zeigt die UserService Klasse. Wichtig ist das Sie dabei die Klasse mit der Annotation @Service versehen. Dadurch können sie diese im Controller mittels Autowiring verwenden.

UserService.java

package at.clouddna.demo.service;
import at.clouddna.demo.dto.UserDto;
import at.clouddna.demo.model.User;
import at.clouddna.demo.respository.IUserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class UserService extends ServiceBase {
    @Autowired
    private IUserRepository repository;
    public UserDto create(UserDto userDto) {
        return map(repository.save(map(userDto, User.class)), UserDto.class);
    }
    public UserDto update(UserDto userDto) {
        return map(repository.save(map(userDto, User.class)), UserDto.class);
    }
    public boolean delete(Long id) {
        repository.deleteById(id);
        return true;
    }
    public UserDto findById(Long id) {
        Optional<User> userOptional = repository.findById(id);
        if(!userOptional.isPresent()) {
            return null;
        }
        return map(userOptional.get(), UserDto.class);
    }
    public List<UserDto> findAll() {
        return mapAll(repository.findAll(), UserDto.class);
    }
}

RestController

Abschließend zeige ich Ihnen wie der zuvor entwickelte Service im RestController vollumfänglich für alle CRUD-Methoden verwendet werden kann. Sie werden überrascht sein wie einfach das möglich ist!

UserController.java

package at.clouddna.demo.controller;
import at.clouddna.demo.dto.UserDto;
import at.clouddna.demo.model.User;
import at.clouddna.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    @GetMapping
    public ResponseEntity getAll() {
        return ResponseEntity.ok(userService.findAll());
    }
    @GetMapping("/{id}")
    public ResponseEntity getById(@PathVariable("id") Long id) {
        return ResponseEntity.ok(userService.findById(id));
    }
    @PostMapping
    public ResponseEntity create(@RequestBody UserDto body) {
        return ResponseEntity.ok(userService.create(body));
    }
    @PutMapping("/{id}")
    public ResponseEntity update(@PathVariable("id") Long id,
                                 @RequestBody UserDto body) {
        body.setId(id);
        return ResponseEntity.ok(userService.update(body));
    }
    @DeleteMapping("/{id}")
    public ResponseEntity delete(@PathVariable("id") Long id) {
        return ResponseEntity.ok(userService.delete(id));
    }
}

Fazit zur SAP Java Entwicklung in der Cloud Platform

Ich hoffe dass ich Ihnen in diesem Teil die Java Entwicklung in der SAP Cloud Platform schmackhaft gemacht habe. Wie Sie hoffentlich erkannt haben handelt es sich um kein Hexenwerk und keine Weltraumwissenschaft. Einen Tipp möchte ich Ihnen noch mitgeben – achten Sie bereits in der Planung des Projekts darauf dass alles sauber strukturiert ist und Sie jeweils ein eigenes Paket für Entity, DTO, Repository, Service und Controller definieren.

FAQ und Fakten über  SAP Java Entwicklung

 

Was bringt mir die  Verwendung von Spring- Data?

Die Verwendung von Spring-Data ermöglicht Ihnen eine sehr effiziente Entwicklung der Persistenzschicht. Spring-Data basiert auf dem Hibernate Framework. Sobald wir eine Klasse anlegen und diese mit der Annotation @Entity versehen, wird auf der darunterliegenden Datenbank eine zugehörige Tabelle angelegt.

Welche Technologie kann ich im Java Umfeld verwenden ?

Der Zugriff auf die HANA Datenbank erfolgt über JNDI, aus meiner Sicht eine der spannendsten Technologien im Java Umfeld. Mit einem JNDI Lookup können Sie von der Laufzeitumgebung verwaltete Resourcen laden. Die Idee dahinter ist sehr einfach – die Laufzeitumgebung kümmert sich um das Instanzieren der benötigten Klassen und stellt diese der Applikation bereit.

SAP Java Entwicklung in der Cloud Platform – Teil 1

SAP Java Entwicklung in der Cloud Platform – Teil 1

Java Entwicklung in der SAP Cloud Platform – Teil 1

In dieser Blogreihe zeige ich Ihnen wie die Java Entwicklung für die SAP Cloud Plaform auf Basis von Spring Boot durchgeführt wird. Spring Boot ist das defacto Standard Framework wenn es um die Entwicklung von Java Applikationen in der Cloud / bei Software-as-a-Service geht.

Zur Entwicklung mit Spring Boot gibt es eine Vielzahl an Resourcen im Internet, jedoch blieben die SAP Cloud Platform Spezifika dabei meist auf der Strecke. SAP bietet im Neo Stack der SAP Cloud Platform  ein SDK an, dass Ihnen die Verwendung von SAP-eigenen Funktionalitäten in Java ermöglicht. Dieser Blog fokusiert sich auf die allgemeinen Entwickleraufgaben in der Entwicklung von Java Applikationen. Dabei zeigen wir den Aufbau der pom.xml, ausserdem gehen wir darauf ein wie sein einen einfachen RestController implementierien.

Folgende Inhalte werde ich in zukünftigen Blogs zeigen:

  • Wie greife ich auf HANA DB mittels JNDI ein?
  • Zugriff auf Destinations mittels JNDI
  • Das zugreifen auf Tenant Informationen mittels JNDI
  • Der Zugriff auf User Informationen

Aufbau der pom.xml

Die pom.xml beinhaltet alle relevanten Informationen die für den Build der Applikation erforderlich sind.

Parent-Java Entwicklung

Nachdem wir auf Spring Boot setzen müssen wir dies als parent definieren

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.3.1.RELEASE</version>
  <relativePath />
</parent>

Properties – Java Entwicklung

In den properties können Sie die Java Version sowie die Version des SAP Cloud SDK definieren.

<properties>
  <java.version>1.8</java.version>
  <sap.cloud.sdk.version>3.107.18</sap.cloud.sdk.version>
</properties>

Dependencies- Java Entwicklung

In den dependencies können Sie die Abhängigkeiten definieren. Dabei wird u.a. auf SpringBoot Starter Web verwiesen und auch auf das SAP Cloud SDK. Beim SAP Cloud SDK ist es wichtig, dass Sie auf die in den properties definierte Version verlinken. Ausserdem sollten Sie den scope auf den Wert provided setzen. Damit wird diese Abhängigkeit nicht in das WAR file gepackt und auf als dem Server bereitgestellt betrachtet.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId> 
</dependency>
<dependency> 
  <groupId>com.sap.cloud</groupId>
  <artifactId>neo-java-web-api</artifactId>
  <version>${sap.cloud.sdk.version}</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
  <version>2.8.6</version>
</dependency>

Profile anlegen- Java Entwicklung

In diesem Beispiel der Java Entwicklung werden wir für das Deployment auf den Neo Stack ein eigenes Profil angelegen. Darin definieren sie, dass bestimmte Abhängigkeiten vom Server bereitgestellt werden und diese nicht in das WAR file eingebettet werden. Andernfalls würde der Start der Applikation fehlschlagen.

<profile>
  <id>neo</id>
  <activation>
    <activeByDefault>true</activeByDefault>
  </activation>
  <properties>
    <packaging.type>war</packaging.type>
  </properties> 
  <dependencies> 
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <scope>provided</scope>
    </dependency>  
  </dependencies>
</profile>

Build Plugins

Abschließend müssen Sie im Build noch das Spring Boot Maven Plugin definieren.

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

Build der Applikation für die SAP Cloud Platform

Sie können den Build der Applikation über die CommandLine mit folgenden Befehl durchführen.

mvn clean package -Pneo

Damit Sie die Applikation innerhalb vom Tomcat erfolgreich starten können, müssen Sie die configure Methode aus dem SpringBootServletInitializer redefinieren. Dies kann entweder direkt in der Hauptklasse der Applikation erfolgen oder in einer eigenen Klasse. Ich persönlich bevorzuge an dieser Stelle die Verwendung einer eigenen Klasse.

package at.clouddna.demo;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer {
  @Override
  protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    return application.sources(DocmgmtApplication.class);
  }
}

Implementierung eines einfachen Rest Controllers

Damit ein Funktionstest durchgeführt werden kann, implementiere ich immer eine Ping Methode in einem eigenen Controller.

package at.clouddna.docmgmt.controller; 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 
import org.springframework.http.ResponseEntity; 
import org.springframework.web.bind.annotation.GetMapping; 
import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.RestController; 
@RestController 
@RequestMapping({ "/ping" }) 
public class PingController { 
  private static final Logger logger = LoggerFactory.getLogger(PingController.class); 
  @GetMapping
  public ResponseEntity<?> ping() { 
    logger.debug("ping called"); 
    return ResponseEntity.ok("Hello World"); 
  }
}

Damit sind wir mit einer einfachen Applikation bereits fertig. Beim Deployment über das SAP Cloud Platform Cockpit sollten Sie darauf achten, dass die Runtime Java Web Tomcat 8 ausgewählt wird und dass das Profile als JVM Parameter wie folgt übergeben wird.

-Dspring.profiles.active=neo

Den Ping Controller können Sie direkt aus dem Browser heraus testen, da die entsprechende Methode über einen HTTP GET Request aufgerufen wird.

Nach dem Ping gehts weiter zum 2. Teil unseres Blogserie über SAP Java Entwicklung.