附註:

使用 OKE 上的 Spring Boot 建立以 Oracle GraalVM 為基礎的 Java 應用程式,將 SOAP 訊息儲存在可承諾量並傳送至 OCI 佇列

簡介

我們的許多客戶都仰賴傳統的簡單物件存取通訊協定 (SOAP) 訊息傳遞,以便在應用系統之間進行通訊。他們通常需要儲存交易資料、確保服務分離、實現內送要求負載平衡,以及啟用這些訊息的非同步通訊。

在本教學課程中,我們將瞭解如何使用部署在 Oracle Cloud Infrastructure Kubernetes Engine (OKE) 基礎架構上的 Spring Boot 建立以 Oracle GraalVM 為基礎的 Java 應用程式,這些基礎架構將作為具有不同 Oracle Cloud Infrastructure (OCI) 服務 (例如 Oracle Autonomous Transaction Processing (ATP) 和 OCI Queue) 的交易整合器。此設定可讓系統在沒有直接連線的情況下進行互動,以促進不同處理速度的應用程式之間的通訊。此外,一旦資訊儲存在資料庫中,便可加以諮詢或分析。

我們將採用以下技術:

Oracle Cloud Infrastructure Services (OCI): OCI 是一個安全、高效能的雲端平台,提供超過 150 多個雲端服務。專為擴展性、安全性和效能而設計。

Oracle 技術:

其他技術:

OCI 高層級架構:

OCI 架構

使用案例架構

注意:

目標

必要條件

作業 1:啟動設定及設定 OKE 叢集

在這項任務中,我們將佈建 Kubernetes 平台,應用程式將支援所有要儲存在 ATP 中的 SOAP 高交易性訊息,並即時將每個訊息傳送至 OCI Queue。

  1. 登入 OCI 主控台,瀏覽至開發人員服務Kubernetes 叢集 (OKE) ,然後選取您偏好的區間

    建立 OKE 叢集的方法有兩種:

    • 快速建立
    • 自訂建立
  2. 選取快速建立,因為此方法較簡單、更快且會自動部署 OKE 所需的所有元素以進行作業,例如:

    • 虛擬雲端網路 (VCN)。
    • 網際網路閘道。
    • 網路位址轉譯 (NAT) 閘道。
    • 服務閘道。
    • Kubernetes 叢集。
    • Kubernetes 工作節點與節點集區。

    注意:對於已經有客戶的服務、網路、基礎架構的企業環境,請務必自訂符合規範的 OKE 部署,並符合用戶端架構、資源和遵循最佳實務。

    快速建立

  3. 按一下建立叢集,然後輸入下列資訊。

    • 名稱:輸入 OKE 叢集的名稱。
    • 區間: 選取為此專案建立的區間。
    • Kubernetes 版本:選取可用的最新 Kubernetes 版本。
    • Kubernetes API 端點:在此教學課程中,選取公用端點,但您也可以選取專用端點
    • 節點類型:選取受管理節點。
    • Kubernetes 工作節點: 選取專用工作節點
    • 資源配置和影像:選取 VM.Standard.E5。Flex 、自訂 OCPU 數目 (2) 和記憶體 (16GB),並保留預設 Oracle Linux 8 影像。
    • 節點計數:輸入要在 OKE 節點集區部署的 2 個工作節點。

    建立 OKE 叢集

    複查 OKE 叢集正在運作。

    OKE 執行中

作業 2:佈建、設定及存取 OCI Container Registry Classic

我們需要管理儲存區域中的專案映像檔。為了達到此目的,我們將佈建 OCI Container Registry Classic。將映像檔儲存在 OCI Container Registry Classic 之後,便能夠在 OKE 中進行部署。

  1. 移至 OCI 主控台,瀏覽至開發人員服務容器與使用者自建物件容器登錄,然後按一下建立儲存區域

  2. 輸入下列資訊,然後按一下建立

    • 在區間中建立: 選取為此專案建立的區間。
    • 存取:選取公用
    • 儲存庫名稱:輸入 springboot/tutorialapp

    建立儲存區域

  3. 儲存區域建立之後,從您的 Oracle 管理主機使用下列命令來存取儲存區域。

    docker login -u 'tenancy_namespace/domain/username' regionID.ocir.io
    
    Password: xxxxxx
    

    Oracle Registry 認證

    正常

作業 3:佈建及設定 Oracle Autonomous Transaction Processing (ATP) 無伺服器資料庫

在 ATP 資料庫中,我們將儲存每筆交易收到的每個 SOAP 訊息的資料,大約每一則訊息會以毫秒為順序,並進行平行與循序插入。

  1. 移至 OCI 主控台,瀏覽至 Oracle Database 並按一下 Autonomous Transaction Processing

  2. 按一下建立 Autonomous Database ,然後輸入下列資訊。

    • 選取區間: 選取為此專案建立的區間。
    • 顯示名稱:輸入顯示名稱。
    • 顯示資料庫名稱:輸入資料庫名稱。
    • 選擇工作負載類型:選取交易處理
    • 選擇部署類型:選取無伺服器
    • 設定資料庫:
      • 開發人員:取消選取。
      • 選擇資料庫版本:選取 23ai
      • ECPU 計數:輸入 2
      • 計算自動調整:選取它。
      • 儲存體 (Storage):輸入 1024GB
    • 自動備份保留期間 (天):保留預設選項 60 天。
    • 建立管理員證明資料:
      • 使用者名稱:預設為 ADMIN,無法編輯。
      • 密碼:輸入您偏好的密碼。
      • 確認密碼:再次輸入密碼。
    • 選擇網路存取: 選取僅限專用端點存取,然後選擇為此專案建立的 VCN 和子網路。這項設定只會限制與指定專用網路 (VCN) 的連線。不過,您可以選擇其他選項,這取決於公司的需求。

    建立 Oracle ATP 資料庫

    建立 Oracle ATP 資料庫

    建立 Oracle ATP 資料庫

    複查 ATP 資料庫正在執行。

    可承諾量執行中

作業 4:在 Oracle Autonomous Transaction Processing (ATP) 中連線及建立專案表格

現在,我們需要在「任務 3」中產生的 ATP 資料庫中設定、連接及建立專案表格。

  1. 移至 OCI 主控台,瀏覽至 Oracle DatabaseAutonomous Transaction Processing ,然後按一下資料庫連線。選取 TLS 認證TLS ,然後按一下下載公事包

    資料庫連線

  2. 解壓縮公事包 .zip 檔案和 tnsnames.ora 中,您可以取得資料來源 URL 以取得此資料庫的連線。儲存此資料來源 URL。

    舉例而言:

    tutorialoracleatp_medium = (description= (retry_count=20)(retry_delay=3)(address=(protocol=tcps)(port=1522)(host=xxxxxxxx.adb.sa-saopaulo-1.oraclecloud.com))(connect_data=(service_name=xxxxxxxxxxxxxxx_tutorialoracleatp_medium.adb.oraclecloud.com))(security=(ssl_server_dn_match=no)))
    
  3. 現在需要存取資料庫。佈建 ATP 時,已啟用 Oracle REST Data Services (ORDS) 存取。如需詳細資訊,請參閱 Oracle REST Data Services

    移至 Autonomous Database 詳細資訊頁面,按一下資料庫動作。請注意,您只能從在相同虛擬雲端網路 (VCN) 中執行的運算執行處理存取。

    資料庫動作

  4. 在瀏覽器中貼上 URL,並使用先前在 ATP 資料庫中輸入的使用者與密碼來存取 ORDS,並存取 Oracle SQL Developer Web 模組。

    ORDS

  5. 使用下列查詢來建立與將收到的 SOAP 訊息相關的 USERSCARSHOUSES 表格。

    CREATE TABLE USERS (
    username varchar(50) NOT NULL,
    userlastname varchar(50) NOT NULL,
    id int NOT NULL,
    email varchar(50) NOT NULL,
    dateuser varchar(50) NOT NULL,
    attributeuser varchar(50) NOT NULL  
    );
    
    CREATE TABLE CARS (
    userid int NOT NULL,
    brand varchar(50) NOT NULL,
    color varchar(50) NOT NULL,
    plate varchar(50) NOT NULL  
    );
    
    CREATE TABLE HOUSES (
    userid int NOT NULL,
    floors int NOT NULL,
    locationhouse varchar(50) NOT NULL,
    rooms int NOT NULL,
    bathrooms int NOT NULL
    );
    

    在資料庫中建立的表格

作業 5:佈建並設定 OCI 佇列

在 OCI Queue 中,我們將在特定期間透過 RESTful HTTP API 儲存高度交易訊息。然後消費者可以立即或方便地讀取和刪除訊息,確保解除耦合並防止資料遺失。

  1. 移至 OCI 主控台,瀏覽至開發人員服務應用程式整合,然後按一下佇列

  2. 按一下建立佇列,輸入下列資訊,然後按一下建立佇列

    • 名稱:輸入佇列適用的名稱。
    • 區間: 選取您的工作區間。
    • 佇列設定值:在此教學課程中,我們將選取預設組態,但您也可以根據業務需求自訂多個選項,例如:可見性逾時保留期間上限通道使用量上限信函佇列設定值。
    • 設定加密設定值:選取 Oracle 管理的金鑰,但客戶管理的金鑰也是一個選項。

    建立佇列

    複查 OCI 佇列是否在執行中。

    OCI 佇列執行中

作業 6:使用 Spring Boot 隊建置 Oracle GraalVM 型 Java 應用程式,並部署到 OKE

現在,我們將在 Spring Boot 上開發並部署基於 Oracle GraalVM 的 Java 應用程式,以執行下列任務:

注意:開始之前,請務必先建立管理主機和開發環境,如先決條件 - 管理主機先決條件 - 開發環境段落所示。

一旦您的管理主機和開發環境已設定好,您就可以開始開發 Spring Boot 專案。

  1. 請前往 Spring initializr ,並建立第一個計劃,為我們提供 Spring Boot 專案的資料夾結構和基礎檔案,稍後再根據我們的需求進行修改。輸入下列資訊並按一下產生,這將會自動下載 Spring Boot 專案,並儲存並解壓縮到您的開發主機中。

    • 專案:選取 Maven
    • 語言:選取 Java
    • Spring 啟動:選取 3.3.6
    • 專案描述資料:
      • 群組:輸入 com.tutorial_springboot
      • 物件:輸入教學課程
      • 名稱:輸入教學課程
      • 描述:輸入 Spring Boot Application (讀取 SOAP,轉換成 JSON,插入 ATP 並放入 OCI 佇列)
    • 封裝:選取 Jar
    • Java:選取 17
    • 相依性:選取 Oracle 驅動程式Spring Web 服務Spring Web

    注意:我們可以根據需求,在專案中新增一些相依性,之後可以直接在 pom.xml 檔案中新增更多相依性。

    Springboot 專案

  2. 現在我們有彈簧開機結構專案。

    結構 Spring 啟動專案

    複查 pom.xml 檔案,我們將開始使用該檔案。

    原始 POM 檔案

    根據本教學課程中建議的範圍更新 pom.xml 檔案。

    • 新增 oci sdk 版本和下列相依性。

      • oci-java-sdk-common.
      • oci-java-sdk-queue.
      • oci-java-sdk-addons-oke-workload-identity.
      • oci-java-sdk-common-httpclient-jersey3.

      應用程式需要使用 OCI 進行認證、連線及管理 OCI 服務 (例如 OCI Queue)。

    • 已經是我們的 pom.xml 檔案具有 spring-boot-starter-web-services 相依性,因此我們必須增加 wsdl4j 相依性。主要目的是取得從 SOAP 訊息接收的資料,並將其放入 Java 物件中,建立操控 XML 有效負載的彈簧 Web 服務,以促進合約優先的 SOAP 服務開發。也允許設定從 XML 綱要定義 (XSD) 檔案載入的連接埠、URI 及設定 XML 綱要。

    • 新增 JSON 相依性。此程式庫將用於使用從 SOAP 訊息擷取的資料產生 JSON 格式。

    • 在「組建」段落中新增 spring-boot-maven-plugin Plugin。此外掛程式將允許我們產生 jar 執行檔的 Spring Boot 專案檔。

    • 在「組建」段落中新增 jaxb2-maven-plugin Plugin。此外掛程式將使用 Java API for XML 連結 (JAXB),從 XML 綱要產生 Java 類別,如此一來,我們就可以將資料從 SOAP 訊息傳送至我們建立的 Java 類別物件。

      此外掛程式區段中,請務必放置可在 Spring Boot 專案中包含 XSD 檔案的路徑組態。

      <configuration>
         <sources>
         <source>${project.basedir}/src/main/resources/messages.xsd<source>
         </sources>
      </configuration>
      
    • 在「組建」段落的「相依性」段落和 jasypt-maven-plugin Plugin 中新增 jasypt-spring-boot-starter 相依性,讓我們能夠加密 application.properties 檔案中的機密參數,確保應用程式內的安全使用。

    複查 pom.xml 檔案中新增的下列相依性。

    已修改 POM 檔案

    已修改 POM 檔案

  3. 下載程式庫並執行下列命令。

    1. 在您的開發環境中,執行下列命令以存取您的專案。

      cd tutorial
      

      Spring Boot 專案資料夾

    2. 清除專案並移除上一個組建產生的所有檔案。

      mvn clean
      

      Maven 清除

    3. 從本機 maven 儲存區域永久清除 (刪除並選擇性重新解析) 使用者自建物件。

      mvn dependency:purge-local-repository
      

    Maven 永久清除相依性

  4. 在專案中已設定相依性與 pom.xml 檔案,我們將繼續檢查 SOAP XML 檔案,因為它代表用戶端的要求,以及解譯 Spring Boot 專案端要求的 XSD 檔案。

    1. 此 SOAP XML 檔案有兩則訊息,其中包含個人資訊以及我們根據要求傳送的兩個不同用戶端的其他各種屬性,如下圖所示。

      Soap 檔案

    2. 現在在我們的 Spring Boot 專案端,需要 XML 綱要來定義 Spring Web 服務自動匯出為 WSDL 的 Web 服務網域,以下影像顯示為此教學課程定義的 messages.xsd 檔案。

      messages.xsd:

      XSD 檔案

    3. messages.xsd 檔案儲存在 Spring Boot 專案的資源資料夾中。

      資料夾中的 XSD 檔案

  5. 以 jar 檔案建立及安裝專案檔案。執行下列指令,並確定您位於 Spring Boot 專案資料夾中。

    mvn install
    

    注意:執行 maven install 命令之後,會自動產生目標資料夾,並且以相同的方式產生 Java 類別 (根據先前建立的 XSD 檔案和專案的可執行 .jar 檔案)。

    目標資料夾

  6. 現在,我們可以在 Spring Boot 專案中加入必要的 Java 類別。

    WebServiceConfig.java Class: 此 Java 類別是開發來建立 SOAP Web 服務:

    • 設定 Servlet 來處理 SOAP 要求。
    • 根據 XML 綱要產生 WSDL 定義。
    • 定義 SOAP Web 服務的存取端點。
    • 使用來自類別路徑的 messages.xsd 綱要檔案。
    //Imports
    import org.springframework.boot.web.servlet.ServletRegistrationBean; //import the ServletRegistrationBean class
    import org.springframework.context.ApplicationContext; //import the ApplicationContext class
    import org.springframework.context.annotation.Bean; //import the Bean class
    import org.springframework.context.annotation.Configuration; //import the Configuration class
    import org.springframework.core.io.ClassPathResource; //import the ClassPathResource class
    import org.springframework.ws.config.annotation.EnableWs; //import the EnableWs class
    import org.springframework.ws.config.annotation.WsConfigurerAdapter; //import the WsConfigurerAdapter class
    import org.springframework.ws.transport.http.MessageDispatcherServlet; //import the MessageDispatcherServlet class
    import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition; //import the DefaultWsdl11Definition class
    import org.springframework.xml.xsd.SimpleXsdSchema; //import the SimpleXsdSchema class
    import org.springframework.xml.xsd.XsdSchema; //import the XsdSchema class
    
    //Configuration class for the Web Service configuration
    @EnableWs //Enable the Web Service
    @Configuration //Define the class as a Configuration class 
    
    public class WebServiceConfig extends WsConfigurerAdapter {
    
       //Create a ServletRegistrationBean object to register the MessageDispatcherServlet object with the application context
       @Bean
       public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(ApplicationContext applicationContext) {
          MessageDispatcherServlet servlet = new MessageDispatcherServlet(); //Create a MessageDispatcherServlet object
          servlet.setApplicationContext(applicationContext); //Set the application context for the MessageDispatcherServlet object
          servlet.setTransformWsdlLocations(true); //Set the transformWsdlLocations property to true
          return new ServletRegistrationBean<>(servlet, "/ws/*"); //Return a new ServletRegistrationBean object with the MessageDispatcherServlet object and the URL pattern
       }
    
       //Create a DefaultWsdl11Definition object to define the WSDL
       @Bean(name = "messages")
       public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema messagesSchema) {
          DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition(); //Create a DefaultWsdl11Definition object
          wsdl11Definition.setPortTypeName("MessagesPort"); //Set the port type name
          wsdl11Definition.setLocationUri("/ws"); //Set the location URI
          wsdl11Definition.setTargetNamespace("http://tutorial_example.com/ns0"); //Set the target namespace
          wsdl11Definition.setSchema(messagesSchema); //Set the schema
          return wsdl11Definition; //Return the DefaultWsdl11Definition object
       }
    
       //Create a XsdSchema object to define the schema
       @Bean
       public XsdSchema messagesSchema() {
          return new SimpleXsdSchema(new ClassPathResource("messages.xsd")); //Return a new SimpleXsdSchema object with the messages.xsd file
       }
    }
    

    注意:如果您要測試 Web 服務,可以在相同的本機開發環境桌面中執行 Spring Boot 專案,並利用 curl 傳送 HTTP 要求,如下所示:

    mvn spring-boot:run
    

    執行 Spring boot 專案

    執行 Spring boot 專案

    專案執行並啟動 Web 服務之後,請使用 curl 執行本機 SOAP HTTP 要求,如下所示:

    curl --location 'http://localhost:8080/ws/'
    

    您將從 Spring Boot 專案中公開的 Web 服務得到回應。

    SOAP Web 服務

  7. 建立一個名為 model 的資料夾,我們將在此資料夾中新增下列 Java 類別。

    模型資料夾

    注意:這些 Java 類別 CarHouseUser 會根據從 HTTP SOAP 要求擷取之每個 SOAP 訊息中的資料來擷取資訊。

    • Car.java class: 此 Java 類別代表一個 Car 物件,其屬性會連結至每個使用者。

      ```
      //Imports
      import org.springframework.stereotype.Component; // For component scanning
      import org.springframework.context.annotation.Scope; // For defining bean scope
      
      @Component // Marks a class as a Spring-managed component
      @Scope("prototype") //A new instance is created every time the bean is requested
      
      public class Car {
      
         //Attributes
      
         private String brand; 
         private String color; 
         private String plate; 
      
         //"getter" and "setter" methods to get and set the information in each object
      
         public String getBrand(){
         return brand;
         }
      
         public void setBrand(String brand){
         this.brand = brand;
         }
      
         public String getColor(){
         return color;
         }
      
         public void setColor(String color){
         this.color = color;
         }
      
         public String getPlate(){
         return plate;
         }
      
         public void setPlate(String plate){
         this.plate = plate;
         }
      
      }
      ```
      
      • House.java class: 此 Java 類別代表一個 House 物件,其屬性會連結至每個使用者。

        //Imports
        import org.springframework.stereotype.Component; // For component scanning
        import org.springframework.context.annotation.Scope; // For defining bean scope
        
        @Component // Marks a class as a Spring-managed component
        @Scope("prototype") //A new instance is created every time the bean is requested
        
        public class House {
        
           //Attributes
        
           private int floors;
           private String location;
           private int rooms;
           private int bathrooms;
        
        
           //"getter" and "setter" methods to get and set the information in each object
        
           public int getFloors(){
              return floors;
           }
        
           public void setFloors(int floors){
              this.floors = floors;
           }
        
           public String getLocation(){
              return location;
           }
        
           public void setLocation(String location){
              this.location = location;
           }
        
           public int getRooms(){
              return rooms;
           }
        
           public void setRooms(int rooms){
              this.rooms = rooms;
           }
        
           public int getBathRooms(){
              return bathrooms;
           }
        
           public void setBathRooms(int bathrooms){
              this.bathrooms = bathrooms;
           }
        
        }
        
      • User.java class: 此 Java 類別代表一個含有 CarHouse 物件之屬性的 User 物件。

        //Imports
        import org.springframework.stereotype.Component; // For component scanning
        import org.springframework.context.annotation.Scope; // For defining bean scope
        
        @Component // Marks a class as a Spring-managed component
        @Scope("prototype") //A new instance is created every time the bean is requested
        
        
        public class User {
        
           //Attributes
        
           private String username; 
           private String userlastname;
           private int id;
           private String email;
           private String date;
           private String attribute;
           private Car car;
           private House house;
        
        
           //"getter" and "setter" methods to get and set the information in each object
        
           public String getUserName(){
           return username;
           }
        
           public void setUserName(String username){
           this.username = username;
           }
        
           public String getUserLastName(){
           return userlastname;
           }
        
           public void setUserLastName(String userlastname){
           this.userlastname = userlastname;
           }    
        
           public int getID(){
           return id;
           }
        
           public void setID(int id){
           this.id = id;
           }        
        
           public String getEmail(){
           return email;
           }
        
           public void setEmail(String email){
           this.email = email;
           }
        
           public String getDate(){
           return date;
           }
        
           public void setDate(String date){
           this.date = date;
           }
        
           public String getAttribute(){
           return attribute;
           }
        
           public void setAttribute(String attribute){
           this.attribute = attribute;
           }
        
           public Car getCar(){
           return car;
           }
        
           public void setCar(Car car){
           this.car = car;
           }
        
           public House getHouse(){
           return house;
           }
        
           public void setHouse(House house){
           this.house = house;
           }
        }
        
  8. 現在,我們將在 Spring Boot 專案中設定必要參數,以將取得的資料儲存在 OCI ATP 資料庫中。在 resources 資料夾中,您必須找到用來新增應用程式所需參數的 application.properties 檔案。它會自動產生,並在應用程式啟動時由 Spring Boot 載入。

    注意:管理加密和安全方法非常重要,以確保機密資訊 (例如密碼或相關資料) 無法被駭客擷取或檢視。在本教學課程中,我們使用 pom.xml 檔案中設定的 jasypt 程式庫。如需更多資訊,請參閱 How to encrypt passwords in a Spring Boot project using Jasypt 。此外,在 Spring Boot 上的 Java 類別中,也記錄了如何新增與此程式庫相關的註解和原始程式碼,以解密 application.properties 參數。

    application.properties 檔案中新增 ATP 資料庫所需的適當參數,如下圖所示。

    應用程式特性檔

    建立 Spring Boot Java 類別以將每則訊息儲存在資料庫中,並如下列影像所示,它將位於 database 資料夾中。

    Database Folder

    • SoapObjectRepository.java: 此 Spring Boot Java 類別可讓您使用 JDBC 驅動程式,以即時交易形式 (ATP 中的每個訊息) 插入。

      //Java Classes USER, CAR, HOUSE Imports
      import com.oracle_springboot_tutorial.tutorial.model.*;
      
      //Spring Boot Imports
      import org.springframework.stereotype.Repository;
      import org.springframework.jdbc.core.JdbcTemplate;
      import org.springframework.beans.factory.annotation.Autowired;
      
      //Repository Class to save SOAP Messages in the Database
      @Repository
      public class SoapObjectRepository {
         private JdbcTemplate template;
         private User user;
      
         //Getters and Setters for JdbcTemplate template object
         public JdbcTemplate getTemplate(){
            return template;
         }
      
         //Autowired annotation to inject JdbcTemplate object into the template object
         @Autowired
         public void setTemplate(JdbcTemplate template){
            this.template = template;
         }
      
         //Method to save User SOAP Message in the Database
         public void saveUserSOAPMessage(User user){
            this.user = user;
            String sql = "INSERT INTO USERS (username, userlastname, id, email, dateuser, attributeuser) VALUES(?, ?, ?, ?, ?, ?)";
            template.update(sql, user.getUserName(), user.getUserLastName(), user.getID(), user.getEmail(), user.getDate(), user.getAttribute());
         }
      
         //Method to save Car SOAP Message in the Database
         public void saveCarSOAPMessage(Car car){
            String sql = "INSERT INTO CARS (userid, brand, color, plate) VALUES(?, ?, ?, ?)";
            template.update(sql, user.getID(), car.getBrand(), car.getColor(), car.getPlate());
         }
      
         //Method to save House SOAP Message in the Database
         public void saveHouseSOAPMessage(House house){
            String sql = "INSERT INTO HOUSES (userid, floors, locationhouse, rooms, bathrooms) VALUES(?, ?, ?, ?, ?)";
            template.update(sql, user.getID(), house.getFloors(), house.getLocation(), house.getRooms(), house.getBathRooms());
         }
      
      
      }
      

      現在新增 JSON 軟體程式碼,請先建立 json_message 資料夾及其 Java Spring Boot 類別,如下圖所示。

      Json 資料夾

    • JsonBuilder.java: 此 Spring Boot Java 類別會從 SOAP XML 格式轉換為 JSON 格式。

      //Imports to be used for JSON
      import org.json.JSONArray;
      import org.json.JSONObject;
      
      //Imports to be used for the User class
      import com.oracle_springboot_tutorial.tutorial.model.*;
      
      
      //Imports to be used for the ArrayList class
      import java.util.ArrayList;
      
      
      public class JsonBuilder {
      
         //The buildJsonMessage method creates a JSON object from the ArrayList of User objects
         public JSONObject buildJsonMessage(ArrayList<User> usersMessageArray) {
      
            JSONObject rootJson = new JSONObject(); //Create a new JSON object called rootJson
            JSONObject messagesJson = new JSONObject(); //Create a new JSON object called messagesJson
            JSONArray messageArray = new JSONArray(); //Create a new JSON array called messageArray
      
      
            //Iterate through the ArrayList of User objects and create a JSON object for each User object in the ArrayList
            for (User user : usersMessageArray) {
                  JSONObject messageJson = new JSONObject();
                  messageJson.put("username", user.getUserName()); //Add the username of the user to the messageJson object
                  messageJson.put("userlastname", user.getUserLastName()); //Add the userlastname of the user to the messageJson object
                  messageJson.put("id", user.getID()); //Add the id of the user to the messageJson object
                  messageJson.put("email", user.getEmail()); //Add the email of the user to the messageJson object
                  messageJson.put("date", user.getDate()); //Add the date of the user to the messageJson object
                  messageJson.put("attribute", user.getAttribute()); //Add the attribute of the user to the messageJson object
      
      
                  //
                  JSONObject bodyJson = new JSONObject(); //Create a new JSON object called bodyJson
                  JSONObject envelopeJson = new JSONObject(); //Create a new JSON object called envelopeJson
      
                  //Switch statement to check the attribute of the User object
                  switch (user.getAttribute()) {
                     case "CAR":
                              Car car = user.getCar();
                              envelopeJson.put("brand", car.getBrand()); //Add the brand of the car to the envelopeJson object
                              envelopeJson.put("color", car.getColor()); //Add the color of the car to the envelopeJson object
                              envelopeJson.put("plate", car.getPlate()); //Add the plate of the car to the envelopeJson object                       
                        break;
                     case "HOUSE":
                              House house = user.getHouse();
                              envelopeJson.put("floors", house.getFloors()); //Add the floors of the house to the envelopeJson object
                              envelopeJson.put("location", house.getLocation()); //Add the location of the house to the envelopeJson object
                              envelopeJson.put("rooms", house.getRooms()); //Add the rooms of the house to the envelopeJson object
                              envelopeJson.put("bathrooms", house.getBathRooms()); //Add the bathrooms of the house to the envelopeJson object                     
                        break;
                     default:
                        System.out.println("Unknown subject: " + user.getAttribute());
                  }
      
      
                  bodyJson.put("envelope", envelopeJson); //Add the envelopeJson object to the bodyJson object
      
                  messageJson.put("body", bodyJson); //Add the bodyJson object to the messageJson object
      
               messageArray.put(messageJson); //Add the messageJson object to the messageArray array
            }
      
            messagesJson.put("message", messageArray); //Add the messageArray array to the messagesJson object
            rootJson.put("messages", messagesJson); //Add the messagesJson object to the rootJson object
      
            return rootJson;
         }
      }
      
  9. 現在,我們可以將 JSON 格式的訊息傳送至 OCI Queue。建立 oci_queue 資料夾及其 Java Spring Boot 類別,如下圖所示。

    佇列資料夾

    注意:OCIQueue.java 類別中,我們需要定義從 OKE 到 OCI Queue 的存取權。在本教學課程中,我們將使用工作負載存取授予 OCI 資源的存取權,無須處理與您租用戶關聯的使用者、密碼、OCID 等機密資訊。如需詳細資訊,請參閱將工作負載授予 OCI 資源的存取權

    開始開發 OCIQueue.java 類別之前,我們將設定租用戶的工作負載存取權。首先,我們需要建立一個與 Oracle GraalVM 型 Java 應用程式關聯的命名空間。確定您位於管理主機中。

    kubectl create ns-tutorial
    

    接著,建立應用程式的 Kubernetes 服務帳戶。

    kubectl create serviceaccount tutorialserviceaccount --namespace ns-tutorial
    

    現在,請定義 OCI IAM 原則,以允許工作負載存取必要的 OCI 資源。在本教學課程中,OCI Queue。

    移至 OCI 主控台,瀏覽至識別與安全原則,然後按一下建立原則。輸入下列資訊,然後按一下建立

    • 名稱:輸入偏好的原則名稱。
    • 描述:輸入從 oke 到 oci 佇列的存取
    • 原則產生器:

      Allow any-user to use queues in compartment id ocid1.compartment.oc1..xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx where all {request.principal.type = 'workload', request.principal.namespace = 'ns-tutorial', request.principal.service_account = 'tutorialserviceaccount', request.principal.cluster_id = 'ocid1.cluster.oc1.sa-saopaulo-1.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'}
      

    工作負載存取原則

    設定工作負載存取原則、命名空間和服務帳戶之後,即可繼續進行作業。

    application.properties 檔案中,新增連線和管理在任務 5 中建立之特定 OCI 佇列所需的佇列參數。

    OCIID 設定值

    • OCIQueue.java: 此 Spring Boot Java 類別可讓您存取訊息,並將訊息放入 OCI Queue。

      //Imports
      import com.oracle.bmc.auth.okeworkloadidentity.OkeWorkloadIdentityAuthenticationDetailsProvider; //Import OkeWorkloadIdentityAuthenticationDetailsProvider to enable access and use OCI Workload Identity
      import com.oracle.bmc.queue.QueueClient; //Import QueueClient to have access and manage of OCI Queue
      import com.oracle.bmc.queue.model.PutMessagesDetails; //Import PutMessagesDetails to send messages to the OCI Queue
      import com.oracle.bmc.queue.model.PutMessagesDetailsEntry; //Import PutMessagesDetailsEntry to send messages to the OCI Queue
      import com.oracle.bmc.queue.requests.PutMessagesRequest; //Import PutMessagesRequest to send messages to the OCI Queue
      
      //Imports for the ArrayList and List
      import java.util.ArrayList;
      import java.util.List;
      
      public class OCIQueue {
         //Set the required parameters to access to OCI and the Queue
      
         //Variables
         private String queueId; 
         private String endPoint;
         private String region;
      
         //Constructor to initialize the OCI Queue object with the required parameters
         public OCIQueue(String queueId, String endPoint, String region){
            this.queueId = queueId;
            this.endPoint = endPoint;
            this.region = region;   
         }
      
      
         //The sendMessages method sends a message to the OCI Queue
         public void sendMessages(String jsonMessage){
            try{    
      
                  //Create an OkeWorkloadIdentityAuthenticationDetailsProvider object to authenticate the OCI Queue
                  OkeWorkloadIdentityAuthenticationDetailsProvider provider = new OkeWorkloadIdentityAuthenticationDetailsProvider.OkeWorkloadIdentityAuthenticationDetailsProviderBuilder().build();
      
                  //Create a QueueClient object to send the message to the OCI Queue
                  QueueClient queueClient = QueueClient.builder().build(provider);
                  queueClient.setRegion(region);
                  queueClient.setEndpoint(endPoint);
      
      
      
                  //Create a PutMessagesDetailsEntry object to send the message
                  PutMessagesDetailsEntry message = PutMessagesDetailsEntry.builder()
                     .content(jsonMessage)
                     .build();
      
                  //Create a List of PutMessagesDetailsEntry objects to send the message
                  List<PutMessagesDetailsEntry> messages = new ArrayList<>();
                  messages.add(message);
      
                  //Create a PutMessagesDetails object to send the message
                  PutMessagesDetails putMessagesDetails = PutMessagesDetails.builder()
                     .messages(messages)
                     .build();
      
      
                  //  Create a PutMessagesRequest object to send the message
                  PutMessagesRequest putMessagesRequest = PutMessagesRequest.builder()
                     .queueId(queueId)
                     .putMessagesDetails(putMessagesDetails)
                     .build();
      
                  // Send the request and get the response
                  queueClient.putMessages(putMessagesRequest);
      
            }catch(Exception e){
                  System.out.println("Exception sending message to OCI Queue: "+e);
            }
         }
      }
      
  10. 取得適用於資料庫、JSON 和 OCI 佇列的 Spring Boot Java 類別之後,即可繼續進行 MessagesEndpoint.java 類別。

    因此,我們將建立一個名為 endpoint 的資料夾及其 Spring Boot Java 類別。

    端點資料夾

    注意:MessagesEndpoint.java 中,我們需要匯入一些自動產生的類別。若要這麼做,請在 pom.xml 檔案的「組態」區段中新增下列來源:

    <configuration>
       <sources>
       <source>${project.build.directory}/generated-sources</source>
       </sources>
    </configuration>
    

    pom.xml 檔案應該類似。

    產生的來源路徑

    • MessagesEndpoint.java: 此 Spring Boot Java 類別的用途是擷取 SOAP HTTP 要求,並將其值對應至每個訊息的 User、Car 和 House Java 物件。然後,它會針對每個 SOAP XML 交易,將擷取的資料儲存在 ATP 資料庫中、將資料從 XML 轉換為 JSON 格式,並將訊息置於 OCI 佇列中。這些訊息稍後可由用戶從佇列中擷取和刪除。

      //Imports
      import com.oracle_springboot_tutorial.tutorial.model.*; //Import all the classes from the model package
      import com.oracle_springboot_tutorial.tutorial.oci_queue.OCIQueue; //Import the OCIQueue class from the oci_queue package
      
      
      import com.tutorial_example.ns0.Messages;//Import the Messages class from the tutorial_example.ns0 package (Auto generated Java Classes from the WSDL)
      import com.tutorial_example.ns0.MessageType; //Import the MessageType class from the tutorial_example.ns0 package (Auto generated Java Classes from the WSDL)
      
      //Import the ArrayList class from the java.util package
      import java.util.ArrayList;
      
      //Spring Boot imports to be used for the SOAP Web Service
      import org.springframework.beans.factory.annotation.Autowired; //Import the @Autowired annotation to inject the SoapObjectRepository object
      import org.springframework.beans.factory.annotation.Value; //Import the @Value annotation to inject the values from the application.properties file
      import org.springframework.stereotype.Component; //Import the @Component annotation to register the class with Spring
      
      //Spring Boot imports 
      import org.springframework.ws.server.endpoint.annotation.Endpoint; //Import the @Endpoint annotation to register the class with Spring WS
      import org.springframework.ws.server.endpoint.annotation.PayloadRoot; //Import the @PayloadRoot annotation to specify the namespace URI and local part of the request payload
      import org.springframework.ws.server.endpoint.annotation.RequestPayload; //Import the @RequestPayload annotation to map the request payload to the method parameter
      import org.springframework.ws.server.endpoint.annotation.ResponsePayload; //Import the @ResponsePayload annotation to map the returned value to the response payload
      
      //Imports to be used storing SOAP information in the database 
      import com.oracle_springboot_tutorial.tutorial.database.SoapObjectRepository; //Import the SoapObjectRepository class from the database package
      
      //Imports to be used for JSON
      import com.oracle_springboot_tutorial.tutorial.json_message.JsonBuilder; //Import the JsonBuilder class from the json_message package 
      import org.json.JSONObject; //Import the JSONObject class from the org.json package
      
      
      //The @Endpoint annotation registers the class with Spring WS.
      //The @Component annotation registers the class with Spring to be used as a Spring Bean.
      @Endpoint
      @Component
      public class MessagesEndpoint {
      
         //Inject not encrypted and decrypted values using jasypt library from the application.properties file
         @Value("${oci.queue.queueId}")
         private String queueId;
         @Value("${oci.queue.endPoint}")
         private String endPoint;
         @Value("${oci.queue.region}")   
         private String region;
         @Value("${spring.datasource.password}")
         private String datasourcePassword;
      
         //The @Autowired loads JDBC template in SoapObjectRepository.
         @Autowired
         private SoapObjectRepository soapObjectRepository = new SoapObjectRepository();
         //Create a new instance of the JsonBuilder class
         JsonBuilder jsonBuilder = new JsonBuilder();
      
      
         //The namespace URI
         private static final String NAMESPACE_URI = "http://tutorial_example.com/ns0";
      
         //The handleMessagesRequest method is annotated with @PayloadRoot, which means that it is invoked when a request with the specified namespace URI and local part is received.
         @PayloadRoot(namespace = NAMESPACE_URI, localPart = "messages")
         //The @ResponsePayload annotation makes Spring WS map the returned value to the response payload.
         @ResponsePayload
         //The handleMessagesRequest method processes the request and sends the message to the OCI Queue.
         public void handleMessagesRequest(@RequestPayload Messages request) {
            OCIQueue ociQueue = new OCIQueue(queueId, endPoint, region);
      
            //Create an ArrayList to store the users
            ArrayList<User> usersMessageArray = new ArrayList<User>();
      
            //Iterate over the messages, extracting the SOAP Messages and storing in the Java Objects (Car, House, User) 
            for (MessageType message : request.getMessage()) {
      
                  User user = new User();
                  user.setUserName(message.getUsername());
                  user.setUserLastName(message.getUserlastname());
                  user.setID(message.getId());
                  user.setEmail(message.getEmail());
                  user.setDate(message.getDate());
                  user.setAttribute(message.getAttribute());
      
                  //Insert User in Oracle ATP
                  soapObjectRepository.saveUserSOAPMessage(user);
      
                  //Process the attributes Car or House depending of the kind of User
                  processMessage(user, message);
                  //Add the user to the ArrayList
                  usersMessageArray.add(user);
            }
      
      
            //Convert to JSON format
            JSONObject jsonObject = jsonBuilder.buildJsonMessage(usersMessageArray);
      
            //Send the JSON message to OCI Queue
            ociQueue.sendMessages(jsonObject.toString());
      
         }
      
         //The processMessage method processes the message based on the user's attribute.
         private void processMessage(User user, MessageType message) {
            String subject = user.getAttribute();
            switch (subject) {
                  case "CAR":
                     handleCAR(user, message);
                     break;
                  case "HOUSE":
                     handleHouse(user, message);
                     break;
                  default:
                     System.out.println("Unknown subject: " + subject);
            }
         }
      
         //The handleCAR method processes the CAR message.
         private void handleCAR(User user, MessageType message) {
            Car car = new Car();
            car.setBrand(message.getBody().getEnvelope().getBrand());
            car.setColor(message.getBody().getEnvelope().getColor());
            car.setPlate(message.getBody().getEnvelope().getPlate());
      
            user.setCar(car);
            //Insert Car in Oracle ATP
            soapObjectRepository.saveCarSOAPMessage(user.getCar());
      
         }
      
         //The handleHouse method processes the HOUSE message.
         private void handleHouse(User user, MessageType message) {
            House house = new House();
            house.setFloors(message.getBody().getEnvelope().getFloors());
            house.setLocation(message.getBody().getEnvelope().getLocation());
            house.setRooms(message.getBody().getEnvelope().getRooms());
            house.setBathRooms(message.getBody().getEnvelope().getBathrooms());
      
            user.setHouse(house);
      
            //Insert Houses in Oracle ATP
            soapObjectRepository.saveHouseSOAPMessage(user.getHouse());
      
         }
      }
      
  11. 現在已完成 Spring Boot 專案的整個建構作業,我們將在專案資料夾中建立 Dockerfile

  1. 執行下列命令,在本機 Docker 儲存區域中建立及推送專案映像檔。

    docker build . -t springbootapp:latest
    

    建立 Docker 映像檔

  2. 執行下列命令以驗證本機 Docker 儲存區域中的映像檔。

    docker images
    

    Docker 映像檔本機儲存區域

  3. 您可以使用 OCI Container Registry Classic 儲存區域的完整路徑來標記 Spring Boot 應用程式映像檔。

    docker tag springbootapp:latest gru.ocir.io/xxxxxxxxxx/springboot/tutorialapp:latest
    
  4. 執行下列命令以在本機 Docker 儲存區域中進行驗證。

    docker images
    

    標記 OKE 應用程式

  5. 執行下列命令,將映像檔推送至 OCI 容器登錄 (經典)。

    docker push gru.ocir.io/xxxxxxxxxx/springboot/tutorialapp:latest
    

    推送 OKE 應用程式

  6. 若要複查 OCI Container Registry Classic 中的 OKE 映像檔應用程式,請前往開發人員服務容器與使用者自建物件,然後按一下容器登錄

    OKE Containe 登錄中的影像

    映像檔在 OCI Container Registry Classic 中之後,即可前往我們的開發環境,並在 OKE 中部署此映像檔。對於此教學課程,請執行下列命令來建立必要的組態。

    注意:命名空間和服務帳戶之前已經設定過,因此需要加密密碼。

    1. 執行下列命令以存取專案資料夾。

      cd tutorial/
      
    2. 執行下列命令以建立 OKE 的加密密碼。

      kubectl create secret -n ns-tutorial generic ocir --from-file=.dockerconfigjson=../.docker/config.json --type=kubernetes.io/dockerconfigjson
      

      建立 Secrete oke

  7. 我們已經準備好 OKE 環境,因此請將應用程式映像檔從 OCI Container Registry Classic 部署至 OKE。

    注意:若要部署應用程式影像,必須要有資訊清單檔案。在本教學課程中,下列 yaml 檔案是資訊清單檔案,可用來部署應用程式,並建立 OCI Load Balancer 中使用 80 連接埠監聽的傳入服務。

    • springboot_application.yaml:

      apiVersion: apps/v1
      kind: Deployment
      metadata:
      name: soap-oci-queue-app
      namespace: ns-tutorial 
      labels:
         app: soap-oci-queue-app
      spec:
      replicas: 6
      selector:
         matchLabels:
            app: soap-oci-queue-app
      template:
         metadata:
            labels:
            app: soap-oci-queue-app
         spec:
            serviceAccountName: tutorialserviceaccount
            automountServiceAccountToken: true      
            containers:
            - name: soap-oci-queue-app
            image: gru.ocir.io/xxxxxxxxxxxx/springboot/tutorialapp:latest
            ports:
            - containerPort: 8080
            imagePullSecrets:
            - name: ocir-docker-config
      ---
      
      apiVersion: v1
      kind: Service
      metadata:
      name: svc-dev-app 
      namespace: ns-tutorial 
      spec:
      selector:
         app: soap-oci-queue-app
      ports:
         - port: 80
            targetPort: 8080
      type: LoadBalancer
      
  8. 在您儲存清單檔案的資料夾中執行 kubectl 指令。

    kubectl apply -f springboot_application.yaml
    

    現在,系統會部署應用程式,並在 OKE 中建立傳入負載平衡器服務。

    在 OKE 中套用資訊清單

  9. 若要驗證在 OKE 中建立的 Pod 與服務,請執行下列指令。

    kubectl get pods -A
    

    OKE PODS

    kubectl get svc -A
    

    OKE 服務

    注意:請從此處下載 Spring Boot Oracle GraalVM 型 Java 應用程式專案:tutorial.zip

作業 7:使用 JMeter 測試 Spring Boot Oracle Graal VM 應用程式

如需有關 JMeter 安裝的詳細資訊,請參閱 JMeter 入門

安裝並設定 JMeter 之後,您就可以傳送 HTTP POST SOAP 要求。例如,將繫線數目設為 2 代表 2 個同時的使用者或應用程式,並將迴圈計數設為 3000,表示每位使用者或應用程式將傳送 3000 個要求,總計為 6000 個 SOAP 要求。

SOAP 訊息

在 JMeter 中設定 OCI Load Balancer IP,在 Spring Boot 專案和主體中設定 SOAP XML 的路徑。

HTTP SOAP 要求

執行含有 6000 個 SOAP 交易的 JMeter,然後進行驗證。

注意:由於我們正在模擬從屬端應用程式的 SOAP 訊息傳遞,因此對於每個 SOAP HTTP 要求,資訊會與上面顯示的 SOAP XML 檔案中看到的資訊相同,而且不會變更,但在實際的客戶環境中,資訊一定會有所不同。

執行下列敘述句:

  1. 查看 ATP 中儲存的資料總計。

    Oracle ATP

  2. 查看 ATP 中每個表格中儲存的資料明細。

    • CARS:

      CARS

    • 在家:

      住宅

    • USERS:

      USERS

  3. 查看儲存在 OCI 佇列中的 OCI 佇列要求總計。

    OCI 佇列要求總計

  4. 查看 OCI 佇列中 JSON 格式的訊息詳細資訊。

    OCI 佇列訊息詳細資訊

認可

其他學習資源

探索 docs.oracle.com/learn 上的其他實驗室,或存取 Oracle Learning YouTube 頻道上的更多免費學習內容。此外,請造訪 education.oracle.com/learning-explorer 以成為 Oracle Learning Explorer。

如需產品文件,請造訪 Oracle Help Center