この文章は Salesforce 機械翻訳システムを使用して翻訳されました。詳細はこちらをご参照ください。
英語に切り替える

サンプルコードの説明

クライアントの設定が完了したら、Bulk API を使用するクライアントアプリケーションの構築を開始できます。次のサンプルを使用して、クライアントアプリケーションを作成します。各セクションで、コードの特定部分を順に説明していきます。詳細なサンプルは最後に記載します。

次のコードは、WSC ツールキット内のパッケージとクラスを設定し、さらに Partner WSDL から生成されたコードを設定します。

1
2import java.io.*;
3import java.util.*;
4
5import com.sforce.async.*;
6import com.sforce.soap.partner.PartnerConnection;
7import com.sforce.ws.ConnectionException;
8import com.sforce.ws.ConnectorConfig;
9

main() メソッドの設定

次のコードは、クラスの main() メソッドを設定します。main() メソッドは、サンプルの処理ロジックを含む runSample() メソッドを呼び出します。runSample() で呼び出されるメソッドについては、後続のセク��ョンで取り上げます。

1
2    public static void main(String[] args)
3      throws AsyncApiException, ConnectionException, IOException {
4        BulkExample example = new BulkExample();
5        // Replace arguments below with your credentials and test file name
6        // The first parameter indicates that we are loading Account records
7        example.runSample("Account", "myUser@myOrg.com", "myPassword", "mySampleData.csv");
8    }
9
10    /**
11     * Creates a Bulk API job and uploads batches for a CSV file.
12     */
13    public void runSample(String sobjectType, String userName,
14              String password, String sampleFileName)
15            throws AsyncApiException, ConnectionException, IOException {
16        BulkConnection connection = getBulkConnection(userName, password);
17        JobInfo job = createJob(sobjectType, connection);
18        List<BatchInfo> batchInfoList = createBatchesFromCSVFile(connection, job,
19            sampleFileName);
20        closeJob(connection, job.getId());
21        awaitCompletion(connection, job, batchInfoList);
22        checkResults(connection, job, batchInfoList);
23    }
24

ログインと BulkConnection の設定

次のコードは、パートナー接続 (PartnerConnection) を使用してログインし、セッションを再利用して Bulk API 接続 (BulkConnection) を作成します。

1
2    /**
3     * Create the BulkConnection used to call Bulk API operations.
4     */
5    private BulkConnection getBulkConnection(String userName, String password)
6          throws ConnectionException, AsyncApiException {
7        ConnectorConfig partnerConfig = new ConnectorConfig();
8        partnerConfig.setUsername(userName);
9        partnerConfig.setPassword(password);
10        partnerConfig.setAuthEndpoint("https://login.salesforce.com/services/Soap/u/34.0");
11        // Creating the connection automatically handles login and stores
12        // the session in partnerConfig
13        new PartnerConnection(partnerConfig);
14        // When PartnerConnection is instantiated, a login is implicitly
15        // executed and, if successful,
16        // a valid session is stored in the ConnectorConfig instance.
17        // Use this key to initialize a BulkConnection:
18        ConnectorConfig config = new ConnectorConfig();
19        config.setSessionId(partnerConfig.getSessionId());
20        // The endpoint for the Bulk API service is the same as for the normal
21        // SOAP uri until the /Soap/ part. From here it's '/async/versionNumber'
22        String soapEndpoint = partnerConfig.getServiceEndpoint();
23        String apiVersion = "34.0";
24        String restEndpoint = soapEndpoint.substring(0, soapEndpoint.indexOf("Soap/"))
25            + "async/" + apiVersion;
26        config.setRestEndpoint(restEndpoint);
27        // This should only be false when doing debugging.
28        config.setCompression(true);
29        // Set this to true to see HTTP requests and responses on stdout
30        config.setTraceMessage(false);
31        BulkConnection connection = new BulkConnection(config);
32        return connection;
33    }
34

この BulkConnection インスタンスは、Bulk API を使用するための基盤であり、アプリケーションのライフサイクルにわたって繰り返し再利用できます。

ジョブの新規作成

接続が作成できたら、新しいジョブを作成します。データは常にジョブ単位で処理されます。ジョブは、処理するデータの詳細、つまり実行する処理の種類 (挿入、更新、更新/挿入、削除) やオブジェクト種別を指定します。次のコードは、Account オブジェクトを対象とした挿入処理のジョブを新規作成します。

1
2    /**
3     * Create a new job using the Bulk API.
4     * 
5     * @param sobjectType
6     *            The object type being loaded, such as "Account"
7     * @param connection
8     *            BulkConnection used to create the new job.
9     * @return The JobInfo for the new job.
10     * @throws AsyncApiException
11     */
12    private JobInfo createJob(String sobjectType, BulkConnection connection)
13          throws AsyncApiException {
14        JobInfo job = new JobInfo();
15        job.setObject(sobjectType);
16        job.setOperation(OperationEnum.insert);
17        job.setContentType(ContentType.CSV);
18        job = connection.createJob(job);
19        System.out.println(job);
20        return job;
21    }
22

作成したばかりのジョブの状態は、Open になります。ジョブがこの状態にある間は、新しいバッチをジョブに追加できます。ジョブの状態が Closed になると、それ以上バッチを追加することはできません。

ジョブへのバッチの追加

データは一連のバッチ要求を通じて処理されます。各要求は、リクエストボディに XML 形式のデータセットを含む HTTP POST です。Bulk API の制限で説明されているバッチサイズと、1 日あたりの処理バッチ数の上限を超過しない限り、データセット全体をどの程度に分割して処理するかは、クライアントアプリケーション側で決定できます。

各バッチの処理にはオーバーヘッドが伴います。オーバーヘッドの処理コストを最小限に抑えられるよう、バッチを処理と転送に適したサイズに調整する必要があります。レコード数 1,000 ~ 10,000 件の範囲が、適切なバッチサイズとみなされます。

次のコードは、CSV ファイルを小さいバッチファイルに分割して、Salesforce にアップロードします。

1
2    /**
3     * Create and upload batches using a CSV file.
4     * The file into the appropriate size batch files.
5     * 
6     * @param connection
7     *            Connection to use for creating batches
8     * @param jobInfo
9     *            Job associated with new batches
10     * @param csvFileName
11     *            The source file for batch data
12     */
13    private List<BatchInfo> createBatchesFromCSVFile(BulkConnection connection,
14          JobInfo jobInfo, String csvFileName)
15            throws IOException, AsyncApiException {
16        List<BatchInfo> batchInfos = new ArrayList<BatchInfo>();
17        BufferedReader rdr = new BufferedReader(
18            new InputStreamReader(new FileInputStream(csvFileName))
19        );
20        // read the CSV header row
21        byte[] headerBytes = (rdr.readLine() + "\n").getBytes("UTF-8");
22        int headerBytesLength = headerBytes.length;
23        File tmpFile = File.createTempFile("bulkAPIInsert", ".csv");
24
25        // Split the CSV file into multiple batches
26        try {
27            FileOutputStream tmpOut = new FileOutputStream(tmpFile);
28            int maxBytesPerBatch = 10000000; // 10 million bytes per batch
29            int maxRowsPerBatch = 10000; // 10 thousand rows per batch
30            int currentBytes = 0;
31            int currentLines = 0;
32            String nextLine;
33            while ((nextLine = rdr.readLine()) != null) {
34                byte[] bytes = (nextLine + "\n").getBytes("UTF-8");
35                // Create a new batch when our batch size limit is reached
36                if (currentBytes + bytes.length > maxBytesPerBatch
37                  || currentLines > maxRowsPerBatch) {
38                    createBatch(tmpOut, tmpFile, batchInfos, connection, jobInfo);
39                    currentBytes = 0;
40                    currentLines = 0;
41                }
42                if (currentBytes == 0) {
43                    tmpOut = new FileOutputStream(tmpFile);
44                    tmpOut.write(headerBytes);
45                    currentBytes = headerBytesLength;
46                    currentLines = 1;
47                }
48                tmpOut.write(bytes);
49                currentBytes += bytes.length;
50                currentLines++;
51            }
52            // Finished processing all rows
53            // Create a final batch for any remaining data
54            if (currentLines > 1) {
55                createBatch(tmpOut, tmpFile, batchInfos, connection, jobInfo);
56            }
57        } finally {
58            tmpFile.delete();
59        }
60        return batchInfos;
61    }
62
63    /**
64     * Create a batch by uploading the contents of the file.
65     * This closes the output stream.
66     * 
67     * @param tmpOut
68     *            The output stream used to write the CSV data for a single batch.
69     * @param tmpFile
70     *            The file associated with the above stream.
71     * @param batchInfos
72     *            The batch info for the newly created batch is added to this list.
73     * @param connection
74     *            The BulkConnection used to create the new batch.
75     * @param jobInfo
76     *            The JobInfo associated with the new batch.
77     */
78    private void createBatch(FileOutputStream tmpOut, File tmpFile,
79      List<BatchInfo> batchInfos, BulkConnection connection, JobInfo jobInfo)
80              throws IOException, AsyncApiException {
81        tmpOut.flush();
82        tmpOut.close();
83        FileInputStream tmpInputStream = new FileInputStream(tmpFile);
84        try {
85            BatchInfo batchInfo =
86              connection.createBatchFromStream(jobInfo, tmpInputStream);
87            System.out.println(batchInfo);
88            batchInfos.add(batchInfo);
89
90        } finally {
91            tmpInputStream.close();
92        }
93    }
94

サーバは、受け取ったバッチをただちに処理待ちのキューに格納します。バッチの送信時には、形式にエラーがあっても報告はされません。こうしたエラーはバッチの処理が完了した後、結果データとして報告されます。

バイナリ型の添付ファイルをインポートするには、以下のメソッドを使用して、batchContent パラメータにバッチのコンテンツ (CSV、XML のいずれか) を指定するか、添付ファイルに request.txt を追加して、batchContent パラメータには null を渡します。これらのメソッドは、com.async.BulkConnection クラス内に含まれます。

  • createBatchFromDir()
  • createBatchWithFileAttachments()
  • createBatchWithInputStreamAttachments()
  • createBatchFromZipStream()

ヒント

ジョブの終了

すべてのバッチをジョブに追加したら、ジョブを終了します。ジョブを終了すると、すべてのバッチの処理が確実に完了します。

1
2    private void closeJob(BulkConnection connection, String jobId)
3          throws AsyncApiException {
4        JobInfo job = new JobInfo();
5        job.setId(jobId);
6        job.setState(JobStateEnum.Closed);
7        connection.updateJob(job);
8    }
9

バッチの状況の確認

バッチはバックグラウンドで処理されます。バッチが完了するまでの処理時間は、データセットのサイズによって異なります。処理の実行中に、すべてのバッチの状況を取得して、バッチが完了しているかどうかを確認することができます。

1
2    /**
3     * Wait for a job to complete by polling the Bulk API.
4     * 
5     * @param connection
6     *            BulkConnection used to check results.
7     * @param job
8     *            The job awaiting completion.
9     * @param batchInfoList
10     *            List of batches for this job.
11     * @throws AsyncApiException
12     */
13    private void awaitCompletion(BulkConnection connection, JobInfo job,
14          List<BatchInfo> batchInfoList)
15            throws AsyncApiException {
16        long sleepTime = 0L;
17        Set<String> incomplete = new HashSet<String>();
18        for (BatchInfo bi : batchInfoList) {
19            incomplete.add(bi.getId());
20        }
21        while (!incomplete.isEmpty()) {
22            try {
23                Thread.sleep(sleepTime);
24            } catch (InterruptedException e) {}
25            System.out.println("Awaiting results..." + incomplete.size());
26            sleepTime = 10000L;
27            BatchInfo[] statusList =
28              connection.getBatchInfoList(job.getId()).getBatchInfo();
29            for (BatchInfo b : statusList) {
30                if (b.getState() == BatchStateEnum.Completed
31                  || b.getState() == BatchStateEnum.Failed) {
32                    if (incomplete.remove(b.getId())) {
33                        System.out.println("BATCH STATUS:\n" + b);
34                    }
35                }
36            }
37        }
38    }
39

バッチは、状況が Failed か Completed のいずれかになった場合に終了となります。このコードでは、ジョブのすべてのバッチが終了するまでループ処理を実行します。

ジョブの結果の取得

すべてのバッチの処理が完了したら、各バッチの結果を取得できます。バッチが成功した場合も、失敗した場合も、またジョブが途中で中止された場合も、必ず結果を取得してください。結果セットを取得しないと、個々のレコードの状況を確認できません。各レコードの結果を正しく取得するには、バッチと対応する元のデータセットをコード内で正確に追跡する必要がありまが、そのためには、バッチの作成時のリストを保持し、結果の取得に使用するようにします。次のコードはそのための処理を記述しています。

1
2    /**
3     * Gets the results of the operation and checks for errors.
4     */
5    private void checkResults(BulkConnection connection, JobInfo job,
6              List<BatchInfo> batchInfoList)
7            throws AsyncApiException, IOException {
8        // batchInfoList was populated when batches were created and submitted
9        for (BatchInfo b : batchInfoList) {
10            CSVReader rdr =
11              new CSVReader(connection.getBatchResultStream(job.getId(), b.getId()));
12            List<String> resultHeader = rdr.nextRecord();
13            int resultCols = resultHeader.size();
14
15            List<String> row;
16            while ((row = rdr.nextRecord()) != null) {
17                Map<String, String> resultInfo = new HashMap<String, String>();
18                for (int i = 0; i < resultCols; i++) {
19                    resultInfo.put(resultHeader.get(i), row.get(i));
20                }
21                boolean success = Boolean.valueOf(resultInfo.get("Success"));
22                boolean created = Boolean.valueOf(resultInfo.get("Created"));
23                String id = resultInfo.get("Id");
24                String error = resultInfo.get("Error");
25                if (success && created) {
26                    System.out.println("Created row with id " + id);
27                } else if (!success) {
28                    System.out.println("Failed with error: " + error);
29                }
30            }
31        }
32    }
33

このコードは、各レコードの結果を取得し、処理が成���したか失敗したかを報告します。レコードでエラーが発生した場合にはエラーを出力します。

クイックスタートサンプルの完全版

ジョブやバッチについての理解が深まったでしょうか。クイックスタートサンプルのコード全体を次に示します。コピーして活用してください。

1swfobject.registerObject("clippy.codeblock-8", "9");
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import java.io.*;
19import java.util.*;
20
21import com.sforce.async.*;
22import com.sforce.soap.partner.PartnerConnection;
23import com.sforce.ws.ConnectionException;
24import com.sforce.ws.ConnectorConfig;
25
26    
27public class BulkExample {
28
29
30    public static void main(String[] args)
31      throws AsyncApiException, ConnectionException, IOException {
32        BulkExample example = new BulkExample();
33        // Replace arguments below with your credentials and test file name
34        // The first parameter indicates that we are loading Account records
35        example.runSample("Account", "myUser@myOrg.com", "myPassword", "mySampleData.csv");
36    }
37
38    /**
39     * Creates a Bulk API job and uploads batches for a CSV file.
40     */
41    public void runSample(String sobjectType, String userName,
42              String password, String sampleFileName)
43            throws AsyncApiException, ConnectionException, IOException {
44        BulkConnection connection = getBulkConnection(userName, password);
45        JobInfo job = createJob(sobjectType, connection);
46        List<BatchInfo> batchInfoList = createBatchesFromCSVFile(connection, job,
47            sampleFileName);
48        closeJob(connection, job.getId());
49        awaitCompletion(connection, job, batchInfoList);
50        checkResults(connection, job, batchInfoList);
51    }
52
53
54
55    /**
56     * Gets the results of the operation and checks for errors.
57     */
58    private void checkResults(BulkConnection connection, JobInfo job,
59              List<BatchInfo> batchInfoList)
60            throws AsyncApiException, IOException {
61        // batchInfoList was populated when batches were created and submitted
62        for (BatchInfo b : batchInfoList) {
63            CSVReader rdr =
64              new CSVReader(connection.getBatchResultStream(job.getId(), b.getId()));
65            List<String> resultHeader = rdr.nextRecord();
66            int resultCols = resultHeader.size();
67
68            List<String> row;
69            while ((row = rdr.nextRecord()) != null) {
70                Map<String, String> resultInfo = new HashMap<String, String>();
71                for (int i = 0; i < resultCols; i++) {
72                    resultInfo.put(resultHeader.get(i), row.get(i));
73                }
74                boolean success = Boolean.valueOf(resultInfo.get("Success"));
75                boolean created = Boolean.valueOf(resultInfo.get("Created"));
76                String id = resultInfo.get("Id");
77                String error = resultInfo.get("Error");
78                if (success && created) {
79                    System.out.println("Created row with id " + id);
80                } else if (!success) {
81                    System.out.println("Failed with error: " + error);
82                }
83            }
84        }
85    }
86
87
88
89    private void closeJob(BulkConnection connection, String jobId)
90          throws AsyncApiException {
91        JobInfo job = new JobInfo();
92        job.setId(jobId);
93        job.setState(JobStateEnum.Closed);
94        connection.updateJob(job);
95    }
96
97
98
99    /**
100     * Wait for a job to complete by polling the Bulk API.
101     * 
102     * @param connection
103     *            BulkConnection used to check results.
104     * @param job
105     *            The job awaiting completion.
106     * @param batchInfoList
107     *            List of batches for this job.
108     * @throws AsyncApiException
109     */
110    private void awaitCompletion(BulkConnection connection, JobInfo job,
111          List<BatchInfo> batchInfoList)
112            throws AsyncApiException {
113        long sleepTime = 0L;
114        Set<String> incomplete = new HashSet<String>();
115        for (BatchInfo bi : batchInfoList) {
116            incomplete.add(bi.getId());
117        }
118        while (!incomplete.isEmpty()) {
119            try {
120                Thread.sleep(sleepTime);
121            } catch (InterruptedException e) {}
122            System.out.println("Awaiting results..." + incomplete.size());
123            sleepTime = 10000L;
124            BatchInfo[] statusList =
125              connection.getBatchInfoList(job.getId()).getBatchInfo();
126            for (BatchInfo b : statusList) {
127                if (b.getState() == BatchStateEnum.Completed
128                  || b.getState() == BatchStateEnum.Failed) {
129                    if (incomplete.remove(b.getId())) {
130                        System.out.println("BATCH STATUS:\n" + b);
131                    }
132                }
133            }
134        }
135    }
136
137
138
139    /**
140     * Create a new job using the Bulk API.
141     * 
142     * @param sobjectType
143     *            The object type being loaded, such as "Account"
144     * @param connection
145     *            BulkConnection used to create the new job.
146     * @return The JobInfo for the new job.
147     * @throws AsyncApiException
148     */
149    private JobInfo createJob(String sobjectType, BulkConnection connection)
150          throws AsyncApiException {
151        JobInfo job = new JobInfo();
152        job.setObject(sobjectType);
153        job.setOperation(OperationEnum.insert);
154        job.setContentType(ContentType.CSV);
155        job = connection.createJob(job);
156        System.out.println(job);
157        return job;
158    }
159
160    
161
162    /**
163     * Create the BulkConnection used to call Bulk API operations.
164     */
165    private BulkConnection getBulkConnection(String userName, String password)
166          throws ConnectionException, AsyncApiException {
167        ConnectorConfig partnerConfig = new ConnectorConfig();
168        partnerConfig.setUsername(userName);
169        partnerConfig.setPassword(password);
170        partnerConfig.setAuthEndpoint("https://login.salesforce.com/services/Soap/u/34.0");
171        // Creating the connection automatically handles login and stores
172        // the session in partnerConfig
173        new PartnerConnection(partnerConfig);
174        // When PartnerConnection is instantiated, a login is implicitly
175        // executed and, if successful,
176        // a valid session is stored in the ConnectorConfig instance.
177        // Use this key to initialize a BulkConnection:
178        ConnectorConfig config = new ConnectorConfig();
179        config.setSessionId(partnerConfig.getSessionId());
180        // The endpoint for the Bulk API service is the same as for the normal
181        // SOAP uri until the /Soap/ part. From here it's '/async/versionNumber'
182        String soapEndpoint = partnerConfig.getServiceEndpoint();
183        String apiVersion = "34.0";
184        String restEndpoint = soapEndpoint.substring(0, soapEndpoint.indexOf("Soap/"))
185            + "async/" + apiVersion;
186        config.setRestEndpoint(restEndpoint);
187        // This should only be false when doing debugging.
188        config.setCompression(true);
189        // Set this to true to see HTTP requests and responses on stdout
190        config.setTraceMessage(false);
191        BulkConnection connection = new BulkConnection(config);
192        return connection;
193    }
194
195
196
197    /**
198     * Create and upload batches using a CSV file.
199     * The file into the appropriate size batch files.
200     * 
201     * @param connection
202     *            Connection to use for creating batches
203     * @param jobInfo
204     *            Job associated with new batches
205     * @param csvFileName
206     *            The source file for batch data
207     */
208    private List<BatchInfo> createBatchesFromCSVFile(BulkConnection connection,
209          JobInfo jobInfo, String csvFileName)
210            throws IOException, AsyncApiException {
211        List<BatchInfo> batchInfos = new ArrayList<BatchInfo>();
212        BufferedReader rdr = new BufferedReader(
213            new InputStreamReader(new FileInputStream(csvFileName))
214        );
215        // read the CSV header row
216        byte[] headerBytes = (rdr.readLine() + "\n").getBytes("UTF-8");
217        int headerBytesLength = headerBytes.length;
218        File tmpFile = File.createTempFile("bulkAPIInsert", ".csv");
219
220        // Split the CSV file into multiple batches
221        try {
222            FileOutputStream tmpOut = new FileOutputStream(tmpFile);
223            int maxBytesPerBatch = 10000000; // 10 million bytes per batch
224            int maxRowsPerBatch = 10000; // 10 thousand rows per batch
225            int currentBytes = 0;
226            int currentLines = 0;
227            String nextLine;
228            while ((nextLine = rdr.readLine()) != null) {
229                byte[] bytes = (nextLine + "\n").getBytes("UTF-8");
230                // Create a new batch when our batch size limit is reached
231                if (currentBytes + bytes.length > maxBytesPerBatch
232                  || currentLines > maxRowsPerBatch) {
233                    createBatch(tmpOut, tmpFile, batchInfos, connection, jobInfo);
234                    currentBytes = 0;
235                    currentLines = 0;
236                }
237                if (currentBytes == 0) {
238                    tmpOut = new FileOutputStream(tmpFile);
239                    tmpOut.write(headerBytes);
240                    currentBytes = headerBytesLength;
241                    currentLines = 1;
242                }
243                tmpOut.write(bytes);
244                currentBytes += bytes.length;
245                currentLines++;
246            }
247            // Finished processing all rows
248            // Create a final batch for any remaining data
249            if (currentLines > 1) {
250                createBatch(tmpOut, tmpFile, batchInfos, connection, jobInfo);
251            }
252        } finally {
253            tmpFile.delete();
254        }
255        return batchInfos;
256    }
257
258    /**
259     * Create a batch by uploading the contents of the file.
260     * This closes the output stream.
261     * 
262     * @param tmpOut
263     *            The output stream used to write the CSV data for a single batch.
264     * @param tmpFile
265     *            The file associated with the above stream.
266     * @param batchInfos
267     *            The batch info for the newly created batch is added to this list.
268     * @param connection
269     *            The BulkConnection used to create the new batch.
270     * @param jobInfo
271     *            The JobInfo associated with the new batch.
272     */
273    private void createBatch(FileOutputStream tmpOut, File tmpFile,
274      List<BatchInfo> batchInfos, BulkConnection connection, JobInfo jobInfo)
275              throws IOException, AsyncApiException {
276        tmpOut.flush();
277        tmpOut.close();
278        FileInputStream tmpInputStream = new FileInputStream(tmpFile);
279        try {
280            BatchInfo batchInfo =
281              connection.createBatchFromStream(jobInfo, tmpInputStream);
282            System.out.println(batchInfo);
283            batchInfos.add(batchInfo);
284
285        } finally {
286            tmpInputStream.close();
287        }
288    }
289
290
291}