このトピックでは、COPY 文を使用して Hologres にデータをインポートする方法、または Hologres からデータをエクスポートする方法について説明します。
制限事項
COPY 文を使用してデータをインポートまたはエクスポートする場合は、次の点に注意してください。
COPY 文を使用してパーティションテーブルから Hologres にデータをインポートする場合、親パーティションテーブルではなく子パーティションテーブルにのみデータをインポートできます。
Hologres V1.1.43 以降では、
COPY FROM STDIN
文は、DEFAULT 制約で作成されたテーブルと、SERIAL データ型の列を含むテーブルをサポートしています。 COPY FROM STDIN 文は、以前のバージョンの Hologres ではこれらのテーブルをサポートしていません。
操作プラットフォーム
このトピックで説明されている文は、PostgreSQL クライアントで実行する必要があります。 詳細については、「PostgreSQL クライアント」をご参照ください。
概要
COPY FROM
文を使用して、クライアントの標準入力から Hologres にデータをインポートし、COPY TO
文を使用して Hologres データをエクスポートできます。
COPY 文は、Hologres でサポートされているデータ型をサポートしています。 詳細については、「データ型」をご参照ください。
Hologres にデータをインポートするには、
COPY FROM STDIN
文を実行する必要があります。 Hologres からデータをエクスポートするには、COPY ( query ) TO STDOUT
文を実行する必要があります。COPY 文は、データのインポートおよびエクスポート操作の原子性を保証します。 固定コピーモードでは、テーブルレベルロックではなく行レベルロックが使用され、原子性は保証されません。 したがって、固定コピーモードでは、ダーティデータが生成された場合、ダーティデータレコードに対してのみエラーが報告され、残りのデータは部分的に書き込まれるか、完全に書き込みに失敗する可能性があります。
Hologres V3.0 より前のバージョンでは、メタデータ ウェアハウスの hologres.hg_query_log テーブルの COPY 操作に対して 1 つのレコードのみが生成されます。 Hologres V3.0 以降では、メタデータ ウェアハウスの COPY 操作に対して 2 つのレコードが生成されます。 1 つのレコードは COPY 操作をログに記録し、もう 1 つのレコードは COPY 操作で実行される INSERT 文をログに記録します。 これら 2 つのレコードは、トランザクション ID に基づいて関連付けることができます。 例:
SELECT query_id, query, extended_info FROM hologres.hg_query_log WHERE extended_info ->> 'source_trx' = '<transaction_id>' -- COPY 操作のレコードの trans_id フィールドの値に基づいてトランザクション ID を取得できます。 ORDER BY query_start ;
構文
COPY 文の構文:
COPY table_name [ ( column_name [, ...] ) ]
FROM STDIN
[ [ WITH ] ( option [, ...] ) ]
COPY { ( query ) }
TO STDOUT
[ [ WITH ] ( option [, ...] ) ]
where option can be one of:
FORMAT format_name
DELIMITER 'delimiter_character'
NULL 'null_string'
HEADER [ boolean ]
QUOTE 'quote_character'
ESCAPE 'escape_character'
FORCE_QUOTE { ( column_name [, ...] ) | * }
FORCE_NOT_NULL ( column_name [, ...] )
ENCODING 'encoding_name'
STREAM_MODE [ boolean]
ON_CONFLICT 'none/ignore/update'
パラメーター
パラメーター | 説明 |
table_name | データをインポートする Hologres テーブルの名前。 |
query | クエリ文。 |
STDIN | 指定されたクライアントの stdin から Hologres にデータがインポートされます。 |
STDOUT | Hologres から指定されたクライアントにデータがエクスポートされます。 |
FORMAT | ファイル形式。 有効な値:TEXT、CSV、BINARY。 デフォルト値:TEXT。 BINARY データ型のデータをエクスポートできます。 固定コピーモードでのみ、BINARY データ型のデータをインポートできます。 |
DELIMITER | 列を区切るために使用するデリミタ。 デフォルトのデリミタは、テキスト形式ではタブ文字、CSV 形式ではカンマ (,) です。 例: |
NULL | null 値を表すために使用する文字列。
|
HEADER | Hologres にインポートまたはエクスポートされるファイルにヘッダー行が含まれているかどうかを指定します。 ヘッダー行の値は、ファイル内の列名です。 説明 このパラメーターは、CSV データ型のデータに対してのみ有効です。 |
QUOTE | データ値を参照するために使用される 1 バイト文字。 説明 このパラメーターは、CSV データ型のデータに対してのみ有効です。 デフォルトでは、二重引用符 (") が使用されます。 |
ESCAPE |
説明 このパラメーターは、CSV データ型のデータに対してのみ有効です。 デフォルトでは、値は |
FORCE_QUOTE | 指定された列のすべての非 NULL 値に引用符 (') を強制的に使用します。 説明 このパラメーターは、 |
FORCE_NOT_NULL | 指定された列では、NULL 値を表す文字列は一致しません。 代わりに、NULL 値は長さゼロの文字列として読み取られます。 説明 このパラメーターは、 |
ENCODING | 生成されるファイルに使用するエンコード方式。 デフォルトでは、クライアントのエンコード方式が使用されます。 |
STREAM_MODE | 固定コピーモードを使用するかどうかを指定します。 デフォルト値:FALSE。 詳細については、「FIXED COPY」をご参照ください。 有効な値:
|
ON_CONFLICT | プライマリキーが競合する場合に使用されるポリシー。 有効な値: 説明 単一引用符が使用されていない場合、次の値は大文字と小文字が区別されません。 単一引用符を追加する場合は、'none' のように小文字にする必要があります。
説明
|
COPY 文を使用して Hologres にデータをインポートする
ローカルファイルを Hologres にインポートする
COPY 文を使用して、ローカルファイルを Hologres にインポートしたり、Hologres からローカルファイルにデータをエクスポートしたりできます。 COPY 文の原則と使用方法の詳細については、PostgreSQL ドキュメントの COPY を参照してください。
例:
stdin から Hologres にデータをインポートします。 サンプル文:
-- Hologres にテーブルを作成します。 CREATE TABLE copy_test ( id int, age int, name text ) ; -- 作成した Hologres テーブルにデータをインポートします。 COPY copy_test FROM STDIN WITH DELIMITER AS ',' NULL AS ''; 53444,24,wangming 55444,38,ligang 55444,38,luyong \. -- Hologres テーブルのデータをクエリします。 SELECT * FROM copy_test;
説明PostgreSQL クライアントを使用する場合、stdin からのみデータをインポートできます。 HoloWeb コンソールでは、コマンドを実行して stdin から Hologres にデータをインポートすることはできません。
stdin から Hologres に CSV ファイルをインポートします。 サンプル文:
-- Hologres にテーブルを作成します。 CREATE TABLE partsupp ( ps_partkey integer NOT NULL, ps_suppkey integer NOT NULL, ps_availqty integer NOT NULL, ps_supplycost float NOT NULL, ps_comment text NOT NULL ); -- 作成した Hologres テーブルに CSV ファイルをインポートします。 COPY partsupp FROM STDIN WITH DELIMITER '|' CSV; 1|2|3325|771.64|final theodolites 1|25002|8076|993.49|ven ideas \. -- Hologres テーブルのデータをクエリします。 SELECT * FROM partsupp;
説明PostgreSQL クライアントを使用する場合、stdin から CSV ファイルをインポートできます。 HoloWeb コンソールでは、コマンドを実行して stdin から Hologres に CSV ファイルをインポートすることはできません。
ローカルファイルを Hologres にインポートします。 サンプル文:
psql -U <username> -p <port> -h <endpoint> -d <databasename> -c "COPY <table> FROM STDIN WITH DELIMITER '|' CSV;" <<filename>;
説明PostgreSQL クライアントを使用する場合、stdin からのみデータをインポートできます。 したがって、インポートするファイルのデータ形式を標準入力形式に変換する必要があります。 HoloWeb コンソールでは、コマンドを実行して stdin から Hologres にローカルファイルをインポートすることはできません。
次の表は、上記の構文のパラメーターについて説明しています。
パラメーター
説明
username
Alibaba Cloud アカウント:Alibaba Cloud アカウントの AccessKey ID。AccessKey ページから AccessKey ID を取得できます。
環境変数を構成し、環境変数から AccessKey ID と AccessKey シークレットを取得することをお勧めします。 これは、リークのリスクを軽減するのに役立ちます。
カスタムアカウント:カスタムアカウントのユーザー名。 例:BASIC$abc。
port
Hologres インスタンスのパブリックポート番号。
例:
80
。endpoint
Hologres インスタンスのパブリック エンドポイント。
例:
xxx-cn-hangzhou.hologres.aliyuncs.com
。databasename
Hologres データベースの名前。
詳細については、「データベースを作成する」をご参照ください。
例:
mydb
。table
データをインポートする Hologres テーブルの名前。
filename
Hologres にインポートするローカルファイルのパス。
例:
D:\tmp\copy_test.csv
。次の例は、stdin から Hologres にローカルファイルをインポートする文を実行する方法を示しています。
copy_test という名前のオンプレミスファイルを stdin から Hologres にインポートする文を実行します。
インポートされた標準ファイルには、次の内容が含まれています。
01,01,name1 02,01,name2 03,01,name3 04,01,name4
文が実行されたら、PostgreSQL クライアントに戻ります。 次に、次の図に示すように、新しくインポートされたデータをクエリできます。
CopyManager を使用して JDBC クライアントから Hologres にファイルをインポートする
CopyManager を使用して、Java Database Connectivity ( JDBC ) クライアント上のファイルのデータをコピーできます。
CopyManager は、PostgreSQL の COPY 文用に JDBC によってカプセル化された API です。 Hologres は PostgreSQL プロトコルと互換性があります。 CopyManager を使用して JDBC クライアントファイルを Hologres にインポートしたり、Hologres から JDBC クライアントファイルにデータをエクスポートしたりできます。 CopyManager の詳細については、JDBC ドキュメントの CopyManager を参照してください。
例:CopyManager を使用して JDBC クライアントから Hologres にファイルをインポートします。 サンプル文:
package com.aliyun.hologram.test.jdbc;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
import org.postgresql.copy.CopyManager;
import org.postgresql.core.BaseConnection;
public class jdbcCopyFile {
public static void main(String args[]) throws Exception {
System.out.println(copyFromFile(getConnection(), "/Users/feng/Workspace/region.tbl", "region"));
}
public static Connection getConnection() throws Exception {
Class.forName("org.postgresql.Driver");
String url = "jdbc:postgresql://endpoint:port/dbname";
Properties props = new Properties();
//DB ユーザーを設定します
props.setProperty("user", "AAA");// 現在のアカウントの AccessKey ID。 データリークのリスクを軽減するために、環境変数を構成し、環境変数から AccessKey ID を取得することをお勧めします。
//DB パスワードを設定します
props.setProperty("password", "BBB");// 現在のアカウントの AccessKey シークレット。 データリークのリスクを軽減するために、環境変数を構成し、環境変数から AccessKey シークレットを取得することをお勧めします。
return DriverManager.getConnection(url, props);
}
/**
* Hologres データベースにファイルをインポートします。
*
* @param connection
* @param filePath
* @param tableName
* @return
* @throws SQLException
* @throws IOException
*/
public static long copyFromFile(Connection connection, String filePath, String tableName)
throws SQLException, IOException {
long count = 0;
FileInputStream fileInputStream = null;
try {
CopyManager copyManager = new CopyManager((BaseConnection) connection);
fileInputStream = new FileInputStream(filePath);
count = copyManager.copyIn("COPY " + tableName + " FROM STDIN delimiter '|' csv", fileInputStream);
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return count;
}
}
FIXED COPY
固定コピーは、固定プランを使用して COPY 文の実行を最適化する新しいモードです。 固定コピーは Hologres 実行エンジン専用に開発されたもので、Hologres へのデータインポートのみをサポートしています。 固定プランの原則の詳細については、「固定プランを使用して SQL 文の実行を高速化する」をご参照ください。
Hologres V1.3.17 以降では、固定コピーモードを使用して Hologres にデータをインポートできます。 固定コピーモードと他のデータインポートモードの違いの詳細については、「バッチ書き込みモードの比較」をご参照ください。
COPY 操作が一部の列でのみ実行される場合、固定コピーモードの実行は異なります。
COPY 文を使用して一部の列にデータを書き込み、データが部分的に更新されます。 サンプルコード:
CREATE TABLE t0 (id int NOT NULL, name text, age int, primary key(id)); COPY t0(id, name) FROM STDIN WITH ( STREAM_MODE TRUE, ON_CONFLICT UPDATE); -- COPY 文は、次の INSERT INTO 文と同等です。 INSERT INTO t0(id, name) VALUES(?,?) ON CONFLICT(id) DO UPDATE SET id = excluded.id, name = excluded.name;
COPY 文を使用して一部の列にデータを書き込み、データが書き込まれていない列にデフォルト値が含まれています。 サンプルコード:
CREATE TABLE t0 (id int not null, name text, age int DEFAULT 0, primary key(id)); COPY t0(id, name) FROM STDIN WITH ( STREAM_MODE TRUE, ON_CONFLICT UPDATE); -- COPY 文は、次の INSERT INTO 文と同等です。 -- データ行の場合、行の id 列にデータが含まれていない場合、行の age 列にはデフォルト値が割り当てられます。 -- id 列にデータがある場合、age 列は更新されません。 INSERT INTO t0(id, name, age) VALUES(?, ?, default) ON CONFLICT(id) DO UPDATE SET id = excluded.id, name = excluded.name;
COPY 文を使用して Hologres からデータをエクスポートする
Hologres からローカルファイルにデータをエクスポートする
例:
\copy
文を実行して、Hologres データをオンプレミスファイルにエクスポートします。説明このメソッドを使用して Hologres データをローカルファイルにエクスポートできるのは、PostgreSQL クライアント上のみです。
-- テーブルを作成します。 CREATE TABLE copy_to_local ( id int, age int, name text ) ; -- 作成したテーブルにデータを挿入します。 INSERT INTO copy_to_local VALUES (1,1,'a'), (1,2,'b'), (1,3,'c'), (1,4,'d'); -- テーブルのデータをクエリします。 SELECT * FROM copy_to_local; -- ローカルファイルにデータをエクスポートします。 \COPY (SELECT * FROM copy_to_local) TO '/root/localfile.txt';
stdout を使用して Hologres データをローカルファイルにエクスポートします。
説明このメソッドを使用して Hologres データをローカルファイルにエクスポートできるのは、PostgreSQL クライアント上のみです。
psql -U <username> -p <port> -h <endpoint> -d <databasename> -c "COPY (SELECT * FROM <tablename>) TO STDOUT WITH DELIMITER '|' CSV;" ><filename>;
OSS にデータをエクスポートする
Alibaba Cloud Object Storage Service ( OSS ) は、安全で費用対効果が高く、信頼性の高いクラウドストレージサービスです。 COPY 文を実行することで、Hologres から OSS にデータをエクスポートできます。
制限事項
Hologres インスタンスでは、pg_execute_server_program 権限が付与されたユーザーとスーパーユーザーのみが
hg_dump_to_oss
文を実行して、Hologres から特定の OSS バケットにデータをエクスポートできます。 スーパーユーザーは、次のいずれかの文を実行して、ユーザーに pg_execute_server_program 権限を付与できます。-- 現在のデータベースが簡易権限モデル ( SPM ) を使用している場合は、次の文を実行します。 CALL spm_grant('pg_execute_server_program','Alibaba Cloud アカウント ID、Alibaba Mail アドレス、または RAM ユーザーアカウント'); -- 現在のデータベースが標準 PostgreSQL 認証モデルを使用している場合は、次の文を実行します。 GRANT pg_execute_server_program TO Alibaba Cloud アカウント ID、Alibaba Cloud メールアドレス、または RAM ユーザーアカウント;
Hologres から OSS に一度にエクスポートできるデータ量は最大 5 GB です。
文の説明
COPY TO
文:COPY ( query ) TO { PROGRAM 'command' | STDOUT } [ [ WITH ] ( option [, ...] ) ]
PROGRAM パラメーターは、実行する文を指定します。 エクスポートされるデータは、文の標準入力に書き込まれます。 他のパラメーターの詳細については、「パラメーター」をご参照ください。
hg_dump_to_oss
文:この文は、COPY TO
文と一緒に使用する必要があります。COPY (query) TO PROGRAM 'hg_dump_to_oss --AccessKeyId <accessid> --AccessKeySecret <accesskey> --Endpoint <ossendpoint> --BucketName <bucketname> --DirName <dirname> --FileName <filename> --BatchSize <xxx> ' (DELIMITER ',', HEADER true, FORMAT CSV);
重要DirName パラメーターの値は、スラッシュ (/) やバックスラッシュ (\) などの文字で始めることはできません。
次の表は、上記の構文のパラメーターについて説明しています。
パラメーター
説明
サンプル値
query
クエリ文。
select * from dual;
AccessKeyId
Alibaba Cloud アカウントの AccessKey ID。
AccessKey ページで AccessKey ID を取得できます。
環境変数を構成し、環境変数から AccessKey ID と AccessKey シークレットを取得することをお勧めします。 これは、データリークのリスクを軽減するのに役立ちます。
なし
AccessKeySecret
AccessKey ID に対応する AccessKey シークレット。
環境変数を構成し、環境変数から AccessKey ID と AccessKey シークレットを取得することをお勧めします。 これは、データリークのリスクを軽減するのに役立ちます。
なし
Endpoint
OSS バケットのクラシックネットワーク エンドポイント。 次のいずれかの方法でエンドポイントを取得できます。
バケットの詳細ページでクラシックネットワーク エンドポイントを確認できます。 バケットの詳細ページに移動するには、OSS コンソールにログインし、バケット ページでバケット名をクリックします。
OSS のリージョンに基づいて パブリッククラウドのリージョンと OSS エンドポイント からエンドポイントを取得できます。
oss-cn-beijing-internal.aliyuncs.com
BucketName
OSS バケットの名前。
dummy_bucket
DirName
データをエクスポートする OSS ディレクトリ。
testdemo/
FileName
オプション。 OSS バケット内のファイルの名前。
説明ファイル名には、
;#'|?~<()"$\{}[]&*\n\r
などの文字を含めることはできません。file_name
BatchSize
hg_dump_to_oss
文によって一度に処理される行数。 デフォルト値:1000。5000
DELIMITER
エクスポートされたファイルのフィールドを区切るために使用するデリミタ。 デフォルトのデリミタはタブ文字 (\t) です。
,
例
次の例は、Hologres で
hg_dump_to_oss
文とCOPY TO
文を実行する方法を示しています。-- Hologres 内部テーブルから指定された OSS バケットにデータをダンプします。 COPY (SELECT * FROM holo_test LIMIT 2) TO PROGRAM 'hg_dump_to_oss --AccessKeyId <アクセス ID> --AccessKeySecret <アクセスキー> --Endpoint oss-cn-hangzhou-internal.aliyuncs.com --BucketName hologres-demo --DirName holotest/ --FileName file_name --BatchSize 3000' DELIMITER ','; -- Hologres 外部テーブルから指定された OSS バケットにデータをダンプします。 COPY (SELECT * FROM foreign_holo_test LIMIT 20) TO PROGRAM 'hg_dump_to_oss --AccessKeyId <アクセス ID> --AccessKeySecret <アクセスキー> --Endpoint oss-cn-hangzhou-internal.aliyuncs.com --BucketName hologres-demo --DirName holotest/ --FileName file_name --BatchSize 3000' (DELIMITER ',', HEADER true); -- Hologres テーブルから別のリージョンの指定された OSS バケットにデータをダンプします。 COPY (SELECT * FROM holo_test_1 LIMIT 20) TO PROGRAM 'hg_dump_to_oss --AccessKeyId <アクセス ID> --AccessKeySecret <アクセスキー> --Endpoint oss-cn-beijing-internal.aliyuncs.com --BucketName hologres-demo --DirName holotest/ --FileName file_name --BatchSize 3000' (DELIMITER ',', HEADER true, FORMAT CSV);
説明Hologres から別のリージョンの OSS バケットにデータをエクスポートできます。 たとえば、中国 (杭州) リージョンの Hologres インスタンスから中国 (北京) リージョンの OSS バケットにデータをエクスポートできます。
一般的なエラーとトラブルシューティング
次の表に、一般的なエラーと解決策を示します。
エラーメッセージ
解決策
ERROR: syntax error at or near ")"LINE 1: COPY (select 1,2,3 from ) TO PROGRAM 'hg_dump_to_oss2 --Acce...
このエラーメッセージは、query パラメーターで指定されたクエリ文が無効であるために報告されます。 クエリ文を確認して修正してください。
DETAIL: child process exited with exit code 255
このエラーメッセージは、OSS バケットに指定されたネットワークタイプが無効であるために報告されます。 Alibaba Cloud パブリッククラウドの OSS バケットにアクセスするには、OSS バケットの クラシックネットワーク エンドポイントを使用します。
DETAIL: command not found
DUMP TO OSS 文の program パラメーターが hg_dump_to_oss に設定されていないために、このエラーメッセージが報告されます。
ERROR: program "hg_dump_to_oss ..." failed DETAIL: child process exited with exit code 101
指定された AccessKeyId パラメーターが無効であるために、このエラーメッセージが報告されます。 Alibaba Cloud アカウントの AccessKey ID を使用してください。
ERROR: program "hg_dump_to_oss ..." failed DETAIL: child process exited with exit code 102
指定された AccessKeySecret パラメーターが無効であるために、このエラーメッセージが報告されます。 Alibaba Cloud アカウントの AccessKey シークレットを使用してください。
ERROR: program "hg_dump_to_oss ..." failed DETAIL: child process exited with exit code 103
指定された Endpoint パラメーターが無効であるために、このエラーメッセージが報告されます。 正しい OSS バケットのクラシックネットワーク エンドポイントを使用してください。
ERROR: program "hg_dump_to_oss ..." failed DETAIL: child process exited with exit code 104
指定された BucketName パラメーターが無効であるために、このエラーメッセージが報告されます。 正しいバケット名を使用してください。
ERROR: program "hg_dump_to_oss ..." failed DETAIL: child process exited with exit code 105
必須パラメーターが空になっているため、このエラーメッセージが報告されます。 すべての必須パラメーターが正しく指定されているかどうかを確認してください。
ERROR: program "hg_dump_to_oss ..." failed DETAIL: child process exited with exit code 255
Hologres インスタンスが存在するサーバーが、指定された OSS バケットが存在するネットワークに接続できないため、このエラーメッセージが報告されます。 指定された OSS バケットのエンドポイントを変更してください。 たとえば、OSS バケットのクラシックネットワーク エンドポイントを使用できます。 OSS バケットのエンドポイントの詳細については、「OSS リージョンとエンドポイント」をご参照ください。
CopyManager を使用して Hologres から JDBC クライアントのファイルにデータをエクスポートする
例:CopyManager を使用して Hologres から JDBC クライアントのファイルにデータをエクスポートします。 サンプルコード:
import org.postgresql.copy.CopyManager;
import org.postgresql.core.BaseConnection;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class copy_to_local_file {
public static void main(String args[]) throws Exception {
System.out.println(copyToFile(getConnection(), "/Users/feng/Workspace/region.tbl", "select * from region"));
}
public static Connection getConnection() throws Exception {
Class.forName("org.postgresql.Driver");
String url = "jdbc:postgresql://endpoint:port/dbname";
Properties props = new Properties();
//DB ユーザーを設定します
props.setProperty("user", "AAA");// 現在のアカウントの AccessKey ID。 データリークのリスクを軽減するために、環境変数を構成し、環境変数から AccessKey ID を取得することをお勧めします。
//DB パスワードを設定します
props.setProperty("password", "BBB");// 現在のアカウントの AccessKey シークレット。 データリークのリスクを軽減するために、環境変数を構成し、環境変数から AccessKey シークレットを取得することをお勧めします。
return DriverManager.getConnection(url, props);
}
/**
* JDBC クライアントファイルにデータをインポートします。
*
* @param connection
* @param filePath
* @param SQL_Query
* @return
* @throws SQLException
* @throws IOException
*/
public static String copyToFile(Connection connection, String filePath, String SQL_Query)
throws SQLException, IOException {
FileOutputStream fileOutputStream = null;
try {
CopyManager copyManager = new CopyManager((BaseConnection)connection);
fileOutputStream = new FileOutputStream(filePath);
copyManager.copyOut("COPY " + "(" + SQL_Query + ")" + " TO STDOUT DELIMITER '|' csv ", fileOutputStream);
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return filePath;
}
}