5

次のエラーが発生しました。

Datasource names for all the database tags within the cftransaction tag must be the same.

これは、次のコードから生じています。

transaction action="begin" {
  try {
    var data = {};

    data.time = getTickCount();

    addToLog("Persist", "Started persist operations");

    doClientPersist();
    cleanUp(arguments.importId);

    addToLog("Persist", "Completed the persist operations successfully", ((getTickCount()-data.time)/1000));

    return true;
  } catch (any e) {
    transactionRollback();
    data.error = e;
  }
}

トランザクションは、下位レベルのメソッドの割り当てを効果的にラップしていますdoClientPersist()。このような呼び出しの 1 つは、フレームワーク データベースの抽象化レイヤーの奥深くにあり、別のデータ ソース (Postcode データ ソースとしましょう) から経度と緯度の情報を取得 (SELECT) します。このデータ ソースは厳密には読み取り専用です。

<cffunction name="getLatitudeAndLongitude" access="package" returntype="query" output="false">
  <cfargument name="postcode" type="string" required="true" />
  <cfset var qPostcode = ''/>
  <cfquery name="qPostcode" datasource="postcodesDatasource">
    SELECT 
      a.latitude, 
      a.longitude
    FROM 
      postcodes AS a
    WHERE 
      a.postcode = <cfqueryparam cfsqltype="CF_SQL_VARCHAR" value="#postcode#"/>
  </cfquery>
  <cfreturn qPostcode/>
</cffunction>

<cffunction name="getPostcodeCoordinates" access="public" returntype="struct" output="false">
  <cfargument name="postcode" type="string" required="true"/>
  <cfscript>
    var data = {};

    data.postcode = getFormattedPostcode(arguments.postcode);
    data.valid    = isValidPostcode(data.postcode);
    data.coords   = {};

    if (data.valid) {
      data.query = getLatitudeAndLongitude(data.postcode);
      if (isQuery(data.query) && data.query.recordCount) {
        data.coords["latitude"]  = data.query["latitude"][1];
        data.coords["longitude"] = data.query["longitude"][1];
      } else if (data.valid == 2) {
        /** No match, try short postcode (RECURSIVE) **/
        data.coords = getPostcodeCoordinates(trim(left(data.postcode, len(data.postcode)-3)));
      }
    }
    return data.coords;
  </cfscript>
</cffunction>

問題を読むと、ドキュメントは次のように述べています。

In a transaction block, you can write queries to more than one database, but you must commit or roll back a transaction to one database before writing a query to another.

残念ながら、上記のように、この郵便番号データをフェッチするコードは、実際の永続化操作とはまったく関係がありません。これは、変更できない低レベル メソッドの Web を実行するためです。リモート データソース。

とにかく、トランザクション内で「最上位」メソッドをラップし、「郵便番号」データソースへの呼び出しを保持できるということはありますか?クライアントごとに郵便番号情報を複製する必要があるのはばかげていますが、操作は何か問題が発生した場合にロールバックします。

前もって感謝します。

4

4 に答える 4

3

ご覧のとおり、基本的に 2 つの選択肢があります。

1) トランザクションの外部でデータをクエリします。アプリケーションの仕様によっては、トランザクション ブロックの前にそのメソッドを移動する、メソッドを分割してトランザクション ブロックの前にその一部を移動する、データを RAM にプリフェッチする (データをおそらくクエリとして変数) を作成し、データベースに直接クエリを実行するのではなく、メソッドでこのプリフェッチされたデータを使用するようにします。

ただし、これらすべてのソリューションの結果は同じです。つまり、SELECT クエリ自体がトランザクションの外部で実行されます。

何らかの理由でそれが現実的でない場合は、次に進みます...

2) 同じデータソースを使用します。同じデータベースを使用する必要はなく、同じデータソースだけを使用することに注意してください。したがって、MySQL で database.tablename 構文を使用できます。

簡単に検索しただけで、これの良い例が見つかりました。 一度に複数のデータベースにクエリを実行する

おそらく、私よりも優れた Google-fu を持っている人なら、より良い例をすぐに思いつくでしょう。

基本は、クエリで FROM tablename の代わりに FROM database.tablename を使用することです。

ただし、これにはデータベースが同じ MySQL サーバー上にある必要があると思います。

于 2013-02-26T14:57:44.597 に答える
2

そのため、これを解決する方法について少し混乱しています。Steve の回答を受け入れました (ありがとう)。ただし、ソリューションの簡単な例を示すために、以下のコードを追加しました。

私にとって、データソース データは複製できず、上記のコードにはラッピング トランザクションが必要でした。だから、それは本当に1つの解決策、私の意見では中途半端な解決策だけを残しました...

<cffunction name="methodA" access="public" returntype="query" output="false">
  <cfset var q = ""/>
  <cfquery name="q" datasource="test_1">
    SELECT id, name FROM table_a
  </cfquery>
  <cfset methodB = methodB()/>
  <cfreturn q/>
</cffunction>

<cffunction name="methodB" access="public" returntype="query" output="false">
  <cfset var q = ""/>
  <cfquery name="q" datasource="test_1">
    SELECT id, name FROM table_b
  </cfquery>
  <cfset methodC = methodC()/>
  <cfreturn q/>
</cffunction>

<cffunction name="methodC" access="public" returntype="void" output="false">
  <cfset var q = ""/>
  <!--- 
  This will error with the following: 
    Datasource test_2 verification failed. 
    The root cause was that: java.sql.SQLException: 
    Datasource names for all the database tags within the cftransaction tag must be the same.
  <cfquery name="q" datasource="test_1">
    INSERT INTO test_2.table_z (`id`, `name`) VALUES ('1','test'); 
  </cfquery>
  --->

  <!--- This is the same query, however I have reused the datasource test_1
  and specified the DATABASE test_2 --->
  <cfquery name="q" datasource="test_1">
    INSERT INTO test_2.table_z (`id`, `name`) VALUES ('1','test'); 
  </cfquery>

</cffunction>

<cftransaction action="begin">
  <cfset data = methodA()/>
  <cftransaction action="commit"/>
</cftransaction>

明確でない場合、私にとっての解決策は、2 番目のデータソースへの参照を削除し、代わりにデータソース test_2を使用することでした。これは、クエリ内で 2 番目のデータベース名をハード コーディングすることを意味します。明らかに、これは動的に実行できますが、既存のクエリを変更する必要があるため、問題が発生します。これに加えて、2 番目のデータソースが MSSQL などの別のデータベース プラットフォームである場合、これは機能しません。ありがたいことに、これは私には当てはまりませんでした。test_1 test_2

于 2013-02-27T16:50:24.517 に答える
0

同じ問題があり、cftransaction タグを 2 番目 (または最初) のデータソース cfquery から移動しました。コード全体で CFC を使用している場合、これには CFC が含まれます。

于 2014-09-15T19:26:12.177 に答える
0

この質問は古いと思いますが、2021 年にはまだ Lucee、そしておそらく Adob​​e の「問題」です。私が考案した解決策は次のとおりです。

私が作成した電話アプリケーションにデバッグ メッセージを送信する方法が必要でした。データベースへのアクセスを共有します。

これが私の解決策の適応です。私の実際のコードは他のことをしているので、これは直接テストされていません

public numeric function QueryQueue(required string sql, struct options = {}, struct params = {}, queryset = "default") {

  param name="request.queryQueue" default="#{}#";
  if (!StructKeyExists(request.queryQueue, arguments.queryset)) {
    request.queryQueue[arguments.queryset] = []
  }

  request.queryQueue[arguments.querySet].append({sql: "#arguments.sql#",
    options: arguments.options,
    params: arguments.params,
    removed: false});

  return request.queryQueue[arguments.queryset].len();
  // returning the length, and thus the position of the query,
  // so it can be specifically called if desired.
  // This is query QueryQueueExecute doesn't actually
  // delete elements, but marks them.
}

public any function QueryQueueExecute(required numeric pos, boolean remove = true, string queryset = "default") {
  if (!request.queryQueue[arguments.queryset][arguments.pos].removed) {
    var theQuery = QueryExecute(sql = request.queryQueue[arguments.queryset][arguments.pos].sql,
      options = request.queryQueue[arguments.queryset][arguments.pos].options,
      params = request.queryQueue[arguments.queryset][arguments.pos].params);
    if (arguments.remove) {
      request.queryQueue[arguments.queryset][arguments.pos].removed = true;
    }
    return theQuery;
  } else {
    return {recordcount: -1}; // a flag to show that the query wasn't executed, because it's already been "removed"
  }
}

public array function QueryQueueExecuteAll(boolean remove = true, string queryset = "default") {
  var queryArray = [];
  for (i = 1; i <= request.queryQueue[arguments.queryset].len(); i++) {
    queryArray.append(QueryQueueExecute(i, false));
    // false is deliberately set here, rather than passing the remove argument
    // since the array will be cleared after the loop.
  }
  if (arguments.remove) {
    request.queryQueue[arguments.queryset].clear();
  }
  return queryArray;
}

これらの関数を使用すると、クエリをキューに入れたり、特定のクエリを実行したり、すべてを実行したりできます。必要に応じて削除するフラグもありますが、削除しない理由は想像できません。

私の場合は、これを OnRequestEnd と ErrorHandler で実行できます。これは、デバッグに使用するためです。

于 2021-01-14T06:18:56.653 に答える