DataLoaderとDatabaseを連動する【V58】【SQLite編】

## SalesforceのバッチモードではCSVファイル以外にデータベースと連動する機能がある。
データベース接続およびSQL定義はdatabase-conf.xmlで定義する。
本記事ではSqliteを利用してSalesforceと連動を実施する。

前提条件
 - Java 11以上
 - DataLoder Version 58.0.4
 - Sqlite-JDBC Version 3.42

## セキュリティトークンの取得
[別の記事を参照](https://qiita.com/KangHyewon/items/b08dad6e7fd8506945e7)

## データローダバッチの構成
```
C:\DataLoaderProcess
  ├─config.properties              ⇒　データローダ環境設定ファイル
  ├─my.properties                  ⇒　独自の環境設定ファイル
  │─extractAccountDbToSf.bat       ⇒　データベースからデータを取得してSalesforceへUpsertする
  │─extractAccountSfToDb.bat       ⇒　Salesforceからデータを取得してデータベースへInsertする
  │─extractAccountDbToSfMap.sdl    ⇒　DataLoaderマッピングファイル(Database⇒Salesforce)
  │─extractAccountSfToDbMap.sd     ⇒　DataLoaderマッピングファイル(Salesforce⇒Database)
  │─dataloader.key                 ⇒　暗号鍵
  │─dataloader-58.0.4.jar          ⇒　データローダJAR
  │─spring-context-5.3.27.jar      ⇒　spring-context
  │─spring-core-5.3.27.jar         ⇒　spring-core
  │─sqlite-jdbc-3.42.0.1.jar       ⇒　Sqlite-jdbcライブラリ
  │─sqlite.db                      ⇒　Sqliteデータベースファイル
  ├─process.bat                    ⇒　バッチ起動ファイル(ProcessId指定)
  ├─process-conf.xml               ⇒　データローダの実行設定ファイル
  └─log4j2.properties              ⇒　ログ出力用の設定ファイル
```

## 必要なJarファイルの取得する

### DataLoader jarファイルの取得
本記事にはJava 11をサポートする最後のバージョンであるV58.0.4を利用する
取得先：https://github.com/forcedotcom/dataloader/releases/tag/v58.0.4

欲しいのは[dataloader-58.0.4.jar]ファイルのみなので任意のフォルダにコピーしておく
この記事では[C:\DataLoaderProcess]にコピーした。

### Spring-Core,Spring-Context jarファイルの取得
mavenサイトからダウンロードする。
[spring-core-5.3.27.jar](https://mvnrepository.com/artifact/org.springframework/spring-core/5.3.27)
[spring-context-5.3.27.jar](https://mvnrepository.com/artifact/org.springframework/spring-context/5.3.27)

### Sqlite-JDBC jarファイルの取得
[sqlite-jdbc-3.42.0.1.jar](https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc/3.42.0.1)

※あなたが経験あるJavaプログラマーであれば、何で細かいマイナバージョンまで指定しているか理解してくれるはずです。

## 暗号化されたパスワードの生成
[別の記事を参照](https://qiita.com/KangHyewon/items/b08dad6e7fd8506945e7)

## データベースを用意する
C:\DataLoaderProcess\sqlite.dbファイルを作成し、以下のテーブルを作成する
```sql
CREATE TABLE ACCOUNT_TABLE (
	ID TEXT(18),
	NAME TEXT(255),
	NUMBER_OF_EMPLOYEES INTEGER,
	INDUSTRY TEXT(40),
	ANNUAL_REVENUE REAL(18),
	CREATED_DATE TEXT
);
```

## 設定ファイルを作成する

### config.properties
```properties
# Data LodaerLoader Config
# https://help.salesforce.com/apex/HTViewHelpDoc?id=loader_params.htm

# 接続先情報
# sandboxの場合：https://test.salesforce.com
sfdc.endpoint=https://login.salesforce.com
sfdc.username=<ログインユーザ名>
sfdc.password=<コマンドで生成した暗号化パスワード>
process.encryptionKeyFile=./dataLoader.key
```

### my.properties
```properties
# 独自の環境設定

outputdirectory=C:/DataLoaderProcess
```

### log4j2.properties
```properties
name = Salesforce Data Loader
monitorInterval = 5
property.basePath = ${sys:java.io.tmpdir}
# RollingFileAppender name, pattern, path and rollover policy
appender.rolling.type = RollingFile
appender.rolling.name = fileAppender
appender.rolling.fileName= ./log/sdl.log
appender.rolling.filePattern= ./log/sdl-%d{yyyy-MM-dd}.log
appender.rolling.layout.type = PatternLayout
appender.rolling.layout.pattern = %d %-5p [%t] %C{2} %M (%F:%L) - %m%n
appender.rolling.policies.type = Policies

# CONSOLE Appender
appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d %-5p [%t] %C{2} %M (%F:%L) - %m%n

# RollingFileAppender rotation policy
appender.rolling.policies.size.type = SizeBasedTriggeringPolicy
appender.rolling.policies.size.size = 100KB

# Configure root logger
rootLogger.level = info
rootLogger.appenderRef.rolling.ref = fileAppender
rootLogger.appenderRef.stdout.ref = STDOUT
```

### database-conf.xml
```xml
<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p" 
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/util
           http://www.springframework.org/schema/util/spring-util.xsd
           http://www.springframework.org/schema/tool
           http://www.springframework.org/schema/tool/spring-tool.xsd">

    <!-- 独自の環境設定をロード -->
    <context:property-placeholder location="classpath:my.properties"/>

    <!-- データソースの設定(必ずdbcp2を使用する必要がある) -->
    <bean id="dbDataSource"
        class="org.apache.commons.dbcp2.BasicDataSource"
        destroy-method="close"
        scope="prototype">
        <property name="driverClassName" value="org.sqlite.JDBC"/>
        <property name="url" value="jdbc:sqlite:./sqlite.db"/>
    </bean>
    <!-- データベースから取得するBean定義 -->
    <bean id="queryAccountTable"
        class="com.salesforce.dataloader.dao.database.DatabaseConfig"
        scope="singleton">
        <property name="sqlConfig" ref="queryAccountTableSql"/>
        <property name="dataSource" ref="dbDataSource"/>
    </bean>

    <!-- データベースへ登録するBean定義 -->
    <bean id="insertAccountTable"
        class="com.salesforce.dataloader.dao.database.DatabaseConfig"
        scope="singleton">
        <property name="sqlConfig" ref="insertAccountTableSql"/>
        <property name="dataSource" ref="dbDataSource"/>
    </bean>
    <!-- SELECT SQL -->
    <bean id="queryAccountTableSql"
        class="com.salesforce.dataloader.dao.database.SqlConfig"
        scope="singleton">
        <property name="sqlString">
            <value>
                SELECT ID, NAME, NUMBER_OF_EMPLOYEES, INDUSTRY, ANNUAL_REVENUE
                FROM ACCOUNT_TABLE
                WHERE created_date > @process.lastRunDate@
            </value>
        </property>
        <property name="columnNames">
            <list>
                <value>id</value>
                <value>name</value>
                <value>number_of_employees</value>
                <value>industry</value>
                <value>annual_revenue</value>
            </list>
        </property>
        <property name="sqlParams">
            <map>
                <entry key="process.lastRunDate" value="java.sql.Timestamp"/>
            </map>
        </property>
    </bean>
    <!-- INSERT SQL -->
    <bean id="insertAccountTableSql"
        class="com.salesforce.dataloader.dao.database.SqlConfig"
        scope="singleton">
        <property name="sqlString">
            <value>
                INSERT INTO ACCOUNT_TABLE (
                ID, NAME, NUMBER_OF_EMPLOYEES, INDUSTRY, ANNUAL_REVENUE, CREATED_DATE)
                VALUES (@id@, @name@, @number_of_employees@, @industry@, @annual_revenue@, @created_date@)
            </value>
        </property>
        <property name="sqlParams">
            <map>
                <entry key="id"                  value="java.lang.String" />
                <entry key="name"                value="java.lang.String" />
                <entry key="number_of_employees" value="java.lang.Long" />
                <entry key="industry"            value="java.lang.Double" />
                <entry key="annual_revenue"      value="java.lang.String" />
                <entry key="created_date"        value="java.lang.String" />
            </map>
        </property>
    </bean>
</beans>
```

### process-conf.xml
```xml
<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p" 
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/util
           http://www.springframework.org/schema/util/spring-util.xsd
           http://www.springframework.org/schema/tool
           http://www.springframework.org/schema/tool/spring-tool.xsd">

    <!-- 独自の環境設定をロード -->
    <context:property-placeholder location="classpath:my.properties"/>

    <!-- Salesforce⇒Database出力ジョブを定義 -->
    <bean id="extractAccountSfToDb"
          class="com.salesforce.dataloader.process.ProcessRunner"
          scope="prototype">
        <description>extractAccountSfToDbジョブは、Salesforceから取引先情報を取得し、データベースへ保存します。</description>
        <property name="name" value="extractAccountSfToDb"/>
        <property name="configOverrideMap">
            <map>
                <entry key="sfdc.extractionSOQL" value="Select Id, Name, NumberOfEmployees, Industry, AnnualRevenue, CreatedDate FROM Account LIMIT 10" />
                <entry key="process.outputSuccess" value="${outputdirectory}/extractAccountDbSuccess.csv"/>
                <entry key="process.outputError" value="${outputdirectory}/extractAccountDbError.csv"/>
                <entry key="sfdc.debugMessagesFile" value="${outputdirectory}/extractAccountSfToDbSoapTrace.log"/>
                <entry key="sfdc.extractionRequestSize" value="1000"/>
                <entry key="sfdc.entity" value="Account"/>
                <entry key="process.operation" value="extract"/>
                <entry key="process.mappingFile" value="${outputdirectory}/extractAccountSfToDbMap.sdl"/>
                <entry key="dataAccess.type" value="databaseWrite"/>
                <entry key="dataAccess.name" value="insertAccountTable"/>
                <entry key="dataAccess.writeBatchSize" value="750"/>
            </map>
        </property>
    </bean>

    <!-- Database⇒Salesforce入力ジョブを定義 -->
    <bean id="extractAccountDbToSf"
          class="com.salesforce.dataloader.process.ProcessRunner"
          scope="prototype">
        <description>extractAccountDbToSfジョブは、データベースから取引先情報を取得し、SalesforceへUpsertします。</description>
        <property name="name" value="extractAccountDbToSf"/>
        <property name="configOverrideMap">
            <map>
                <entry key="sfdc.debugMessages" value="false"/>
                <entry key="sfdc.debugMessagesFile" value="${outputdirectory}/extractAccountSfToDbSoapTrace.log"/>
                <entry key="sfdc.externalIdField" value="Id"/>
                <entry key="sfdc.entity" value="Account"/>
                <entry key="process.operation" value="upsert"/>
                <entry key="process.mappingFile" value="${outputdirectory}/extractAccountDbToSfMap.sdl"/>
                <entry key="dataAccess.name" value="queryAccountTable"/>
                <entry key="dataAccess.type" value="databaseRead"/>
                <entry key="process.initialLastRunDate" value="2005-12-01T00:00:00.000-0800"/>
            </map>
        </property>
    </bean>

</beans>
```

### process.bat
```batch
@echo off

SET MIN_JAVA_VERSION=11
echo Data Loader requires Java JRE %MIN_JAVA_VERSION% or later. Checking if it is installed...
java -version 1>nul 2>nul || (
echo Did not find java command.
EXIT -1
)

java -cp ".\dataloader-58.0.4.jar;.\spring-core-5.3.27.jar;.\spring-context-5.3.27.jar;.\sqlite-jdbc-3.42.0.1.jar;." com.salesforce.dataloader.process.DataLoaderRunner .\ %1 run.mode=batch
EXIT /b %ERRORLEVEL%
```

### extractAccountDbToSf.bat
```@echo off

CALL process.bat extractAccountDbToSf
```

### extractAccountSfToDb.bat
```@echo off

CALL process.bat extractAccountSfToDb
```

## バッチファイルの実行
データベースにはデータが何もないのでまず、Salesforceからデータを取得してデータベースへ登録してみる。

### extractAccountSfToDb.batファイルを実行して起動

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3680784/d2cb35d7-4b0a-034b-12da-04e53756a3d1.png)

次はデータベースへ登録された取引先情報をSalesforceへUpsertする。
データは同じなので最終更新日が更新されたことは確認できる
### extractAccountDbToSf.batファイルを実行して起動

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3680784/278677a1-8677-aeff-6b4e-fb2ee1bc4bd7.png)