Spring Boot DB連携

Spring Bootで、OracleのテーブルにBLOB,CLOBのカラムを含む場合のOracle接続を実装してみた

Oracleのデータ型に、大量のデータやバイナリデータを格納できる「BLOB」「CLOB」がある。「BLOB」にはバイナリデータ、「CLOB」にはテキストデータを格納できる。

OracleテーブルとJavaオブジェクトのデータ型の関連付けを行うものが「TypeHandler」である。MyBatis3.4以降のバージョンでは、「BLOB」と「java.io.InputStream」または「byte[]」が、「CLOB」と「java.lang.String」または「java.io.Reader」を関連付けする「TypeHandler」をサポートしている。

今回は、Spring Bootのmybatisを利用して、「BLOB」「CLOB」を含むテーブルへのデータ追加・データ参照を行ってみたので、そのサンプルプログラムを共有する。
ここでは、エンティティクラスで「BLOB」型データを「java.io.InputStream」で、「CLOB」型データを「java.lang.String」で定義している。

前提条件

以下の記事の「Oracle JDBC接続用jarの配置」までの処理が完了していること。

Spring BootでOracle接続処理を実装してみたSpring BootのWEBアプリケーションを開発する際、なんらかのデータベースにアクセスすることが多いが、SpringのJPAライブ...

やってみたこと

  1. BLOB,CLOBを含むテーブルの作成
  2. 利用するファイルの配置
  3. サンプルプログラムの作成
  4. サンプルプログラムの実行結果

 

BLOB,CLOBを含むテーブルの作成

今回作成するサンプルプログラムがアクセスするテーブル「file_data」を作成した。実行したSQLは以下の通り。

create table file_data (
      ID NUMBER(6) PRIMARY KEY NOT NULL
    , file_path CLOB
    , file_obj BLOB
);

上記SQLを実行した結果は下図の通り。
file_dataテーブルのdesc



利用するファイルの配置

今回作成するサンプルプログラム内でアクセスするファイル「テスト.txt」を作成した。そのファイルの中身は以下の通り。
アクセスするファイル1

アクセスするファイル2

サンプルプログラムの作成

今回作成したサンプルプログラムの構成は以下の通り。
サンプルプログラムの構成

「build.gradle」の内容は以下の通りで、lombokとmybatisの定義を入れている。

plugins {
	id 'org.springframework.boot' version '2.1.7.RELEASE'
	id 'java'
}

apply plugin: 'io.spring.dependency-management'

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	compileOnly 'org.projectlombok:lombok:1.18.10'
	annotationProcessor 'org.projectlombok:lombok:1.18.10'
	compile files('lib/ojdbc6.jar')
	implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1'
}

また、「application.properties」には以下のように、DB接続定義とSQLログ出力定義を追加している。

server.port = 8084
# DB接続情報
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:xe
spring.datasource.username=USER01
spring.datasource.password=USER01
spring.datasource.driverClassName=oracle.jdbc.driver.OracleDriver
# SQLログ出力
logging.level.org.springframework=warn
logging.level.com.example.demo.FileDataMapper=debug



エンティティクラス「FileData.java」は以下の通りで、「BLOB」型データを「java.io.InputStream」で、「CLOB」型データを「java.lang.String」で定義している。
さらに、直列化可能にするために、Serializableインタフェースを継承し、シリアルバージョンIDを設定している。

package com.example.demo;

import lombok.Data;
import java.io.InputStream;
import java.io.Serializable;

/**
 * ファイルデータテーブル(file_data)アクセス用エンティティ
 */
@Data
public class FileData implements Serializable{

    /** シリアルバージョンID */
    private static final long serialVersionUID = 1L;

    /** ID */
    private long id;

    /** ファイルパス */
    private String filePath;

    /** ファイルオブジェクト */
    private InputStream fileObj;

}

なお、Serializableについては、以下のページが参考になる。
https://qiita.com/NBT/items/9f76c9fd1c7a90506658

また、「file_data」テーブルとアクセスするMapperクラスは以下の通りで、VARCHAR2等他のデータ型の場合と同等の記載となっている。

package com.example.demo;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface FileDataMapper {

    /**
     * 指定したIDをもつファイルデータテーブル(file_data)のデータを取得する
     * @param id ID
     * @return ファイルデータテーブル(file_data)の指定したIDのデータ
     */
    @Select("SELECT id, file_path as filePath, file_obj as fileObj "
            + " FROM file_data WHERE id = #{id}")
    FileData findById(Long id);

    /**
     * 指定したファイルデータテーブル(file_data)のデータを追加する
     * @param fileData ファイルデータテーブル(file_data)
     */
    @Insert("INSERT INTO file_data ( id, file_path, file_obj ) "
            + " VALUES ( #{id}, #{filePath}, #{fileObj} )")
    void insert(FileData fileData);

    /**
     * ファイルデータテーブル(file_data)のデータを全件削除する
     */
    @Delete("DELETE FROM file_data")
    void deleteAll();
}



さらに、FileDataMapperクラスのメソッドを呼び出すコントローラクラスの定義は、以下の通りとなる。InputStreamデータは、下記getFileObjDataメソッドで記載した通り、BufferedReaderクラスのreadLineメソッドを利用して取得できる。

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.transaction.annotation.Transactional;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;

@Controller
public class DemoController {

    @Autowired
    private FileDataMapper fileDataMapper;

    @RequestMapping("/")
    @Transactional(readOnly = false)
    public String index(){

        //全件削除する
        fileDataMapper.deleteAll();

        //1件追加する
        insertFileData("C:\\tmp\\テスト.txt");

        //追加したデータを確認する
        getFileData();

        //サンプルページへ移動する
        return "sample";
    }

    /**
     * 指定したファイルパスのデータを追加する
     * @param filePath
     */
    private void insertFileData(String filePath){
        FileData fileData = new FileData();
        fileData.setId(1);
        fileData.setFilePath(filePath);
        try{
            fileData.setFileObj(new FileInputStream(filePath));
        }catch(Exception e){
            System.err.println(e);
        }
        //1件追加する
        fileDataMapper.insert(fileData);
    }

    /**
     * ファイルデータ(file_data)テーブルのデータを確認する
     */
    private void getFileData(){
        //指定したIDのデータを取得する
        FileData fileData = fileDataMapper.findById(Long.valueOf(1));
        if(fileData != null){
            System.out.println("idの値 : " + fileData.getId());
            System.out.println("file_pathの値 : " + fileData.getFilePath());
            System.out.println("file_objの値 : " 
                                 + getFileObjData(fileData.getFileObj()));
        }else{
            System.out.println("fileDataはnull");
        }
    }

    /**
     * 入力ストリームを文字列に変換し返す
     * @param inputStream 入力ストリーム
     * @return ファイルオブジェクトの文字列
     */
    private String getFileObjData(InputStream inputStream){
        StringBuilder builder = new StringBuilder();
        if(inputStream != null){
            try (BufferedReader br = new BufferedReader(
                      new InputStreamReader(inputStream))) {
                String line;
                while((line = br.readLine()) != null){
                    builder.append(line);
                }
            }catch(Exception e){
                System.err.println(e);
            }
        }
        return builder.toString();
    }
}

その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-oracle-lob/demo

サンプルプログラムの実行結果

Spring Bootアプリケーションを起動し、「http:// (ホスト名):(ポート番号)」とアクセスした場合、以下の画面が表示される。
画面表示

このときのコンソールログは以下の通りで、SQLログが出力されていることや、取得データの内容が確認できる。
コンソールログ

また、実行後の「file_data」テーブルのデータの内容は以下の通り。なお、「UTL_RAW.CAST_TO_VARCHAR2」を利用すると、BLOBのデータをテキストで表示できる。

select * from file_data
実行後のfile_dataデータ1
select UTL_RAW.CAST_TO_VARCHAR2(FILE_OBJ) from file_data
実行後のfile_dataデータ2

要点まとめ

  • Oracleのデータ型に、大量のデータやバイナリデータを格納できる「BLOB」「CLOB」がある。「BLOB」にはバイナリデータ、「CLOB」にはテキストデータを格納できる。
  • Javaのエンティティクラスでは、「BLOB」型データを「java.io.InputStream」で、「CLOB」型データを「java.lang.String」で定義すれよい。