Salesforce と kintone 間で顧客データを同期する方法 (Java - シンプルなJDBC)

Salesforce と kintone 間で顧客データを同期する方法として、**CDataドライバー**を利用したWindowsフォームアプリケーション(C#/ADO.NET)によるものを別記事でご紹介しました。
CDataドライバーは、ADO.NETのみならず様々なテクノロジーで提供されています。今回は、Salesforce と kintone 間で顧客データを同期するアプリケーションの Java による例をご紹介したいと思います。

![000_構成図.png](https://qiita-image-store.s3.amazonaws.com/0/141193/946bec73-b425-5f62-56a5-bd05c004d6c7.png)

このアプリケーションの概要は次の通りです。

* JDK 8
* JavaFX によるデスクトップアプリケーション
* Eclipse 4.6
* Salesforce および kintone からデータを読み込む
* インポートしたい行を選択して、Salesforce から kintone へデータをインポートする

それでは、このサンプルアプリケーションの開発手順を見ていきます。

## ソースコード
このサンプルアプリケーションのソースコードは[こちら](https://github.com/kasoorimethi/Salesforce2KintoneJDBC)からどうぞ。

## CData製品のインストール
CData Software をインストールします。今回必要になるのは、次の2つです。

* [Salesforce JDBC Driver](https://www.cdata.com/jp/drivers/salesforce/jdbc/ "Salesforce JDBC Driver")
* [kintone JDBC Driver](https://www.cdata.com/jp/drivers/kintone/jdbc/ "kintone JDBC Driver")

## 開発環境の準備
Eclipse 4.6 を使用します。以下の機能を Eclipse にインストールしておいてください。

* e(fx)clipse - IDE

※ サイトのロケーションは http://download.eclipse.org/efxclipse/updates-released/2.4.0/site を使用

## プロジェクト作成
新規プロジェクトを作成します。JavaFX Project を選択します。

![001_プロジェクト作成.png](https://qiita-image-store.s3.amazonaws.com/0/141193/a0e68e96-f9fc-2ec8-092f-de3947aa7c9f.png)

デフォルトのまま順次「次へ」ボタンをクリックしていきます。最後のダイアログでは、「言語」に"FXML"、「Root-Type」に"javax.scene.layout.AnchorPane"、「ファイル名」に"MainPane"、「Controller Name」に"MainPaneController"を指定し、完了ボタンをクリックします。

![002_プロジェクト作成2.png](https://qiita-image-store.s3.amazonaws.com/0/141193/44f51596-8988-0bfc-6de1-2318614e14d7.png)

## 処理の実装
### データモデルクラス
Salesforce側はContact、kintone側は顧客リスト に対応するクラスを実装します。これは単純なDTOです。

```java
package application;


public class Contact {
	
	private String id;

	private String accountId;

	private String department;

	private String email;

	private String fax;

	private String name;

	private String phone;
	
	private String accountName;
	
	private String accountAddress;
	
	private String accountBillingPostalCode;

	// 以下略 それぞれのsetter/getter
}
```

```java
package application;


public class 顧客リスト  {

	private int recordId;

	private String fax_数字のみ_;

	private String tel_数字のみ_;

	private String メールアドレス;
	
	private String レコード番号;

	private String 会社名;

	private String 住所;

	private String 担当者名;

	private String 部署名;

	private String 郵便番号_数字のみ_;

	// 以下略 それぞれのsetter/getter
}
```

### MainPaneController
今回のアプリケーションの主たるクラスです。javafx.fxml.Initializableを実装して初期化できるようにします。さらに、Salesforce/kintoneに接続するためのJDBCURL、そしてそれらのデータを表示するためのTableViewのフィールドを追加します。
※JDBCURLの形式はマニュアルに記載されています。

```java
public class MainPaneController implements Initializable {

	private String salesforceJdbcUrl = "<SalesforceのJDBCURL>";
	
	private String kintoneJdbcUrl = "<kintoneのJDBCURL>";
	
	@FXML
	private TableView<Contact> salesforceTable;
	
	@FXML
	private TableView<顧客リスト> kintoneTable;
```

### データの読み込み
MainPaneControllerに、Salesforce/kintoneのデータをTableViewに読み込む処理を実装します。JDBCを使ったシンプルなものです。

```java
	private void loadSalesforceTableView() {
		
		salesforceTable.getItems().clear();
		
		try (
			Connection conn = DriverManager.getConnection(salesforceJdbcUrl);
			PreparedStatement pstmt = conn.prepareStatement(
				"SELECT c.*, " +
				"a.Name AccountName, a.BillingPostalCode AccountBillingPostalCode, CONCAT(a.BillingState, a.BillingCity, a.BillingStreet) AccountAddress " +
				"FROM Contact c INNER JOIN Account a ON a.Id=c.AccountId");
			ResultSet rs = pstmt.executeQuery();
			) {

			while (rs.next()) {
				Contact c = new Contact();
				c.setId(rs.getString("Id"));
				c.setAccountId(rs.getString("AccountId"));
				c.setDepartment(rs.getString("Department"));
				c.setEmail(rs.getString("Email"));
				c.setFax(rs.getString("Fax"));
				c.setName(rs.getString("Name"));
				c.setPhone(rs.getString("Phone"));
				c.setAccountName(rs.getString("AccountName"));
				c.setAccountAddress(rs.getString("AccountAddress"));
				c.setAccountBillingPostalCode(rs.getString("AccountBillingPostalCode"));
				salesforceTable.getItems().add(c);
			}
			
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
	}
```

```java
	private void loadKintoneTableView() {
	
		kintoneTable.getItems().clear();
		
		try (
			Connection conn = DriverManager.getConnection(kintoneJdbcUrl);
			PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM 顧客リスト");
			ResultSet rs = pstmt.executeQuery();
			) {

			while (rs.next()) {
				顧客リスト c = new 顧客リスト();
				c.setRecordId(rs.getInt("RecordId"));
				c.setFax_数字のみ_(rs.getString("Fax(数字のみ)"));
				c.setTel_数字のみ_(rs.getString("Tel(数字のみ)"));
				c.setメールアドレス(rs.getString("メールアドレス"));
				c.setレコード番号(rs.getString("レコード番号"));
				c.set会社名(rs.getString("会社名"));
				c.set住所(rs.getString("住所"));
				c.set担当者名(rs.getString("担当者名"));
				c.set部署名(rs.getString("部署名"));
				c.set郵便番号_数字のみ_(rs.getString("郵便番号(数字のみ)"));
				kintoneTable.getItems().add(c);
			}
			
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
	}
```

### アプリケーションの初期化
MainPaneControllerのinitializeを実装します。主な処理はデータ読み込みのメソッドを呼び出すだけです。

```java
	@Override
	public void initialize(URL location, ResourceBundle resources) {
		
		salesforceTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
		
		loadSalesforceTableView();
		loadKintoneTableView();
	}
```

### インポート処理
インポート処理を実装します。やっていることは、Salesforce側のTableViewで選択された行のデータで、JDBCでシンプルにkintone側にINSERT/UPDATE文を実行しているだけです。

```java
	@FXML
	private void importOnClick(MouseEvent event) {
		
		try (Connection conn = DriverManager.getConnection(kintoneJdbcUrl)) {
			
			conn.setAutoCommit(false);
			
			for (Contact contact : salesforceTable.getSelectionModel().getSelectedItems()) {
				
				顧客リスト kintoneItem = kintoneTable.getItems().stream()
					.filter(o -> o.getメールアドレス().equals(contact.getEmail()))
					.findFirst()
					.orElse(new 顧客リスト());
				
				PreparedStatement pstmt;
				
				if (kintoneItem.getRecordId() == 0) {
					pstmt = conn.prepareStatement("INSERT INTO 顧客リスト(メールアドレス, 会社名, 住所, 担当者名, 部署名, [郵便番号(数字のみ)], [Tel(数字のみ)], [Fax(数字のみ)]) VALUES(?, ? ,? ,? ,? ,? ,? ,?)");
				} else {
					pstmt = conn.prepareStatement("UPDATE 顧客リスト SET メールアドレス=?, 会社名=?, 住所=?, 担当者名=?, 部署名=?, [郵便番号(数字のみ)]=?, [Tel(数字のみ)]=?, [Fax(数字のみ)]=? WHERE RecordId=?");
					pstmt.setInt(9, kintoneItem.getRecordId());
				}
				
				pstmt.setString(1, contact.getEmail());
				pstmt.setString(2, contact.getAccountName());
				pstmt.setString(3, contact.getAccountAddress());
				pstmt.setString(4, contact.getName());
				pstmt.setString(5, Optional.ofNullable(contact.getDepartment()).orElse(""));
				pstmt.setString(6, Optional.ofNullable(contact.getAccountBillingPostalCode()).orElse("").replace("-", ""));
				pstmt.setString(7, contact.getPhone());
				pstmt.setString(8, contact.getFax());
				
				pstmt.execute();
				pstmt.close();
			}
			
			conn.commit();
			
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		
		loadKintoneTableView();
	}
```


### 画面の作成
最後に画面を作成します。application/MainPane.fxmlを開き、以下のコードを実装します。データモデルクラスの各プロパティとテーブルの列をマッピングしています。

```xml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.cell.PropertyValueFactory?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane prefHeight="660.0" prefWidth="1200.0" xmlns="http://javafx.com/javafx/8.0.102" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.MainPaneController">
   <children>
      <TableView fx:id="salesforceTable" layoutX="14.0" layoutY="53.0" prefHeight="600.0" prefWidth="485.0">
        <columns>
          <TableColumn prefWidth="150.0" text="担当者名">
            <cellValueFactory><PropertyValueFactory property="name" /></cellValueFactory>
          </TableColumn>
          <TableColumn prefWidth="150.0" text="メールアドレス">
            <cellValueFactory><PropertyValueFactory property="email" /></cellValueFactory>
          </TableColumn>
          <TableColumn prefWidth="150.0" resizable="false" sortable="false" text="会社名">
          	<cellValueFactory><PropertyValueFactory property="accountName" /></cellValueFactory>
          </TableColumn>
          <TableColumn prefWidth="150.0" text="部署名">
            <cellValueFactory><PropertyValueFactory property="department" /></cellValueFactory>
          </TableColumn>
          <TableColumn prefWidth="150.0" text="住所">
            <cellValueFactory><PropertyValueFactory property="accountAddress" /></cellValueFactory>
          </TableColumn>
        </columns>
      </TableView>
      <Button layoutX="542.0" layoutY="263.0" mnemonicParsing="false" onMouseClicked="#importOnClick" prefHeight="35.0" prefWidth="116.0" text="インポート" />
      <TableView fx:id="kintoneTable" layoutX="696.0" layoutY="53.0" prefHeight="600.0" prefWidth="485.0">
        <columns>
          <TableColumn editable="false" prefWidth="75.0" sortable="false" text="レコード番号">
            <cellValueFactory><PropertyValueFactory property="レコード番号" /></cellValueFactory>
          </TableColumn>
          <TableColumn editable="false" prefWidth="150.0" sortable="false" text="担当者名">
            <cellValueFactory><PropertyValueFactory property="担当者名" /></cellValueFactory>
          </TableColumn>
          <TableColumn editable="false" prefWidth="150.0" sortable="false" text="メールアドレス">
            <cellValueFactory><PropertyValueFactory property="メールアドレス" /></cellValueFactory>
          </TableColumn>
          <TableColumn editable="false" prefWidth="150.0" sortable="false" text="会社名">
            <cellValueFactory><PropertyValueFactory property="会社名" /></cellValueFactory>
          </TableColumn>
          <TableColumn editable="false" prefWidth="100.0" sortable="false" text="部署名">
            <cellValueFactory><PropertyValueFactory property="部署名" /></cellValueFactory>
          </TableColumn>
          <TableColumn editable="false" prefWidth="150.0" sortable="false" text="住所">
            <cellValueFactory><PropertyValueFactory property="住所" /></cellValueFactory>
          </TableColumn>
        </columns>
      </TableView>
   </children>
</AnchorPane>
```

ウィンドウのサイズを設定します。application/Main.java を開き、Sceneのサイズを変更します。

```java
Scene scene = new Scene(root, 1200, 660);
```

実装は以上です。

## 実行
実行構成を作成します。[実行]->[実行構成]を開き、「Javaアプリケーション」を右クリックして「新規」を選択します。
クラスパスタブを開き、CDataのJDBCドライバーを2つ指定し、実行ボタンをクリックします。

![018_実行構成.png](https://qiita-image-store.s3.amazonaws.com/0/141193/59ebffed-caf6-e5c0-b8ce-5db80fa6ca3d.png)

実行すると、テーブルビューにSalesforceとkintoneのデータが表示されたウィンドウが立ち上がります。

![019_実行直後.png](https://qiita-image-store.s3.amazonaws.com/0/141193/191ca2c1-a4ee-82ba-9e81-81dee464535b.png)

データ移行したい行を選択して「インポート」ボタンをクリックすると、選択したデータが kintone に登録されます。

![020_移行後.png](https://qiita-image-store.s3.amazonaws.com/0/141193/116c0bc6-5329-7be3-6ac7-59e8007f14de.png)

![021_移行後kintone.png](https://qiita-image-store.s3.amazonaws.com/0/141193/4081afb2-ad53-8945-82cc-79464e1abef7.png)