1. 要件分析
(1) 要件:
Solr内のドキュメントに新しいフィールドを追加したり、既存のフィールドを変更したりするが、変更しないフィールドは元の値のままにしておきたい。つまり、完全な上書き操作は行わない。
(2) 前提条件:
追加するフィールドは、事前にschema.xmlファイルで定義されている必要がある。そうでないと、Solrはそのフィールドを処理できず、追加に失敗する。
schema.xmlファイルの設定については、以下を参照: Solrのschema.xmlスキーマファイルの解説(Solrのスキーマ設計と最適化)
(3) 分析: Solrが提供するアトミック更新機能を利用することで、この要件を実現できます。
Solrがサポートするアトミック更新:
set: 指定されたドキュメントのフィールドの値を変更します。このフィールドが既に存在する場合は更新され、存在しない場合はドキュメントに追加されます。単一値でも複数値でも可能です。add: 指定されたドキュメントのフィールドに値を追加します。このフィールドは複数値(multi-valued)タイプでなければなりません。そうでない場合、エラーが発生します。複数値のみ対応。inc: 指定されたドキュメントの数値型の値をインクリメント(増加)します。数値型(int、long、float、doubleなど)のみ対応。
2. 実装方法
2.1. pom.xmlの依存関係
(プロジェクトが古いため、4.10.4バージョンのSolrを使用しています)
<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>4.10.4</version>
</dependency>
2.2. Javaコードの例
(1) まずSolrへの接続を確立します:
String zkHost = "ip:port,ip:port,ip:port";
// 同時接続数を増やす
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(HttpClientUtil.PROP_MAX_CONNECTIONS, 1000);
params.set(HttpClientUtil.PROP_MAX_CONNECTIONS_PER_HOST, 100);
HttpClient client = HttpClientUtil.createClient(params);
LBHttpSolrServer lbServer = new LBHttpSolrServer(client);
CloudSolrServer solrServer = new CloudSolrServer(zkHost, lbServer);
// Solr接続用のデフォルトコレクションを設定
solrServer.setDefaultCollection("C_Book");
// ZooKeeperの接続タイムアウト時間を設定
solrServer.setZkClientTimeout(18000);
solrServer.setZkConnectTimeout(36000);
(2) 処理対象のSolrドキュメントを準備します。コード内のコメントで詳細な注意点を説明しています:
// 効率化のためにバッチ操作を使用
Collection<SolrInputDocument> documentsToUpdate = new ArrayList<>();
// 更新するドキュメントのIDリスト(例)
List<String> documentIds = Arrays.asList("1", "2", "3", "4", "5");
for (String docId : documentIds) {
SolrInputDocument solrDoc = new SolrInputDocument();
// 部分更新では、schema.xmlで定義された主キー(id)を指定する必要があります。
// 主キーにはset、addなどの情報を追加する必要はありません。他のアトミック更新が必要なフィールドはMapとして構築する必要があります。
solrDoc.addField("id", docId);
// 出版社を「人民邮电出版社」に設定するためのMap。部分更新にはMapが必要で、キーは「set」である必要があります。
Map<String, String> publisherUpdateMap = new HashMap<>();
publisherUpdateMap.put("set", "人民邮电出版社");
// 図書の出版社を更新します。キーはフィールド名、値は上記のMapです。
solrDoc.addField("publisher", publisherUpdateMap);
// 既存の在庫に複数の都市を追加します。注意:このフィールドは複数値タイプでなければなりません。
Map<String, List<String>> stockCityUpdateMap = new HashMap<>();
List<String> citiesToAdd = new ArrayList<>();
citiesToAdd.add("广州");
citiesToAdd.add("深圳");
// 部分追加にはMapが必要で、キーは「add」である必要があります。
stockCityUpdateMap.put("add", citiesToAdd);
// 図書の在庫都市を更新します。キーはフィールド名、値は上記のMapです。
solrDoc.addField("stockCity", stockCityUpdateMap);
// 既存の図書価格に基づき:各冊を9.50元増加させます。注意:このフィールドは数値型でなければなりません。
Map<String, Long> priceUpdateMap = new HashMap<>();
// 部分インクリメントにはMapが必要で、キーは「inc」である必要があります。
priceUpdateMap.put("inc", 950L); // 9.50元をlong型で表現
// 図書の価格を更新します。キーはフィールド名、値は上記のMapです。
solrDoc.addField("price", priceUpdateMap);
// _version_の値が0の場合:更新対象のドキュメントが存在すれば更新し、存在しなければ追加します。
solrDoc.addField("_version_", 0);
documentsToUpdate.add(solrDoc);
}
(3) SolrCloudにバッチ追加リクエストを送信します:
// SolrCloudに接続
solrServer.connect();
// 更新するドキュメントのリストを追加
UpdateResponse rsp = solrServer.add(documentsToUpdate);
System.out.println("操作ステータス: " + rsp.getStatus() + ", 操作時間:" + rsp.getQTime());
// コミット戦略:手動でコミットする必要はなく、Solrサービスが設定に基づいて自動的にソフトコミットを実行します。
// 手動でコミットする場合、引数なしのメソッドは使用せず、コミット戦略を指定することを推奨します:リフレッシュを待つか(ブロックする可能性があるため非推奨)、検索可能になるのを待つか(ブロックする可能性があるため非推奨)、ソフトコミット
UpdateResponse rspCommit = solrServer.commit(false, false, true);
System.out.println("コミットステータス: " + " result:" + rspCommit.getStatus() + ", 操作時間: " + rspCommit.getQTime());
3. 補足説明
3.1. `_version_`フィールドの値について
(1) `version < 0`: 更新対象のドキュメントが存在する場合、Solrは更新を拒否します。存在しない場合は、そのドキュメントを追加します。
(2) `version = 0`: 更新対象のドキュメントが存在する場合、そのドキュメントを更新します。存在しない場合は、そのドキュメントを追加します。
(3) `version = 1`: 更新対象のドキュメントが存在する場合、そのドキュメントを更新します。存在しない場合は、Solrはそれを拒否し、以下のようなエラーメッセージをスローします:
version conflict for 1 expected=1 actual=-1
(4) `version > 1`: ドキュメントの`_version_`値と渡された`_version_`値が異なる場合、Solrは更新を拒否します。値が同じ場合のみ、更新が実行されます。
3.2. `store=true/false`の違い
(1) あるフィールドがschema.xmlで`store=false`と指定されている場合、そのフィールドに値があっても、更新時にSolrによって破棄されます。`store=true`と指定されたフィールドは破棄されません。
(2) 複数フィールド(multi-field)の場合、`store=false`と指定されていると、アトミック更新で`add`を使用した際に、そのフィールドの以前のデータが連鎖的にクリアされます。